Files
Wellspringmanagement01/apps/Backend/src/ai/internal-chat-graph.ts
T
GiteadandClaude Sonnet 4.6 ba2882957a feat: Users AI Chat multi-step workflows with CDT lookup and alias management
- Add eligibility_by_id and check_and_claim intents to internal chat
- New cdt-lookup.ts: keyword search against fee schedule JSON (no LLM)
- New internal-chat-workflow.ts: deterministic orchestration — patient
  resolution, insurance siteKey derivation, CDT code mapping
- Custom CDT aliases stored per-user in DB (TwilioSettings JSON blob)
  with GET/PUT /api/ai/cdt-aliases endpoints
- Chatbot UI: new steps for eligibility-id-ready, check-and-claim-ready,
  and need-insurance-clarification with insurance picker
- Settings UI: CDT Aliases CRUD table with built-in alias reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 17:44:19 -04:00

102 lines
4.8 KiB
TypeScript

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": "<intent>",
"patientName": "<full name if mentioned by name>",
"memberId": "<member/insurance ID if given explicitly>",
"dob": "<date of birth in MM/DD/YYYY if given>",
"insuranceHint": "<insurance name only if explicitly stated in the message, e.g. 'masshealth', 'BCBS MA', 'CCA'>",
"procedureNames": ["<raw procedure name>", ...],
"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<ChatClassification> {
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;
}
}