Files
Brancheneinstufung2/gtm_architect_orchestrator.py

338 lines
14 KiB
Python

import os
import sys
import json
import argparse
from pathlib import Path
# Add project root to Python path
project_root = Path(__file__).resolve().parents[1]
sys.path.append(str(project_root))
from helpers import call_openai_chat
import market_db_manager
__version__ = "1.3.0_SyntaxFix"
# Ensure DB is ready
market_db_manager.init_db()
def get_system_instruction(lang):
if lang == 'de':
return """
# IDENTITY & PURPOSE
Du bist die "GTM Architect Engine" für Roboplanet. Deine Aufgabe ist es, für neue technische Produkte (Roboter) eine präzise Go-to-Market-Strategie zu entwickeln.
Du handelst nicht als kreativer Werbetexter, sondern als strategischer Analyst. Dein oberstes Ziel ist Product-Market-Fit und operative Umsetzbarkeit.
Antworte IMMER auf DEUTSCH.
# CONTEXT: THE PARENT COMPANY (WACKLER)
Wir sind Teil der Wackler Group, einem großen Facility-Management-Dienstleister.
Unsere Strategie ist NICHT "Roboter ersetzen Menschen", sondern "Hybrid-Reinigung":
- 80% der Arbeit (monotone Flächenleistung) = Roboter.
- 20% der Arbeit (Edge Cases, Winterdienst, Treppen, Grobschmutz) = Manuelle Reinigung durch Wackler.
# STRICT ANALYSIS RULES (MUST FOLLOW):
1. TECHNICAL FACT-CHECK (Keine Halluzinationen):
- Analysiere technische Daten extrem konservativ.
- Vakuumsystem = Kein "Winterdienst" (Schnee) und keine "Schwerindustrie" (Metallspäne), außer explizit genannt.
- Erfinde keine Features, nur um eine Zielgruppe passend zu machen.
2. REGULATORY LOGIC (StVO-Check):
- Wenn Vmax < 20 km/h: Schließe "Öffentliche Städte/Kommunen/Straßenreinigung" kategorisch aus (Verkehrshindernis).
- Fokusänderung: Konzentriere dich stattdessen ausschließlich auf "Große, zusammenhängende Privatflächen" (Gated Areas).
3. STRATEGIC TARGETING (Use-Case-Logik):
- Priorisiere Cluster A (Efficiency): Logistikzentren & Industrie-Hubs (24/7 Betrieb, Sicherheit).
- Priorisiere Cluster B (Experience): Shopping Center, Outlets & Freizeitparks (Sauberkeit als Visitenkarte).
- Entferne reine E-Commerce-Händler ohne physische Kundenfläche.
4. THE "HYBRID SERVICE" LOGIC (RULE 5):
Wann immer du ein "Hartes Constraint" oder eine technische Limitierung identifizierst (z.B. "Kein Winterdienst" oder "Kommt nicht in Ecken"), darfst du dies niemals als reines "Nein" stehen lassen.
Wende stattdessen die **"Yes, and..." Logik** an:
1. **Identifiziere die Lücke:** (z.B. "Roboter kann bei Schnee nicht fahren").
2. **Fülle die Lücke mit Service:** Schlage explizit vor, diesen Teil durch "Wackler Human Manpower" abzudecken.
3. **Formuliere den USP:** Positioniere das Gesamtpaket als "100% Coverage" (Roboter + Mensch aus einer Hand).
# THE PRODUCT MATRIX (CONTEXT)
Behalte immer im Hinterkopf, dass wir bereits folgende Produkte im Portfolio haben:
1. "Indoor Scrubber 50": Innenreinigung, Hartboden, Fokus: Supermärkte. Message: "Sauberkeit im laufenden Betrieb."
2. "Service Bot Bella": Service/Gastro, Indoor. Fokus: Restaurants. Message: "Entlastung für Servicekräfte."
""
else:
return """
# IDENTITY & PURPOSE
You are the "GTM Architect Engine" for Roboplanet. Your task is to develop a precise Go-to-Market strategy for new technical products (robots).
You do not act as a creative copywriter, but as a strategic analyst. Your top goal is product-market fit and operational feasibility.
ALWAYS respond in ENGLISH.
# CONTEXT: THE PARENT COMPANY (WACKLER)
We are part of the Wackler Group, a major facility management service provider.
Our strategy is NOT "Robots replace humans", but "Hybrid Cleaning":
- 80% of work (monotonous area coverage) = Robots.
- 20% of work (Edge cases, winter service, stairs, heavy debris) = Manual cleaning by Wackler.
# STRICT ANALYSIS RULES (MUST FOLLOW):
1. TECHNICAL FACT-CHECK (No Hallucinations):
- Analyze technical data extremely conservatively.
- Vacuum System = No "Winter Service" (snow) and no "Heavy Industry" (metal shavings), unless explicitly stated.
- Do not invent features just to fit a target audience.
2. REGULATORY LOGIC (Traffic Regs):
- If Vmax < 20 km/h: Categorically exclude "Public Cities/Streets" (traffic obstruction).
- Change Focus: Concentrate exclusively on "Large, contiguous private areas" (Gated Areas).
3. STRATEGIC TARGETING (Use Case Logic):
- Prioritize Cluster A (Efficiency): Logistics Centers & Industrial Hubs (24/7 ops, safety).
- Prioritize Cluster B (Experience): Shopping Centers, Outlets & Theme Parks (Cleanliness as a calling card).
- Remove pure E-commerce retailers without physical customer areas.
4. THE "HYBRID SERVICE" LOGIC (RULE 5):
Whenever you identify a "Hard Constraint" or a technical limitation (e.g., "No winter service" or "Cannot reach corners"), never let this stand as a simple "No".
Instead, apply the **"Yes, and..." logic**:
1. **Identify the gap:** (e.g., "Robot cannot operate in snow").
2. **Fill the gap with service:** Explicitly suggest covering this part with "Wackler Human Manpower".
3. **Formulate the USP:** Position the total package as "100% Coverage" (Robot + Human from a single source).
# THE PRODUCT MATRIX (CONTEXT)
Always keep in mind that we already have the following products in our portfolio:
1. "Indoor Scrubber 50": Indoor cleaning, hard floor, supermarkets.
2. "Service Bot Bella": Service/Hospitality, indoor. Focus: restaurants. Message: "Relief for service staff."
"""
# --- Database Handlers ---
def save_project_handler(data):
if 'name' not in data:
input_text = data.get('productInput', '')
derived_name = input_text.split('\n')[0][:30] if input_text else "Untitled Strategy"
data['name'] = derived_name
result = market_db_manager.save_project(data)
print(json.dumps(result))
def list_projects_handler(data):
projects = market_db_manager.get_all_projects()
print(json.dumps(projects))
def load_project_handler(data):
project_id = data.get('id')
project = market_db_manager.load_project(project_id)
if project:
print(json.dumps(project))
else:
print(json.dumps({"error": "Project not found"}))
def delete_project_handler(data):
project_id = data.get('id')
result = market_db_manager.delete_project(project_id)
print(json.dumps(result))
# --- AI Handlers ---
def analyze_product(data):
product_input = data.get('productInput')
lang = data.get('language', 'de')
sys_instr = get_system_instruction(lang)
# Statische Prompt-Teile (keine f-Strings)
if lang == 'en':
extraction_prompt_static_part = """
PHASE 1-A: TECHNICAL EXTRACTION
Input Product Description: "{product_description}"
Task:
1) Extract key technical features (specs, capabilities).
2) Derive "Hard Constraints". IMPORTANT: Check Vmax (<20km/h = Private Grounds) and Cleaning Type (Vacuum != Heavy Debris/Snow).
3) Create a short raw analysis summary.
Output JSON format ONLY.
"""
else:
extraction_prompt_static_part = """
PHASE 1-A: TECHNICAL EXTRACTION
Input Product Description: "{product_description}"
Aufgabe:
1) Extrahiere technische Hauptmerkmale (Specs, Fähigkeiten).
2) Leite "Harte Constraints" ab. WICHTIG: Prüfe Vmax (<20km/h = Privatgelände) und Reinigungstyp (Vakuum != Grobschmutz/Schnee).
3) Erstelle eine kurze Rohanalyse-Zusammenfassung.
Output JSON format ONLY.
"""
# Variablen separat formatieren
extraction_prompt = extraction_prompt_static_part.format(product_description=product_input[:25000])
full_extraction_prompt = sys_instr + "\n\n" + extraction_prompt
extraction_response = call_openai_chat(full_extraction_prompt, response_format_json=True)
extraction_data = json.loads(extraction_response)
features_json = json.dumps(extraction_data.get('features'))
constraints_json = json.dumps(extraction_data.get('constraints'))
if lang == 'en':
conflict_prompt_static_part = """
PHASE 1-B: PORTFOLIO CONFLICT CHECK
New Product Features: {features_json_var}
New Product Constraints: {constraints_json_var}
Existing Portfolio:
1. "Indoor Scrubber 50": Indoor cleaning, hard floor, supermarkets.
2. "Service Bot Bella": Service/Gastro, indoor, restaurants.
Task:
Check if the new product overlaps significantly with existing ones (is it just a clone?).
Output JSON format ONLY.
"""
conflict_prompt = conflict_prompt_static_part.format(features_json_var=features_json, constraints_json_var=constraints_json)
else:
conflict_prompt_static_part = """
PHASE 1-B: PORTFOLIO CONFLICT CHECK
Neue Produkt-Features: {features_json_var}
Neue Produkt-Constraints: {constraints_json_var}
Existierendes Portfolio:
1. "Indoor Scrubber 50": Innenreinigung, Hartboden, Supermärkte.
2. "Service Bot Bella": Service/Gastro, Indoor, Restaurants.
Aufgabe:
Prüfe, ob das neue Produkt signifikant mit bestehenden Produkten überlappt (Ist es nur ein Klon?).
Output JSON format ONLY.
"""
conflict_prompt = conflict_prompt_static_part.format(features_json_var=features_json, constraints_json_var=constraints_json)
full_conflict_prompt = sys_instr + "\n\n" + conflict_prompt
conflict_response = call_openai_chat(full_conflict_prompt, response_format_json=True)
conflict_data = json.loads(conflict_response)
final_result = {**extraction_data, **conflict_data}
print(json.dumps(final_result))
def discover_icps(data):
phase1_result = data.get('phase1Result')
lang = data.get('language', 'de')
sys_instr = get_system_instruction(lang)
features_json = json.dumps(phase1_result.get('features'))
constraints_json = json.dumps(phase1_result.get('constraints'))
if lang == 'en':
prompt_template = """
PHASE 2: ICP DISCOVERY & DATA PROXIES
Based on the product features: {features_json_var}
And constraints: {constraints_json_var}
Output JSON format ONLY:
{{
"icps": [ {{ "name": "Industry Name", "rationale": "Rationale" }} ],
"dataProxies": [ {{ "target": "Criteria", "method": "How" }} ]
}}
"""
prompt = prompt_template.format(features_json_var=features_json, constraints_json_var=constraints_json)
else:
prompt_template = """
PHASE 2: ICP DISCOVERY & DATA PROXIES
Basierend auf Features: {features_json_var}
Und Constraints: {constraints_json_var}
Output JSON format ONLY:
{{
"icps": [ {{ "name": "Branchen Name", "rationale": "Begründung" }} ],
"dataProxies": [ {{ "target": "Kriterium", "method": "Methode" }} ]
}}
"""
prompt = prompt_template.format(features_json_var=features_json, constraints_json_var=constraints_json)
full_prompt = sys_instr + "\n\n" + prompt
response = call_openai_chat(full_prompt, response_format_json=True)
print(response)
def hunt_whales(data):
phase2_result = data.get('phase2Result')
lang = data.get('language', 'de')
sys_instr = get_system_instruction(lang)
icps_json = json.dumps(phase2_result.get('icps'))
prompt_template = """
PHASE 3: WHALE HUNTING for {icps_json_var}. Identify 3-5 concrete DACH companies per industry and buying center roles. Output JSON ONLY.
"""
prompt = prompt_template.format(icps_json_var=icps_json)
full_prompt = sys_instr + "\n\n" + prompt
response = call_openai_chat(full_prompt, response_format_json=True)
print(response)
def develop_strategy(data):
phase3_result = data.get('phase3Result')
phase1_result = data.get('phase1Result')
lang = data.get('language', 'de')
sys_instr = get_system_instruction(lang)
phase3_json = json.dumps(phase3_result)
prompt_template = """
PHASE 4: STRATEGY Matrix for {phase3_json_var}. Apply Hybrid logic. Output JSON ONLY.
"""
prompt = prompt_template.format(phase3_json_var=phase3_json)
full_prompt = sys_instr + "\n\n" + prompt
response = call_openai_chat(full_prompt, response_format_json=True)
print(response)
def generate_assets(data):
lang = data.get('language', 'de')
sys_instr = get_system_instruction(lang)
data_json = json.dumps(data)
prompt_template = """
PHASE 5: GTM STRATEGY REPORT Markdown. Use facts, TCO, ROI. Data: {data_json_var}
"""
prompt = prompt_template.format(data_json_var=data_json)
full_prompt = sys_instr + "\n\n" + prompt
response = call_openai_chat(full_prompt)
print(json.dumps(response))
def generate_sales_enablement(data):
lang = data.get('language', 'de')
sys_instr = get_system_instruction(lang)
data_json = json.dumps(data)
prompt_template = """
PHASE 6: Battlecards and MJ Prompts. Data: {data_json_var}. Output JSON ONLY.
"""
prompt = prompt_template.format(data_json_var=data_json)
full_prompt = sys_instr + "\n\n" + prompt
response = call_openai_chat(full_prompt, response_format_json=True)
print(response)
def main():
parser = argparse.ArgumentParser(description="GTM Architect Orchestrator")
parser.add_argument("--mode", type=str, required=True, help="Execution mode")
args = parser.parse_args()
print(f"DEBUG: Orchestrator v{__version__} loaded (Mode: {args.mode})", file=sys.stderr)
if not sys.stdin.isatty():
try:
data = json.loads(sys.stdin.read())
except:
data = {}
else:
data = {}
modes = {
"analyze_product": analyze_product,
"discover_icps": discover_icps,
"hunt_whales": hunt_whales,
"develop_strategy": develop_strategy,
"generate_assets": generate_assets,
"generate_sales_enablement": generate_sales_enablement,
"save_project": save_project_handler,
"list_projects": list_projects_handler,
"load_project": load_project_handler,
"delete_project": delete_project_handler
}
if args.mode in modes:
modes[args.mode](data)
else:
print(json.dumps({"error": f"Unknown mode: {args.mode}"}))
if __name__ == "__main__":
main()