Spaces:
Sleeping
Sleeping
| """Deterministic keyless-path generators for every LLM route. | |
| When no Anthropic API key is configured (the assessment's allowed 'mock' path), the | |
| stage wiring calls these pure functions instead of the live model. They return the | |
| exact same pydantic schemas as ``messages.parse`` would, so downstream persistence | |
| and UI code never branch on keyed vs keyless. No LLM, no randomness, no I/O. | |
| """ | |
| from typing import Literal, NamedTuple | |
| from app.llm.schemas import ( | |
| AdjudicationSummaryLLM, | |
| ClaimantEmailLLM, | |
| ConsistencyCheck, | |
| ConsistencyWithHistory, | |
| DiagnosticReportLLM, | |
| RecommendationNoteLLM, | |
| RiskFactor, | |
| SupportingFinding, | |
| ) | |
| from app.ml.base import ImagingAnalysis | |
| _KNOWN_MODALITIES = ("xray", "ct", "mri") | |
| # ---------------------------------------------------------------- stage 1c: diagnostic report | |
| def fallback_diagnostic_report( | |
| analysis: ImagingAnalysis, *, declared_modality: str | None | |
| ) -> DiagnosticReportLLM: | |
| """Render the stage-1 classifier/forensics output as a draft diagnostic report. | |
| This path *is* the classifier, so ``modality_agrees_with_classifier`` is always | |
| True; no clinical findings are asserted and confidence is pinned to 0.0 so the | |
| imaging specialist owns the full read. | |
| """ | |
| modality = analysis.modality if analysis.modality in _KNOWN_MODALITIES else "other" | |
| impression = ( | |
| f"Automated keyless assessment: classifier identified modality " | |
| f"'{analysis.modality}' with confidence {analysis.modality_confidence:.2f}; " | |
| f"authenticity verdict: {analysis.authenticity_verdict}. No clinical findings " | |
| f"are asserted on this path; a licensed imaging specialist must perform the full read." | |
| ) | |
| if declared_modality is not None and declared_modality != analysis.modality: | |
| impression += ( | |
| f" Note: declared modality '{declared_modality}' differs from the classifier result." | |
| ) | |
| return DiagnosticReportLLM( | |
| modality_assessment=modality, # type: ignore[arg-type] | |
| modality_agrees_with_classifier=True, | |
| anatomical_region="not assessed (keyless path)", | |
| view=None, | |
| image_quality="degraded" if analysis.quality_flags else "adequate", | |
| quality_issues=list(analysis.quality_flags), | |
| findings=[], | |
| impression=impression, | |
| visual_inconsistencies=[s.finding for s in analysis.signals if s.score >= 0.5], | |
| confidence=0.0, | |
| ) | |
| # ---------------------------------------------------------------- stage 2: recommendation note | |
| _DICOM_NEXT_STEP = ( | |
| "request original DICOM from the imaging provider; re-acquisition if unavailable" | |
| ) | |
| def fallback_recommendation(bundle: dict) -> RecommendationNoteLLM: | |
| """Rule-engine recommendation over the evidence bundle assembled by the stage wiring. | |
| Expected ``bundle`` keys (all assembled by the caller): | |
| - ``claim``: dict with ``claim_type``, ``procedure_code``, ``diagnosis_code``, | |
| ``amount_claimed``, ``incident_date``. | |
| - ``diagnostic_report``: dict with ``modality``, ``authenticity_verdict``, | |
| ``authenticity_risk``, ``requires_mandatory_review``, ``impression``; | |
| or ``None``/absent when stage 1 has not produced a report. | |
| - ``uploads``: list of dicts with ``filename``, ``kind``, ``text_extract_ok`` (bool). | |
| - ``modality_for_procedure``: ``str | None``, the modality the caller expects for | |
| the claimed procedure code (looked up by the caller, not here). | |
| Rules, in priority order: | |
| 1. report present and (verdict != "authentic" or requires_mandatory_review) | |
| -> REQUIRES_FURTHER_TESTING. | |
| 2. no diagnostic report -> INSUFFICIENT_EVIDENCE (gap: imaging analysis missing). | |
| 3. expected modality set and != report modality -> INSUFFICIENT_EVIDENCE with an | |
| inconsistent ``imaging_matches_stated_procedure`` check. | |
| 4. otherwise -> SUPPORTS_CLAIM. | |
| """ | |
| claim: dict = bundle.get("claim") or {} | |
| report: dict | None = bundle.get("diagnostic_report") | |
| uploads: list[dict] = bundle.get("uploads") or [] | |
| expected_modality: str | None = bundle.get("modality_for_procedure") | |
| verdict = report.get("authenticity_verdict") if report else None | |
| mandatory = bool(report.get("requires_mandatory_review")) if report else False | |
| report_modality = report.get("modality") if report else None | |
| procedure_code = claim.get("procedure_code") | |
| failed_uploads = [u["filename"] for u in uploads if not u.get("text_extract_ok", False)] | |
| recommendation: Literal[ | |
| "SUPPORTS_CLAIM", "INSUFFICIENT_EVIDENCE", "REQUIRES_FURTHER_TESTING" | |
| ] | |
| identified_gaps: list[str] = [] | |
| if report is not None and (verdict != "authentic" or mandatory): | |
| recommendation = "REQUIRES_FURTHER_TESTING" | |
| if verdict != "authentic": | |
| identified_gaps.append( | |
| f"imaging authenticity is not established (verdict '{verdict}')" | |
| ) | |
| if mandatory: | |
| identified_gaps.append("imaging is flagged for mandatory human review") | |
| next_steps = [_DICOM_NEXT_STEP] | |
| elif report is None: | |
| recommendation = "INSUFFICIENT_EVIDENCE" | |
| identified_gaps.append("imaging analysis missing: no diagnostic report is available") | |
| next_steps = ["run imaging analysis on the submitted study before specialist review"] | |
| elif expected_modality is not None and expected_modality != report_modality: | |
| recommendation = "INSUFFICIENT_EVIDENCE" | |
| identified_gaps.append( | |
| f"submitted imaging modality '{report_modality}' does not match the modality " | |
| f"'{expected_modality}' expected for procedure {procedure_code}" | |
| ) | |
| next_steps = [ | |
| "confirm the procedure code with the provider or request imaging of the " | |
| "expected modality" | |
| ] | |
| else: | |
| recommendation = "SUPPORTS_CLAIM" | |
| next_steps = ["proceed to specialist review and sign-off"] | |
| for filename in failed_uploads: | |
| identified_gaps.append(f"text could not be extracted from upload:{filename}") | |
| checks = [ | |
| _check_procedure(report, expected_modality, report_modality, procedure_code), | |
| _check_diagnosis(report), | |
| _check_documents(uploads, failed_uploads), | |
| _check_dates(claim), | |
| _check_authenticity(report, verdict, mandatory), | |
| ] | |
| findings = [ | |
| SupportingFinding( | |
| source_document="claim_form", | |
| finding=( | |
| f"{claim.get('claim_type')} claim for procedure {procedure_code} " | |
| f"(diagnosis {claim.get('diagnosis_code')}), amount claimed " | |
| f"{claim.get('amount_claimed')}, incident date {claim.get('incident_date')}" | |
| ), | |
| relevance="establishes the claimed procedure and claim context", | |
| ) | |
| ] | |
| if report is not None: | |
| findings.append( | |
| SupportingFinding( | |
| source_document="diagnostic_report", | |
| finding=( | |
| f"{report_modality} study; authenticity verdict '{verdict}'; " | |
| f"impression: {report.get('impression')}" | |
| ), | |
| relevance="primary imaging evidence for the claimed procedure", | |
| ) | |
| ) | |
| for upload in uploads: | |
| extracted = "text extracted" if upload.get("text_extract_ok") else "text extraction failed" | |
| findings.append( | |
| SupportingFinding( | |
| source_document=f"upload:{upload['filename']}", | |
| finding=f"{upload.get('kind')} document ({extracted})", | |
| relevance="supporting documentation submitted with the claim", | |
| ) | |
| ) | |
| summary = ( | |
| f"Automated keyless rule-engine assessment of this {claim.get('claim_type')} claim " | |
| f"(procedure {procedure_code}): recommendation {recommendation}. " | |
| + ( | |
| "No diagnostic report was available, so the imaging evidence could not be " | |
| "evaluated. " | |
| if report is None | |
| else ( | |
| f"The diagnostic report records a {report_modality} study with authenticity " | |
| f"verdict '{verdict}'. " | |
| ) | |
| ) | |
| + ( | |
| f"Identified gaps: {'; '.join(identified_gaps)}. " | |
| if identified_gaps | |
| else "No gaps were identified by the rule engine. " | |
| ) | |
| + "This is an advisory rule-based result; the reviewing specialist remains " | |
| "responsible for the final recommendation." | |
| ) | |
| return RecommendationNoteLLM( | |
| recommendation=recommendation, | |
| confidence=0.0, | |
| summary=summary, | |
| supporting_findings=findings, | |
| identified_gaps=identified_gaps, | |
| suggested_next_steps=next_steps, | |
| consistency_checks=checks, | |
| ) | |
| def _check_procedure( | |
| report: dict | None, | |
| expected_modality: str | None, | |
| report_modality: str | None, | |
| procedure_code: object, | |
| ) -> ConsistencyCheck: | |
| if report is None: | |
| result, detail = ( | |
| "indeterminate", | |
| "no diagnostic report available to compare against the stated procedure", | |
| ) | |
| elif expected_modality is None: | |
| result, detail = ( | |
| "indeterminate", | |
| f"no expected modality on file for procedure {procedure_code}", | |
| ) | |
| elif expected_modality == report_modality: | |
| result, detail = ( | |
| "consistent", | |
| f"report modality '{report_modality}' matches the modality expected for " | |
| f"procedure {procedure_code}", | |
| ) | |
| else: | |
| result, detail = ( | |
| "inconsistent", | |
| f"report modality '{report_modality}' does not match expected modality " | |
| f"'{expected_modality}' for procedure {procedure_code}", | |
| ) | |
| return ConsistencyCheck(check="imaging_matches_stated_procedure", result=result, detail=detail) | |
| def _check_diagnosis(report: dict | None) -> ConsistencyCheck: | |
| detail = ( | |
| "no diagnostic report available" | |
| if report is None | |
| else "rule engine cannot map diagnosis codes to imaging content; specialist " | |
| "judgement required" | |
| ) | |
| return ConsistencyCheck( | |
| check="imaging_matches_diagnosis_code", result="indeterminate", detail=detail | |
| ) | |
| def _check_documents(uploads: list[dict], failed_uploads: list[str]) -> ConsistencyCheck: | |
| if failed_uploads: | |
| detail = f"text extraction failed for: {', '.join(failed_uploads)}" | |
| elif not uploads: | |
| detail = "no supporting uploads to cross-check" | |
| else: | |
| detail = "automated cross-document comparison is not available on the keyless path" | |
| return ConsistencyCheck( | |
| check="documents_internally_consistent", result="indeterminate", detail=detail | |
| ) | |
| def _check_dates(claim: dict) -> ConsistencyCheck: | |
| incident_date = claim.get("incident_date") | |
| detail = ( | |
| f"incident date {incident_date} recorded; no comparison dates are available to " | |
| "the rule engine" | |
| if incident_date | |
| else "no dates are available to the rule engine" | |
| ) | |
| return ConsistencyCheck(check="dates_plausible", result="indeterminate", detail=detail) | |
| def _check_authenticity( | |
| report: dict | None, verdict: str | None, mandatory: bool | |
| ) -> ConsistencyCheck: | |
| if report is None: | |
| result, detail = "indeterminate", "no diagnostic report available" | |
| elif verdict == "authentic" and not mandatory: | |
| result, detail = ( | |
| "consistent", | |
| f"authenticity verdict '{verdict}' (risk {report.get('authenticity_risk')})", | |
| ) | |
| else: | |
| result, detail = ( | |
| "inconsistent", | |
| f"authenticity verdict '{verdict}'" | |
| + ("; flagged for mandatory human review" if mandatory else ""), | |
| ) | |
| return ConsistencyCheck(check="authenticity_concerns", result=result, detail=detail) | |
| # ---------------------------------------------------------------- stage 3: adjudication summary | |
| _SIMILAR_CASE_NOTE = "(automated) same modality and procedure family" | |
| def fallback_adjudication( | |
| specialist_recommendation: str | None, | |
| history_stats: dict, | |
| similar_cases: list[dict], | |
| authenticity_verdict: str | None, | |
| ) -> AdjudicationSummaryLLM: | |
| """Rule-based adjudication summary from history stats and the specialist recommendation. | |
| ``history_stats`` keys: ``total``, ``approved``, ``rejected``, ``recent_12mo``, | |
| ``prior_rejections``. SUPPORTS_CLAIM leans approve; anything else (or no | |
| recommendation) yields no clear lean; a non-authentic imaging verdict always forces | |
| NO_CLEAR_LEAN plus a high-severity risk factor. | |
| """ | |
| total = int(history_stats.get("total", 0)) | |
| approved = int(history_stats.get("approved", 0)) | |
| rejected = int(history_stats.get("rejected", 0)) | |
| recent_12mo = int(history_stats.get("recent_12mo", 0)) | |
| prior_rejections = int(history_stats.get("prior_rejections", 0)) | |
| not_authentic = authenticity_verdict is not None and authenticity_verdict != "authentic" | |
| lean: Literal["LEAN_APPROVE", "LEAN_REJECT", "NO_CLEAR_LEAN"] = ( | |
| "LEAN_APPROVE" if specialist_recommendation == "SUPPORTS_CLAIM" else "NO_CLEAR_LEAN" | |
| ) | |
| if not_authentic: | |
| lean = "NO_CLEAR_LEAN" | |
| risk_factors: list[RiskFactor] = [] | |
| if prior_rejections >= 2: | |
| risk_factors.append( | |
| RiskFactor( | |
| factor="history of rejected claims", | |
| severity="medium", | |
| source="claim_history", | |
| ) | |
| ) | |
| if recent_12mo >= 5: | |
| risk_factors.append( | |
| RiskFactor( | |
| factor="high recent claim frequency", | |
| severity="medium", | |
| source="claim_history", | |
| ) | |
| ) | |
| if not_authentic: | |
| risk_factors.append( | |
| RiskFactor( | |
| factor=f"imaging authenticity not established (verdict '{authenticity_verdict}')", | |
| severity="high", | |
| source="diagnostic_report", | |
| ) | |
| ) | |
| if total == 0: | |
| consistency = ConsistencyWithHistory( | |
| assessment="no_history", | |
| details="claimant has no prior claims on record", | |
| ) | |
| elif prior_rejections >= 2: | |
| consistency = ConsistencyWithHistory( | |
| assessment="minor_discrepancies", | |
| details=( | |
| f"{prior_rejections} prior rejection(s) across {total} claim(s); " | |
| "pattern warrants agent attention" | |
| ), | |
| ) | |
| else: | |
| consistency = ConsistencyWithHistory( | |
| assessment="consistent", | |
| details=f"claim pattern is in line with {total} prior claim(s) on record", | |
| ) | |
| summary = ( | |
| "Automated keyless adjudication summary. Specialist recommendation: " | |
| f"{specialist_recommendation or 'none recorded'}. Claim history: {total} total " | |
| f"claim(s) ({approved} approved, {rejected} rejected), {recent_12mo} in the last " | |
| f"12 months, {prior_rejections} prior rejection(s). {len(similar_cases)} similar " | |
| f"case(s) retrieved. Recommendation lean: {lean}. The final approve/reject " | |
| "decision rests solely with the insurance agent." | |
| ) | |
| return AdjudicationSummaryLLM( | |
| summary=summary, | |
| risk_factors=risk_factors, | |
| consistency_with_history=consistency, | |
| similar_case_relevance_notes=[_SIMILAR_CASE_NOTE] * len(similar_cases), | |
| recommendation_lean=lean, | |
| confidence=0.0, | |
| ) | |
| # ---------------------------------------------------------------- claimant email | |
| class _EmailTemplate(NamedTuple): | |
| subject: str | |
| greeting: str | |
| body: tuple[str, ...] | |
| closing: str | |
| EMAIL_APPROVED_EN_FORMAL = _EmailTemplate( | |
| subject="Your claim {claim_ref} has been approved", | |
| greeting="Dear {first_name},", | |
| body=( | |
| "We are pleased to inform you that your {claim_type} claim ({claim_ref}) has " | |
| "been approved.", | |
| "Payment will be processed in accordance with the terms of your policy. No " | |
| "further action is required on your part.", | |
| "Should you have any questions, please contact our claims team and quote " | |
| "reference {claim_ref}.", | |
| ), | |
| closing="Sincerely,\nThe ClaimFlow Claims Team", | |
| ) | |
| EMAIL_APPROVED_EN_PLAIN = _EmailTemplate( | |
| subject="Good news about your claim {claim_ref}", | |
| greeting="Hi {first_name},", | |
| body=( | |
| "Good news: your {claim_type} claim ({claim_ref}) has been approved.", | |
| "You do not need to do anything else. Payment will follow under your plan.", | |
| "Questions? Just reply to this email and mention {claim_ref}.", | |
| ), | |
| closing="Thanks,\nThe ClaimFlow Team", | |
| ) | |
| EMAIL_REJECTED_EN_FORMAL = _EmailTemplate( | |
| subject="Decision on your claim {claim_ref}", | |
| greeting="Dear {first_name},", | |
| body=( | |
| "We have completed our review of your {claim_type} claim ({claim_ref}). After " | |
| "careful consideration, we are unable to approve it at this time.", | |
| "You may request an appeal within 30 days of this notice. Our team will review " | |
| "any additional documentation you choose to provide.", | |
| "Should you have any questions, please contact our claims team and quote " | |
| "reference {claim_ref}.", | |
| ), | |
| closing="Sincerely,\nThe ClaimFlow Claims Team", | |
| ) | |
| EMAIL_REJECTED_EN_PLAIN = _EmailTemplate( | |
| subject="An update on your claim {claim_ref}", | |
| greeting="Hi {first_name},", | |
| body=( | |
| "We have finished reviewing your {claim_type} claim ({claim_ref}). We are not " | |
| "able to approve it right now.", | |
| "You can ask us to take another look. You have 30 days from today to start an " | |
| "appeal, and you can send any extra documents that may help.", | |
| "Questions? Just reply to this email and mention {claim_ref}.", | |
| ), | |
| closing="Thanks,\nThe ClaimFlow Team", | |
| ) | |
| EMAIL_APPROVED_FR_FORMAL = _EmailTemplate( | |
| subject="Votre demande {claim_ref} a été approuvée", | |
| greeting="Bonjour {first_name},", | |
| body=( | |
| "Nous avons le plaisir de vous informer que votre demande de règlement " | |
| "({claim_type}, dossier {claim_ref}) a été approuvée.", | |
| "Le paiement sera traité conformément aux modalités de votre contrat. Aucune " | |
| "autre démarche n'est requise de votre part.", | |
| "Pour toute question, veuillez communiquer avec notre équipe des réclamations " | |
| "en mentionnant le numéro de dossier {claim_ref}.", | |
| ), | |
| closing="Veuillez agréer nos salutations distinguées,\nL'équipe des réclamations ClaimFlow", | |
| ) | |
| EMAIL_APPROVED_FR_PLAIN = _EmailTemplate( | |
| subject="Bonne nouvelle pour votre demande {claim_ref}", | |
| greeting="Bonjour {first_name},", | |
| body=( | |
| "Bonne nouvelle : votre demande ({claim_type}, dossier {claim_ref}) a été " | |
| "approuvée.", | |
| "Vous n'avez rien d'autre à faire. Le paiement suivra selon votre contrat.", | |
| "Des questions? Répondez à ce courriel en indiquant le numéro {claim_ref}.", | |
| ), | |
| closing="Merci,\nL'équipe ClaimFlow", | |
| ) | |
| EMAIL_REJECTED_FR_FORMAL = _EmailTemplate( | |
| subject="Décision concernant votre demande {claim_ref}", | |
| greeting="Bonjour {first_name},", | |
| body=( | |
| "Nous avons terminé l'examen de votre demande de règlement ({claim_type}, " | |
| "dossier {claim_ref}). Après étude, nous ne pouvons malheureusement pas " | |
| "l'approuver pour le moment.", | |
| "Vous disposez de 30 jours à compter du présent avis pour demander une révision " | |
| "de la décision. Notre équipe examinera tout document supplémentaire que vous " | |
| "voudrez bien nous transmettre.", | |
| "Pour toute question, veuillez communiquer avec notre équipe des réclamations " | |
| "en mentionnant le numéro de dossier {claim_ref}.", | |
| ), | |
| closing="Veuillez agréer nos salutations distinguées,\nL'équipe des réclamations ClaimFlow", | |
| ) | |
| EMAIL_REJECTED_FR_PLAIN = _EmailTemplate( | |
| subject="Une mise à jour sur votre demande {claim_ref}", | |
| greeting="Bonjour {first_name},", | |
| body=( | |
| "Nous avons terminé l'étude de votre demande ({claim_type}, dossier " | |
| "{claim_ref}). Malheureusement, nous ne pouvons pas l'approuver pour le moment.", | |
| "Vous pouvez nous demander de revoir la décision. Vous avez 30 jours à partir " | |
| "d'aujourd'hui pour le faire, et vous pouvez nous envoyer tout document qui " | |
| "pourrait aider.", | |
| "Des questions? Répondez à ce courriel en indiquant le numéro {claim_ref}.", | |
| ), | |
| closing="Merci,\nL'équipe ClaimFlow", | |
| ) | |
| _EMAIL_TEMPLATES: dict[tuple[str, str, str], _EmailTemplate] = { | |
| ("APPROVED", "en", "formal"): EMAIL_APPROVED_EN_FORMAL, | |
| ("APPROVED", "en", "plain_language"): EMAIL_APPROVED_EN_PLAIN, | |
| ("REJECTED", "en", "formal"): EMAIL_REJECTED_EN_FORMAL, | |
| ("REJECTED", "en", "plain_language"): EMAIL_REJECTED_EN_PLAIN, | |
| ("APPROVED", "fr", "formal"): EMAIL_APPROVED_FR_FORMAL, | |
| ("APPROVED", "fr", "plain_language"): EMAIL_APPROVED_FR_PLAIN, | |
| ("REJECTED", "fr", "formal"): EMAIL_REJECTED_FR_FORMAL, | |
| ("REJECTED", "fr", "plain_language"): EMAIL_REJECTED_FR_PLAIN, | |
| } | |
| def fallback_claimant_email( | |
| *, | |
| decision: Literal["APPROVED", "REJECTED"], | |
| first_name: str, | |
| language: Literal["en", "fr"], | |
| tone: Literal["formal", "plain_language"], | |
| claim_ref: str, | |
| claim_type: str, | |
| ) -> ClaimantEmailLLM: | |
| """Render one of the eight static claimant-email templates. | |
| Templates never mention scores, fraud, risk, or medical findings; rejections cite | |
| the 30-day appeal window without blame language. | |
| """ | |
| template = _EMAIL_TEMPLATES[(decision, language, tone)] | |
| slots = {"first_name": first_name, "claim_ref": claim_ref, "claim_type": claim_type} | |
| return ClaimantEmailLLM( | |
| subject=template.subject.format(**slots), | |
| greeting=template.greeting.format(**slots), | |
| body_paragraphs=[paragraph.format(**slots) for paragraph in template.body], | |
| closing=template.closing.format(**slots), | |
| ) | |