feat: extract individual values from stage outputs (Issue #47)
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
This commit is contained in:
parent
e799edbae4
commit
da803da816
|
|
@ -858,17 +858,41 @@ async def execute_unified_prompt(
|
||||||
stages_debug = result['debug'].get('stages', [])
|
stages_debug = result['debug'].get('stages', [])
|
||||||
|
|
||||||
# First, collect stage outputs (outputs from base prompts in each stage)
|
# 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:
|
for stage_debug in stages_debug:
|
||||||
stage_num = stage_debug.get('stage', 0)
|
stage_num = stage_debug.get('stage', 0)
|
||||||
stage_output = stage_debug.get('output', {})
|
stage_output = stage_debug.get('output', {})
|
||||||
if isinstance(stage_output, dict):
|
if isinstance(stage_output, dict):
|
||||||
for output_key, output_value in stage_output.items():
|
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}"
|
placeholder_key = f"stage_{stage_num}_{output_key}"
|
||||||
stage_outputs[placeholder_key] = output_value
|
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 stage_debug in stages_debug:
|
||||||
for prompt_debug in stage_debug.get('prompts', []):
|
for prompt_debug in stage_debug.get('prompts', []):
|
||||||
resolved_keys = []
|
resolved_keys = []
|
||||||
|
|
@ -883,9 +907,10 @@ async def execute_unified_prompt(
|
||||||
# Get value: first try stage outputs, then cleaned_values
|
# Get value: first try stage outputs, then cleaned_values
|
||||||
value = stage_outputs.get(key, cleaned_values.get(key, ''))
|
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_'):
|
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:
|
else:
|
||||||
# Find description in catalog
|
# Find description in catalog
|
||||||
desc = None
|
desc = None
|
||||||
|
|
@ -895,10 +920,12 @@ async def execute_unified_prompt(
|
||||||
desc = matching[0].get('description', '')
|
desc = matching[0].get('description', '')
|
||||||
break
|
break
|
||||||
desc = desc or ''
|
desc = desc or ''
|
||||||
|
is_stage_raw = False
|
||||||
|
|
||||||
metadata['placeholders'][key] = {
|
metadata['placeholders'][key] = {
|
||||||
'value': value if isinstance(value, str) else json.dumps(value, ensure_ascii=False),
|
'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
|
# Save to database with metadata
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,15 @@ function InsightCard({ ins, onDelete, defaultOpen=false, prompts=[] }) {
|
||||||
const metadata = metadataRaw
|
const metadata = metadataRaw
|
||||||
const allPlaceholders = placeholdersRaw
|
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
|
const placeholders = expertMode
|
||||||
? allPlaceholders
|
? allPlaceholders
|
||||||
: Object.fromEntries(
|
: Object.fromEntries(
|
||||||
Object.entries(allPlaceholders).filter(([key, data]) => {
|
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 || ''
|
const val = data.value || ''
|
||||||
return val.trim() !== '' && val !== 'nicht verfügbar' && val !== '[Nicht verfügbar]'
|
return val.trim() !== '' && val !== 'nicht verfügbar' && val !== '[Nicht verfügbar]'
|
||||||
})
|
})
|
||||||
|
|
@ -148,36 +152,50 @@ function InsightCard({ ins, onDelete, defaultOpen=false, prompts=[] }) {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{Object.entries(placeholders).map(([key, data]) => (
|
{Object.entries(placeholders).map(([key, data]) => {
|
||||||
<tr key={key} style={{ borderBottom: '1px solid var(--border)' }}>
|
const isExtracted = data.is_extracted
|
||||||
<td style={{
|
const isStageRaw = data.is_stage_raw
|
||||||
padding: '6px 8px',
|
|
||||||
fontFamily: 'monospace',
|
return (
|
||||||
color: 'var(--accent)',
|
<tr key={key} style={{
|
||||||
whiteSpace: 'nowrap',
|
borderBottom: '1px solid var(--border)',
|
||||||
verticalAlign: 'top'
|
background: isStageRaw && expertMode ? 'var(--surface)' : 'transparent'
|
||||||
}}>
|
}}>
|
||||||
{key}
|
<td style={{
|
||||||
</td>
|
padding: '6px 8px',
|
||||||
<td style={{
|
fontFamily: 'monospace',
|
||||||
padding: '6px 8px',
|
color: isStageRaw ? 'var(--text3)' : (isExtracted ? '#6B8E23' : 'var(--accent)'),
|
||||||
fontFamily: 'monospace',
|
whiteSpace: 'nowrap',
|
||||||
wordBreak: 'break-word',
|
verticalAlign: 'top',
|
||||||
maxWidth: '400px',
|
fontSize: isStageRaw ? 10 : 11
|
||||||
verticalAlign: 'top'
|
}}>
|
||||||
}}>
|
{isExtracted && '↳ '}
|
||||||
{data.value}
|
{isStageRaw && '🔬 '}
|
||||||
</td>
|
{key}
|
||||||
<td style={{
|
</td>
|
||||||
padding: '6px 8px',
|
<td style={{
|
||||||
color: 'var(--text3)',
|
padding: '6px 8px',
|
||||||
fontSize: 10,
|
fontFamily: 'monospace',
|
||||||
verticalAlign: 'top'
|
wordBreak: 'break-word',
|
||||||
}}>
|
maxWidth: '400px',
|
||||||
{data.description || '—'}
|
verticalAlign: 'top',
|
||||||
</td>
|
fontSize: isStageRaw ? 9 : 11,
|
||||||
</tr>
|
color: isStageRaw ? 'var(--text3)' : 'var(--text1)'
|
||||||
))}
|
}}>
|
||||||
|
{data.value}
|
||||||
|
</td>
|
||||||
|
<td style={{
|
||||||
|
padding: '6px 8px',
|
||||||
|
color: 'var(--text3)',
|
||||||
|
fontSize: 10,
|
||||||
|
verticalAlign: 'top',
|
||||||
|
fontStyle: isExtracted ? 'italic' : 'normal'
|
||||||
|
}}>
|
||||||
|
{data.description || '—'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user