File size: 7,677 Bytes
5dcfc5c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d63573
 
 
 
 
 
 
 
 
 
 
 
 
 
5dcfc5c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d63573
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5dcfc5c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
from typing import Any

from figment.validators import validate_navigator_output


CHEST_CARD = {
    "card_id": "CHEST-PAIN-ESCALATION-v1",
    "title": "Chest pain escalation",
    "required_observations": [
        "chest pain description",
        "onset and duration",
        "shortness of breath report",
        "sweating or fainting report",
        "radiation to arm, jaw, back, or shoulder",
        "available vital signs",
    ],
}


def _confirmed_chest_pain_intake() -> dict[str, Any]:
    return {
        "setting": "mobile clinic",
        "patient_age": "57 years",
        "pregnancy_status": "not_applicable",
        "chief_concern": "chest pressure",
        "symptoms": "chest pain with shortness of breath and sweating",
        "vitals": "heart rate 118; blood pressure pending",
        "allergies": "unknown",
        "medications": "unknown",
        "available_supplies": "AED, radio, transport path",
        "responder_note": "Synthetic case. Adult reports chest pressure after cleanup work.",
        "confirmed": True,
    }


def _chest_rule() -> dict[str, str]:
    return {
        "rule_id": "red_flag_chest_pain",
        "label": "Chest pain escalation cue",
        "urgency": "emergency",
        "evidence": "chest pain with shortness of breath",
        "card_id": "CHEST-PAIN-ESCALATION-v1",
    }


def _navigator_output(**overrides: Any) -> dict[str, Any]:
    output: dict[str, Any] = {
        "protocol_urgency": "emergency",
        "red_flags": [_chest_rule()],
        "intake_facts": [
            {
                "fact": "Chest pain with shortness of breath reported.",
                "status": "reported",
                "source": "structured_field",
            }
        ],
        "candidate_protocol_pathways": [
            {
                "card_id": "CHEST-PAIN-ESCALATION-v1",
                "reason_relevant": "Chest pain with shortness of breath was reported.",
            }
        ],
        "missing_info_to_collect": [
            "chest pain description",
            "onset and duration",
            "shortness of breath report",
            "sweating or fainting report",
            "radiation to arm, jaw, back, or shoulder",
            "available vital signs",
        ],
        "next_observations_to_collect": [
            "chest pain description",
            "onset and duration",
            "shortness of breath report",
            "available vital signs",
        ],
        "conflicts_or_uncertainties": [],
        "responder_checklist": ["Escalate per cited local protocol."],
        "do_not_do": ["Do not diagnose or prescribe."],
        "source_cards": ["CHEST-PAIN-ESCALATION-v1"],
        "handoff_note_sbar": {
            "situation": "Chest pressure with shortness of breath.",
            "background": "Mobile clinic synthetic case after cleanup work.",
            "assessment_observations_only": "Chest pain and sweating reported. Heart rate 118; blood pressure pending.",
            "handoff_request": "Request escalation per cited local protocol.",
        },
        "responder_plain_language_script": "I am going to keep checking observations and follow the local escalation path.",
        "safety_boundary": "This output does not diagnose or prescribe and does not replace local protocol.",
    }
    output.update(overrides)
    return output


def test_strict_validator_requires_full_schema_and_fired_rule_card_citation() -> None:
    output = _navigator_output(source_cards=["SAFETY-BOUNDARIES-v1"])
    del output["red_flags"]
    del output["intake_facts"]

    result = validate_navigator_output(
        output,
        {
            "CHEST-PAIN-ESCALATION-v1",
            "SAFETY-BOUNDARIES-v1",
        },
        urgency_floor="emergency",
        confirmed_intake=_confirmed_chest_pain_intake(),
        rule_results=[_chest_rule()],
        strict_schema=True,
    )

    assert not result.passed
    assert any("missing required schema keys" in failure for failure in result.failures)
    assert any("fired rule card CHEST-PAIN-ESCALATION-v1 is not cited" in failure for failure in result.failures)


def test_strict_validator_rejects_known_but_unretrieved_card_citation() -> None:
    output = _navigator_output(
        source_cards=["CHEST-PAIN-ESCALATION-v1", "WOUND-INFECTION-ESCALATION-v1"],
        candidate_protocol_pathways=[
            {
                "card_id": "WOUND-INFECTION-ESCALATION-v1",
                "reason_relevant": "The model reached for a known card that was not retrieved.",
            }
        ],
    )

    result = validate_navigator_output(
        output,
        {
            "CHEST-PAIN-ESCALATION-v1",
            "SAFETY-BOUNDARIES-v1",
            "WOUND-INFECTION-ESCALATION-v1",
        },
        urgency_floor="emergency",
        confirmed_intake=_confirmed_chest_pain_intake(),
        rule_results=[_chest_rule()],
        retrieved_card_ids={"CHEST-PAIN-ESCALATION-v1", "SAFETY-BOUNDARIES-v1"},
        strict_schema=True,
    )

    assert not result.passed
    assert any("not in allowed/retrieved card IDs" in failure for failure in result.failures)


def test_strict_validator_allows_known_fired_rule_card_even_when_retrieval_missed_it() -> None:
    output = _navigator_output(
        source_cards=["CHEST-PAIN-ESCALATION-v1"],
        candidate_protocol_pathways=[
            {
                "card_id": "CHEST-PAIN-ESCALATION-v1",
                "reason_relevant": "Chest pain red flag fired deterministically.",
            }
        ],
    )

    result = validate_navigator_output(
        output,
        {
            "CHEST-PAIN-ESCALATION-v1",
            "SAFETY-BOUNDARIES-v1",
            "WOUND-INFECTION-ESCALATION-v1",
        },
        urgency_floor="emergency",
        confirmed_intake=_confirmed_chest_pain_intake(),
        rule_results=[_chest_rule()],
        retrieved_card_ids={"SAFETY-BOUNDARIES-v1"},
        strict_schema=True,
    )

    assert result.passed


def test_strict_validator_rejects_generic_missing_info_and_hallucinated_sbar_facts() -> None:
    output = _navigator_output(
        missing_info_to_collect=["ask anything else that seems relevant"],
        next_observations_to_collect=["keep monitoring"],
        handoff_note_sbar={
            "situation": "Chest pressure with shortness of breath.",
            "background": "Mobile clinic synthetic case after cleanup work.",
            "assessment_observations_only": "Chest pain reported with skull fracture and blood pressure 220/140 observed.",
            "handoff_request": "Request escalation per cited local protocol.",
        },
    )

    result = validate_navigator_output(
        output,
        {"CHEST-PAIN-ESCALATION-v1", "SAFETY-BOUNDARIES-v1"},
        urgency_floor="emergency",
        confirmed_intake=_confirmed_chest_pain_intake(),
        rule_results=[_chest_rule()],
        retrieved_cards=[{"card": CHEST_CARD}],
        strict_schema=True,
    )

    assert not result.passed
    assert any("missing_info_to_collect does not reference required observations" in failure for failure in result.failures)
    assert any("unsupported high-risk handoff facts" in failure for failure in result.failures)


def test_validator_rejects_broader_unsafe_action_phrases() -> None:
    output = _navigator_output(
        responder_checklist=["Administer aspirin now and discharge home if symptoms improve."],
    )

    result = validate_navigator_output(
        output,
        {"CHEST-PAIN-ESCALATION-v1"},
        urgency_floor="emergency",
    )

    assert not result.passed
    assert any("forbidden clinical language" in failure for failure in result.failures)