Spaces:
Runtime error
Runtime error
| """Specialist workflow end-to-end through the API: forward -> stage-2 note -> | |
| recommendation queue -> send-to-insurer -> stage-3 summary, plus the return / | |
| request-further-testing / regenerate paths. TestClient runs BackgroundTasks | |
| synchronously, so each action's inference completes before the next assertion.""" | |
| import hashlib | |
| import json | |
| import shutil | |
| from pathlib import Path | |
| from fastapi.testclient import TestClient | |
| from PIL import Image | |
| from sqlalchemy import select | |
| from sqlalchemy.orm import Session | |
| from app.claimguard import audit | |
| from app.models import ( | |
| AdjudicationSummary, | |
| ArtifactStatus, | |
| Claim, | |
| ClaimState, | |
| DiagnosticReport, | |
| Document, | |
| DocumentKind, | |
| Modality, | |
| Notification, | |
| Recommendation, | |
| RecommendationNote, | |
| User, | |
| ) | |
| from tests.conftest import login | |
| SEED_TAMPERED = Path(__file__).resolve().parents[1] / "seed-assets" / "tampered_xray.dcm" | |
| CLAIM_BODY = { | |
| "claim_type": "imaging", | |
| "description": "Wrist X-ray after a fall on ice", | |
| "procedure_code": "IMG-2-0042", | |
| "diagnosis_code": "S62.10", | |
| "incident_date": "2026-05-20", | |
| "amount_claimed": 420.5, | |
| } | |
| def _attach_imaging_doc( | |
| session: Session, | |
| tmp_path: Path, | |
| claim_id: int, | |
| uploader_id: int, | |
| filename: str, | |
| source: Path | None = None, | |
| ) -> Document: | |
| image_path = tmp_path / filename | |
| if source is not None: | |
| shutil.copyfile(source, image_path) | |
| else: | |
| Image.new("L", (64, 64), color=128).save(image_path, format="PNG") | |
| data = image_path.read_bytes() | |
| doc = Document( | |
| claim_id=claim_id, | |
| uploader_id=uploader_id, | |
| kind=DocumentKind.IMAGING, | |
| modality=Modality.XRAY, | |
| filename=filename, | |
| mime="image/png", | |
| size_bytes=len(data), | |
| sha256=hashlib.sha256(data).hexdigest(), | |
| storage_path=str(image_path), | |
| ) | |
| session.add(doc) | |
| session.commit() | |
| return doc | |
| def _claim_in_imaging_review( | |
| client: TestClient, | |
| session: Session, | |
| users: dict[str, User], | |
| tmp_path: Path, | |
| *, | |
| filename: str = "wrist_xray.png", | |
| source: Path | None = None, | |
| ) -> dict: | |
| """Claim + imaging doc + COMPLETE stage-1 report, parked at IMAGING_REVIEW.""" | |
| login(client, "claimant@demo.ca") | |
| resp = client.post("/api/claims", json=CLAIM_BODY) | |
| assert resp.status_code == 201, resp.text | |
| claim = resp.json() | |
| _attach_imaging_doc( | |
| session, tmp_path, claim["id"], users["claimant"].id, filename, source | |
| ) | |
| assert client.post(f"/api/claims/{claim['id']}/analyze").status_code == 200 | |
| session.expire_all() | |
| row = session.get(Claim, claim["id"]) | |
| assert row is not None and row.state is ClaimState.IMAGING_REVIEW | |
| return claim | |
| def _forward(client: TestClient, claim_id: int) -> dict: | |
| login(client, "imaging@demo.ca") | |
| resp = client.post(f"/api/specialist/cases/{claim_id}/forward") | |
| assert resp.status_code == 200, resp.text | |
| return resp.json() | |
| def test_forward_to_adjudication_end_to_end( | |
| client: TestClient, session: Session, users: dict[str, User], tmp_path: Path | |
| ) -> None: | |
| claim = _claim_in_imaging_review(client, session, users, tmp_path) | |
| forwarded = _forward(client, claim["id"]) | |
| assert forwarded["state"] == "SPECIALIST_REVIEW" | |
| assert forwarded["note_status"] == "pending" # status at response time; ran after | |
| session.expire_all() | |
| note = session.get(RecommendationNote, forwarded["note_id"]) | |
| assert note is not None | |
| assert note.status is ArtifactStatus.COMPLETE | |
| assert note.error is None | |
| assert note.completed_at is not None | |
| assert note.recommendation is Recommendation.SUPPORTS_CLAIM # authentic + xray matches IMG-2 | |
| assert note.generated_by == "fallback_template" | |
| assert note.fallback_reason == "no_api_key" | |
| assert note.payload_json is not None | |
| payload = json.loads(note.payload_json) | |
| assert payload["recommendation"] == "SUPPORTS_CLAIM" | |
| assert "advisory_notice" in payload | |
| # double-forward: claim already left IMAGING_REVIEW | |
| assert client.post(f"/api/specialist/cases/{claim['id']}/forward").status_code == 409 | |
| # recommendation queue is medical-specialist only | |
| resp = client.get("/api/specialist/queue", params={"stage": "recommendation"}) | |
| assert resp.status_code == 403 # still logged in as imaging specialist | |
| login(client, "specialist@demo.ca") | |
| resp = client.get("/api/specialist/queue", params={"stage": "recommendation"}) | |
| assert resp.status_code == 200, resp.text | |
| items = resp.json() | |
| assert len(items) == 1 | |
| item = items[0] | |
| assert item["claim_id"] == claim["id"] | |
| assert item["state"] == "SPECIALIST_REVIEW" | |
| assert item["note_status"] == "complete" | |
| assert item["recommendation"] == "SUPPORTS_CLAIM" | |
| assert item["requires_mandatory_review"] is True # keyless fallback confidence 0.0 | |
| # full case detail carries all artifact versions with parsed payloads | |
| detail = client.get(f"/api/specialist/cases/{claim['id']}").json() | |
| assert detail["claimant"] == "Casey Claimant" | |
| assert detail["claim_ref"] == claim["claim_ref"] | |
| assert len(detail["documents"]) == 1 | |
| assert len(detail["diagnostic_reports"]) == 1 | |
| assert detail["diagnostic_reports"][0]["status"] == "complete" | |
| assert detail["diagnostic_reports"][0]["payload"]["classifier"]["modality"] == "xray" | |
| assert detail["recommendation_notes"][0]["payload"]["recommendation"] == "SUPPORTS_CLAIM" | |
| resp = client.post(f"/api/specialist/cases/{claim['id']}/send-to-insurer") | |
| assert resp.status_code == 200, resp.text | |
| sent = resp.json() | |
| assert sent["state"] == "ADJUDICATION" | |
| session.expire_all() | |
| summary = session.get(AdjudicationSummary, sent["summary_id"]) | |
| assert summary is not None | |
| assert summary.status is ArtifactStatus.COMPLETE | |
| assert summary.error is None | |
| assert summary.recommendation_lean == "LEAN_APPROVE" # SUPPORTS_CLAIM + authentic | |
| assert summary.generated_by == "fallback_template" | |
| assert summary.fallback_reason == "no_api_key" | |
| assert summary.payload_json is not None | |
| payload = json.loads(summary.payload_json) | |
| assert isinstance(payload["similar_case_outcomes"], list) # system-copied; may be empty | |
| assert payload["recommendation_lean"] == "LEAN_APPROVE" | |
| assert "advisory_notice" in payload | |
| session.expire_all() | |
| valid, _ = audit.verify_chain(session) | |
| assert valid is True | |
| def test_return_path_notifies_claimant_and_records_timeline_note( | |
| client: TestClient, session: Session, users: dict[str, User], tmp_path: Path | |
| ) -> None: | |
| claim = _claim_in_imaging_review(client, session, users, tmp_path) | |
| login(client, "imaging@demo.ca") | |
| resp = client.post( | |
| f"/api/specialist/cases/{claim['id']}/return", | |
| json={"note": "image too blurry; please rescan"}, | |
| ) | |
| assert resp.status_code == 200, resp.text | |
| body = resp.json() | |
| assert body["state"] == "RETURNED_TO_CLAIMANT" | |
| session.expire_all() | |
| notification = session.get(Notification, body["notification_id"]) | |
| assert notification is not None | |
| assert notification.claim_id == claim["id"] | |
| assert notification.recipient_id == users["claimant"].id | |
| assert claim["claim_ref"] in notification.subject | |
| assert "image too blurry; please rescan" in notification.body_text | |
| assert "Casey" in notification.body_text # first name only | |
| login(client, "claimant@demo.ca") | |
| timeline = client.get(f"/api/claims/{claim['id']}/timeline").json() | |
| returned = [e for e in timeline if e["action"] == "return_to_claimant"] | |
| assert len(returned) == 1 | |
| assert returned[0]["note"] == "image too blurry; please rescan" | |
| assert returned[0]["to_state"] == "RETURNED_TO_CLAIMANT" | |
| valid, _ = audit.verify_chain(session) | |
| assert valid is True | |
| def test_request_further_testing_path( | |
| client: TestClient, session: Session, users: dict[str, User], tmp_path: Path | |
| ) -> None: | |
| claim = _claim_in_imaging_review(client, session, users, tmp_path) | |
| _forward(client, claim["id"]) | |
| login(client, "specialist@demo.ca") | |
| resp = client.post( | |
| f"/api/specialist/cases/{claim['id']}/request-further-testing", | |
| json={"note": "please provide a follow-up CT study"}, | |
| ) | |
| assert resp.status_code == 200, resp.text | |
| body = resp.json() | |
| assert body["state"] == "PENDING_FURTHER_TESTING" | |
| session.expire_all() | |
| notification = session.get(Notification, body["notification_id"]) | |
| assert notification is not None | |
| assert notification.recipient_id == users["claimant"].id | |
| assert "please provide a follow-up CT study" in notification.body_text | |
| login(client, "claimant@demo.ca") | |
| timeline = client.get(f"/api/claims/{claim['id']}/timeline").json() | |
| requested = [e for e in timeline if e["action"] == "request_further_testing"] | |
| assert len(requested) == 1 | |
| assert requested[0]["note"] == "please provide a follow-up CT study" | |
| def test_regenerate_failed_recommendation_note( | |
| client: TestClient, session: Session, users: dict[str, User], tmp_path: Path | |
| ) -> None: | |
| claim = _claim_in_imaging_review(client, session, users, tmp_path) | |
| forwarded = _forward(client, claim["id"]) | |
| # regenerate refuses while the latest note is COMPLETE | |
| login(client, "specialist@demo.ca") | |
| resp = client.post( | |
| f"/api/specialist/cases/{claim['id']}/regenerate", | |
| json={"stage": "recommendation"}, | |
| ) | |
| assert resp.status_code == 409 | |
| note = session.get(RecommendationNote, forwarded["note_id"]) | |
| assert note is not None | |
| note.status = ArtifactStatus.FAILED | |
| note.error = "boom (forced by test)" | |
| session.commit() | |
| # wrong role for the stage | |
| login(client, "imaging@demo.ca") | |
| assert ( | |
| client.post( | |
| f"/api/specialist/cases/{claim['id']}/regenerate", | |
| json={"stage": "recommendation"}, | |
| ).status_code | |
| == 403 | |
| ) | |
| login(client, "specialist@demo.ca") | |
| resp = client.post( | |
| f"/api/specialist/cases/{claim['id']}/regenerate", | |
| json={"stage": "recommendation"}, | |
| ) | |
| assert resp.status_code == 200, resp.text | |
| assert resp.json() == { | |
| "claim_id": claim["id"], | |
| "stage": "recommendation", | |
| "artifact_id": forwarded["note_id"], | |
| "status": "pending", | |
| } | |
| session.expire_all() | |
| note = session.get(RecommendationNote, forwarded["note_id"]) | |
| assert note is not None | |
| assert note.status is ArtifactStatus.COMPLETE | |
| assert note.error is None | |
| assert note.recommendation is not None | |
| def test_tampered_image_forces_further_testing_and_mandatory_review( | |
| client: TestClient, session: Session, users: dict[str, User], tmp_path: Path | |
| ) -> None: | |
| assert SEED_TAMPERED.is_file() | |
| claim = _claim_in_imaging_review( | |
| client, session, users, tmp_path, filename="tampered_xray.dcm", source=SEED_TAMPERED | |
| ) | |
| report = session.scalar( | |
| select(DiagnosticReport).where(DiagnosticReport.claim_id == claim["id"]) | |
| ) | |
| assert report is not None | |
| assert report.authenticity_verdict == "likely_fraudulent" | |
| assert report.requires_mandatory_review is True | |
| forwarded = _forward(client, claim["id"]) | |
| session.expire_all() | |
| note = session.get(RecommendationNote, forwarded["note_id"]) | |
| assert note is not None | |
| assert note.status is ArtifactStatus.COMPLETE | |
| assert note.recommendation is Recommendation.REQUIRES_FURTHER_TESTING # fallback rule 1 | |
| assert note.requires_mandatory_review is True | |
| payload = json.loads(note.payload_json or "{}") | |
| assert payload["recommendation"] == "REQUIRES_FURTHER_TESTING" | |
| def test_wrong_roles_get_403_on_every_action( | |
| client: TestClient, session: Session, users: dict[str, User], tmp_path: Path | |
| ) -> None: | |
| claim = _claim_in_imaging_review(client, session, users, tmp_path) | |
| cases = f"/api/specialist/cases/{claim['id']}" | |
| login(client, "specialist@demo.ca") # medical specialist cannot act on imaging stage | |
| assert client.post(f"{cases}/forward").status_code == 403 | |
| assert client.post(f"{cases}/return", json={"note": "x"}).status_code == 403 | |
| assert client.get("/api/specialist/queue", params={"stage": "imaging"}).status_code == 403 | |
| login(client, "imaging@demo.ca") # imaging specialist cannot act on specialist stage | |
| assert client.post(f"{cases}/send-to-insurer").status_code == 403 | |
| assert client.post(f"{cases}/request-further-testing", json={"note": "x"}).status_code == 403 | |
| assert ( | |
| client.post(f"{cases}/regenerate", json={"stage": "adjudication"}).status_code == 403 | |
| ) | |
| login(client, "claimant@demo.ca") # claimants never reach specialist endpoints | |
| assert client.get("/api/specialist/queue").status_code == 403 | |
| assert client.get(cases).status_code == 403 | |
| assert client.post(f"{cases}/forward").status_code == 403 | |
| assert client.post(f"{cases}/return", json={"note": "x"}).status_code == 403 | |
| assert client.post(f"{cases}/regenerate", json={"stage": "imaging"}).status_code == 403 | |
| login(client, "agent@demo.ca") # agents only regenerate adjudication artifacts | |
| assert client.get(cases).status_code == 403 | |
| assert client.post(f"{cases}/forward").status_code == 403 | |
| assert client.post(f"{cases}/regenerate", json={"stage": "imaging"}).status_code == 403 | |
| # adjudication regenerate is allowed for agents, but there is no failed summary yet | |
| assert ( | |
| client.post(f"{cases}/regenerate", json={"stage": "adjudication"}).status_code == 409 | |
| ) | |
| login(client, "imaging@demo.ca") | |
| assert client.get("/api/specialist/cases/999999").status_code == 404 | |