import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from src.openbmb_client import EXTRACTION_PROMPT, ExtractionResult from src.pipeline_trace import build_pipeline_trace, empty_trace_html, trace_hover_js, trace_to_html from src.report_pipeline import build_health_report def _sample_extraction() -> ExtractionResult: return ExtractionResult( patient={"age": "42y", "age_years": 42.0, "sex": "female", "age_group": "adult"}, tests=[ { "marker": "Hemoglobin", "value": "11.2", "unit": "g/dL", "reference_range": "12.0-16.0", "status": "low", "source_text": "Hgb 11.2", "confidence": 0.95, }, { "marker": "WBC", "value": "6.5", "unit": "10^3/uL", "reference_range": "4.5-11.0", "status": "normal", "source_text": "WBC 6.5", "confidence": 0.9, }, ], notes=["Sample note"], raw_response='{"tests":[{"marker":"Hemoglobin","value":"11.2"}]}', request_summary={ "backend": "test", "extraction_prompt": EXTRACTION_PROMPT, "document_parts": 2, "http_status": 200, "return_code": 0, "duration_ms": 842, "user_message_preview": {"image_count": 1, "text_characters": 120}, }, ) def test_build_pipeline_trace_has_five_steps(): extraction = _sample_extraction() report = build_health_report(extraction) steps = build_pipeline_trace(extraction, report, source_path="/tmp/report.pdf") assert len(steps) == 5 assert [step.id for step in steps] == [ "document_intake", "vision_extraction", "schema_normalization", "knowledge_graph", "pattern_detection", ] def test_extraction_step_includes_full_prompt(): extraction = _sample_extraction() report = build_health_report(extraction) steps = build_pipeline_trace(extraction, report) extraction_step = steps[1] assert extraction_step.prompt == EXTRACTION_PROMPT assert extraction_step.return_code == 0 assert extraction_step.metadata["http_status"] == 200 assert extraction_step.metadata["duration_ms"] == 842 def test_trace_to_html_collapsible_steps(): extraction = _sample_extraction() report = build_health_report(extraction) steps = build_pipeline_trace(extraction, report) html = trace_to_html(steps, interactive=True) assert "bte-trace-step" in html assert html.count("bte-trace-step-collapse") == len(steps) assert '
' not in html assert "bte-trace-status--complete" not in html assert "Return code" in html assert "Full prompt" in html assert "bte-trace-explanation" in html assert "Technical details" in html assert "This step gets your upload ready for the AI." in html assert "In this run:" in html assert 'data-interactive="true"' in html assert "' not in html assert "bte-trace-status--pending" not in html assert "Step 1 — Document intake" in html assert "Step 5 — Cross-marker pattern detection" in html assert "Waiting for you to upload a lab report." not in html assert 'data-interactive="false"' in html def test_trace_hover_js_registers_listeners_once(): js = trace_hover_js() assert "__bteTraceHoverInit" in js assert "OPEN_DELAY_MS" in js assert "is-open" in js assert 'panel.dataset.interactive === "true"' in js assert "