309 lines
13 KiB
Python
309 lines
13 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
|
|
|
|
# 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)
|
|
|
|
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, 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'))
|
|
|
|
conflict_prompt = f"""
|
|
PHASE 1-B: PORTFOLIO CONFLICT CHECK
|
|
|
|
New Product Features: {features_json}
|
|
New Product Constraints: {constraints_json}
|
|
|
|
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: {features_json}
|
|
Neue Produkt-Constraints: {constraints_json}
|
|
|
|
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, 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'))
|
|
|
|
prompt = f"""
|
|
PHASE 2: ICP DISCOVERY & DATA PROXIES
|
|
Based on the product features: {features_json}
|
|
And constraints: {constraints_json}
|
|
|
|
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: {features_json}
|
|
Und Constraints: {constraints_json}
|
|
|
|
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, 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 = f"PHASE 3: WHALE HUNTING for {icps_json}. 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, 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 = f"PHASE 4: STRATEGY Matrix for {phase3_json}. Apply Hybrid logic. Output JSON ONLY."
|
|
# Fix: Prepend system instruction manually
|
|
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 = f"PHASE 5: GTM STRATEGY REPORT Markdown. Use facts, TCO, ROI. Data: {data_json}"
|
|
# 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)
|
|
data_json = json.dumps(data)
|
|
prompt = f"PHASE 6: Battlecards and MJ Prompts. Data: {data_json}. Output JSON ONLY."
|
|
# Fix: Prepend system instruction manually
|
|
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()
|
|
|
|
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() |