feat: AI chat claim confirmation, CDT alias learning, and eligibility auto-trigger fixes
- Claim flow: show green confirm card (patient, CDT codes, service date) before Selenium starts - CDT lookup: add DIRECT_CODE_MAP + ALIAS_MAP with 60+ dental abbreviations from office fee schedule (2BW→D0272, 4BW→D0274, PA→D0220, FL→D1208, RCT codes, composite tooth#/surface parser, etc.) - Composite filling parser: auto-map "#29 OB" → D2392 based on tooth# (front/back) and surface count - Ask-and-learn: unknown CDT terms block claim and ask user; answer saved to DB alias map for future use - Cancel on confirm card returns to chat (not full reset) so user can correct and retry - Eligibility auto-trigger fix: reset autoTriggeredRef when autoTrigger resets to false so second chatbot eligibility check on same page visit fires correctly (all 5 provider buttons fixed) - check_eligibility by name now returns eligibility_id_ready with correct siteKey for non-MH patients - DDMA/CCA/United/Tufts fee schedules updated with office prices (single Price field, no age split) - getCodeMap case-insensitive matching fix (ddma/cca/etc. now correctly selected) - Family plan member disambiguation: insuranceId+DOB composite lookup prevents overwriting siblings - AI chat date fix: send clientDate from browser to avoid UTC midnight rollover (EST→wrong day) - AI prompt: appointmentDate extracted for claim_only intent when user says "today" or a date Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -92,10 +92,11 @@ const TUFTSSCO_CODE_MAP: Map<string, CodeRow> = (() => {
|
||||
|
||||
/** Return the correct fee-schedule map for the given insurance type. */
|
||||
function getCodeMap(insuranceSiteKey?: string): Map<string, CodeRow> {
|
||||
if (insuranceSiteKey === "CCA") return CCA_CODE_MAP;
|
||||
if (insuranceSiteKey === "DDMA") return DDMA_CODE_MAP;
|
||||
if (insuranceSiteKey === "UNITED_SCO" || insuranceSiteKey === "UnitedSCO" || insuranceSiteKey === "UNITEDDH") return UNITEDDH_CODE_MAP;
|
||||
if (insuranceSiteKey === "TuftsSCO") return TUFTSSCO_CODE_MAP;
|
||||
const k = (insuranceSiteKey ?? "").replace(/_/g, "").toLowerCase();
|
||||
if (k === "cca") return CCA_CODE_MAP;
|
||||
if (k === "ddma") return DDMA_CODE_MAP;
|
||||
if (k === "unitedsco" || k === "uniteddh" || k === "dentalhub") return UNITEDDH_CODE_MAP;
|
||||
if (k === "tuftssco" || k === "tufts") return TUFTSSCO_CODE_MAP;
|
||||
return CODE_MAP; // default: MassHealth
|
||||
}
|
||||
|
||||
@@ -175,43 +176,28 @@ export function pickPriceForRowByAge(
|
||||
): Decimal {
|
||||
// Special-case rules (add more codes here if needed)
|
||||
if (normalizedCode) {
|
||||
// D1110: only valid for age >=14 (14..21 => PriceLTEQ21, >21 => PriceGT21)
|
||||
// D1110: only valid for age >=14
|
||||
if (normalizedCode === "D1110") {
|
||||
if (age < 14) {
|
||||
// D1110 not applicable to children <14 (those belong to D1120)
|
||||
return new Decimal(0);
|
||||
}
|
||||
if (age >= 14 && age <= 21) {
|
||||
// use PriceLTEQ21 only if present
|
||||
if (!isBlankPrice(row.PriceLTEQ21))
|
||||
return toDecimalOrZero(row.PriceLTEQ21);
|
||||
return new Decimal(0);
|
||||
}
|
||||
// age > 21
|
||||
if (!isBlankPrice(row.PriceGT21)) return toDecimalOrZero(row.PriceGT21);
|
||||
if (age < 14) return new Decimal(0); // D1110 not for children <14
|
||||
// age >= 14: use age-split if present, then flat Price
|
||||
if (age <= 21 && !isBlankPrice(row.PriceLTEQ21)) return toDecimalOrZero(row.PriceLTEQ21);
|
||||
if (age > 21 && !isBlankPrice(row.PriceGT21)) return toDecimalOrZero(row.PriceGT21);
|
||||
if (!isBlankPrice(row.Price)) return toDecimalOrZero(row.Price);
|
||||
return new Decimal(0);
|
||||
}
|
||||
|
||||
// D1120: child 0-13 => PriceLTEQ21, otherwise no price (NC)
|
||||
// D1120: valid for child 0-13 only
|
||||
if (normalizedCode === "D1120") {
|
||||
if (age < 14) {
|
||||
if (!isBlankPrice(row.PriceLTEQ21))
|
||||
return toDecimalOrZero(row.PriceLTEQ21);
|
||||
return new Decimal(0);
|
||||
}
|
||||
// age >= 14 => NC / no price
|
||||
if (age >= 14) return new Decimal(0); // NC for adults
|
||||
if (!isBlankPrice(row.PriceLTEQ21)) return toDecimalOrZero(row.PriceLTEQ21);
|
||||
if (!isBlankPrice(row.Price)) return toDecimalOrZero(row.Price);
|
||||
return new Decimal(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Generic/default behavior (unchanged)
|
||||
if (age <= 21) {
|
||||
if (!isBlankPrice(row.PriceLTEQ21)) return toDecimalOrZero(row.PriceLTEQ21);
|
||||
} else {
|
||||
if (!isBlankPrice(row.PriceGT21)) return toDecimalOrZero(row.PriceGT21);
|
||||
}
|
||||
|
||||
// Fallback to Price if tiered not available/blank
|
||||
// Generic/default: age-split first, flat Price as fallback
|
||||
if (age <= 21 && !isBlankPrice(row.PriceLTEQ21)) return toDecimalOrZero(row.PriceLTEQ21);
|
||||
if (age > 21 && !isBlankPrice(row.PriceGT21)) return toDecimalOrZero(row.PriceGT21);
|
||||
if (!isBlankPrice(row.Price)) return toDecimalOrZero(row.Price);
|
||||
return new Decimal(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user