Open raw ↗"""Tier 4: Real-World Clinical Workflows & End-to-End Scenarios.
Replicates complete oncology journeys in Hospitalisation à Domicile (HAD):
- Scenario 1: Acute Febrile Neutropenia Emergency Journey
- Scenario 2: Routine Home Nurse Clinical Observation & Summary Export
- Scenario 3: Multi-Patient Home Hospitalization Cycle (AC-T vs FOLFOX6)
- Scenario 4: Clinician Observation vs Patient Self-Report Differentiation
"""
import pytest
@pytest.mark.tier4
class TestClinicalWorkflows:
"""End-to-end clinical oncology journeys."""
def test_scenario_febrile_neutropenia_emergency(
self, patient_client, oncologist_client, nurse_client, session_server
):
"""Clinical Scenario 1: Acute Febrile Neutropenia Emergency.
Context: Jeanne Durand (Patient 1, on AC-T protocol) experiences chills
and fever (38.7°C) on Day 8 of Cycle 4.
"""
# Step 1: Patient logs in and submits constitutional symptom report
report_payload = {
"patient_id": 1,
"symptom_id": "febrile_neutropenia",
"symptom_category": "constitutional",
"severity_score": 3,
"lab_values": {"anc_mm3": 800, "temp_celsius": 38.7},
"notes": "Température 38.7°C mesurée à 14h, frissons intenses, neutropénie connue",
}
rep_resp = patient_client.post("/api/reports", json=report_payload)
assert rep_resp.status_code == 201
rep_data = rep_resp.json()
report_id = rep_data["report_id"]
grading = rep_data["grading"]
alert = rep_data["alert"]
# Step 2: Verify CTCAE Grade 3/4 and Emergency Alert
assert grading["grade"] in (3, 4)
assert alert is not None
assert alert["alert_type"] == "emergency"
assert alert["patient_id"] == 1
# Step 3: Verify French patient emergency guidance is accessible
guidance_resp = patient_client.get("/api/guidance?symptom_id=fever&grade=3")
assert guidance_resp.status_code == 200
guidance_data = guidance_resp.json()
assert guidance_data["symptom_id"] == "fever"
assert "guidance" in guidance_data
# Step 4: Oncologist Dr. Martin reviews alerts and timeline
onco_alerts_resp = oncologist_client.get("/api/alerts?patient_id=1")
assert onco_alerts_resp.status_code == 200
emergency_alerts = [
a for a in onco_alerts_resp.json().get("alerts", [])
if a["alert_type"] == "emergency" and a["report_id"] == report_id
]
assert len(emergency_alerts) == 1
emergency_alert_id = emergency_alerts[0]["id"]
# Step 5: Oncologist acknowledges emergency alert
ack_resp = oncologist_client.post(f"/api/alerts/{emergency_alert_id}/acknowledge")
assert ack_resp.status_code == 200
# Step 6: Oncologist dispatches urgent message to HAD nurse
msg_payload = {
"recipient_id": nurse_client.current_user["id"],
"recipient_role": "had_nurse",
"patient_id": 1,
"subject": "URGENT - Suspicion neutropénie fébrile Jeanne Durand",
"body": "Passage immédiat au domicile requis. Bilan sanguin en urgence (NFS + hémocultures) et antibiothérapie probabiliste selon protocole.",
"message_type": "care_team",
}
msg_resp = oncologist_client.post("/api/messages", json=msg_payload)
assert msg_resp.status_code == 201
# Step 7: Nurse reads the instruction message
inbox_resp = nurse_client.get("/api/messages?patient_id=1")
assert inbox_resp.status_code == 200
nurse_msgs = inbox_resp.json().get("messages", [])
assert any("neutropénie fébrile" in m.get("subject", "") for m in nurse_msgs)
def test_scenario_nurse_home_visit_and_clinical_export(
self, nurse_client, oncologist_client
):
"""Clinical Scenario 2: Routine Home Nurse Visit and Export.
Context: Nurse Moret conducts scheduled home visit, reviews symptom history,
enters clinical observation, and exports summary.
"""
# Step 1: Nurse reviews historical reports for patient 1
reports_resp = nurse_client.get("/api/reports?patient_id=1")
assert reports_resp.status_code == 200
reports = reports_resp.json().get("reports", [])
assert len(reports) >= 1
# Step 2: Nurse enters observation report
obs_payload = {
"patient_id": 1,
"symptom_id": "fatigue",
"symptom_category": "constitutional",
"severity_score": 1,
"notes": "Visite HAD: TA 120/75, pouls 74 bpm, apyretique. Fatigue legere post-chimio bien toleree.",
}
obs_resp = nurse_client.post("/api/reports", json=obs_payload)
assert obs_resp.status_code == 201
obs_report_id = obs_resp.json()["report_id"]
# Step 3: Timeline reflects the newly added clinical note
tl_resp = nurse_client.get("/api/timeline?patient_id=1")
assert tl_resp.status_code == 200
events = tl_resp.json()["events"]
assert any(e["event_type"] == "report" for e in events)
# Step 4: Nurse generates structured clinical export summary
export_resp = nurse_client.get("/api/export/summary?patient_id=1")
assert export_resp.status_code == 200
summary = export_resp.json()
# Step 5: Validate clinical summary structure
assert "patient" in summary
assert summary["patient"]["mrn"] == "MRN-2024-001"
assert "treatment_plans" in summary
assert len(summary["treatment_plans"]) >= 1
assert "toxicity_summary" in summary
assert "fatigue" in summary["toxicity_summary"]
assert "generated_at" in summary
assert summary["generated_by"] == nurse_client.current_user["username"]
def test_scenario_multi_patient_chemotherapy_cycles(
self, api_client, session_server
):
"""Clinical Scenario 3: Multi-Patient Chemo Cycle Segregation.
Simulates Patient 1 (Breast Cancer, AC-T) and Patient 2 (Colorectal, FOLFOX6)
reporting symptoms on their respective cycles, verified by Dr. Martin.
"""
from conftest import HADClient
# Authenticate Patient 1 (Jeanne Durand)
p1 = HADClient(session_server.base_url)
p1.login("patient.durand", "demo123")
# Authenticate Patient 2 (Pierre Moreau)
p2 = HADClient(session_server.base_url)
p2.login("patient.moreau", "demo123")
# Patient 1 reports Grade 2 Nausea
r1 = p1.post("/api/reports", json={
"patient_id": 1,
"symptom_id": "nausea",
"symptom_category": "gastrointestinal",
"severity_score": 5,
"notes": "Patient 1 Cycle 4 AC-T nausea",
})
assert r1.status_code == 201
assert r1.json()["grading"]["grade"] == 2
assert r1.json()["alert"]["alert_type"] == "urgent"
# Patient 2 reports Grade 1 Peripheral Neuropathy (common in FOLFOX/Oxaliplatin)
r2 = p2.post("/api/reports", json={
"patient_id": 2,
"symptom_id": "peripheral_neuropathy",
"symptom_category": "neurologic",
"severity_score": 1,
"notes": "Patient 2 FOLFOX cycle paresthesies distales",
})
assert r2.status_code == 201
assert r2.json()["grading"]["grade"] == 1
assert r2.json().get("alert") is None # Grade 1 is routine
# Oncologist Dr. Martin reviews both patient timelines
onco = HADClient(session_server.base_url)
onco.login("dr.martin", "demo123")
tl1 = onco.get("/api/timeline?patient_id=1")
assert tl1.status_code == 200
tl1_events = [e["title"].lower() for e in tl1.json()["events"]]
assert any("nausea" in t for t in tl1_events)
tl2 = onco.get("/api/timeline?patient_id=2")
assert tl2.status_code == 200
tl2_events = [e["title"].lower() for e in tl2.json()["events"]]
assert any("neuropathy" in t for t in tl2_events)
def test_scenario_clinician_observation_vs_patient_self_report(
self, patient_client, nurse_client, session_server
):
"""Clinical Scenario 4: Clinician Observation vs Patient Self-Report."""
# Patient submits subjective self-report
p_resp = patient_client.post("/api/reports", json={
"patient_id": 1,
"symptom_id": "nausea",
"symptom_category": "gastrointestinal",
"severity_score": 2,
"notes": "Auto-evaluation patient: nausees presentes apres le petit dejeuner",
})
assert p_resp.status_code == 201
p_rep_id = p_resp.json()["report_id"]
# Nurse submits clinician observation
n_resp = nurse_client.post("/api/reports", json={
"patient_id": 1,
"symptom_id": "nausea",
"symptom_category": "gastrointestinal",
"severity_score": 1,
"notes": "Evaluation IDE a domicile: bon etat general, prise efficace du primperan",
})
assert n_resp.status_code == 201
n_rep_id = n_resp.json()["report_id"]
# Verify distinct reporter IDs in SQLite
p_row = session_server.query_db("SELECT reporter_id FROM toxicity_reports WHERE id = ?", (p_rep_id,))
n_row = session_server.query_db("SELECT reporter_id FROM toxicity_reports WHERE id = ?", (n_rep_id,))
assert p_row[0]["reporter_id"] == patient_client.current_user["id"]
assert n_row[0]["reporter_id"] == nurse_client.current_user["id"]
assert p_row[0]["reporter_id"] != n_row[0]["reporter_id"]