feat(gtm): v2.5 - Hard Fact Extraction & UI
- Backend: Implemented secondary extraction phase for structured specs (JSON schema). - Backend: Added strict normalization rules (min, cm, kg). - Frontend: Added 'Phase1Data' interface update for specs. - Frontend: Implemented new UI component for 'Technical Specifications' in Phase 1. - Frontend: Updated header and sidebar to display 'v2.5' build marker. - Docs: Updated architectural documentation.
This commit is contained in:
@@ -1153,6 +1153,95 @@ const App: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* NEW: Hard Facts Specs Display */}
|
||||
{state.phase1Result?.specs && 'metadata' in state.phase1Result.specs && (
|
||||
<div className="p-6 rounded-xl border transition-colors bg-white border-slate-200 dark:bg-robo-800 dark:border-robo-700">
|
||||
<h2 className="text-xl font-bold mb-4 flex items-center gap-2 text-slate-900 dark:text-white">
|
||||
<Database className="text-blue-500" /> Technical Specifications (Hard Facts)
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
{/* Core Data */}
|
||||
<div className="p-4 bg-slate-50 dark:bg-robo-900 rounded-lg border border-slate-200 dark:border-robo-700">
|
||||
<h3 className="text-xs font-bold uppercase tracking-wider text-slate-500 mb-3">Core Data</h3>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between border-b border-slate-200 dark:border-robo-800 pb-1">
|
||||
<span className="text-slate-500">Model</span>
|
||||
<span className="font-bold text-slate-800 dark:text-slate-200">{state.phase1Result.specs.metadata.brand} {state.phase1Result.specs.metadata.model_name}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-slate-200 dark:border-robo-800 pb-1">
|
||||
<span className="text-slate-500">Category</span>
|
||||
<span className="capitalize text-slate-800 dark:text-slate-200">{state.phase1Result.specs.metadata.category}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-slate-200 dark:border-robo-800 pb-1">
|
||||
<span className="text-slate-500">Runtime</span>
|
||||
<span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.core_specs.battery_runtime_min ? `${state.phase1Result.specs.core_specs.battery_runtime_min} min` : '-'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-slate-200 dark:border-robo-800 pb-1">
|
||||
<span className="text-slate-500">Weight</span>
|
||||
<span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.core_specs.weight_kg ? `${state.phase1Result.specs.core_specs.weight_kg} kg` : '-'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-slate-200 dark:border-robo-800 pb-1">
|
||||
<span className="text-slate-500">Dimensions</span>
|
||||
<span className="text-slate-800 dark:text-slate-200">
|
||||
{state.phase1Result.specs.core_specs.dimensions_cm.l ? `${state.phase1Result.specs.core_specs.dimensions_cm.l}x${state.phase1Result.specs.core_specs.dimensions_cm.w}x${state.phase1Result.specs.core_specs.dimensions_cm.h} cm` : '-'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Navigation</span>
|
||||
<span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.core_specs.navigation_type || '-'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Layer Data */}
|
||||
<div className="p-4 bg-slate-50 dark:bg-robo-900 rounded-lg border border-slate-200 dark:border-robo-700">
|
||||
<h3 className="text-xs font-bold uppercase tracking-wider text-slate-500 mb-3">Performance Layer</h3>
|
||||
|
||||
{!state.phase1Result.specs.layers.cleaning && !state.phase1Result.specs.layers.service && !state.phase1Result.specs.layers.security && (
|
||||
<div className="text-sm text-slate-400 italic">No specific layer data detected.</div>
|
||||
)}
|
||||
|
||||
{state.phase1Result.specs.layers.cleaning && (
|
||||
<div className="space-y-2 text-sm mb-4">
|
||||
<div className="text-emerald-600 font-bold mb-1 flex items-center gap-1"><Sparkles size={12}/> Cleaning Mode</div>
|
||||
<div className="flex justify-between"><span className="text-slate-500">Area Perf:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.cleaning.area_performance_sqm_h || '-'} m²/h</span></div>
|
||||
<div className="flex justify-between"><span className="text-slate-500">Fresh Water:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.cleaning.fresh_water_l || '-'} L</span></div>
|
||||
</div>
|
||||
)}
|
||||
{state.phase1Result.specs.layers.service && (
|
||||
<div className="space-y-2 text-sm mb-4">
|
||||
<div className="text-blue-600 font-bold mb-1 flex items-center gap-1"><Briefcase size={12}/> Service Mode</div>
|
||||
<div className="flex justify-between"><span className="text-slate-500">Payload:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.service.max_payload_kg || '-'} kg</span></div>
|
||||
<div className="flex justify-between"><span className="text-slate-500">Trays:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.service.number_of_trays || '-'}</span></div>
|
||||
</div>
|
||||
)}
|
||||
{state.phase1Result.specs.layers.security && (
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="text-red-600 font-bold mb-1 flex items-center gap-1"><Shield size={12}/> Security Mode</div>
|
||||
<div className="flex justify-between"><span className="text-slate-500">Night Vision:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.security.night_vision ? 'Yes' : 'No'}</span></div>
|
||||
<div className="flex justify-between"><span className="text-slate-500">Cameras:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.security.camera_types.join(', ') || '-'}</span></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Extended Features */}
|
||||
{state.phase1Result.specs.extended_features.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-xs font-bold uppercase tracking-wider text-slate-500 mb-2">Extended Features</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{state.phase1Result.specs.extended_features.map((feat, idx) => (
|
||||
<span key={idx} className="px-2 py-1 bg-slate-100 dark:bg-robo-900 text-xs rounded border border-slate-200 dark:border-robo-700 text-slate-700 dark:text-slate-300">
|
||||
<span className="font-bold">{feat.feature}:</span> {feat.value} {feat.unit}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{state.phase1Result?.conflictCheck.hasConflict ? (
|
||||
<div className="border p-6 rounded-xl flex gap-4 transition-colors
|
||||
bg-red-50 border-red-200
|
||||
|
||||
@@ -78,7 +78,7 @@ export const Layout: React.FC<LayoutProps> = ({
|
||||
<div>
|
||||
<h1 className="text-xl font-bold font-mono tracking-tighter flex items-center gap-2 text-slate-900 dark:text-white">
|
||||
<div className="w-3 h-3 bg-robo-500 dark:bg-robo-accent rounded-full animate-pulse"></div>
|
||||
ROBOPLANET
|
||||
ROBOPLANET v2.5
|
||||
</h1>
|
||||
<p className="text-xs mt-1 uppercase tracking-widest text-slate-500 dark:text-robo-400">GTM Architect Engine</p>
|
||||
</div>
|
||||
|
||||
@@ -28,6 +28,47 @@ export interface Phase1Data {
|
||||
relatedProduct?: string;
|
||||
};
|
||||
rawAnalysis: string;
|
||||
specs?: {
|
||||
metadata: {
|
||||
product_id: string;
|
||||
brand: string;
|
||||
model_name: string;
|
||||
category: string;
|
||||
manufacturer_url: string;
|
||||
};
|
||||
core_specs: {
|
||||
battery_runtime_min: number | null;
|
||||
charge_time_min: number | null;
|
||||
weight_kg: number | null;
|
||||
dimensions_cm: { l: number | null; w: number | null; h: number | null };
|
||||
max_slope_deg: number | null;
|
||||
ip_rating: string | null;
|
||||
climb_height_cm: number | null;
|
||||
navigation_type: string | null;
|
||||
connectivity: string[];
|
||||
};
|
||||
layers: {
|
||||
cleaning?: {
|
||||
fresh_water_l: number | null;
|
||||
dirty_water_l: number | null;
|
||||
area_performance_sqm_h: number | null;
|
||||
mop_pressure_kg: number | null;
|
||||
};
|
||||
service?: {
|
||||
max_payload_kg: number | null;
|
||||
number_of_trays: number | null;
|
||||
display_size_inch: number | null;
|
||||
ads_capable: boolean | null;
|
||||
};
|
||||
security?: {
|
||||
camera_types: string[];
|
||||
night_vision: boolean | null;
|
||||
gas_detection: string[];
|
||||
at_interface: boolean | null;
|
||||
};
|
||||
};
|
||||
extended_features: { feature: string; value: string; unit: string }[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface Phase2Data {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Dokumentation: GTM Architect Engine (v2.4)
|
||||
# Dokumentation: GTM Architect Engine (v2.5)
|
||||
|
||||
## 1. Projektübersicht
|
||||
|
||||
@@ -26,7 +26,7 @@ graph LR
|
||||
|
||||
1. **Frontend (`/gtm-architect`):**
|
||||
* Framework: **React** (Vite + TypeScript).
|
||||
* Features: **Session History** (Laden/Löschen alter Projekte) und **Markdown Upload**.
|
||||
* Features: **Session History**, **Hard Fact Extraction UI** und **Markdown Upload**.
|
||||
|
||||
2. **Backend Bridge (`server.cjs`):**
|
||||
* Runtime: **Node.js** (Express).
|
||||
@@ -67,7 +67,7 @@ Die `call_gemini_image`-Funktion wählt automatisch die beste Methode basierend
|
||||
|
||||
| Phase | Modus | Input | Output | Beschreibung |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **1** | `phase1` | Rohtext / URL | Features, Constraints | Extrahiert technische Daten & **erstellt DB-Projekt**. |
|
||||
| **1** | `phase1` | Rohtext / URL | Features, Constraints, **Specs** | Extrahiert technische Daten, **Hard Facts (Specs)** & erstellt DB-Projekt. |
|
||||
| **2** | `phase2` | Phase 1 Result | ICPs, Data Proxies | Identifiziert ideale Kundenprofile. |
|
||||
| **3** | `phase3` | Phase 2 Result | Whales, Rollen | Identifiziert Zielkunden & Buying Center. |
|
||||
| **4** | `phase4` | Phase 1 & 3 | Strategy Matrix | Entwickelt "Angles" und Pain-Points. |
|
||||
@@ -91,6 +91,13 @@ Das System verwaltet persistente Sitzungen in der SQLite-Datenbank:
|
||||
|
||||
## 7. Historie & Fixes (Jan 2026)
|
||||
|
||||
* **[UPGRADE] v2.5: Hard Fact Extraction**
|
||||
* **Phase 1 Erweiterung:** Implementierung eines sekundären Extraktions-Schritts für "Hard Facts" (Specs).
|
||||
* **Strukturiertes Daten-Schema:** Integration von `templates/json_struktur_roboplanet.txt`.
|
||||
* **Normalisierung:** Automatische Standardisierung von Einheiten (Minuten, cm, kg, m²/h).
|
||||
* **Frontend Update:** Neue UI-Komponente zur Anzeige der technischen Daten (Core Data, Layer, Extended Features).
|
||||
* **Sidebar & Header:** Update auf "ROBOPLANET v2.5".
|
||||
|
||||
* **[UPGRADE] v2.4:**
|
||||
* Dokumentation der Kern-Engine (`helpers.py`) mit Dual SDK & Hybrid Image Generation.
|
||||
* Aktualisierung der Architektur-Übersicht und Komponenten-Beschreibungen.
|
||||
|
||||
@@ -203,6 +203,90 @@ def phase1(payload):
|
||||
|
||||
try:
|
||||
data = json.loads(response)
|
||||
|
||||
# --- PART 2: HARD FACTS EXTRACTION ---
|
||||
spec_schema = """
|
||||
{
|
||||
"metadata": {
|
||||
"product_id": "string (slug)",
|
||||
"brand": "string",
|
||||
"model_name": "string",
|
||||
"category": "cleaning | service | security | industrial",
|
||||
"manufacturer_url": "string"
|
||||
},
|
||||
"core_specs": {
|
||||
"battery_runtime_min": "integer (standardized to minutes)",
|
||||
"charge_time_min": "integer (standardized to minutes)",
|
||||
"weight_kg": "float",
|
||||
"dimensions_cm": { "l": "float", "w": "float", "h": "float" },
|
||||
"max_slope_deg": "float",
|
||||
"ip_rating": "string",
|
||||
"climb_height_cm": "float",
|
||||
"navigation_type": "string (e.g. SLAM, LiDAR, VSLAM)",
|
||||
"connectivity": ["string"]
|
||||
},
|
||||
"layers": {
|
||||
"cleaning": {
|
||||
"fresh_water_l": "float",
|
||||
"dirty_water_l": "float",
|
||||
"area_performance_sqm_h": "float",
|
||||
"mop_pressure_kg": "float"
|
||||
},
|
||||
"service": {
|
||||
"max_payload_kg": "float",
|
||||
"number_of_trays": "integer",
|
||||
"display_size_inch": "float",
|
||||
"ads_capable": "boolean"
|
||||
},
|
||||
"security": {
|
||||
"camera_types": ["string"],
|
||||
"night_vision": "boolean",
|
||||
"gas_detection": ["string"],
|
||||
"at_interface": "boolean"
|
||||
}
|
||||
},
|
||||
"extended_features": [
|
||||
{ "feature": "string", "value": "string", "unit": "string" }
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
specs_prompt = f"""
|
||||
PHASE 1 (Part 2): HARD FACT EXTRACTION
|
||||
Input: "{analysis_content}"
|
||||
|
||||
Task: Extract technical specifications strictly according to the provided JSON schema.
|
||||
|
||||
NORMALIZATION RULES (STRICTLY FOLLOW):
|
||||
1. Time: Convert ALL time values (runtime, charging) to MINUTES (Integer). Example: "1:30 h" -> 90, "2 hours" -> 120.
|
||||
2. Dimensions/Weight: All lengths in CM, weights in KG.
|
||||
3. Performance: Area performance always in m²/h.
|
||||
4. Booleans: Use true/false (not strings).
|
||||
5. Unknowns: If a value is not in the text, set it to null. DO NOT HALLUCINATE.
|
||||
|
||||
LOGIC FOR LAYERS:
|
||||
- If product uses water/brushes -> Fill 'layers.cleaning'.
|
||||
- If product delivers items/trays -> Fill 'layers.service'.
|
||||
- If product patrols/detects -> Fill 'layers.security'.
|
||||
|
||||
EXTENDED FEATURES:
|
||||
- Put any technical feature that doesn't fit the schema into 'extended_features'.
|
||||
|
||||
Output JSON format ONLY based on this schema:
|
||||
{spec_schema}
|
||||
"""
|
||||
|
||||
log_and_save(project_id, "phase1_specs", "prompt", specs_prompt)
|
||||
specs_response = call_gemini_flash(specs_prompt, system_instruction=sys_instr, json_mode=True)
|
||||
log_and_save(project_id, "phase1_specs", "response", specs_response)
|
||||
|
||||
try:
|
||||
specs_data = json.loads(specs_response)
|
||||
data['specs'] = specs_data
|
||||
except json.JSONDecodeError:
|
||||
logging.error(f"Failed to decode JSON from Gemini response in phase1 (specs): {specs_response}")
|
||||
data['specs'] = {"error": "Failed to extract specs", "raw": specs_response}
|
||||
|
||||
db_manager.save_gtm_result(project_id, 'phase1_result', json.dumps(data))
|
||||
|
||||
# WICHTIG: ID zurückgeben, damit Frontend sie speichert
|
||||
|
||||
43
templates/json_struktur_roboplanet.txt
Normal file
43
templates/json_struktur_roboplanet.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"metadata": {
|
||||
"product_id": "string (slug)",
|
||||
"brand": "string",
|
||||
"model_name": "string",
|
||||
"category": "cleaning | service | security | industrial",
|
||||
"manufacturer_url": "string"
|
||||
},
|
||||
"core_specs": {
|
||||
"battery_runtime_min": "integer (standardized to minutes)",
|
||||
"charge_time_min": "integer (standardized to minutes)",
|
||||
"weight_kg": "float",
|
||||
"dimensions_cm": { "l": "float", "w": "float", "h": "float" },
|
||||
"max_slope_deg": "float",
|
||||
"ip_rating": "string",
|
||||
"climb_height_cm": "float",
|
||||
"navigation_type": "string (e.g. SLAM, LiDAR, VSLAM)",
|
||||
"connectivity": ["string"]
|
||||
},
|
||||
"layers": {
|
||||
"cleaning": {
|
||||
"fresh_water_l": "float",
|
||||
"dirty_water_l": "float",
|
||||
"area_performance_sqm_h": "float",
|
||||
"mop_pressure_kg": "float"
|
||||
},
|
||||
"service": {
|
||||
"max_payload_kg": "float",
|
||||
"number_of_trays": "integer",
|
||||
"display_size_inch": "float",
|
||||
"ads_capable": "boolean"
|
||||
},
|
||||
"security": {
|
||||
"camera_types": ["string"],
|
||||
"night_vision": "boolean",
|
||||
"gas_detection": ["string"],
|
||||
"at_interface": "boolean"
|
||||
}
|
||||
},
|
||||
"extended_features": [
|
||||
{ "feature": "string", "value": "string", "unit": "string" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user