From 528a30efc6cc23fd149eb411c84d7cc48a3434cf Mon Sep 17 00:00:00 2001 From: Gitead Date: Wed, 24 Jun 2026 09:05:21 -0400 Subject: [PATCH] fix: recognize limited exam, 1 PA (#tooth), composite # NN surfaces, and check [patient name] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add limited exam / emergency exam → D0140 to DIRECT_CODE_MAP for reliable lookup - Fix parseCompositeCode regex to allow space between # and digit (# 10 DL, # 11 ML) - Strip empty parens from strippedCleaned so "1 pa (#13)" → "1 pa" hits DIRECT_CODE_MAP - LLM prompt: add single-PA-with-tooth examples (1 PA (#13) → ["1 pa, #13"]) - LLM prompt: clarify check_eligibility vs navigate_eligibility — "check Qu" is patient name, not insurance Co-Authored-By: Claude Sonnet 4.6 --- apps/Backend/src/ai/cdt-lookup.ts | 6 +++++- apps/Backend/src/ai/internal-chat-graph.ts | 13 +++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/Backend/src/ai/cdt-lookup.ts b/apps/Backend/src/ai/cdt-lookup.ts index b5942d3a..77e74a39 100644 --- a/apps/Backend/src/ai/cdt-lookup.ts +++ b/apps/Backend/src/ai/cdt-lookup.ts @@ -147,6 +147,9 @@ const ALIAS_MAP: Record = { // Direct phrase → CDT code for terms not in the MH fee schedule JSON // or short abbreviations that need precise mapping (checked before keyword search) const DIRECT_CODE_MAP: Record = { + // Exams + "limited exam": "D0140", + "emergency exam": "D0140", // X-rays "2bw": "D0272", "2 bw": "D0272", @@ -265,7 +268,7 @@ const RCT_MOLAR = new Set([1, 2, 3, 14, 15, 16, 17, 18, 19, 30, 31, 32]); / function parseCompositeCode(input: string): CdtMatch | null { const s = input.trim(); // Match #NN SURFACES or tooth NN SURFACES (surfaces = 1-5 letters, valid dental surface chars) - const m = s.match(/#(\d{1,2})\s+([A-Za-z]{1,5})\b/) ?? s.match(/\btooth\s+(\d{1,2})\s+([A-Za-z]{1,5})\b/i); + const m = s.match(/#\s*(\d{1,2})\s+([A-Za-z]{1,5})\b/) ?? s.match(/\btooth\s+(\d{1,2})\s+([A-Za-z]{1,5})\b/i); if (!m) return null; const toothNum = parseInt(m[1]!, 10); @@ -467,6 +470,7 @@ export function lookupCdtCodes( const strippedCleaned = cleaned .replace(/,?\s*#\s*\d{1,2}\b/g, "") .replace(/,?\s*#\s*[a-t]\b/gi, "") + .replace(/\(\s*\)/g, "") .replace(/\s+/g, " ") .trim(); diff --git a/apps/Backend/src/ai/internal-chat-graph.ts b/apps/Backend/src/ai/internal-chat-graph.ts index 7c00b7bd..746e9d8c 100644 --- a/apps/Backend/src/ai/internal-chat-graph.ts +++ b/apps/Backend/src/ai/internal-chat-graph.ts @@ -71,6 +71,8 @@ Omit any field that is not present in the message or history. 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" + e.g. "check Qu", "check Li", "check Kim" — a short word after "check" is a patient last name, NOT an insurance abbreviation + Insurance abbreviations are: mh, masshealth, bcbs, cca, dentaquest — anything else is a patient name - eligibility_by_id : user provides a SINGLE member ID and date of birth (no patient name) e.g. "check masshealth for 100xxxx, 10/10/1988" ALSO use this when user wants to check eligibility AND schedule/add an appointment on a date @@ -121,8 +123,9 @@ Intents: Use this when the user says "preauth", "pre auth", "pre-auth", or "prior auth" - navigate_claims : open the claims page - navigate_schedule : open the appointments/schedule page -- navigate_eligibility : open the insurance eligibility page +- navigate_eligibility : open the insurance eligibility page — ONLY when user says a known insurance keyword with no patient name e.g. "check mh", "check masshealth", "open eligibility", "go to eligibility", "check insurance" + Do NOT use this for "check [name]" — that is check_eligibility - general : anything else Rules: @@ -134,7 +137,8 @@ Rules: do NOT include it in procedureNames. It refers to a file attachment, not a billable procedure. Only include actual clinical procedures in procedureNames. - For composite fillings with a tooth number, preserve the EXACT notation including tooth# and surfaces: - e.g. "composite #29 O", "#8 MO", "composite #11 MOD" — keep the #number and surface letters together as one entry + e.g. "composite #29 O", "#8 MO", "composite #11 MOD", "# 10 DL", "# 11 ML" — keep the #number and surface letters together as one entry + Note: "# 10 DL" and "composite on # 10 DL" are the same — preserve the space-after-# as-is - #number always means a TOOTH number (never a case or pre-auth reference). When a single #number appears before a comma-separated list of procedures, apply it to EVERY procedure in the list. e.g. "#20 rct, post, crown" → ["#20 rct", "#20 post", "#20 crown"] e.g. "preauth #20 rct, pos, crown" → ["#20 rct", "#20 pos", "#20 crown"] @@ -147,8 +151,9 @@ Rules: The tooth# after a CDT code belongs only to that procedure. - For SRP with a quadrant abbreviation (UL, UR, LL, LR), keep the code and quadrant together as one entry: e.g. "D4341 UL", "4341 LR", "D4342 UR" — the quadrant always travels with the SRP code -- For multiple PA X-rays with tooth numbers, expand each PA into its own entry: - ALWAYS use "1 pa, #T" for the first tooth and "2nd pa, #T" for EVERY additional tooth (never "3rd pa", "4th pa", etc.) +- For PA X-rays with tooth numbers, always use "1 pa, #T" format for the first/only PA and "2nd pa, #T" for every additional: + e.g. "1 PA (#13)" → ["1 pa, #13"] + e.g. "PA #13" → ["1 pa, #13"] e.g. "2 PA (#30, 15)" → ["1 pa, #30", "2nd pa, #15"] e.g. "3 PA (#3, 14, 30)" → ["1 pa, #3", "2nd pa, #14", "2nd pa, #30"] e.g. "4 PA (#3, 14, 19, 30)" → ["1 pa, #3", "2nd pa, #14", "2nd pa, #19", "2nd pa, #30"]