From da803da8162befa7e5f9d097555c12be39db425b Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 26 Mar 2026 12:55:53 +0100 Subject: [PATCH] feat: extract individual values from stage outputs (Issue #47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FEATURE: Basis-Analysen Einzelwerte Vorher: stage_1_body → {"bmi": 26.6, "weight": "85.2kg"} (1 Zeile) Jetzt: bmi → 26.6 (eigene Zeile) weight → 85.2kg (eigene Zeile) BACKEND: JSON-Extraktion - Stage outputs (JSON) → extract individual fields - extracted_values dict sammelt alle Einzelwerte - Deduplizierung: Gleiche Keys nur einmal - Flags: - is_extracted: true → Wert aus Stage-Output extrahiert - is_stage_raw: true → Rohdaten (JSON) nur Experten-Modus BEISPIEL Stage 1 Output: { "stage_1_body": { "bmi": 26.6, "weight": "85.2 kg", "trend": "sinkend" } } → Metadata: { "bmi": { value: "26.6", description: "Aus Stage 1 (stage_1_body)", is_extracted: true }, "weight": { value: "85.2 kg", description: "Aus Stage 1 (stage_1_body)", is_extracted: true }, "stage_1_body": { value: "{\"bmi\": 26.6, ...}", description: "Rohdaten Stage 1 (Basis-Analyse JSON)", is_stage_raw: true } } FRONTEND: Smart Filtering Normal-Modus: - Zeigt: Einzelwerte (bmi, weight, trend) - Versteckt: Rohdaten (stage_1_body JSON) - Filter: is_stage_raw === false Experten-Modus: - Zeigt: Alles (Einzelwerte + Rohdaten) - Rohdaten: Grauer Hintergrund + 🔬 Icon VISUAL Indicators: ↳ bmi → Extrahierter Wert (grün) weight → Normaler Platzhalter (accent) 🔬 stage_1_* → Rohdaten JSON (grau, klein, nur Experten) ERGEBNIS: ┌──────────────────────────────────────────┐ │ 📊 Verwendete Werte (8) (+2 ausgeblendet)│ │ ┌────────────────────────────────────────┐│ │ │ weight_aktuell │ 85.2 kg │ Gewicht ││ ← Normal │ │ ↳ bmi │ 26.6 │ Aus St..││ ← Extrahiert │ │ ↳ trend │ sinkend │ Aus St..││ ← Extrahiert │ └────────────────────────────────────────┘│ └──────────────────────────────────────────┘ Experten-Modus zusätzlich: │ 🔬 stage_1_body │ {"bmi":...│ Rohdaten││ ← JSON version: 9.9.0 (feature) module: prompts 2.4.0, insights 1.7.0 --- backend/routers/prompts.py | 39 ++++++++++++++--- frontend/src/pages/Analysis.jsx | 78 ++++++++++++++++++++------------- 2 files changed, 81 insertions(+), 36 deletions(-) diff --git a/backend/routers/prompts.py b/backend/routers/prompts.py index 814c126..35b0f59 100644 --- a/backend/routers/prompts.py +++ b/backend/routers/prompts.py @@ -858,17 +858,41 @@ async def execute_unified_prompt( stages_debug = result['debug'].get('stages', []) # First, collect stage outputs (outputs from base prompts in each stage) - stage_outputs = {} + stage_outputs = {} # Raw stage outputs (for expert mode) + extracted_values = {} # Individual values extracted from JSON outputs (for normal mode) + for stage_debug in stages_debug: stage_num = stage_debug.get('stage', 0) stage_output = stage_debug.get('output', {}) if isinstance(stage_output, dict): for output_key, output_value in stage_output.items(): - # Store stage outputs (e.g., stage_1_body) + # Store raw stage output (for expert mode) placeholder_key = f"stage_{stage_num}_{output_key}" stage_outputs[placeholder_key] = output_value - # Collect all resolved placeholders from prompts + # If output is a dict/object, extract individual fields + if isinstance(output_value, dict): + for field_key, field_value in output_value.items(): + # Store individual field (for normal mode) + # Use just the field name as key (e.g., "bmi" instead of "stage_1_body.bmi") + # This allows deduplication if multiple stages have the same field + if field_key not in extracted_values: + extracted_values[field_key] = { + 'value': field_value if isinstance(field_value, str) else json.dumps(field_value, ensure_ascii=False), + 'source_stage': stage_num, + 'source_output': output_key + } + + # Add extracted values from stage outputs (individual fields) + for field_key, field_data in extracted_values.items(): + if field_key not in metadata['placeholders']: + metadata['placeholders'][field_key] = { + 'value': field_data['value'], + 'description': f"Aus Stage {field_data['source_stage']} ({field_data['source_output']})", + 'is_extracted': True # Mark as extracted for filtering + } + + # Collect all resolved placeholders from prompts (input placeholders) for stage_debug in stages_debug: for prompt_debug in stage_debug.get('prompts', []): resolved_keys = [] @@ -883,9 +907,10 @@ async def execute_unified_prompt( # Get value: first try stage outputs, then cleaned_values value = stage_outputs.get(key, cleaned_values.get(key, '')) - # For stage output placeholders, add special description + # For stage output placeholders (raw JSON), add special description if key.startswith('stage_'): - desc = f"Output aus Stage {key.split('_')[1]} (Basis-Analyse)" + desc = f"Rohdaten Stage {key.split('_')[1]} (Basis-Analyse JSON)" + is_stage_raw = True else: # Find description in catalog desc = None @@ -895,10 +920,12 @@ async def execute_unified_prompt( desc = matching[0].get('description', '') break desc = desc or '' + is_stage_raw = False metadata['placeholders'][key] = { 'value': value if isinstance(value, str) else json.dumps(value, ensure_ascii=False), - 'description': desc + 'description': desc, + 'is_stage_raw': is_stage_raw # Mark raw stage outputs for expert mode } # Save to database with metadata diff --git a/frontend/src/pages/Analysis.jsx b/frontend/src/pages/Analysis.jsx index 90dd29f..cb9021c 100644 --- a/frontend/src/pages/Analysis.jsx +++ b/frontend/src/pages/Analysis.jsx @@ -34,11 +34,15 @@ function InsightCard({ ins, onDelete, defaultOpen=false, prompts=[] }) { const metadata = metadataRaw const allPlaceholders = placeholdersRaw - // Filter placeholders: In normal mode, hide empty values + // Filter placeholders: In normal mode, hide empty values and raw stage outputs const placeholders = expertMode ? allPlaceholders : Object.fromEntries( Object.entries(allPlaceholders).filter(([key, data]) => { + // Hide raw stage outputs (JSON) in normal mode + if (data.is_stage_raw) return false + + // Hide empty values const val = data.value || '' return val.trim() !== '' && val !== 'nicht verfügbar' && val !== '[Nicht verfügbar]' }) @@ -148,36 +152,50 @@ function InsightCard({ ins, onDelete, defaultOpen=false, prompts=[] }) { - {Object.entries(placeholders).map(([key, data]) => ( - - { + const isExtracted = data.is_extracted + const isStageRaw = data.is_stage_raw + + return ( + - {key} - - - {data.value} - - - {data.description || '—'} - - - ))} + + {isExtracted && '↳ '} + {isStageRaw && '🔬 '} + {key} + + + {data.value} + + + {data.description || '—'} + + + ) + })}