mitai-jinkendo/backend/workflow_executor.py

1222 lines
48 KiB
Python
Raw Normal View History

feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
"""
Workflow Executor (Phase 4)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
Führt Workflows mit conditional branching und path consolidation aus.
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
Phase 2: Sequential execution
Phase 3: Conditional branching, Logic Nodes, Fallback strategies
Phase 4: Join Nodes, Path consolidation
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
Konzept-Basis: konzept_workflow_engine_konsolidated.md
Anforderungsanalyse: anforderungsanalyse_umsetzungsplan.md (Phase 2-4)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
"""
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
from typing import Dict, Any, List, Optional, Set
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
from datetime import datetime
import uuid
import logging
import json
from jinja2 import Environment, ChainableUndefined, TemplateError
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
from workflow_models import (
WorkflowGraph, WorkflowNode, NodeExecutionState, ExecutionResult,
NodeStatus, NormalizedSignal, FallbackStrategy, SignalStatus,
EndNodeOutputMode
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
)
from workflow_engine import parse_workflow_graph, get_execution_order
from question_augmenter import (
augment_prompt_with_questions,
parse_question_augmentations_from_jsonb
)
from result_container_parser import parse_result_container
from normalization_engine import normalize_all_signals, normalize_decision_signal, load_question_catalog
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
from logic_evaluator import evaluate_logic_expression, resolve_signal_reference
from join_evaluator import evaluate_join_node as evaluate_join_node_core
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
from db import get_db, get_cursor
logger = logging.getLogger(__name__)
async def execute_workflow(
workflow_id: Optional[str] = None,
graph_data: Optional[Dict] = None, # Phase 5: Direkt von ai_prompts.graph_data
profile_id: str = None,
variables: Dict[str, Any] = None,
openrouter_call_func = None, # Callback für LLM-Calls: async (prompt, model) -> str
enable_debug: bool = False,
progress_callback = None # NEW: Optional callback für Progress-Updates: async (event_type, data) -> None
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
) -> ExecutionResult:
"""
Führt einen Workflow aus (mit conditional branching und path consolidation).
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
Phase 2: Linear execution in topological order.
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
Phase 3: Conditional branching basierend auf logic nodes.
Phase 4: Join nodes und path consolidation.
Phase 5: Unterstützt graph_data direkt (aus ai_prompts, nicht workflow_definitions)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
Args:
workflow_id: UUID des Workflows (legacy, für workflow_definitions Tabelle)
graph_data: Workflow-Graph als Dict (NEU, für ai_prompts.graph_data)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
profile_id: UUID des Profils
variables: Platzhalter-Werte (z.B. {"name": "Lars", ...})
openrouter_call_func: async (prompt, model) -> str
enable_debug: Debug-Modus
Returns:
ExecutionResult mit allen node_states
Beispiel (Phase 5):
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
>>> result = await execute_workflow(
... graph_data={"nodes": [...], "edges": [...]},
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
... profile_id="test-profile",
... variables={"name": "Lars"},
... openrouter_call_func=my_llm_func
... )
"""
execution_id = str(uuid.uuid4())
started_at = datetime.utcnow().isoformat()
logger.info(f"Starting workflow execution: {execution_id}")
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# NEW: Progress-Callback für Start
if progress_callback:
await progress_callback("execution_started", {
"execution_id": execution_id,
"started_at": started_at
})
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
try:
# 1. Lade Workflow-Definition
if graph_data:
# Phase 5: Graph direkt aus ai_prompts.graph_data (already a dict)
graph_dict = graph_data
logger.debug(f"Using provided graph_data")
elif workflow_id:
# Phase 0-4: Graph aus workflow_definitions Tabelle (legacy)
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"SELECT graph FROM workflow_definitions WHERE id = %s AND active = true",
(workflow_id,)
)
row = cur.fetchone()
if not row:
raise ValueError(f"Workflow not found: {workflow_id}")
graph_dict = row['graph'] # PostgreSQL JSONB returns dict
logger.debug(f"Loaded graph from workflow_definitions: {workflow_id}")
else:
raise ValueError("Entweder workflow_id oder graph_data muss übergeben werden")
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# 2. Parse Graph
graph = parse_workflow_graph(graph_dict)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
logger.debug(f"Parsed graph: {len(graph.nodes)} nodes, {len(graph.edges)} edges")
# 4. Lade Question Catalog
with get_db() as conn:
catalog = load_question_catalog(conn)
logger.debug(f"Loaded catalog: {len(catalog)} question types")
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
# 5. Execute Nodes mit conditional branching (Phase 3)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
node_states: List[NodeExecutionState] = []
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
context = {
"variables": variables,
"profile_id": profile_id,
"node_results": {}, # Phase 3: Store full NodeExecutionState
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
"active_edges": {}, # Phase 3: Track edge activation
"graph": graph # Phase 4: Needed for End Node to access questions
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
}
# Phase 3: BFS traversal mit edge activation
start_node = next((n for n in graph.nodes if n.type == "start"), None)
if not start_node:
raise ValueError("No start node found in workflow")
visited: Set[str] = set()
queue = [start_node.id]
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
while queue:
node_id = queue.pop(0)
if node_id in visited:
continue
visited.add(node_id)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
node = next(n for n in graph.nodes if n.id == node_id)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
# Prüfe ob Node aktiv ist (mindestens eine incoming edge aktiv)
if not _has_active_incoming_edge(node, graph, context):
logger.info(f"Skipping node {node_id}: no active incoming edges")
node_states.append(NodeExecutionState(
node_id=node_id,
status=NodeStatus.SKIPPED,
error="no_active_incoming_edges",
started_at=datetime.utcnow().isoformat(),
completed_at=datetime.utcnow().isoformat()
))
continue
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
logger.info(f"Executing node: {node_id} (type: {node.type})")
node_state = await execute_node(
node=node,
context=context,
catalog=catalog,
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
graph=graph, # Phase 3: Needed for logic nodes
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
openrouter_call_func=openrouter_call_func,
enable_debug=enable_debug
)
node_states.append(node_state)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
context["node_results"][node_id] = node_state
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# NEW: Progress-Callback aufrufen (für SSE Streaming)
if progress_callback:
# Create a meaningful label for the node
node_label = node.prompt_slug if hasattr(node, 'prompt_slug') and node.prompt_slug else f"{node.type.value}-{node_id[:8]}"
await progress_callback("node_complete", {
"node_id": node_id,
"node_type": node.type,
"node_label": node_label,
"status": node_state.status.value,
"total_nodes": len(graph.nodes),
"completed_nodes": len([ns for ns in node_states if ns.status in [NodeStatus.EXECUTED, NodeStatus.SKIPPED]]),
"error": node_state.error if node_state.status == NodeStatus.FAILED else None
})
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
# Füge Nachfolger zur Queue hinzu
outgoing_edges = [e for e in graph.edges if e.from_node == node_id]
for edge in outgoing_edges:
# Prüfe ob Edge aktiv ist (default: True, Logic Nodes setzen auf False)
if context["active_edges"].get(edge.id, True):
queue.append(edge.to_node)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# 6. Aggregiere Ergebnisse
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
aggregated = aggregate_results(node_states, graph)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# 7. Speichere Execution State
completed_at = datetime.utcnow().isoformat()
save_execution_state(
execution_id=execution_id,
workflow_id=workflow_id,
profile_id=profile_id,
node_states=node_states,
status="completed",
started_at=started_at,
completed_at=completed_at
)
logger.info(f"Workflow execution completed: {execution_id}")
# NEW: Progress-Callback für erfolgreiche Fertigstellung
if progress_callback:
await progress_callback("execution_complete", {
"execution_id": execution_id,
"status": "completed",
"aggregated_result": aggregated,
"total_nodes": len(node_states),
"completed_nodes": len([ns for ns in node_states if ns.status == NodeStatus.EXECUTED]),
"skipped_nodes": len([ns for ns in node_states if ns.status == NodeStatus.SKIPPED]),
"failed_nodes": len([ns for ns in node_states if ns.status == NodeStatus.FAILED]),
"completed_at": completed_at
})
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
return ExecutionResult(
execution_id=execution_id,
workflow_id=workflow_id or "N/A", # Placeholder when graph_data is used directly
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
status="completed",
node_states=node_states,
aggregated_result=aggregated,
started_at=started_at,
completed_at=completed_at
)
except Exception as e:
logger.error(f"Workflow execution failed: {e}", exc_info=True)
# NEW: Progress-Callback für Fehler
if progress_callback:
await progress_callback("execution_failed", {
"execution_id": execution_id,
"status": "failed",
"error": str(e),
"completed_at": datetime.utcnow().isoformat()
})
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# Speichere Failed State
completed_at = datetime.utcnow().isoformat()
save_execution_state(
execution_id=execution_id,
workflow_id=workflow_id, # Can be None when graph_data is used
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
profile_id=profile_id,
node_states=node_states if 'node_states' in locals() else [],
status="failed",
started_at=started_at,
completed_at=completed_at,
error=str(e)
)
return ExecutionResult(
execution_id=execution_id,
workflow_id=workflow_id or "N/A", # ExecutionResult requires string, use placeholder
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
status="failed",
node_states=node_states if 'node_states' in locals() else [],
aggregated_result={},
started_at=started_at,
completed_at=completed_at,
error=str(e)
)
async def execute_node(
node,
context: Dict[str, Any],
catalog: Dict[str, Dict],
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
graph: WorkflowGraph, # Phase 3: Needed for logic nodes
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
openrouter_call_func,
enable_debug: bool = False
) -> NodeExecutionState:
"""
Führt einen einzelnen Knoten aus.
Args:
node: WorkflowNode (aus graph.nodes)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
context: Execution context (variables, profile_id, node_results, active_edges)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
catalog: Question catalog
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
graph: WorkflowGraph (für Logic Nodes)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
openrouter_call_func: LLM callback: async (prompt, model) -> str
enable_debug: Debug mode
Returns:
NodeExecutionState
Node Types:
- start/end: No-op
- analysis: Load prompt → augment → LLM → parse → normalize
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
- logic: Evaluate condition → activate/deactivate edges (Phase 3)
- join: Consolidate paths → merge results (Phase 4)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
"""
started_at = datetime.utcnow().isoformat()
try:
# Start Node: No-Op
if node.type == "start":
logger.debug(f"Node {node.id}: No-op (start)")
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.EXECUTED,
started_at=started_at,
completed_at=datetime.utcnow().isoformat()
)
# End Node: Output Generation (Phase 4)
if node.type == "end":
return execute_end_node(node, context)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
# Logic Nodes (Phase 3)
if node.type == "logic":
return execute_logic_node(node, context, graph, enable_debug)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
# Join Nodes (Phase 4)
if node.type == "join":
return execute_join_node(node, context, graph, enable_debug)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# Analysis Nodes
if node.type == "analysis":
# 1. Lade Prompt (Part 3: inline_template support)
prompt_template = await load_prompt_template(node, context)
source_type = "inline" if node.inline_template else "reference"
logger.debug(f"Node {node.id}: Loaded prompt from {source_type}")
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# 2. Parse question_augmentations
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
questions = []
if node.question_augmentations:
# Convert list of dicts to JSONB-like format for parser
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
questions_jsonb = [q.model_dump() if hasattr(q, 'model_dump') else q for q in node.question_augmentations]
questions = parse_question_augmentations_from_jsonb(questions_jsonb)
logger.debug(f"Node {node.id}: {len(questions)} question augmentations")
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# 3. Augment Prompt
if questions:
augmented_prompt = augment_prompt_with_questions(
base_prompt=prompt_template,
questions=questions
)
else:
augmented_prompt = prompt_template
# 4. LLM Call
logger.debug(f"Node {node.id}: Calling LLM")
llm_response = await openrouter_call_func(
augmented_prompt,
"anthropic/claude-sonnet-4" # Default model
)
# 5. Parse Result Container
parsed = parse_result_container(llm_response)
logger.debug(f"Node {node.id}: Parsed response (status: {parsed['parsing_status']})")
# 6. Normalize Signals
fix: CRITICAL - Use question ID (not type) for LLM communication ROOT ARCHITECTURAL CHANGE: Multiple questions with same type are now supported! Problem: - question_augmenter used q.type as LLM key - If two questions had type="unsicherheit": - LLM saw duplicate keys: "- unsicherheit: [ja/nein]" - Could only answer one - Signals were ambiguous Solution: - Use question.id as LLM key (unique by design) - Keep type for normalization logic - Map id → type internally Backend question_augmenter.py: - format_question_list() now uses q.id as key - Format: "- **q21**: [ja/nein] # Question text" - Question text as comment for LLM context Backend workflow_executor.py: - Removed type→id mapping (no longer needed) - decision_signals now keyed by id (from LLM) - Build id→type catalog for normalization - NormalizedSignal.question_type stores id (not type!) - End Node template: signal_{id} directly available Flow: 1. Questions sent to LLM: "- q21: [ja/nein] # Ist Protein unsicher?" 2. LLM answers: "- q21: nein" 3. Normalization: id→type lookup for spectrum/rules 4. Template: {{ node_4.signal_q21 }} = "nein" Example (TWO unsicherheit questions): Questions: - q21: type=unsicherheit, question="Ist Protein unsicher?" - q22: type=unsicherheit, question="Ist Energie unsicher?" LLM Prompt: ``` ## Entscheidungsfragen - **q21**: [ja/nein] # Ist Protein unsicher? - **q22**: [ja/nein] # Ist Energie unsicher? ``` LLM Response: ``` - q21: nein - q22: ja ``` Template: ``` {{ node_4.signal_q21 }} → "nein" {{ node_4.signal_q22 }} → "ja" ``` BREAKING CHANGE: - Old workflows with decision_signals keyed by type will break - Need to re-execute workflows after update Issue: Cannot have multiple questions with same type Version: 0.9p (workflow module) Part 3: End Node Template Engine - ARCHITECTURAL FIX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:13:50 +02:00
# NOTE: decision_signals now use question.id as key (not type)
# We need to build a catalog: id → {type, spectrum} for normalization
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
normalized_signals = []
if parsed["decision_signals"]:
fix: CRITICAL - Use question ID (not type) for LLM communication ROOT ARCHITECTURAL CHANGE: Multiple questions with same type are now supported! Problem: - question_augmenter used q.type as LLM key - If two questions had type="unsicherheit": - LLM saw duplicate keys: "- unsicherheit: [ja/nein]" - Could only answer one - Signals were ambiguous Solution: - Use question.id as LLM key (unique by design) - Keep type for normalization logic - Map id → type internally Backend question_augmenter.py: - format_question_list() now uses q.id as key - Format: "- **q21**: [ja/nein] # Question text" - Question text as comment for LLM context Backend workflow_executor.py: - Removed type→id mapping (no longer needed) - decision_signals now keyed by id (from LLM) - Build id→type catalog for normalization - NormalizedSignal.question_type stores id (not type!) - End Node template: signal_{id} directly available Flow: 1. Questions sent to LLM: "- q21: [ja/nein] # Ist Protein unsicher?" 2. LLM answers: "- q21: nein" 3. Normalization: id→type lookup for spectrum/rules 4. Template: {{ node_4.signal_q21 }} = "nein" Example (TWO unsicherheit questions): Questions: - q21: type=unsicherheit, question="Ist Protein unsicher?" - q22: type=unsicherheit, question="Ist Energie unsicher?" LLM Prompt: ``` ## Entscheidungsfragen - **q21**: [ja/nein] # Ist Protein unsicher? - **q22**: [ja/nein] # Ist Energie unsicher? ``` LLM Response: ``` - q21: nein - q22: ja ``` Template: ``` {{ node_4.signal_q21 }} → "nein" {{ node_4.signal_q22 }} → "ja" ``` BREAKING CHANGE: - Old workflows with decision_signals keyed by type will break - Need to re-execute workflows after update Issue: Cannot have multiple questions with same type Version: 0.9p (workflow module) Part 3: End Node Template Engine - ARCHITECTURAL FIX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:13:50 +02:00
# Build catalog: id → answer_spectrum (for normalization)
id_catalog = {}
if questions:
for q in questions:
q_dict = q.model_dump() if hasattr(q, 'model_dump') else q
fix: CRITICAL - Use question ID (not type) for LLM communication ROOT ARCHITECTURAL CHANGE: Multiple questions with same type are now supported! Problem: - question_augmenter used q.type as LLM key - If two questions had type="unsicherheit": - LLM saw duplicate keys: "- unsicherheit: [ja/nein]" - Could only answer one - Signals were ambiguous Solution: - Use question.id as LLM key (unique by design) - Keep type for normalization logic - Map id → type internally Backend question_augmenter.py: - format_question_list() now uses q.id as key - Format: "- **q21**: [ja/nein] # Question text" - Question text as comment for LLM context Backend workflow_executor.py: - Removed type→id mapping (no longer needed) - decision_signals now keyed by id (from LLM) - Build id→type catalog for normalization - NormalizedSignal.question_type stores id (not type!) - End Node template: signal_{id} directly available Flow: 1. Questions sent to LLM: "- q21: [ja/nein] # Ist Protein unsicher?" 2. LLM answers: "- q21: nein" 3. Normalization: id→type lookup for spectrum/rules 4. Template: {{ node_4.signal_q21 }} = "nein" Example (TWO unsicherheit questions): Questions: - q21: type=unsicherheit, question="Ist Protein unsicher?" - q22: type=unsicherheit, question="Ist Energie unsicher?" LLM Prompt: ``` ## Entscheidungsfragen - **q21**: [ja/nein] # Ist Protein unsicher? - **q22**: [ja/nein] # Ist Energie unsicher? ``` LLM Response: ``` - q21: nein - q22: ja ``` Template: ``` {{ node_4.signal_q21 }} → "nein" {{ node_4.signal_q22 }} → "ja" ``` BREAKING CHANGE: - Old workflows with decision_signals keyed by type will break - Need to re-execute workflows after update Issue: Cannot have multiple questions with same type Version: 0.9p (workflow module) Part 3: End Node Template Engine - ARCHITECTURAL FIX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:13:50 +02:00
id_catalog[q_dict['id']] = {
"type": q_dict['type'], # Keep type for normalization
"answer_spectrum": q_dict['answer_spectrum'],
"normalization_rules": None # Node-Questions haben keine Synonyme
}
fix: CRITICAL - Use question ID (not type) for LLM communication ROOT ARCHITECTURAL CHANGE: Multiple questions with same type are now supported! Problem: - question_augmenter used q.type as LLM key - If two questions had type="unsicherheit": - LLM saw duplicate keys: "- unsicherheit: [ja/nein]" - Could only answer one - Signals were ambiguous Solution: - Use question.id as LLM key (unique by design) - Keep type for normalization logic - Map id → type internally Backend question_augmenter.py: - format_question_list() now uses q.id as key - Format: "- **q21**: [ja/nein] # Question text" - Question text as comment for LLM context Backend workflow_executor.py: - Removed type→id mapping (no longer needed) - decision_signals now keyed by id (from LLM) - Build id→type catalog for normalization - NormalizedSignal.question_type stores id (not type!) - End Node template: signal_{id} directly available Flow: 1. Questions sent to LLM: "- q21: [ja/nein] # Ist Protein unsicher?" 2. LLM answers: "- q21: nein" 3. Normalization: id→type lookup for spectrum/rules 4. Template: {{ node_4.signal_q21 }} = "nein" Example (TWO unsicherheit questions): Questions: - q21: type=unsicherheit, question="Ist Protein unsicher?" - q22: type=unsicherheit, question="Ist Energie unsicher?" LLM Prompt: ``` ## Entscheidungsfragen - **q21**: [ja/nein] # Ist Protein unsicher? - **q22**: [ja/nein] # Ist Energie unsicher? ``` LLM Response: ``` - q21: nein - q22: ja ``` Template: ``` {{ node_4.signal_q21 }} → "nein" {{ node_4.signal_q22 }} → "ja" ``` BREAKING CHANGE: - Old workflows with decision_signals keyed by type will break - Need to re-execute workflows after update Issue: Cannot have multiple questions with same type Version: 0.9p (workflow module) Part 3: End Node Template Engine - ARCHITECTURAL FIX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:13:50 +02:00
# Normalize each signal (signals keyed by ID now)
for signal_id, signal_value in parsed["decision_signals"].items():
if signal_id in id_catalog:
q_config = id_catalog[signal_id]
# Use the type-based catalog for normalization rules (if any)
type_catalog_entry = catalog.get(q_config['type'], {})
# Normalize with question-specific spectrum
normalized_signal = normalize_decision_signal(
question_type=signal_id, # Use ID as question_type
fix: CRITICAL - Use question ID (not type) for LLM communication ROOT ARCHITECTURAL CHANGE: Multiple questions with same type are now supported! Problem: - question_augmenter used q.type as LLM key - If two questions had type="unsicherheit": - LLM saw duplicate keys: "- unsicherheit: [ja/nein]" - Could only answer one - Signals were ambiguous Solution: - Use question.id as LLM key (unique by design) - Keep type for normalization logic - Map id → type internally Backend question_augmenter.py: - format_question_list() now uses q.id as key - Format: "- **q21**: [ja/nein] # Question text" - Question text as comment for LLM context Backend workflow_executor.py: - Removed type→id mapping (no longer needed) - decision_signals now keyed by id (from LLM) - Build id→type catalog for normalization - NormalizedSignal.question_type stores id (not type!) - End Node template: signal_{id} directly available Flow: 1. Questions sent to LLM: "- q21: [ja/nein] # Ist Protein unsicher?" 2. LLM answers: "- q21: nein" 3. Normalization: id→type lookup for spectrum/rules 4. Template: {{ node_4.signal_q21 }} = "nein" Example (TWO unsicherheit questions): Questions: - q21: type=unsicherheit, question="Ist Protein unsicher?" - q22: type=unsicherheit, question="Ist Energie unsicher?" LLM Prompt: ``` ## Entscheidungsfragen - **q21**: [ja/nein] # Ist Protein unsicher? - **q22**: [ja/nein] # Ist Energie unsicher? ``` LLM Response: ``` - q21: nein - q22: ja ``` Template: ``` {{ node_4.signal_q21 }} → "nein" {{ node_4.signal_q22 }} → "ja" ``` BREAKING CHANGE: - Old workflows with decision_signals keyed by type will break - Need to re-execute workflows after update Issue: Cannot have multiple questions with same type Version: 0.9p (workflow module) Part 3: End Node Template Engine - ARCHITECTURAL FIX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:13:50 +02:00
raw_value=signal_value,
answer_spectrum=q_config['answer_spectrum'],
normalization_rules=type_catalog_entry.get('normalization_rules')
)
normalized_signals.append(normalized_signal)
logger.debug(f"Node {node.id}: Normalized signal '{signal_id}' = '{signal_value}' → '{normalized_signal.normalized_value}' (status: {normalized_signal.status})")
fix: CRITICAL - Use question ID (not type) for LLM communication ROOT ARCHITECTURAL CHANGE: Multiple questions with same type are now supported! Problem: - question_augmenter used q.type as LLM key - If two questions had type="unsicherheit": - LLM saw duplicate keys: "- unsicherheit: [ja/nein]" - Could only answer one - Signals were ambiguous Solution: - Use question.id as LLM key (unique by design) - Keep type for normalization logic - Map id → type internally Backend question_augmenter.py: - format_question_list() now uses q.id as key - Format: "- **q21**: [ja/nein] # Question text" - Question text as comment for LLM context Backend workflow_executor.py: - Removed type→id mapping (no longer needed) - decision_signals now keyed by id (from LLM) - Build id→type catalog for normalization - NormalizedSignal.question_type stores id (not type!) - End Node template: signal_{id} directly available Flow: 1. Questions sent to LLM: "- q21: [ja/nein] # Ist Protein unsicher?" 2. LLM answers: "- q21: nein" 3. Normalization: id→type lookup for spectrum/rules 4. Template: {{ node_4.signal_q21 }} = "nein" Example (TWO unsicherheit questions): Questions: - q21: type=unsicherheit, question="Ist Protein unsicher?" - q22: type=unsicherheit, question="Ist Energie unsicher?" LLM Prompt: ``` ## Entscheidungsfragen - **q21**: [ja/nein] # Ist Protein unsicher? - **q22**: [ja/nein] # Ist Energie unsicher? ``` LLM Response: ``` - q21: nein - q22: ja ``` Template: ``` {{ node_4.signal_q21 }} → "nein" {{ node_4.signal_q22 }} → "ja" ``` BREAKING CHANGE: - Old workflows with decision_signals keyed by type will break - Need to re-execute workflows after update Issue: Cannot have multiple questions with same type Version: 0.9p (workflow module) Part 3: End Node Template Engine - ARCHITECTURAL FIX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:13:50 +02:00
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
logger.info(f"Node {node.id}: Normalized {len(normalized_signals)} signals")
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.EXECUTED,
analysis_core=parsed["analysis_core"],
decision_signals=parsed["decision_signals"],
normalized_signals=normalized_signals,
reasoning_anchors=parsed.get("reasoning_anchors"),
# Debug information (nur wenn enable_debug=True)
debug_prompt=augmented_prompt if enable_debug else None,
debug_raw_response=llm_response if enable_debug else None,
debug_node_type="analysis",
debug_prompt_slug=node.prompt_slug if enable_debug else None,
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
started_at=started_at,
completed_at=datetime.utcnow().isoformat()
)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
# Unbekannter Node-Typ (Phase 4: join)
raise ValueError(f"Node type '{node.type}' not implemented yet (Phase 4+)")
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
except Exception as e:
logger.error(f"Node execution failed ({node.id}): {e}", exc_info=True)
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.FAILED,
error=str(e),
started_at=started_at,
completed_at=datetime.utcnow().isoformat()
)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
def execute_logic_node(
node,
context: Dict[str, Any],
graph: WorkflowGraph,
enable_debug: bool = False
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
) -> NodeExecutionState:
"""
Führt Logic Node aus (Phase 3).
Args:
node: WorkflowNode vom Typ "logic"
context: Execution context mit node_results
graph: WorkflowGraph
Returns:
NodeExecutionState mit evaluation_result in metadata
Logic:
1. Evaluiere node.condition.expression
2. Wähle then_path oder else_path basierend auf Ergebnis
3. Bei Fehler/Unsicherheit: Wende fallback an
4. Markiere gewählte Edge(s) als aktiv, andere als inaktiv
5. Return NodeExecutionState mit evaluation_result
"""
started_at = datetime.utcnow().isoformat()
try:
if not node.condition:
error_msg = f"Logic node {node.id} has no condition defined"
logger.error(error_msg)
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.FAILED,
error=error_msg,
started_at=started_at,
completed_at=datetime.utcnow().isoformat()
)
# Handle both formats (thanks to Union[LogicExpression, Condition] type):
# 1. Direct LogicExpression (UI format): node.condition is LogicExpression
# 2. Wrapped in Condition (legacy): node.condition is Condition with .expression
from workflow_models import LogicExpression, Condition
expression = None
if isinstance(node.condition, LogicExpression):
# UI format: direct LogicExpression
expression = node.condition
elif isinstance(node.condition, Condition):
# Legacy format: wrapped in Condition
expression = node.condition.expression
else:
# Fallback: try to detect format manually
if hasattr(node.condition, 'operator') and hasattr(node.condition, 'operands'):
expression = node.condition # Looks like LogicExpression
elif hasattr(node.condition, 'expression'):
expression = node.condition.expression # Looks like Condition
if expression is None:
error_msg = f"Logic node {node.id} has no valid condition/expression defined"
logger.error(error_msg)
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.FAILED,
error=error_msg,
started_at=started_at,
completed_at=datetime.utcnow().isoformat()
)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
# 1. Evaluiere Bedingung
result, error = evaluate_logic_expression(expression, context)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
if error:
# Fehler bei Evaluation → Fallback anwenden
logger.warning(f"Logic node {node.id}: evaluation error: {error}")
activated_edges = _apply_fallback(node, graph, context, error)
else:
# Erfolgreiche Evaluation
logger.info(f"Logic node {node.id}: evaluated to {result}")
# 2. Wähle Pfad basierend auf Ergebnis
if result:
# Condition TRUE → then_path aktivieren
activated_edges = _get_edges_by_label(node.id, "then", graph)
else:
# Condition FALSE → else_path aktivieren
activated_edges = _get_edges_by_label(node.id, "else", graph)
# 3. Markiere Edges als aktiv/inaktiv
outgoing_edges = [e for e in graph.edges if e.from_node == node.id]
for edge in outgoing_edges:
context["active_edges"][edge.id] = edge.id in activated_edges
logger.debug(f"Logic node {node.id}: activated edges: {activated_edges}")
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.EXECUTED,
started_at=started_at,
completed_at=datetime.utcnow().isoformat(),
analysis_core=json.dumps({
"evaluation_result": result if not error else None,
"error": error,
"activated_edges": activated_edges
}),
# Debug information
debug_prompt=json.dumps(expression.model_dump() if hasattr(expression, 'model_dump') else str(expression), ensure_ascii=False) if enable_debug and expression else None,
debug_raw_response=json.dumps({
"result": result,
"error": error,
"activated_edges": activated_edges
}, ensure_ascii=False) if enable_debug else None,
debug_node_type="logic"
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
)
except Exception as e:
logger.error(f"Logic node execution failed ({node.id}): {e}", exc_info=True)
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.FAILED,
error=str(e),
started_at=started_at,
completed_at=datetime.utcnow().isoformat()
)
def execute_join_node(
node,
context: Dict[str, Any],
graph: WorkflowGraph,
enable_debug: bool = False
) -> NodeExecutionState:
"""
Führt Join Node aus (Phase 4).
Args:
node: WorkflowNode vom Typ "join"
context: Execution context mit node_results
graph: WorkflowGraph
Returns:
NodeExecutionState mit konsolidierten Daten
Logic:
1. Evaluiere Join-Strategie (via join_evaluator)
2. Prüfe ob ready (alle erforderlichen Pfade verfügbar?)
3. Konsolidiere Ergebnisse (merge analysis_core, combine signals)
4. Return NodeExecutionState mit konsolidierten Daten
Error Handling:
- wait_all + fehlende Pfade → FAILED
- wait_any + keine Pfade → FAILED
- best_effort + keine Pfade → EXECUTED (mit metadata)
"""
started_at = datetime.utcnow().isoformat()
try:
logger.info(f"Executing join node: {node.id}")
# 1. Evaluiere Join-Node
join_result = evaluate_join_node_core(node, graph, context)
# 2. Prüfe ob ready
if not join_result.ready:
# Strategie nicht erfüllt → FAILED
logger.warning(f"Join node {node.id}: Not ready - {join_result.error}")
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.FAILED,
error=join_result.error,
started_at=started_at,
completed_at=datetime.utcnow().isoformat(),
metadata=join_result.metadata
)
# 3. Baue konsolidierten analysis_core
# Format: JSON mit node_id → analysis_core mapping
consolidated_core_json = json.dumps(
join_result.consolidated_analysis_core,
ensure_ascii=False,
indent=2
)
# 4. Log Konsolidierung
executed_count = join_result.metadata.get("executed_paths", 0)
total_count = join_result.metadata.get("total_paths", 0)
logger.info(
f"Join node {node.id}: Consolidated {executed_count}/{total_count} paths"
)
# 5. Convert consolidated_signals Dict → List[NormalizedSignal]
# (NodeExecutionState expects List, but join_evaluator returns Dict)
consolidated_signals_list = list(join_result.consolidated_signals.values())
# 6. Return NodeExecutionState
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.EXECUTED,
analysis_core=consolidated_core_json,
normalized_signals=consolidated_signals_list,
metadata=join_result.metadata,
started_at=started_at,
completed_at=datetime.utcnow().isoformat(),
# Debug information
debug_prompt=f"Join Strategy: {node.join_strategy.value if node.join_strategy else 'wait_all'}\nPaths: {total_count}\nMinimum Required: {node.min_paths if node.min_paths else 'all'}" if enable_debug else None,
debug_raw_response=json.dumps({
"executed_paths": executed_count,
"total_paths": total_count,
"strategy": node.join_strategy.value if node.join_strategy else 'wait_all',
"consolidated_nodes": list(join_result.consolidated_analysis_core.keys())
}, ensure_ascii=False) if enable_debug else None,
debug_node_type="join"
)
except Exception as e:
logger.error(f"Join node execution failed ({node.id}): {e}", exc_info=True)
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.FAILED,
error=str(e),
started_at=started_at,
completed_at=datetime.utcnow().isoformat()
)
def execute_end_node(
node,
context: Dict[str, Any]
) -> NodeExecutionState:
"""
Führt End Node aus (Phase 4 - Template Engine).
Args:
node: WorkflowNode vom Typ "end"
context: Execution context mit node_results
Returns:
NodeExecutionState mit finaler Ausgabe in analysis_core
Output Modes:
- AUTO: Concatenates all analysis_core values (backward compatible)
- TEMPLATE: Renders Jinja2 template with {{node_id.property}} placeholders
Template Context:
- {{node_id.analysis_core}}: Analysis text from node
- {{node_id.decision_signals}}: Dict of raw decision signals
- {{node_id.decision_signals.key}}: Specific signal value
- {{node_id.status}}: Node execution status
- Conditional rendering: {% if node_id %}...{% endif %}
- Default values: {{node_id|default("fallback")}}
Example Template:
```
# Final Analysis
## Body Composition
{{body_analysis.analysis_core}}
{% if training_analysis %}
## Training Recommendation
{{training_analysis.analysis_core}}
{% endif %}
## Decision Factors
- Relevance: {{body_analysis.decision_signals.relevanz}}
- Priority: {{body_analysis.decision_signals.prioritaet}}
```
"""
started_at = datetime.utcnow().isoformat()
try:
logger.info(f"Executing end node: {node.id}")
# Determine output mode (default: AUTO for backward compatibility)
output_mode = node.output_mode or EndNodeOutputMode.AUTO
if output_mode == EndNodeOutputMode.AUTO:
# AUTO mode: Concatenate all analysis_core values
logger.debug(f"End node {node.id}: Using AUTO output mode")
# Get graph from context for node label lookup
graph = context.get("graph")
combined_analysis = []
for node_id, node_state in context.get("node_results", {}).items():
if node_state.status == NodeStatus.EXECUTED and node_state.analysis_core:
fix(workflow): Test-Suite Fixes - Issues #5, #8, #9, #11, #12 Addressed test results from Test_status_Wkf.md: **Issue #5: End-Node Überschriften** - Fixed aggregate_results to show node labels instead of "Node 10" - Added graph lookup to get node.data.label from node objects - Modified backend/workflow_executor.py (2 locations) **Issue #8: Löschen-Taste funktioniert nicht** - Added Delete key support to WorkflowCanvas - Set deleteKeyCode={['Backspace', 'Delete']} - Frontend: WorkflowCanvas.jsx **Issue #9: Mehrere End-Nodes verhindern** - Added validation error when multiple End-Nodes exist - Backend supports only 1 End-Node (aggregate_results takes last) - Frontend: workflowValidation.js **Issue #11: Export Fehler "Internal Server Error"** - Added missing fields to export-all endpoint: - graph_data (workflow node graph) - question_augmentations (analysis prompts) - Added missing fields to import endpoint - Proper JSON serialization for all JSONB fields - Backend: routers/prompts.py **Issue #12: Workflow duplizieren funktioniert nicht** - Fixed duplicate endpoint to include all prompt fields: - type, stages, output_format, output_schema - question_augmentations, graph_data (critical for workflows!) - Backend: routers/prompts.py Files changed: - backend/workflow_executor.py: Node label lookup in aggregate_results - backend/routers/prompts.py: Export/import/duplicate fixes - frontend/src/components/workflow/WorkflowCanvas.jsx: Delete key - frontend/src/utils/workflowValidation.js: Max 1 End-Node validation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 14:15:57 +02:00
# Get node label from graph (fallback to node_id if not found)
node_label = node_id # default
if graph:
node_obj = next((n for n in graph.nodes if n.id == node_id), None)
if node_obj and hasattr(node_obj, 'data') and node_obj.data:
node_label = node_obj.data.get('label', node_id)
combined_analysis.append(f"## {node_label}\n{node_state.analysis_core}")
final_output = "\n\n".join(combined_analysis) if combined_analysis else "[No analysis generated]"
elif output_mode == EndNodeOutputMode.TEMPLATE:
# TEMPLATE mode: Render Jinja2 template
logger.debug(f"End node {node.id}: Using TEMPLATE output mode")
if not node.template:
raise ValueError(f"End node {node.id} has output_mode=TEMPLATE but no template defined")
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
# Build template context: {{node_id}} → {analysis_core, signal_X, question_X, status}
template_context = {}
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
graph = context.get("graph") # WorkflowGraph object
for node_id, node_state in context.get("node_results", {}).items():
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
node_context = {
"analysis_core": node_state.analysis_core or "",
"decision_signals": node_state.decision_signals or {},
"reasoning_anchors": node_state.reasoning_anchors or "",
"status": node_state.status.value if node_state.status else "unknown",
}
# Backward-compatible shortcut:
# allow {{node_id.relevanz}} in addition to {{node_id.decision_signals.relevanz}}
if node_state.decision_signals:
for signal_key, signal_value in node_state.decision_signals.items():
node_context[signal_key] = signal_value
# Add normalized signals as {{node_id.signal_ID}}
fix: CRITICAL - Use question ID (not type) for LLM communication ROOT ARCHITECTURAL CHANGE: Multiple questions with same type are now supported! Problem: - question_augmenter used q.type as LLM key - If two questions had type="unsicherheit": - LLM saw duplicate keys: "- unsicherheit: [ja/nein]" - Could only answer one - Signals were ambiguous Solution: - Use question.id as LLM key (unique by design) - Keep type for normalization logic - Map id → type internally Backend question_augmenter.py: - format_question_list() now uses q.id as key - Format: "- **q21**: [ja/nein] # Question text" - Question text as comment for LLM context Backend workflow_executor.py: - Removed type→id mapping (no longer needed) - decision_signals now keyed by id (from LLM) - Build id→type catalog for normalization - NormalizedSignal.question_type stores id (not type!) - End Node template: signal_{id} directly available Flow: 1. Questions sent to LLM: "- q21: [ja/nein] # Ist Protein unsicher?" 2. LLM answers: "- q21: nein" 3. Normalization: id→type lookup for spectrum/rules 4. Template: {{ node_4.signal_q21 }} = "nein" Example (TWO unsicherheit questions): Questions: - q21: type=unsicherheit, question="Ist Protein unsicher?" - q22: type=unsicherheit, question="Ist Energie unsicher?" LLM Prompt: ``` ## Entscheidungsfragen - **q21**: [ja/nein] # Ist Protein unsicher? - **q22**: [ja/nein] # Ist Energie unsicher? ``` LLM Response: ``` - q21: nein - q22: ja ``` Template: ``` {{ node_4.signal_q21 }} → "nein" {{ node_4.signal_q22 }} → "ja" ``` BREAKING CHANGE: - Old workflows with decision_signals keyed by type will break - Need to re-execute workflows after update Issue: Cannot have multiple questions with same type Version: 0.9p (workflow module) Part 3: End Node Template Engine - ARCHITECTURAL FIX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:13:50 +02:00
# NOTE: question_type now IS the ID (not the type!)
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
if node_state.normalized_signals:
for signal in node_state.normalized_signals:
# Convert NormalizedSignal object to dict if needed
signal_dict = signal.model_dump() if hasattr(signal, 'model_dump') else signal
fix: CRITICAL - Use question ID (not type) for LLM communication ROOT ARCHITECTURAL CHANGE: Multiple questions with same type are now supported! Problem: - question_augmenter used q.type as LLM key - If two questions had type="unsicherheit": - LLM saw duplicate keys: "- unsicherheit: [ja/nein]" - Could only answer one - Signals were ambiguous Solution: - Use question.id as LLM key (unique by design) - Keep type for normalization logic - Map id → type internally Backend question_augmenter.py: - format_question_list() now uses q.id as key - Format: "- **q21**: [ja/nein] # Question text" - Question text as comment for LLM context Backend workflow_executor.py: - Removed type→id mapping (no longer needed) - decision_signals now keyed by id (from LLM) - Build id→type catalog for normalization - NormalizedSignal.question_type stores id (not type!) - End Node template: signal_{id} directly available Flow: 1. Questions sent to LLM: "- q21: [ja/nein] # Ist Protein unsicher?" 2. LLM answers: "- q21: nein" 3. Normalization: id→type lookup for spectrum/rules 4. Template: {{ node_4.signal_q21 }} = "nein" Example (TWO unsicherheit questions): Questions: - q21: type=unsicherheit, question="Ist Protein unsicher?" - q22: type=unsicherheit, question="Ist Energie unsicher?" LLM Prompt: ``` ## Entscheidungsfragen - **q21**: [ja/nein] # Ist Protein unsicher? - **q22**: [ja/nein] # Ist Energie unsicher? ``` LLM Response: ``` - q21: nein - q22: ja ``` Template: ``` {{ node_4.signal_q21 }} → "nein" {{ node_4.signal_q22 }} → "ja" ``` BREAKING CHANGE: - Old workflows with decision_signals keyed by type will break - Need to re-execute workflows after update Issue: Cannot have multiple questions with same type Version: 0.9p (workflow module) Part 3: End Node Template Engine - ARCHITECTURAL FIX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:13:50 +02:00
q_id = signal_dict['question_type'] # This is actually the ID now!
signal_key = f"signal_{q_id}"
signal_value = signal_dict['normalized_value'] or signal_dict['raw_value']
node_context[signal_key] = signal_value
logger.info(f"Mapped signal: {q_id} → {signal_key} = '{signal_value}'")
# Add question texts as {{node_id.question_ID}}
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
if graph:
workflow_node = next((n for n in graph.nodes if n.id == node_id), None)
if workflow_node and workflow_node.question_augmentations:
for q in workflow_node.question_augmentations:
q_dict = q.model_dump() if hasattr(q, 'model_dump') else q
q_id = q_dict.get('id')
if q_id:
question_key = f"question_{q_id}"
node_context[question_key] = q_dict.get('question', '')
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
template_context[node_id] = node_context
logger.debug(f"End node {node.id}: Built template context for {len(template_context)} nodes")
# DEBUG: Log template context keys for each node
for node_id, node_ctx in template_context.items():
logger.info(f"End node template context[{node_id}]: {list(node_ctx.keys())}")
# Log signal keys specifically
signal_keys = [k for k in node_ctx.keys() if k.startswith('signal_')]
question_keys = [k for k in node_ctx.keys() if k.startswith('question_')]
if signal_keys:
logger.info(f" Signals: {signal_keys} → values: {[(k, node_ctx[k]) for k in signal_keys]}")
if question_keys:
logger.info(f" Questions: {question_keys} → values: {[(k, node_ctx[k]) for k in question_keys]}")
# Render template
try:
# ChainableUndefined keeps missing nested attributes renderable
# so Jinja's default filter can handle them.
jinja_env = Environment(undefined=ChainableUndefined)
jinja_template = jinja_env.from_string(node.template)
final_output = jinja_template.render(template_context)
logger.info(f"End node {node.id}: Template rendered successfully ({len(final_output)} chars)")
except TemplateError as te:
error_msg = f"Template rendering failed: {str(te)}"
logger.error(f"End node {node.id}: {error_msg}")
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.FAILED,
error=error_msg,
started_at=started_at,
completed_at=datetime.utcnow().isoformat()
)
else:
raise ValueError(f"Unknown output_mode: {output_mode}")
# Return NodeExecutionState with final output
return NodeExecutionState(
node_id=node.id,
status=NodeStatus.EXECUTED,
analysis_core=final_output,
started_at=started_at,
completed_at=datetime.utcnow().isoformat()
)
except Exception as e:
logger.error(f"End node execution failed ({node.id}): {e}", exc_info=True)
return NodeExecutionState(
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
node_id=node.id,
status=NodeStatus.FAILED,
error=str(e),
started_at=started_at,
completed_at=datetime.utcnow().isoformat()
)
def _apply_fallback(
node,
graph: WorkflowGraph,
context: Dict[str, Any],
error: str
) -> List[str]:
"""
Wendet Fallback-Strategie an.
Args:
node: WorkflowNode
graph: WorkflowGraph
context: Execution context
error: Fehler bei Evaluation
Returns:
Liste von Edge-IDs die aktiviert werden sollen
"""
if not node.fallback:
# Default: Konservativ (else-Pfad nehmen)
logger.warning(f"Node {node.id}: No fallback configured, using DEFAULT_PATH")
return _get_edges_by_label(node.id, "else", graph)
strategy = node.fallback.strategy
if strategy == FallbackStrategy.CONSERVATIVE_SKIP:
# Skip alle outgoing edges
logger.info(f"Node {node.id}: CONSERVATIVE_SKIP - deactivating all paths")
return []
elif strategy == FallbackStrategy.DEFAULT_PATH:
# Nimm else-Pfad
logger.info(f"Node {node.id}: DEFAULT_PATH - taking else path")
return _get_edges_by_label(node.id, "else", graph)
elif strategy == FallbackStrategy.UNCERTAINTY_PATH:
# Expliziter Unsicherheits-Pfad (falls vorhanden)
logger.info(f"Node {node.id}: UNCERTAINTY_PATH")
uncertainty_edges = _get_edges_by_label(node.id, "uncertainty", graph)
if uncertainty_edges:
return uncertainty_edges
else:
# Fallback to else
logger.warning(f"Node {node.id}: No uncertainty path found, using else")
return _get_edges_by_label(node.id, "else", graph)
elif strategy == FallbackStrategy.DOCUMENT_ONLY:
# Alle Pfade aktiv lassen (wie ohne Bedingung)
logger.info(f"Node {node.id}: DOCUMENT_ONLY - all paths remain active")
outgoing_edges = [e for e in graph.edges if e.from_node == node.id]
return [e.id for e in outgoing_edges]
else:
logger.warning(f"Node {node.id}: Unknown fallback strategy {strategy}, using DEFAULT_PATH")
return _get_edges_by_label(node.id, "else", graph)
def _get_edges_by_label(node_id: str, label: str, graph: WorkflowGraph) -> List[str]:
"""
Findet alle ausgehenden Edges mit bestimmtem Label.
Unterstützt beide Formate:
- Legacy: e.label == label (z.B. "then", "else")
- UI: e.source_handle == label (z.B. "true", "false")
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
Args:
node_id: Node-ID
label: Edge-Label oder sourceHandle (z.B. "then"/"true", "else"/"false")
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
graph: WorkflowGraph
Returns:
Liste von Edge-IDs
"""
# Map label to sourceHandle equivalents
label_to_handle = {
"then": "true",
"else": "false"
}
handle_equivalent = label_to_handle.get(label, label)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
matching_edges = [
e.id for e in graph.edges
if e.from_node == node_id and (
e.label == label or # Legacy format
(hasattr(e, 'source_handle') and e.source_handle == handle_equivalent) # UI format
)
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
]
return matching_edges
def _has_active_incoming_edge(node, graph: WorkflowGraph, context: Dict[str, Any]) -> bool:
"""
Prüft ob Node mindestens eine aktive incoming edge hat.
Args:
node: WorkflowNode
graph: WorkflowGraph
context: Execution context mit active_edges
Returns:
True wenn mindestens eine incoming edge aktiv ist
"""
# Start-Node hat keine incoming edges → immer aktiv
if node.type == "start":
return True
incoming_edges = [e for e in graph.edges if e.to_node == node.id]
# Keine incoming edges → nicht erreichbar
if not incoming_edges:
return False
# Prüfe ob mindestens eine aktiv ist
active_edges = context.get("active_edges", {})
for edge in incoming_edges:
if active_edges.get(edge.id, True): # Default: True (Phase 2 Kompatibilität)
return True
return False
async def load_prompt_template(node: WorkflowNode, context: Dict[str, Any]) -> str:
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
"""
Lädt Prompt-Template aus DB (reference mode) oder direkt vom Node (inline mode).
Part 3: Inline Prompts - Unterstützt zwei Modi:
- Reference Mode: prompt_slug → Template aus ai_prompts Tabelle
- Inline Mode: inline_template → Template direkt vom Node
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
Args:
node: WorkflowNode mit prompt_slug ODER inline_template
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
context: {"variables": {"name": "Lars", ...}, "profile_id": "..."}
Returns:
Resolved prompt template
Raises:
HTTPException: Wenn weder prompt_slug noch inline_template gesetzt
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
Beispiel:
>>> node = WorkflowNode(id="n1", prompt_slug="pipeline_body")
>>> template = await load_prompt_template(node, {"profile_id": "123"})
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
>>> "{{name}}" not in template
True
"""
from placeholder_resolver import get_placeholder_example_values, get_placeholder_catalog
from prompt_executor import resolve_placeholders
from fastapi import HTTPException
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# Mode 1: Inline Template (NEU)
if node.inline_template:
logger.debug(f"Node {node.id}: Using inline template ({len(node.inline_template)} chars)")
template = node.inline_template
# Mode 2: Reference (bestehend)
elif node.prompt_slug:
logger.debug(f"Node {node.id}: Loading prompt '{node.prompt_slug}' from DB")
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"SELECT template FROM ai_prompts WHERE slug = %s AND active = true",
(node.prompt_slug,)
)
row = cur.fetchone()
if not row:
raise HTTPException(status_code=404, detail=f"Prompt not found: {node.prompt_slug}")
template = row['template']
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# Mode 3: Error - weder inline noch reference
else:
raise HTTPException(
status_code=400,
detail=f"Node {node.id}: Either prompt_slug or inline_template required"
)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
# Resolve Placeholders using modern prompt_executor method
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
profile_id = context.get("profile_id")
if not profile_id:
raise HTTPException(status_code=400, detail="profile_id required in context")
# Build variables dict with ALL registered placeholders
variables = {}
try:
# Get all placeholder values from registry
processed_placeholders = get_placeholder_example_values(profile_id)
logger.info(f"🔍 DEBUG: Loaded {len(processed_placeholders)} placeholders from registry")
logger.info(f"🔍 DEBUG: Sample keys (first 3): {list(processed_placeholders.keys())[:3]}")
# Remove {{ }} from keys (placeholder_resolver returns them with wrappers)
cleaned_placeholders = {
key.replace('{{', '').replace('}}', '').strip(): value
for key, value in processed_placeholders.items()
}
logger.info(f"🔍 DEBUG: Cleaned keys (first 3): {list(cleaned_placeholders.keys())[:3]}")
logger.info(f"🔍 DEBUG: Sample values: name={cleaned_placeholders.get('name')}, age={cleaned_placeholders.get('age')}, geschlecht={cleaned_placeholders.get('geschlecht')}")
variables.update(cleaned_placeholders)
except Exception as e:
logger.error(f"❌ CRITICAL: Failed to load placeholders for workflow: {e}", exc_info=True)
# Add workflow node outputs as placeholders (Part 3: Inline Prompts)
# Format: node_id.analysis_core, node_id.signal_xyz, node_id.question_xyz
node_results = context.get("node_results", {})
if node_results:
logger.info(f"🔍 DEBUG: Adding {len(node_results)} node outputs as placeholders")
for node_id, node_state in node_results.items():
# analysis_core
if hasattr(node_state, 'analysis_core') and node_state.analysis_core:
key = f"{node_id}.analysis_core"
variables[key] = node_state.analysis_core
logger.debug(f" Added placeholder: {key} = {node_state.analysis_core[:50]}...")
# decision_signals (keyed by question ID)
if hasattr(node_state, 'decision_signals') and node_state.decision_signals:
for signal_id, signal_value in node_state.decision_signals.items():
# Signal placeholder: node_id.signal_question_id
signal_key = f"{node_id}.signal_{signal_id}"
variables[signal_key] = signal_value
logger.debug(f" Added placeholder: {signal_key} = {signal_value}")
# Question texts (from graph metadata if available)
# NOTE: Question text placeholders are populated from graph in PlaceholderPicker
# Here we only add if available in node_state metadata
if hasattr(node_state, 'metadata') and isinstance(node_state.metadata, dict):
questions = node_state.metadata.get('questions', [])
for q in questions:
if isinstance(q, dict) and 'id' in q and 'question' in q:
question_key = f"{node_id}.question_{q['id']}"
variables[question_key] = q['question']
logger.debug(f" Added placeholder: {question_key}")
# Load catalog for |d modifier support
try:
catalog = get_placeholder_catalog(profile_id)
except Exception as e:
catalog = None
logger.warning(f"Failed to load placeholder catalog for workflow: {e}")
logger.info(f"🔍 DEBUG: Template before resolution:\n{template[:200]}...")
logger.info(f"🔍 DEBUG: Variables dict has {len(variables)} entries")
# Resolve with modern executor
debug_info = {}
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
resolved = resolve_placeholders(
template=template,
variables=variables,
debug_info=debug_info,
catalog=catalog
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
)
logger.info(f"🔍 DEBUG: Resolved placeholders: {debug_info.get('resolved_placeholders', {})}")
logger.info(f"🔍 DEBUG: Unresolved placeholders: {debug_info.get('unresolved_placeholders', [])}")
logger.info(f"🔍 DEBUG: Template after resolution:\n{resolved[:200]}...")
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
return resolved
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
def aggregate_results(node_states: List[NodeExecutionState], graph) -> Dict[str, Any]:
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
"""
Aggregiert Ergebnisse aller Knoten.
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
Phase 4: Wenn End Node existiert, verwende NUR dessen Output.
Sonst (backward compatible): Kombiniere alle analysis_core Werte.
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
Args:
node_states: Liste aller NodeExecutionState
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
graph: WorkflowGraph object (to identify End Nodes)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
Returns:
{
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
"analysis_core": "Final output (from End Node or combined)",
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
"all_signals": [{question_type, normalized_value, status}, ...],
"total_nodes": 3,
"executed_nodes": 3,
"failed_nodes": 0
}
Beispiel:
>>> states = [
... NodeExecutionState(node_id="n1", status=NodeStatus.EXECUTED, analysis_core="Test 1"),
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
... NodeExecutionState(node_id="n2", status=NodeStatus.EXECUTED, analysis_core="Test 2"),
... NodeExecutionState(node_id="end", status=NodeStatus.EXECUTED, analysis_core="Final")
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
... ]
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
>>> result = aggregate_results(states, graph)
>>> result["analysis_core"]
"Final"
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
"""
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
# Find End Node in graph
end_node_ids = [n.id for n in graph.nodes if n.type == "end"]
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
combined_analysis = []
all_signals = []
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
final_output = None
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
for state in node_states:
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
# Check if this is an End Node
is_end_node = state.node_id in end_node_ids
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
if state.status == NodeStatus.EXECUTED and state.analysis_core:
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
if is_end_node:
# End Node output is the final output (don't add to combined_analysis)
final_output = state.analysis_core
else:
# Regular node - add to combined analysis
fix(workflow): Test-Suite Fixes - Issues #5, #8, #9, #11, #12 Addressed test results from Test_status_Wkf.md: **Issue #5: End-Node Überschriften** - Fixed aggregate_results to show node labels instead of "Node 10" - Added graph lookup to get node.data.label from node objects - Modified backend/workflow_executor.py (2 locations) **Issue #8: Löschen-Taste funktioniert nicht** - Added Delete key support to WorkflowCanvas - Set deleteKeyCode={['Backspace', 'Delete']} - Frontend: WorkflowCanvas.jsx **Issue #9: Mehrere End-Nodes verhindern** - Added validation error when multiple End-Nodes exist - Backend supports only 1 End-Node (aggregate_results takes last) - Frontend: workflowValidation.js **Issue #11: Export Fehler "Internal Server Error"** - Added missing fields to export-all endpoint: - graph_data (workflow node graph) - question_augmentations (analysis prompts) - Added missing fields to import endpoint - Proper JSON serialization for all JSONB fields - Backend: routers/prompts.py **Issue #12: Workflow duplizieren funktioniert nicht** - Fixed duplicate endpoint to include all prompt fields: - type, stages, output_format, output_schema - question_augmentations, graph_data (critical for workflows!) - Backend: routers/prompts.py Files changed: - backend/workflow_executor.py: Node label lookup in aggregate_results - backend/routers/prompts.py: Export/import/duplicate fixes - frontend/src/components/workflow/WorkflowCanvas.jsx: Delete key - frontend/src/utils/workflowValidation.js: Max 1 End-Node validation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 14:15:57 +02:00
# Get node label from graph (fallback to node_id)
node_label = state.node_id # default
node_obj = next((n for n in graph.nodes if n.id == state.node_id), None)
if node_obj and hasattr(node_obj, 'data') and node_obj.data:
node_label = node_obj.data.get('label', state.node_id)
combined_analysis.append(f"## {node_label}\n{state.analysis_core}")
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
if state.normalized_signals:
all_signals.extend([s.model_dump() for s in state.normalized_signals])
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
# Phase 4: If End Node exists, use its output ONLY
# Backward compatible: If no End Node, use combined_analysis
if final_output:
primary_output = final_output
else:
primary_output = "\n\n".join(combined_analysis)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
return {
feat: End Node template placeholders + clean output display Three major improvements for workflow templates: 1. **Normalized Signal Placeholders:** - Signals now available as {{ node_4.signal_kalorienbilanz }} - Uses normalized_value (not raw decision_signals) - Enables structured decision-based outputs 2. **Question Text Placeholders:** - Question texts available as {{ node_4.question_kalorienbilanz }} - Extracted from workflow graph (question_augmentations) - Allows displaying questions alongside answers 3. **Clean End Node Output:** - End Node output no longer duplicated with "## node_4" headers - aggregate_results() detects End Nodes via graph.nodes - Only shows final template-rendered output - Backward compatible: Falls back to combined_analysis if no End Node Backend workflow_executor.py: - execute_end_node(): Added normalized signals to template context - execute_end_node(): Added question texts to template context - execute_workflow(): Added graph to context for End Node access - aggregate_results(): Signature change to accept graph - aggregate_results(): Detects End Nodes and uses only their output Frontend WorkflowResultViewer.jsx: - Now uses aggregated.analysis_core (primary output) - Removed fallback to combined_analysis (was showing duplicates) Example Template: ```jinja2 **Frage:** {{ node_4.question_kalorienbilanz }} **Antwort:** {{ node_4.signal_kalorienbilanz }} --- {{ node_4.analysis_core }} ``` Issue: Signal placeholders empty, question texts unavailable, duplicate output Version: 0.9p (workflow module) Part 3: End Node Template Engine - Complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 20:45:08 +02:00
"analysis_core": primary_output,
"combined_analysis": "\n\n".join(combined_analysis), # Keep for debugging (excludes End Node)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
"all_signals": all_signals,
"total_nodes": len(node_states),
"executed_nodes": sum(1 for s in node_states if s.status == NodeStatus.EXECUTED),
feat: Phase 3 - Logic Nodes + Conditional Branching Backend: - logic_evaluator.py (NEU, 307 Zeilen): Deterministischer Logic Evaluator - Vergleichsoperatoren: EQ, NEQ, IN, NOT_IN, GT, LT, GTE, LTE, CONTAINS - Logische Operatoren: AND, OR, NOT mit Verschachtelung - Resolve signal references (node_id.question_type) - Error handling für UNCLEAR/INVALID/NOT_DECIDABLE Signale - workflow_executor.py (ERWEITERT): - execute_logic_node(): Bedingungen evaluieren, Pfade aktivieren/deaktivieren - execute_workflow(): BFS-Traversierung mit Edge-Activation statt Sequential - _apply_fallback(): 4 Fallback-Strategien (CONSERVATIVE_SKIP, DEFAULT_PATH, UNCERTAINTY_PATH, DOCUMENT_ONLY) - _has_active_incoming_edge(): Prüft ob Node erreichbar ist - _get_edges_by_label(): Findet then/else/uncertainty Pfade - workflow_models.py (ERWEITERT): - LogicOperator.CONTAINS hinzugefügt - version.py: 0.9k → 0.9l, workflow 0.3.0 → 0.4.0 Tests: - test_phase3_logic_evaluator.py (NEU): 20 Unit Tests (alle passing) - Comparison operators (EQ, NEQ, IN, GT, LT, CONTAINS) - Logical operators (AND, OR, NOT) - Nested expressions - Error handling (missing refs, UNCLEAR/INVALID signals) - test_phase2_workflow_executor.py (AKTUALISIERT): 11 Tests (alle passing) - execute_node() graph parameter hinzugefügt (Phase 3 requirement) - test_execute_node_unknown_type: logic → join (logic jetzt implementiert) - test_phase3_workflow_branching.py (NEU): Integration Tests vorbereitet - Erfordert vollständige DB-Mock-Strategie (wird in E2E-Test nachgeholt) Phase 2 Backward Compatibility: ✅ Alle Phase 2 Tests bestehen weiterhin Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md Anforderungsanalyse: .claude/task/Workflow_engine_prompting_engine/phase3_anforderungsanalyse.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:02:22 +02:00
"failed_nodes": sum(1 for s in node_states if s.status == NodeStatus.FAILED),
"skipped_nodes": sum(1 for s in node_states if s.status == NodeStatus.SKIPPED) # Phase 3
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
}
def save_execution_state(
execution_id: str,
workflow_id: Optional[str], # None when using graph_data directly (Phase 5)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
profile_id: str,
node_states: List[NodeExecutionState],
status: str,
started_at: str,
completed_at: Optional[str] = None,
error: Optional[str] = None
):
"""
Speichert Execution State in workflow_executions.
Args:
execution_id: UUID der Execution
workflow_id: UUID des Workflows (None wenn graph_data direkt verwendet wird)
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
profile_id: UUID des Profils
node_states: Liste aller NodeExecutionState
status: 'completed' | 'failed' | 'partial'
started_at: ISO timestamp
completed_at: ISO timestamp (optional)
error: Fehlermeldung (optional)
Beispiel:
>>> save_execution_state(
... execution_id="exec-123",
... workflow_id="wf-456",
... profile_id="prof-789",
... node_states=[],
... status="completed",
... started_at="2026-04-03T12:00:00"
... )
"""
# Serialize node_states to JSON
node_states_json = [s.model_dump() for s in node_states]
with get_db() as conn:
cur = get_cursor(conn)
cur.execute("""
INSERT INTO workflow_executions
(id, workflow_id, profile_id, status, node_states, execution_log, started_at, completed_at)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (id) DO UPDATE SET
status = EXCLUDED.status,
node_states = EXCLUDED.node_states,
execution_log = EXCLUDED.execution_log,
completed_at = EXCLUDED.completed_at
feat: Phase 2 - Normalisierung + Workflow Executor Backend: - normalization_engine.py (200 Zeilen): Synonym-Mapping, 5 Statuswerte * normalize_decision_signal(): Kaskade (exact → case → synonym → invalid) * apply_synonym_mapping(): DB-basierte Synonyme (case-insensitive) * normalize_all_signals(): Batch-Processing gegen Katalog * load_question_catalog(): Lädt normalization_rules aus DB - workflow_executor.py (440 Zeilen): Sequenzielle Workflow-Ausführung * execute_workflow(): Traversiert DAG in topologischer Reihenfolge * execute_node(): Führt analysis nodes aus (start/end = no-op) * aggregate_results(): Kombiniert analysis_core + normalized_signals * save_execution_state(): Persistiert in workflow_executions - workflow_models.py: Erweitert um Phase 2 Models * SignalStatus Enum (valid, normalized, unclear, invalid, not_decidable) * NormalizedSignal (question_type, raw_value, normalized_value, status) * NodeExecutionState (node_id, status, analysis_core, normalized_signals) * ExecutionResult (execution_id, workflow_id, status, node_states, aggregated_result) - workflow_engine.py: Neue Funktion get_execution_order() * Flattened topological sort für sequenzielle Execution * Phase 7: Wird zu levels (parallele Execution) - prompt_executor.py: execute_workflow_prompt() Implementierung * Ruft workflow_executor.execute_workflow() auf * Konvertiert ExecutionResult zu API-Response - routers/workflows.py (230 Zeilen): Workflow Execution API * POST /api/workflows/{id}/execute (mit enable_debug) * GET /api/workflows/executions/{id} (lädt gespeicherten State) * GET /api/workflows (listet alle aktiven Workflows) * GET /api/workflows/{id} (lädt einzelnen Workflow mit Graph) - main.py: Router-Registrierung (workflows.router) Tests: - test_phase2_normalization.py (17 Tests): Alle Normalisierungs-Szenarien * Exact match, case-insensitive, synonym mapping, invalid, whitespace * Batch-Normalisierung, not_in_catalog, mixed validity - test_phase2_workflow_executor.py (10 Tests): Executor + Aggregation * aggregate_results mit verschiedenen Konstellationen * execute_node für start/end/analysis/unknown * Integration mit question_augmenter + result_container_parser Alle 27 Unit-Tests bestanden. version: 0.9k (backend) module: workflow 0.3.0 Konzept: .claude/task/Workflow_engine_prompting_engine/anforderungsanalyse_umsetzungsplan.md (Phase 2)
2026-04-03 21:20:23 +02:00
""", (
execution_id,
workflow_id,
profile_id,
status,
json.dumps(node_states_json),
json.dumps({"error": error} if error else {}),
started_at,
completed_at
))
conn.commit()
logger.info(f"Saved execution state: {execution_id} (status: {status})")