| from __future__ import annotations |
|
|
| from typing import Any |
|
|
| import pytest |
|
|
| from figment.field_provenance import ( |
| DETERMINISTIC_FALLBACK, |
| MODEL_RAW, |
| MODEL_REPAIRED, |
| NAVIGATOR_FIELD_NAMES, |
| merge_field_provenance, |
| ) |
|
|
|
|
| def _fallback_output(**overrides: Any) -> dict[str, Any]: |
| output: dict[str, Any] = { |
| "protocol_urgency": "urgent", |
| "red_flags": [{"rule_id": "rule-1"}], |
| "intake_facts": [ |
| { |
| "fact": "fallback concern", |
| "status": "reported", |
| "source": "structured_field", |
| } |
| ], |
| "candidate_protocol_pathways": [ |
| { |
| "card_id": "SAFETY-BOUNDARIES-v1", |
| "reason_relevant": "fallback", |
| } |
| ], |
| "missing_info_to_collect": ["repeat vitals"], |
| "next_observations_to_collect": ["work of breathing"], |
| "conflicts_or_uncertainties": ["fallback uncertainty"], |
| "responder_checklist": ["use local protocol"], |
| "do_not_do": ["do not downgrade red flags"], |
| "source_cards": ["SAFETY-BOUNDARIES-v1"], |
| "handoff_note_sbar": { |
| "situation": "fallback situation", |
| "background": "fallback background", |
| "assessment_observations_only": "fallback assessment", |
| "handoff_request": "fallback request", |
| }, |
| "responder_plain_language_script": "fallback script", |
| "safety_boundary": "fallback safety boundary", |
| } |
| output.update(overrides) |
| return output |
|
|
|
|
| def test_merge_keeps_raw_model_fields_and_fills_missing_fields_from_fallback() -> None: |
| raw_output = { |
| "protocol_urgency": "emergency", |
| "missing_info_to_collect": ["airway check", "repeat blood pressure"], |
| } |
| fallback_output = _fallback_output() |
|
|
| result = merge_field_provenance(raw_output, None, fallback_output) |
|
|
| assert result.output["protocol_urgency"] == "emergency" |
| assert result.output["missing_info_to_collect"] == ["airway check", "repeat blood pressure"] |
| assert result.output["source_cards"] == ["SAFETY-BOUNDARIES-v1"] |
| assert result.provenance["protocol_urgency"] == MODEL_RAW |
| assert result.provenance["missing_info_to_collect"] == MODEL_RAW |
| assert result.provenance["source_cards"] == DETERMINISTIC_FALLBACK |
| assert set(result.output) == set(NAVIGATOR_FIELD_NAMES) |
| assert set(result.provenance) == set(NAVIGATOR_FIELD_NAMES) |
|
|
|
|
| def test_merge_can_limit_raw_model_fields_to_field_level_acceptance() -> None: |
| raw_output = { |
| "protocol_urgency": "emergency", |
| "source_cards": ["UNRETRIEVED-CARD-v1"], |
| } |
|
|
| result = merge_field_provenance( |
| raw_output, |
| None, |
| _fallback_output(), |
| accepted_raw_fields={"protocol_urgency"}, |
| ) |
|
|
| assert result.output["protocol_urgency"] == "emergency" |
| assert result.output["source_cards"] == ["SAFETY-BOUNDARIES-v1"] |
| assert result.provenance["protocol_urgency"] == MODEL_RAW |
| assert result.provenance["source_cards"] == DETERMINISTIC_FALLBACK |
|
|
|
|
| def test_repaired_fields_override_raw_model_fields_and_are_labeled() -> None: |
| raw_output = { |
| "protocol_urgency": "urgent", |
| "source_cards": ["UNRETRIEVED-CARD-v1"], |
| "handoff_note_sbar": { |
| "situation": "raw situation", |
| "background": "raw background", |
| "assessment_observations_only": "raw assessment", |
| "handoff_request": "raw request", |
| }, |
| } |
| repaired_fields = { |
| "source_cards": ["SAFETY-BOUNDARIES-v1"], |
| "handoff_note_sbar": { |
| "situation": "repaired situation", |
| "background": "repaired background", |
| "assessment_observations_only": "repaired assessment", |
| "handoff_request": "repaired request", |
| }, |
| } |
|
|
| result = merge_field_provenance(raw_output, repaired_fields, _fallback_output()) |
|
|
| assert result.output["protocol_urgency"] == "urgent" |
| assert result.output["source_cards"] == ["SAFETY-BOUNDARIES-v1"] |
| assert result.output["handoff_note_sbar"]["situation"] == "repaired situation" |
| assert result.provenance["protocol_urgency"] == MODEL_RAW |
| assert result.provenance["source_cards"] == MODEL_REPAIRED |
| assert result.provenance["handoff_note_sbar"] == MODEL_REPAIRED |
|
|
|
|
| def test_merge_ignores_unknown_model_fields_and_requires_complete_fallback() -> None: |
| raw_output = { |
| "protocol_urgency": "routine", |
| "diagnosis": "unsupported extra field", |
| } |
| repaired_fields = { |
| "made_up_section": {"unsafe": True}, |
| } |
|
|
| result = merge_field_provenance(raw_output, repaired_fields, _fallback_output()) |
|
|
| assert "diagnosis" not in result.output |
| assert "made_up_section" not in result.output |
| assert "diagnosis" not in result.provenance |
| assert "made_up_section" not in result.provenance |
|
|
| incomplete_fallback = _fallback_output() |
| incomplete_fallback.pop("handoff_note_sbar") |
| with pytest.raises( |
| ValueError, |
| match="deterministic fallback is missing navigator fields: handoff_note_sbar", |
| ): |
| merge_field_provenance({}, None, incomplete_fallback) |
|
|
|
|
| def test_merge_returns_deep_copies_so_callers_cannot_mutate_sources_through_result() -> None: |
| raw_output = {"missing_info_to_collect": ["repeat vitals"]} |
| fallback_output = _fallback_output() |
|
|
| result = merge_field_provenance(raw_output, None, fallback_output) |
| result.output["missing_info_to_collect"].append("mutated") |
| result.output["source_cards"].append("mutated-card") |
|
|
| assert raw_output["missing_info_to_collect"] == ["repeat vitals"] |
| assert fallback_output["source_cards"] == ["SAFETY-BOUNDARIES-v1"] |
|
|