- Add "surg ext" alias mapping to D7210 in the AI CDT lookup.
- Add GET/DELETE endpoints for appointment attachments, and load existing
attachments in the appointment edit modal (they were uploaded correctly
but never re-fetched, so they appeared to vanish on reopen).
- Fix claim-form.tsx so previously-saved appointment attachments are always
loaded (even when a claim already exists) instead of only when creating a
brand-new claim, preventing them from being silently wiped on save.
- Merge existing appointment attachments into every insurance submit handler
(MassHealth, CCA, DDMA, UnitedDH, TuftsSCO) so Selenium submission always
includes files saved earlier via the Schedule editor, not just files
uploaded in the current claim-form session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MassHealth eligibility lookups via the AI chatbot only matched existing
patients on memberId+DOB, so patients imported from PDF extraction (which
have a member ID but no stored birthday) were treated as new/unknown even
though they already exist in the system.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The classifier prompt only told the AI that appointmentDate applies to
schedule_appointment/claim_only/check_and_claim, so a trailing date like
"all on 6/23/26" was dropped for multi-patient claims even though the
workflow already reads c.appointmentDate for those intents and silently
defaulted to today.
Also add Prisma config/seed scripts and shopping-vendor types/schema updates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add multi_claim intent so AI correctly handles "claim X for patient A and Y for patient B"
instead of applying all procedures to all patients (batch_claim)
- Each patient carries their own matchedCodes in the queue
- Fix batch claim PDF race condition: chatbot queue no longer advances in closeClaim(),
instead advances after selenium PDF is downloaded (matching column claim behavior)
- Align United SCO eligibility worker with claim worker: only fill subscriber ID + DOB,
use treatmentLocation by ID instead of arrow-wrapper click
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add "recement" example to LLM classifier so it spreads across multiple
teeth (recement #5, #3 → two D2920 entries). Add "today" to the AI
column claim message so the LLM sets appointmentDate instead of asking
"Which service date?"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Make appointmentId nullable/optional in Prisma Zod schema via @zod rich
comment so claims can be submitted without an existing appointment
- Convert undefined appointmentId to null in all claim form handlers and
the backend claim creation endpoint
- Add AI classifier rule for expanding one procedure across multiple
comma-separated tooth numbers (e.g. "post/core on #23, 24, 25, 26")
- Add "post/core" (slash) alias to CDT lookup maps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add "comp" as alias for composite in CDT lookup. Update classifier
prompt with explicit examples for "claim comp #8 ml for lisa today"
and dental surface letter definitions (M/D/L/F/B/O/V/I) so the LLM
correctly treats #tooth+surfaces as composite fillings, not insurance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add parseMultiPaCode: "4PA #3#14#19#30" expands to D0220 + 3× D0230 with tooth numbers
- Add "3rd pa"–"6th pa" direct aliases → D0230 as fallback
- Tighten LLM prompt to always use "2nd pa" for all additional PAs, never "3rd pa" etc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- appointments-page: save full queue from current patient on confirm so that
cancelling the claim form resumes at the same patient instead of skipping it
- claims-page: advance ai_claim_queue only after successful submission (CCA,
MH selenium PDF download, or direct non-draft submit) via new advanceAiClaimQueue()
- cdt-lookup: fix extractToothSurface regex to allow space between # and digit
(e.g. "# 32") so tooth number is correctly captured
- cdt-lookup: silently skip bare tooth-number tokens ("# 32", "#32") that the
LLM may emit as standalone items, preventing false "unrecognized procedure" errors
- internal-chat-graph: add explicit rule that a CDT code followed by # NN
(e.g. "D7210 # 32") must stay as one entry and not spread to other procedures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add batch_eligibility_by_name intent so "check Mary and Sinthia" resolves multiple names
- Add "Check All & Appointment Today" button to batch eligibility UI — creates appointment for each patient after their eligibility check completes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add batch_eligibility, batch_claim, and batch_check_and_claim intents
to AI classifier so multiple patients can be processed one by one
- Add queue processing on insurance-status and claims pages to auto-start
the next patient after each check/claim completes
- Make patient schema firstName, lastName, phone optional so patients can
be created with just member ID + DOB from eligibility checks
- Cancel buttons now preserve chat history instead of clearing it
- Patient-found card shows Check Eligibility, Eligibility & Appointment
Today, and Cancel buttons
- Claim service date asks user to pick between latest appointment and
today when they differ
- Login page subtitle styled with animated gradient
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add preauth intent to AI chatbot (classifier, workflow, frontend UI card)
- Auto-prefill preauth form with CDT codes, service date, and mapped prices
- Auto-trigger preauth Selenium handler by insurance siteKey (MH/Tufts/CCA/UnitedDH)
- Default tentative service date to today+3 for preauth (user didn't pick a date)
- Fix #number always means tooth number, distributes to all procedures in comma list
- Fix bare "post"/"pos" → D2954 (was matching D2955 via keyword scorer)
- UnitedDH pre-auth: fill procedure date with Ctrl+A to overwrite prefilled value
- UnitedDH pre-auth: select Location "Summit Dental Care" in step1 (same as billing entity)
- UnitedDH pre-auth: remove page refresh in step9 to preserve pre-auth number
- UnitedDH pre-auth: wait for table rows before extracting pre-auth number
- UnitedDH pre-auth/claim: explicit wait for Submit button after file upload (no sleep)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- claims-page: after auto-submit closes the form, automatically navigate
back to /appointments when an AI claim queue is pending resume, eliminating
the manual navigation delay between appointments
- internal-chat-workflow: warn and block claim when D1120 (child prophy) is
requested for a patient aged 14+ — recommend D1110 (adult prophy) instead,
and advise manual claim if user insists on D1120
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- parseSrpCode: recognize "D4341 UL" / "4341 LR" etc., store quadrant in quad field
- matchOne: auto-prefix D for 4-digit inputs like "0120" → D0120
- LLM prompt: keep SRP code and quadrant together as one procedureName entry
- CdtMatch / CdtResult: add quad field, thread through matchedCodes action data
- claim-form.tsx: include quad in chatbot_claim_prefill type and spread to service line
- selenium_claimSubmitWorker.py: pass quad to fill_service_line, select quadrant
dropdown by index (UR=1, UL=2, LL=3, LR=4) matching MassHealth form structure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AI chat extracts 'with provider <name>' and routes claim to that provider
- Claim form reads provider from sessionStorage before any async effects run,
preventing saved claim/procedure data from overriding the chatbot selection
- NPI provider settings table shows Provider #1 / #2 labels with up/down
reorder buttons; Provider #1 is always the default for claims
- Default provider now uses sortOrder instead of hardcoded 'Mary Scannell'
- Added sortOrder column to NpiProvider schema with migration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Claim file uploads (chatbot or manual) now save to both the Cloud
Storage patient folder and the Documents page via new
POST /api/claims/upload-to-cloud endpoint
- MH submit flow now calls uploadAttachmentsToLocalFolder (same as
DDMA/United/Tufts) so chatbot-attached X-rays are persisted
- Removed old /upload-attachments disk route and attachmentDiskStorage
multer config; deleted uploads/patients/ folder
- uploadAttachmentsToLocalFolder now points to /upload-to-cloud and
sends patientId so the backend can create the patient folder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add "Eligibility & Appointment" button to chatbot eligibility-id-ready card
- For known patients: creates today's appointment immediately, then opens eligibility page
- For unknown patients: navigates to eligibility page; after Selenium creates the patient,
auto-creates appointment via tryAppointmentFromChatbot on the insurance-status page
- Update MH Eligibility & Appointment button to create a today's schedule slot instead of
navigating to the appointments page; shows PDF preview on completion
- createAppointmentToday falls back from Column A to Column B when Column A is full;
returns column label in response so UI can display it
- Set AI-scheduled appointment duration to 15 minutes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
extractToothSurface now handles A-T primary tooth letters in addition to
numeric #1-32. strippedCleaned strips both formats so "D7111 # H" reduces
to "D7111" for the CDT pass-through. Pass-through no longer returns null
for codes not in the fee schedule JSON.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Chatbot: add paperclip button to attach X-ray/PDF files to claim submissions;
files flow through chatbotFileStore into claim-form uploadedFiles for Selenium
- CDT lookup: auto-select D3310/D3320/D3330 by tooth number for RCT; strip #NN
from procedure names before alias lookup so "1 pa, #3" → D0220 tooth 3;
add "1 pa" alias for D0220; expand multi-PA notation via AI prompt rule
- AI classifier: add navigate_eligibility intent ("check mh" → /insurance-status);
fix duplicate intent entry; add RCT and multi-PA prompt rules
- Check+claim flow: pass serviceDate through check_and_claim_ready actionData;
tryClaimFromChatbot skips PDF preview and navigates straight to claims with
memberId fallback lookup; wired into all provider onPdfReady callbacks
- Claims table: add Service Date column between Patient Name and Submission Date
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
siteKeyToAutoCheck now accepts an optional dob parameter. When siteKey is MH
and the patient is under 21 years old, returns "cmsp" so the AI chat triggers
the CMSP eligibility & history & remaining button instead of the adult MH one.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added extractToothSurface() helper that parses "#NN [SURFACES]" from any
procedure input (tooth 1-32, surface letters O/M/D/B/L/F/I/V). Applied to
all lookup paths so any procedure with #14 or #29 OB carries toothNumber and
toothSurface through to the DDMA Selenium worker.
Also propagate toothNumber/toothSurface in matchedCodes and chatbot_claim_prefill
so the claim-form prefill sets these fields on the service lines before auto-submit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Add llm-factory.ts: unified LLM provider abstraction (Google/Claude/OpenAI)
- Install @langchain/anthropic and @langchain/openai packages
- resolveAiProvider picks active provider from DB settings (Claude > OpenAI > Google)
- All AI graphs (reminder, new-patient, reschedule, internal-chat) now accept provider+model params
- Add claudeAiModel, openAiModel, googleAiModel columns to ai_settings table
- New PUT /api/ai/provider-model route to save selected model per provider
- UI model dropdowns for Claude (Haiku/Sonnet/Opus), OpenAI (GPT-5.x series), Google (Gemini 2.5/3.x)
- Google AI section also gets model selector alongside existing API key field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Internal AI chat: schedule_appointment intent books earliest available
slot in Column A using office hours; claim_only intent looks up latest
past appointment for service date, asks user when two appointments are
within 7 days, auto-triggers correct Selenium worker with mapped prices
- Gemini model updated to gemini-flash-latest; conversation history (15
messages) passed for pronoun/reference resolution; history trimmed to
start with user turn so Gemini doesn't reject the context
- Insurance alias file (insuranceAliases.json) replaces hardcoded siteKey
matching; "tufs" now resolves to TUFTS_SCO
- DOB format normalized (MM/DD/YYYY → YYYY-MM-DD) before parseLocalDate;
autoCheck now fires for all insurance types, not just MH/CMSP
- Claim form auto-submit: all handlers (MH, CCA, DDMA, UnitedDH, Tufts)
accept formToUse and receive fee-schedule-priced form; prefillDone set
after chatbot code prefill so autoSubmit gate opens correctly
- Chatbot: chat history persisted in sessionStorage, cleared on logout
and auto-logout; Clear button writes fresh state synchronously; message
history window increased to 15
- DentaQuest/TuftsSCO Selenium: "Remember me" checkbox clicked before
sign-in to persist OTP trust cookie across sessions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Add Gemini-powered internal staff chatbot (free-text input in the
upper-right bot panel): type "check MARIA GONZALES" to search patient
and pre-fill eligibility, or "open claims" to navigate directly
- Add /api/ai/internal-chat endpoint with LangGraph + Google Gemini
classifier (intent: check_eligibility, find_patient, navigate_*)
- Add Users AI Chat settings section in Settings > Advanced > AI Chat
to configure a custom system prompt for the internal assistant
- Store internal chat system prompt in existing twilioSettings JSON
blob (no DB migration needed)
- Add AI Input Agent sidebar entry and placeholder page describing
planned keyboard-automation typing into Open Dental / Eaglesoft
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 3-message intro (self-intro → empathetic ack → new/existing question) via single TwiML response to guarantee delivery order
- Detect reschedule intent from first message; look up existing appointment date
- New patient flow: ask insurance type → MassHealth consent → member ID + DOB → Selenium eligibility check
- Post-eligibility: active → ask appointment date/time with office-hours validation; inactive → ask other insurance or collect contact info
- Date/time collection mirrors reschedule flow: check office day open, ask time, validate against office hours
- Auto-create appointment in schedule for known patients on confirmation; use first available staff member
- Add openPhoneReply toggle (Settings → AI Chat) to respond to any number at any time
- Add 5-minute inactivity timeout: reset conversation to initial stage and clear pending state
- Normalize MassHealth DOB to zero-padded MM/DD/YYYY before Selenium submission
- Expand isExistingPatient classifier to recognize "old patient", "old", "previous", "prior"
- Existing patient confirmation message now acknowledges patient type before asking about insurance
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- reminder-graph: add stripIntroFromFallback() to remove 'Hi! My name is Lisa...'
from any saved rescheduleGreeting template before using it as the MSG 2 fallback
- reminder-graph: add explicit 'Do NOT introduce yourself' to rescheduleNode Gemini
prompt so the AI never adds its own intro to MSG 2
- ai-chat-templates-card: add hasIntroPattern() warning on the Reschedule Patients
template field — shows an amber alert if the saved template starts with a
self-introduction, guiding users to remove it
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Reschedule for Column button on schedule page with AI follow-up toggle (default on)
- Add POST /api/twilio/send-reschedule-batch — sends Reschedule by Office template, starts AI reschedule conversation per patient
- Add {officePhone} (office call-in number) and {twilioPhone} (SMS number) variable replacement in both batch endpoints
- Fix broken variable names in Reschedule by Office template ({office phone number) → {officePhone}, {Twilio phone number} → {twilioPhone})
- Fix reschedule-graph: when patient replies with date in same message as YES/NO (e.g. "ok, 5/18"), AI now checks day open and asks for time instead of asking "what day and time?"
- Fix twilio-webhooks: same date-shortcut logic for reminder flow — "no, 5/18" skips "when to reschedule?" and goes straight to day check
- Update LangGraph SVG: rename to Reminder & Reschedule Flow, combine both entry points (Reminder SMS + Reschedule SMS) into one diagram with date-shortcut annotations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
time <= amEnd incorrectly allowed 12:00 when the office closes at 12:00.
Changed to time < amEnd (and time < pmEnd) so the session end time is
treated as a closed boundary — a patient cannot start an appointment at
exactly the time the session ends.
Fixes both the SMS reschedule flow and the schedule grid slot highlighting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add movedByAi boolean column to Appointment table (default false)
- reschedule-graph: set movedByAi=true when AI moves an appointment
- PATCH /api/appointments/:id/confirm endpoint clears the movedByAi flag
- Schedule grid: show teal AI badge on appointment cards where movedByAi=true
- Right-click context menu: 'Manually Confirmed' removes the AI badge via the confirm endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reminder flow: send AI self-introduction as message 1 (Twilio REST API), intent response as message 2 (TwiML) so intro always arrives first
- LangGraph reminder graph: classify yes/no/other from patient reply; 'no' now asks 'When would you like to reschedule?' directly
- Reschedule flow: new asked_reschedule_datetime stage replaces multi-step ASAP/next-week flow
- Date-only reply (e.g. '5/18'): ask for time separately, then confirm
- Date+time reply (e.g. '5/18 at 10am'): go straight to confirmation
- new asked_reschedule_time_for_date and asked_reschedule_confirm_datetime stages
- Date/time parsing: regex handles M/D and am/pm formats first; falls back to Gemini for natural language
- Day-level office hours check: if requested day is closed (e.g. Sunday), reply 'Our office is closed on [date]. Choose another day?'
- Time-level office hours check: if requested time is outside working hours (e.g. 12pm during lunch), reply with actual hours (e.g. '9:00 am – 12:00 pm and 1:00 pm – 5:00 pm')
- Slot availability check: verifies no conflicting appointment for same staff member
- After appointment confirmed: patient thank-you reply triggers warm closing with upcoming appointment time
- Schedule page: office hours summary bar above grid showing today's configured hours with link to settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace in-memory Maps in aiHandoffStore with DB-backed async functions
using new patient_conversation table (stage + aiHandoff per patient)
- Add afterHoursEnabled to ai_settings table (persists across restarts)
- Fix runtime crash in reschedule-graph: mon/tue/wed variables were out
of scope in the next-week fallback branch (ReferenceError)
- Wire rescheduleGreeting and generalFallback chat templates through to
LangGraph nodes so user-configured messages take effect
- Add otherNode to reminder-graph to handle unclassified patient replies
(e.g. "I want another appointment") and route to booking flow
- Fetch chatTemplates once per webhook request instead of per stage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a member ID is typed on the insurance eligibility page, debounced
lookup fills in date of birth, first name, and last name if the patient
already exists in the database.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add floating chat window Hand-off to AI toggle (per-patient) and after-hours AI toggle (global)
- Add LangGraph-powered appointment reminder flow: AI introduces itself, classifies YES/NO, handles confirmation with appointment date/time
- Add multi-step rescheduling flow: ASAP vs next week, tomorrow offer, Mon/Tue/Wed picker, morning/afternoon time slot — automatically updates appointment in DB
- Add new patient / after-hours flow: new vs existing patient, dental insurance check, MassHealth Selenium eligibility check (auto-uses saved member ID + DOB for existing patients), self-pay fallback
- Add AI Chat Settings page (Settings → Advanced) with editable greeting templates and LangGraph flow diagrams for both reminder and new-patient flows
- Add Schedule a New Patient template option in chat window, starts new-patient conversation flow
- Add GET/PUT endpoints for AI handoff, after-hours handoff, and AI chat templates
- Add multilingual support (7 languages) across all AI reply nodes with LLM generation and hardcoded fallbacks
- Add pending reschedule in-memory store and conversation stage tracking across all flows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>