Battlecon's picture
Initial clean deployment commit
05cb41b
Raw
History Blame Contribute Delete
6.81 kB
import io
import json
from PIL import Image
from google import genai
from google.genai import types
from app.core.config import settings
from app.models.schemas import ExtractedClaimData, ItemizedClaimData
from typing import List, Dict
# Initialize the official modern Google GenAI Client
# It automatically picks up GEMINI_API_KEY from the environment/settings
client = genai.Client(api_key=settings.GEMINI_API_KEY)
async def analyze_claim_documents(images_bytes_list: List[Dict[str, any]], rag_policy_context: str) -> ExtractedClaimData:
"""
Sends the medical document image and dynamic RAG policy context to Gemini 2.5 Flash.
Enforces a strict structured JSON output matching the ExtractedClaimData schema.
"""
try:
# Build the single-pass prompt combining extraction rules, RAG guidelines, and formatting schemas
system_prompt = (
"You are an expert medical insurance claims adjudication engine. "
"Your job is to extract data from the provided document image and evaluate it "
"against the accompanying Policy Context constraints.\n\n"
"STRICT RULES:\n"
"1. Extract patient info, treatment dates, provider details, and itemized lists accurately.\n"
"2. Cross-reference all extracted data against the provided Policy Context to determine "
"if any items match listed exclusions.\n"
"3. Assess medical necessity: verify if the clinical diagnosis logically justifies the "
"prescribed drugs, procedures, or diagnostic tests.\n"
"4. Return structural results matching the requested JSON schema exactly."
"5.Cross-reference the Patient's Diagnosis and Treatment against the POLICY CONTEXT below. You must medically evaluate if the treatment is a synonym for an excluded category (e.g., Bariatric/Obesity = Weight Loss). If it matches an exclusion, you MUST set `is_treatment_excluded` to true and provide the exact `exclusion_reason`."
"6.Check the `pre_authorization_required` list in the POLICY CONTEXT. If the billed items include a procedure on that list (like an MRI Scan), you MUST set `requires_pre_auth` to true. If the receipt/prescription does NOT explicitly mention an approved authorization code, you MUST also set `missing_pre_auth` to true."
"CRITICAL - ITEMIZED EVALUATION (NOT reject all if one item fails):\n"
"7. For EVERY line item (medicine, procedure, test, consultation), determine:\n"
" - Is it in the policy exclusions list?\n"
" - Is it medically necessary given the diagnosis?\n"
" - Does it match standard clinical protocols?\n"
"8. Mark is_covered and is_medically_necessary for each item INDEPENDENTLY.\n"
"9. IMPORTANT: Do NOT reject entire claim—evaluate items separately.\n"
" Some items may be covered, others excluded. That's OK—return individual assessments.\n"
"10. Break down the bill into individual line items. For each item, populate the itemized_claims list.\n"
"11. Return ONLY valid JSON matching the schema. No markdown, no code blocks.\n\n"
"RESPONSE FORMAT - Return ONLY this JSON structure (no other text):\n"
"{\n"
' "patient_name": "string",\n'
' "treatment_date": "YYYY-MM-DD",\n'
' "doctor_name": "string",\n'
' "doctor_reg_no": "string",\n'
' "diagnosis": "string",\n'
' "itemized_claims": [\n'
' {\n'
' "description": "item name",\n'
' "amount": 1000.50,\n'
' "category": "medicine|procedure|diagnostic_test|consultation",\n'
' "is_covered": true/false,\n'
' "exclusion_reason": "reason or None",\n'
' "is_medically_necessary": true/false,\n'
' "medical_necessity_reason": "reason"\n'
" }\n"
" ],\n"
' "total_billed_amount": 5000.00,\n'
' "overall_medically_necessary": true,\n'
' "medical_necessity_reasoning": "overall assessment"\n'
"}"
)
user_content = f"""
POLICY CONTEXT (Retrieved rules for this claim context):
-----------------------------------------------------
{rag_policy_context}
-----------------------------------------------------
Please evaluate the attached medical document image according to the rules above.
Extract each line item (medicine, procedure, test, consultation) separately.
For EACH item, determine: is it covered by policy? Is it medically necessary?
Return ONLY JSON, no explanations.
"""
contents = [user_content]
for img_bytes in images_bytes_list:
contents.append(
types.Part.from_bytes(
data=img_bytes["bytes"],
mime_type=img_bytes["mime_type"]
)
)
print(">>> 1. Preparing to call Gemini API...")
# Execute the single-pass multimodal API call without strict schema (use text JSON instead)
response = await client.aio.models.generate_content(
model='gemini-2.5-flash',
contents=contents,
config=types.GenerateContentConfig(
system_instruction=system_prompt,
response_mime_type="application/json",
temperature=0.1, # Low temperature ensures highly predictable, deterministic evaluation
)
)
print(">>> 2. Gemini API Call Complete!")
# Parse the response text as JSON and validate with Pydantic
response_text = response.text.strip()
# Remove markdown code blocks if present
if response_text.startswith("```json"):
response_text = response_text[7:]
if response_text.startswith("```"):
response_text = response_text[3:]
if response_text.endswith("```"):
response_text = response_text[:-3]
response_text = response_text.strip()
print(f"DEBUG: Raw Gemini Response:\n{response_text[:500]}")
response_json = json.loads(response_text)
return ExtractedClaimData.model_validate(response_json)
except json.JSONDecodeError as je:
print(f"❌ JSON Parse Error: {str(je)}")
print(f"Response text: {response_text[:1000]}")
raise RuntimeError(f"Failed to parse Gemini JSON response: {str(je)}")
except Exception as e:
print(f"❌ Gemini Adjudication Call Failed: {str(e)}")
raise RuntimeError(f"Failed to process document via Gemini AI: {str(e)}")