import { ChatGoogleGenerativeAI } from "@langchain/google-genai"; // ─── Intent types ───────────────────────────────────────────────────────────── export type InternalChatIntent = | "check_eligibility" // by patient name → look up in DB | "eligibility_by_id" // by explicit memberId + dob (no name) | "check_and_claim" // eligibility + claim procedures | "find_patient" // look up patient record only | "navigate_claims" | "navigate_schedule" | "general"; export interface ChatClassification { intent: InternalChatIntent; // --- patient resolution (one of name OR id+dob) --- patientName?: string; // for check_eligibility / find_patient memberId?: string; // for eligibility_by_id / check_and_claim dob?: string; // for eligibility_by_id / check_and_claim (MM/DD/YYYY) // --- insurance hint (only if explicitly stated in the message) --- insuranceHint?: string; // raw text, e.g. "masshealth", "BCBS", "CCA" // --- procedures (raw text, NOT CDT codes — CDT lookup is done in workflow) --- procedureNames?: string[]; // for check_and_claim, e.g. ["perio exam", "adult cleaning"] fallbackReply: string; } // ─── System prompt ──────────────────────────────────────────────────────────── const BASE_SYSTEM_PROMPT = `You are an internal assistant for a dental office management app. Staff type natural language commands. Your ONLY job is to classify the intent and extract structured parameters. Do NOT map procedure names to CDT codes — return them as plain text. Respond ONLY with valid JSON (no markdown fences): { "intent": "", "patientName": "", "memberId": "", "dob": "", "insuranceHint": "", "procedureNames": ["", ...], "fallbackReply": "<1-2 sentence reply to show the user>" } Omit any field that is not present in the message. Intents: - check_eligibility : user wants to check insurance for a patient identified by NAME only e.g. "check Maria Jesus", "verify insurance for John Smith" - eligibility_by_id : user provides a member ID and date of birth (no patient name) e.g. "check masshealth for 100xxxx, 10/10/1988" - check_and_claim : user wants to check eligibility AND submit procedures as claims e.g. "check masshealth for 100xxxx, 10/10/1988 and claim perio exam and adult cleaning" e.g. "check Maria Jesus and claim D0120 D1110" - find_patient : look up a patient record only, no eligibility e.g. "find patient John", "look up Smith" - navigate_claims : open the claims page - navigate_schedule : open the appointments/schedule page - general : anything else Rules: - For check_and_claim, procedureNames should be the RAW user text (e.g. "perio exam", "adult cleaning", "D0120") — do NOT translate to codes - insuranceHint is only set when the user explicitly names an insurance in the message - Keep fallbackReply to 1-2 sentences - For navigate intents, fallbackReply = "Opening the [page] page..."`; // ─── Classifier ─────────────────────────────────────────────────────────────── export async function classifyInternalChat( message: string, apiKey: string, extraSystemPrompt?: string ): Promise { const fallback: ChatClassification = { intent: "general", fallbackReply: "I can search for a patient, check eligibility, run check & claim, or navigate to claims or appointments.", }; if (!apiKey) return fallback; const systemPrompt = extraSystemPrompt ? `${BASE_SYSTEM_PROMPT}\n\nAdditional office context:\n${extraSystemPrompt}` : BASE_SYSTEM_PROMPT; try { const llm = new ChatGoogleGenerativeAI({ model: "gemini-1.5-flash", apiKey }); const response = await llm.invoke([ { role: "system", content: systemPrompt }, { role: "user", content: message }, ]); const raw = String(response.content).trim(); const jsonStr = raw.replace(/^```json\s*/i, "").replace(/```\s*$/, "").trim(); const parsed = JSON.parse(jsonStr) as ChatClassification; if (!parsed.intent || !parsed.fallbackReply) return fallback; return parsed; } catch { return fallback; } }