diff --git a/gtm_architect_orchestrator.py b/gtm_architect_orchestrator.py index 63e84757..7e1da592 100644 --- a/gtm_architect_orchestrator.py +++ b/gtm_architect_orchestrator.py @@ -16,23 +16,91 @@ market_db_manager.init_db() def get_system_instruction(lang): if lang == 'de': -# ... (rest of get_system_instruction remains same) + 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). -# ... (rest of english instructions) +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): - # data contains the full application state - # Ensure we have a name for the list view if 'name' not in data: - # Try to derive a name from product input or phases input_text = data.get('productInput', '') - # Take first 30 chars or first line derived_name = input_text.split('\n')[0][:30] if input_text else "Untitled Strategy" data['name'] = derived_name @@ -40,7 +108,6 @@ def save_project_handler(data): print(json.dumps(result)) def list_projects_handler(data): - # data is ignored here projects = market_db_manager.get_all_projects() print(json.dumps(projects)) @@ -60,48 +127,173 @@ def delete_project_handler(data): # --- AI Handlers --- def analyze_product(data): -# ... (rest of analyze_product and other AI handlers remain same) + product_input = data.get('productInput') + lang = data.get('language', 'de') + sys_instr = get_system_instruction(lang) + + extraction_prompt = f""" +PHASE 1-A: TECHNICAL EXTRACTION +Input Product Description: "{product_input[:25000]}..." + +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. +""" if lang == 'en' else f""" +PHASE 1-A: TECHNICAL EXTRACTION +Input Product Description: "{product_input[:25000]}..." + +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. +""" + # Fix: Prepend system instruction manually + full_extraction_prompt = sys_instr + "\n\n" + extraction_prompt + extraction_response = call_openai_chat(full_extraction_prompt, json_mode=True) + extraction_data = json.loads(extraction_response) + + conflict_prompt = f""" +PHASE 1-B: PORTFOLIO CONFLICT CHECK + +New Product Features: {json.dumps(extraction_data.get('features'))} +New Product Constraints: {json.dumps(extraction_data.get('constraints'))} + +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. +""" if lang == 'en' else f""" +PHASE 1-B: PORTFOLIO CONFLICT CHECK + +Neue Produkt-Features: {json.dumps(extraction_data.get('features'))} +Neue Produkt-Constraints: {json.dumps(extraction_data.get('constraints'))} + +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. +""" + # Fix: Prepend system instruction manually + full_conflict_prompt = sys_instr + "\n\n" + conflict_prompt + conflict_response = call_openai_chat(full_conflict_prompt, json_mode=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) + + prompt = f""" +PHASE 2: ICP DISCOVERY & DATA PROXIES +Based on the product features: {json.dumps(phase1_result.get('features'))} +And constraints: {json.dumps(phase1_result.get('constraints'))} + +Output JSON format ONLY: +{{ + "icps": [ {{ "name": "Industry Name", "rationale": "Rationale" }} ], + "dataProxies": [ {{ "target": "Criteria", "method": "How" }} ] +}} +""" if lang == 'en' else f""" +PHASE 2: ICP DISCOVERY & DATA PROXIES +Basierend auf Features: {json.dumps(phase1_result.get('features'))} +Und Constraints: {json.dumps(phase1_result.get('constraints'))} + +Output JSON format ONLY: +{{ + "icps": [ {{ "name": "Branchen Name", "rationale": "Begründung" }} ], + "dataProxies": [ {{ "target": "Kriterium", "method": "Methode" }} ] +}} +""" + # Fix: Prepend system instruction manually + full_prompt = sys_instr + "\n\n" + prompt + response = call_openai_chat(full_prompt, json_mode=True) + print(response) + +def hunt_whales(data): + phase2_result = data.get('phase2Result') + lang = data.get('language', 'de') + sys_instr = get_system_instruction(lang) + prompt = f"PHASE 3: WHALE HUNTING for {json.dumps(phase2_result.get('icps'))}. Identify 3-5 concrete DACH companies per industry and buying center roles. Output JSON ONLY." + # Fix: Prepend system instruction manually + full_prompt = sys_instr + "\n\n" + prompt + response = call_openai_chat(full_prompt, json_mode=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) + prompt = f"PHASE 4: STRATEGY Matrix for {json.dumps(phase3_result)}. Apply Hybrid logic. Output JSON ONLY." + # Fix: Prepend system instruction manually + full_prompt = sys_instr + "\n\n" + prompt + response = call_openai_chat(full_prompt, json_mode=True) + print(response) + +def generate_assets(data): + lang = data.get('language', 'de') + sys_instr = get_system_instruction(lang) + prompt = f"PHASE 5: GTM STRATEGY REPORT Markdown. Use facts, TCO, ROI. Data: {json.dumps(data)}" + # Fix: Prepend system instruction manually + 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) + prompt = f"PHASE 6: Battlecards and MJ Prompts. Data: {json.dumps(data)}. Output JSON ONLY." + # Fix: Prepend system instruction manually + full_prompt = sys_instr + "\n\n" + prompt + response = call_openai_chat(full_prompt, json_mode=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() - # Read stdin only if there is input, otherwise data is empty dict if not sys.stdin.isatty(): try: data = json.loads(sys.stdin.read()) - except json.JSONDecodeError: + except: data = {} else: data = {} - if args.mode == "analyze_product": - analyze_product(data) - elif args.mode == "discover_icps": - discover_icps(data) - elif args.mode == "hunt_whales": - hunt_whales(data) - elif args.mode == "develop_strategy": - develop_strategy(data) - elif args.mode == "generate_assets": - generate_assets(data) - elif args.mode == "generate_sales_enablement": - generate_sales_enablement(data) - # DB Modes - elif args.mode == "save_project": - save_project_handler(data) - elif args.mode == "list_projects": - list_projects_handler(data) - elif args.mode == "load_project": - load_project_handler(data) - elif args.mode == "delete_project": - delete_project_handler(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({"status": "error", "message": f"Unknown mode: {args.mode}"})) - -if __name__ == "__main__": - main() + print(json.dumps({"error": f"Unknown mode: {args.mode}"})) if __name__ == "__main__": main()