Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions apps/gateway/Gateway.API/Services/PostgresPARequestStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,17 +301,21 @@ private async Task<string> GenerateIdAsync(CancellationToken ct)
await IdGenerationLock.WaitAsync(ct).ConfigureAwait(false);
try
{
var maxId = await _context.PriorAuthRequests
// Filter to only sequential PA-NNN IDs (exclude PA-DEMO-* etc.)
var sequentialIds = await _context.PriorAuthRequests
.AsNoTracking()
.Select(e => e.Id)
.OrderByDescending(id => id)
.FirstOrDefaultAsync(ct)
.Where(id => id.StartsWith("PA-") && id.Length <= 7)
.ToListAsync(ct)
.ConfigureAwait(false);

var counter = 1;
if (maxId is not null && maxId.StartsWith("PA-") && int.TryParse(maxId[3..], out var existing))
foreach (var id in sequentialIds)
{
counter = existing + 1;
if (int.TryParse(id[3..], out var existing) && existing >= counter)
{
counter = existing + 1;
}
}

return $"PA-{counter:D3}";
Expand Down
58 changes: 6 additions & 52 deletions apps/intelligence/src/api/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,12 @@
from src.models.clinical_bundle import ClinicalBundle
from src.models.pa_form import PAFormResponse
from src.parsers.pdf_parser import parse_pdf
from src.policies.example_policy import EXAMPLE_POLICY
from src.policies.registry import registry
from src.reasoning.evidence_extractor import extract_evidence
from src.reasoning.form_generator import generate_form_data

router = APIRouter()

# Supported procedure codes (MRI Lumbar Spine)
SUPPORTED_PROCEDURE_CODES = {"72148", "72149", "72158"}


class AnalyzeRequest(BaseModel):
"""Request payload for analysis endpoint."""
Expand All @@ -36,14 +33,8 @@ async def analyze(request: AnalyzeRequest) -> PAFormResponse:
Analyze clinical data and generate PA form response.

Uses LLM to extract evidence from clinical data and generate PA form.
Resolves policy from registry; unknown CPT codes fall back to generic policy.
"""
# Check if procedure is supported
if request.procedure_code not in SUPPORTED_PROCEDURE_CODES:
raise HTTPException(
status_code=400,
detail=f"Procedure code {request.procedure_code} not supported",
)

# Parse clinical data into structured format
bundle = ClinicalBundle.from_dict(request.patient_id, request.clinical_data)

Expand All @@ -55,8 +46,8 @@ async def analyze(request: AnalyzeRequest) -> PAFormResponse:
detail="patient.birth_date is required",
)

# Load policy with requested procedure code
policy = {**EXAMPLE_POLICY, "procedure_codes": [request.procedure_code]}
# Resolve policy from registry (no more 400 rejection for unsupported CPTs)
policy = registry.resolve(request.procedure_code)

# Extract evidence using LLM
evidence = await extract_evidence(bundle, policy)
Expand Down Expand Up @@ -87,21 +78,11 @@ async def analyze_with_documents(
except json.JSONDecodeError as e:
raise HTTPException(status_code=400, detail=f"Invalid clinical data JSON: {e}")

# Check if procedure is supported
if procedure_code not in SUPPORTED_PROCEDURE_CODES:
raise HTTPException(
status_code=400,
detail=f"Procedure code {procedure_code} not supported",
)

# Parse clinical data into structured format
bundle = ClinicalBundle.from_dict(patient_id, clinical_data_dict)

# Read all document bytes, then parse PDFs in parallel
pdf_bytes_list = [await doc.read() for doc in documents]

document_texts = list(await asyncio.gather(*[parse_pdf(b) for b in pdf_bytes_list]))

bundle.document_texts = document_texts

# Validate required patient data
Expand All @@ -112,8 +93,8 @@ async def analyze_with_documents(
detail="patient.birth_date is required",
)

# Load policy with requested procedure code
policy = {**EXAMPLE_POLICY, "procedure_codes": [procedure_code]}
# Resolve policy from registry
policy = registry.resolve(procedure_code)

# Extract evidence using LLM
evidence = await extract_evidence(bundle, policy)
Expand All @@ -122,30 +103,3 @@ async def analyze_with_documents(
form_response = await generate_form_data(bundle, evidence, policy)

return form_response


def _build_field_mappings(bundle: ClinicalBundle, procedure_code: str) -> dict[str, str]:
"""Build PDF field mappings from clinical bundle."""
patient_name = bundle.patient.name if bundle.patient else "Unknown"
patient_dob = (
bundle.patient.birth_date.isoformat()
if bundle.patient and bundle.patient.birth_date
else "Unknown"
)
member_id = (
bundle.patient.member_id
if bundle.patient and bundle.patient.member_id
else "Unknown"
)
diagnosis_codes = ", ".join(c.code for c in bundle.conditions) if bundle.conditions else ""

return {
"PatientName": patient_name,
"PatientDOB": patient_dob,
"MemberID": member_id,
"DiagnosisCodes": diagnosis_codes,
"ProcedureCode": procedure_code,
"ClinicalSummary": "Awaiting production configuration",
"ProviderSignature": "",
"Date": "",
}
4 changes: 4 additions & 0 deletions apps/intelligence/src/models/pa_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@ class PAFormResponse(BaseModel):
field_mappings: dict[str, str] = Field(
description="PDF field name to value mappings"
)
policy_id: str | None = Field(default=None, description="Policy identifier")
lcd_reference: str | None = Field(
default=None, description="LCD article reference"
)
28 changes: 28 additions & 0 deletions apps/intelligence/src/models/policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Policy data models for LCD-backed prior authorization criteria."""

from pydantic import BaseModel


class PolicyCriterion(BaseModel):
"""A single criterion from a coverage policy."""

id: str
description: str
weight: float # 0.0-1.0, clinical importance
required: bool = False # Hard gate — if NOT_MET, caps score
lcd_section: str | None = None # e.g. "L34220 §4.2"
bypasses: list[str] = [] # criterion IDs this one bypasses when MET


class PolicyDefinition(BaseModel):
"""Complete policy definition with LCD metadata."""

policy_id: str
policy_name: str
lcd_reference: str | None = None # e.g. "L34220"
lcd_title: str | None = None
lcd_contractor: str | None = None
payer: str
procedure_codes: list[str]
diagnosis_codes: list[str] = []
criteria: list[PolicyCriterion]
35 changes: 35 additions & 0 deletions apps/intelligence/src/policies/generic_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Generic fallback policy for unsupported procedure codes."""

from src.models.policy import PolicyCriterion, PolicyDefinition


def build_generic_policy(procedure_code: str) -> PolicyDefinition:
"""Build a generic medical necessity policy for any procedure code."""
return PolicyDefinition(
policy_id=f"generic-{procedure_code}",
policy_name="General Medical Necessity",
lcd_reference=None,
payer="General",
procedure_codes=[procedure_code],
diagnosis_codes=[],
criteria=[
PolicyCriterion(
id="medical_necessity",
description="Medical necessity is documented with clinical rationale",
weight=0.40,
required=True,
),
PolicyCriterion(
id="diagnosis_present",
description="Valid diagnosis code is present and supports the procedure",
weight=0.30,
required=True,
),
PolicyCriterion(
id="conservative_therapy",
description="Conservative therapy attempted or documented as not applicable",
weight=0.30,
required=False,
),
],
)
30 changes: 30 additions & 0 deletions apps/intelligence/src/policies/registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Policy registry for resolving procedure codes to policy definitions."""

from src.models.policy import PolicyDefinition
from src.policies.generic_policy import build_generic_policy


class PolicyRegistry:
"""Resolves procedure codes to LCD-backed policy definitions."""

def __init__(self) -> None:
self._by_cpt: dict[str, PolicyDefinition] = {}

def register(self, policy: PolicyDefinition) -> None:
for cpt in policy.procedure_codes:
self._by_cpt[cpt] = policy

def resolve(self, procedure_code: str) -> PolicyDefinition:
"""Return LCD-backed policy if available, else generic fallback."""
if procedure_code in self._by_cpt:
return self._by_cpt[procedure_code]
return build_generic_policy(procedure_code)


# Module-level singleton
registry = PolicyRegistry()

# Import seed policies to register them
from src.policies.seed import register_all_seeds # noqa: E402

register_all_seeds(registry)
13 changes: 13 additions & 0 deletions apps/intelligence/src/policies/seed/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Seed policy loader."""
from src.policies.seed.mri_lumbar import POLICY as MRI_LUMBAR
from src.policies.seed.mri_brain import POLICY as MRI_BRAIN
from src.policies.seed.tka import POLICY as TKA
from src.policies.seed.physical_therapy import POLICY as PHYSICAL_THERAPY
from src.policies.seed.epidural_steroid import POLICY as EPIDURAL_STEROID

ALL_SEED_POLICIES = [MRI_LUMBAR, MRI_BRAIN, TKA, PHYSICAL_THERAPY, EPIDURAL_STEROID]


def register_all_seeds(registry) -> None:
for policy in ALL_SEED_POLICIES:
registry.register(policy)
Comment on lines +11 to +13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add type annotation for registry parameter.

Per coding guidelines, all functions must have complete type annotations.

🔧 Add type hint
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from src.policies.registry import PolicyRegistry
+
 
-def register_all_seeds(registry) -> None:
+def register_all_seeds(registry: "PolicyRegistry") -> None:
     for policy in ALL_SEED_POLICIES:
         registry.register(policy)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/intelligence/src/policies/seed/__init__.py` around lines 11 - 13, The
function register_all_seeds lacks a type annotation for its registry parameter;
update its signature to include the appropriate registry interface/type (the
object expected by register_all_seeds that exposes register), e.g., annotate
registry with the correct protocol or concrete class used in this module so
callers and linters know its type (refer to register_all_seeds,
ALL_SEED_POLICIES, and the registry.register usage to determine the proper type
to import and use).

51 changes: 51 additions & 0 deletions apps/intelligence/src/policies/seed/epidural_steroid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Epidural Steroid Injection seed policy — LCD L39240."""

from src.models.policy import PolicyCriterion, PolicyDefinition

POLICY = PolicyDefinition(
policy_id="lcd-esi-L39240",
policy_name="Epidural Steroid Injection",
lcd_reference="L39240",
lcd_title="Epidural Steroid Injections",
lcd_contractor="Noridian Healthcare Solutions",
payer="CMS Medicare",
procedure_codes=["62322", "62323"],
diagnosis_codes=["M54.10", "M54.16", "M54.17", "M48.06"],
criteria=[
PolicyCriterion(
id="diagnosis_confirmed",
description="Radiculopathy/stenosis confirmed by history, exam, and imaging",
weight=0.25,
required=True,
lcd_section="L39240 — Requirement 1",
),
PolicyCriterion(
id="severity_documented",
description="Pain severe enough to impact QoL/function, documented with standardized scale",
weight=0.20,
required=True,
lcd_section="L39240 — Requirement 2",
),
PolicyCriterion(
id="conservative_care_4wk",
description="4 weeks conservative care failed/intolerable (except acute herpes zoster)",
weight=0.25,
required=True,
lcd_section="L39240 — Requirement 3",
),
PolicyCriterion(
id="frequency_within_limits",
description="<=4 sessions per region per rolling 12 months",
weight=0.15,
required=True,
lcd_section="L39240 — Frequency Limits",
),
PolicyCriterion(
id="image_guidance_planned",
description="Fluoroscopy or CT guidance with contrast planned",
weight=0.15,
required=True,
lcd_section="L39240 — Procedural Requirements",
),
],
)
44 changes: 44 additions & 0 deletions apps/intelligence/src/policies/seed/mri_brain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""MRI Brain seed policy — LCD L37373."""

from src.models.policy import PolicyCriterion, PolicyDefinition

POLICY = PolicyDefinition(
policy_id="lcd-mri-brain-L37373",
policy_name="MRI Brain",
lcd_reference="L37373",
lcd_title="Magnetic Resonance Imaging of the Brain",
lcd_contractor="Noridian Healthcare Solutions",
payer="CMS Medicare",
procedure_codes=["70551", "70552", "70553"],
diagnosis_codes=["G40.909", "R51.9", "G43.909", "G35"],
criteria=[
PolicyCriterion(
id="diagnosis_present",
description="Valid ICD-10 for neurological condition",
weight=0.15,
required=True,
lcd_section="L37373 / A57204 — Covered Diagnoses",
),
PolicyCriterion(
id="neurological_indication",
description="Tumor, stroke, MS, seizures, unexplained neuro deficit",
weight=0.35,
required=True,
lcd_section="L37373 — Indications for MRI",
),
PolicyCriterion(
id="ct_insufficient",
description="CT already performed and insufficient, or MRI specifically indicated",
weight=0.25,
required=False,
lcd_section="L37373 — MRI vs CT Selection",
),
PolicyCriterion(
id="clinical_documentation",
description="Supporting clinical findings documented",
weight=0.25,
required=True,
lcd_section="L37373 — Coverage Requirements",
),
],
)
Loading
Loading