mitai-jinkendo/tests/backend/test_phase1_result_container_parser.py
Lars ca562b7130
All checks were successful
Deploy Development / deploy (push) Successful in 49s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s
feat: Phase 1 - Fragenergänzung + Strukturierter Container
Backend:
- question_augmenter.py (290 Zeilen): Hybrid-Modell für Fragenergänzungen
  * merge_question_augmentations(): Knotengebundene Fragen überschreiben Prompt-Defaults
  * augment_prompt_with_questions(): Markdown-formatierte Fragenergänzung
  * parse_question_augmentations_from_jsonb(): JSONB → QuestionAugmentation[]
- result_container_parser.py (250 Zeilen): Markdown-Sektionen-Parsing
  * parse_result_container(): Extrahiert Analysekern, Entscheidungsanteil, Begründungsanker
  * validate_decision_signal(): Normalisierung gegen answer_spectrum
  * Fallback-Parsing bei unstrukturierten Antworten
- routers/workflow_questions.py (236 Zeilen): CRUD für workflow_question_catalog
  * GET /api/workflow/questions (mit active_only Filter)
  * POST/PUT/DELETE (Admin only, Soft Delete)
- prompt_executor.py: Integration in execute_base_prompt()
  * Fragenergänzung vor LLM-Call (wenn node_questions oder catalog vorhanden)
  * Result-Container-Parsing nach LLM-Response
- main.py: Router-Registrierung (workflow_questions)

Tests:
- test_phase1_question_augmenter.py (8 Tests): Hybrid-Modell, Formatierung, JSONB-Parsing
- test_phase1_result_container_parser.py (17 Tests): Sektion-Extraktion, Decision-Parsing, Validierung

Alle 25 Unit-Tests bestanden.

version: 0.9j (backend)
module:  workflow 0.2.0

Konzept: .claude/task/Workflow_engine_prompting_engine/konzept_workflow_engine_konsolidated.md (Phase 1)
2026-04-03 18:02:25 +02:00

235 lines
6.1 KiB
Python

"""
Unit Tests für result_container_parser.py (Phase 1)
Run with: PYTHONPATH=./backend pytest tests/backend/test_phase1_result_container_parser.py -v
"""
import pytest
from result_container_parser import (
parse_result_container,
extract_section,
parse_decision_questions,
validate_decision_signal,
parse_result_container_robust
)
def test_extract_section_basic():
"""Test: Einfache Sektion extrahieren"""
text = """
## Analyse
Das ist der Analysekern.
Mehrere Zeilen.
## Entscheidungsfragen
- Relevanz: ja
"""
result = extract_section(text, "Analyse")
assert result == "Das ist der Analysekern.\nMehrere Zeilen."
def test_extract_section_not_found():
"""Test: Nicht vorhandene Sektion"""
text = "## Analyse\nInhalt"
result = extract_section(text, "Begründung")
assert result is None
def test_extract_section_empty():
"""Test: Leere Sektion (nur Whitespace am Ende)"""
text = "## Analyse\n\n"
result = extract_section(text, "Analyse")
assert result is None
def test_parse_decision_questions_basic():
"""Test: Standard-Format parsen"""
section = """
- Relevanz: ja
- Priorität: hoch
- Selektion: nein
"""
result = parse_decision_questions(section)
assert result == {
"relevanz": "ja",
"priorität": "hoch",
"selektion": "nein"
}
def test_parse_decision_questions_bold():
"""Test: Format mit **bold** Markup"""
section = """
- **Relevanz**: ja
- **Priorität**: hoch
"""
result = parse_decision_questions(section)
assert result == {
"relevanz": "ja",
"priorität": "hoch"
}
def test_parse_decision_questions_without_dash():
"""Test: Format ohne führendes Minus"""
section = """
Relevanz: ja
Priorität: hoch
"""
result = parse_decision_questions(section)
assert result == {
"relevanz": "ja",
"priorität": "hoch"
}
def test_parse_decision_questions_brackets():
"""Test: Format mit [Klammern]"""
section = """
- Relevanz: [ja]
- Priorität: [hoch]
"""
result = parse_decision_questions(section)
assert result == {
"relevanz": "ja",
"priorität": "hoch"
}
def test_validate_decision_signal_exact_match():
"""Test: Exakte Übereinstimmung"""
value, status = validate_decision_signal("ja", ["ja", "nein", "unklar"])
assert value == "ja"
assert status == "valid"
def test_validate_decision_signal_normalized():
"""Test: Case-insensitive Normalisierung"""
value, status = validate_decision_signal("JA", ["ja", "nein", "unklar"])
assert value == "ja"
assert status == "normalized"
def test_validate_decision_signal_invalid():
"""Test: Ungültige Antwort"""
value, status = validate_decision_signal("vielleicht", ["ja", "nein", "unklar"])
assert value == "vielleicht"
assert status == "invalid"
def test_parse_result_container_complete():
"""Test: Vollständiger Container mit allen Sektionen"""
llm_output = """
## Analyse
Der Nutzer zeigt eine positive Gewichtsentwicklung.
Kaloriendefizit wird eingehalten.
## Entscheidungsfragen
- Relevanz: ja
- Priorität: hoch
## Begründung
Die Gewichtsabnahme ist im Zielbereich von 0.5-1% pro Woche.
"""
result = parse_result_container(llm_output)
assert result["parsing_status"] == "complete"
assert "Gewichtsentwicklung" in result["analysis_core"]
assert result["decision_signals"]["relevanz"] == "ja"
assert result["decision_signals"]["priorität"] == "hoch"
assert "Zielbereich" in result["reasoning_anchors"]
def test_parse_result_container_partial():
"""Test: Container ohne Begründung (partial)"""
llm_output = """
## Analyse
Analyse-Inhalt
## Entscheidungsfragen
- Relevanz: ja
"""
result = parse_result_container(llm_output)
assert result["parsing_status"] == "complete"
assert result["analysis_core"] == "Analyse-Inhalt"
assert result["decision_signals"]["relevanz"] == "ja"
assert result["reasoning_anchors"] is None
def test_parse_result_container_no_structure():
"""Test: Unstrukturierte Antwort (Fallback)"""
llm_output = "Einfache Textantwort ohne Strukturierung."
result = parse_result_container(llm_output)
assert result["parsing_status"] == "fallback"
assert result["analysis_core"] == "Einfache Textantwort ohne Strukturierung."
assert result["decision_signals"] == {}
assert result["reasoning_anchors"] is None
def test_parse_result_container_only_questions():
"""Test: Nur Entscheidungsfragen, keine Analyse (partial)"""
llm_output = """
## Entscheidungsfragen
- Relevanz: nein
- Priorität: niedrig
"""
result = parse_result_container(llm_output)
assert result["parsing_status"] == "partial"
assert result["analysis_core"] == ""
assert result["decision_signals"]["relevanz"] == "nein"
def test_parse_result_container_robust_with_warnings():
"""Test: Robuste Variante mit erwarteten Fragen"""
llm_output = """
## Analyse
Inhalt
## Entscheidungsfragen
- Relevanz: ja
"""
expected_questions = ["relevanz", "prioritaet", "selektion"]
result = parse_result_container_robust(llm_output, expected_questions)
assert "warnings" in result
assert any("Fehlende Entscheidungsfragen" in w for w in result["warnings"])
assert any("prioritaet" in w for w in result["warnings"])
def test_parse_result_container_case_insensitive():
"""Test: Case-insensitive Sektion-Matching"""
llm_output = """
## ANALYSE
Großgeschrieben
## entscheidungsfragen
- Relevanz: ja
"""
result = parse_result_container(llm_output)
assert result["parsing_status"] == "complete"
assert result["analysis_core"] == "Großgeschrieben"
assert result["decision_signals"]["relevanz"] == "ja"
def test_parse_decision_questions_mixed_formats():
"""Test: Gemischte Formate in einer Sektion"""
section = """
- **Relevanz**: ja
Priorität: hoch
- Selektion: [nein]
"""
result = parse_decision_questions(section)
assert result["relevanz"] == "ja"
assert result["priorität"] == "hoch"
assert result["selektion"] == "nein"
if __name__ == "__main__":
pytest.main([__file__, "-v"])