import re import json import os from openai import OpenAI from dotenv import load_dotenv load_dotenv() def get_structured_pan_data(ocr_text: str, app_id: str, provider: str = "gemini", logger=None) -> dict: """ Use AI providers (OpenAI/Gemini) to extract Name, Father's Name, and DOB from PAN OCR text. The PAN number should be redacted BEFORE calling this function, but the prompt also enforces PII protection. """ provider = provider.lower() if logger: logger.send_log_event(f"Application_id: {app_id}. In get_structured_pan_data. Provider: {provider}") config = { "gemini": { "api_key": os.getenv("GEMINI_API_KEY"), "base_url": "https://generativelanguage.googleapis.com/v1beta/openai/", "model": "gemini-2.5-flash", }, "openai": { "api_key": os.getenv("OPENAI_API_KEY"), "base_url": None, "model": "gpt-4o-mini", }, } if provider not in config: if logger: logger.send_log_event(f"Application_id: {app_id}. Invalid provider '{provider}', falling back to gemini.") provider = "gemini" cfg = config[provider] if not cfg["api_key"]: if logger: logger.send_log_event(f"Application_id: {app_id}. API Key for {provider} missing.") return {"name": None, "father_name": None, "dob": None} client = OpenAI(api_key=cfg["api_key"], base_url=cfg.get("base_url")) system_prompt = ( "You are an expert document data extractor for Indian PAN cards.\n" "You will receive OCR text from TWO engines: Tesseract (Engine 1) and EasyOCR (Engine 2).\n" "Tesseract may be faster but can be very noisy; EasyOCR is often more accurate for names.\n" "--- GOALS ---\n" "1. Extract: 'name' (Full Name), 'father_name' (Full Father's Name), 'dob' (DD/MM/YYYY).\n" "2. IMPORTANT: Do NOT truncate names. Capture all words.\n" "3. Cross-reference both engines to resolve typos or missing words.\n" "4. Return CLEAN, STANDARDIZED JSON only.\n" "5. IMPORTANT: DO NOT include the PAN number in your response.\n" "6. STRICT KEYWORD RULE: For 'father_name', only extract it if a corresponding label (e.g., 'Father', 'Father's Name', 'Pita') is detected in the OCR text. If no father-related keyword is found, return null for 'father_name' even if there is text present that looks like a name.\n" "Required keys: name, father_name, dob." ) try: response = client.chat.completions.create( model=cfg["model"], temperature=0, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"OCR Text from PAN Card:\n{ocr_text}"} ], ) content = response.choices[0].message.content # Remove markdown code fences if content.startswith("```"): content = content.split("```")[1] content = content.replace("json", "", 1).strip() if logger: logger.send_log_event(f"Application_id: {app_id}. AI Response: {content}") return json.loads(content) except Exception as e: if logger: logger.send_log_event(f"Application_id: {app_id}. Error in get_structured_pan_data: {str(e)}") return {"name": None, "father_name": None, "dob": None}