feat: chatbot rendering provider override and NPI provider ordering

- 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>
This commit is contained in:
Gitead
2026-06-11 13:17:05 -04:00
co-authored by Claude Sonnet 4.6
parent d4b9c1b889
commit 75c49ab1df
77 changed files with 385 additions and 105 deletions
@@ -124,6 +124,16 @@ export function ClaimForm({
const [prefillDone, setPrefillDone] = useState(false);
const autoSubmittedRef = useRef(false);
// Read chatbot-requested rendering provider synchronously at mount (before any effects run)
// so the npiProviders effect always sees it, even when the provider list is already cached.
const [chatbotRenderingProvider] = useState<string | null>(() => {
try {
const raw = sessionStorage.getItem("chatbot_claim_prefill");
if (!raw) return null;
const parsed = JSON.parse(raw);
return (parsed?.renderingProvider as string | null | undefined) ?? null;
} catch { return null; }
});
// When an existing claim is loaded for the appointment, store its ID so
// the form submits an update instead of creating a new claim.
const [existingClaimId, setExistingClaimId] = useState<number | null>(null);
@@ -190,14 +200,27 @@ export function ClaimForm({
useEffect(() => {
if (!npiProviders.length) return;
// do not override if user already selected
// If chatbot specified a rendering provider, apply it (takes priority over default)
if (chatbotRenderingProvider) {
const needle = chatbotRenderingProvider.toLowerCase();
const matched = npiProviders.find(
(p) =>
p.providerName.toLowerCase().includes(needle) ||
needle.includes(p.providerName.toLowerCase()),
);
if (matched) {
setForm((prev) => ({
...prev,
npiProvider: { npiNumber: matched.npiNumber, providerName: matched.providerName },
}));
return;
}
}
// Do not override if user already selected (or chatbot already applied above)
if (form.npiProvider?.npiNumber) return;
const maryScannell = npiProviders.find(
(p) => p.providerName.toLowerCase() === "mary scannell",
);
const fallback = maryScannell || npiProviders[0];
const fallback = npiProviders[0];
if (fallback) {
setForm((prev) => ({
@@ -208,7 +231,7 @@ export function ClaimForm({
},
}));
}
}, [npiProviders]);
}, [npiProviders, chatbotRenderingProvider]);
// Service date state
const [serviceDateValue, setServiceDateValue] = useState<Date>(new Date());
@@ -401,8 +424,8 @@ export function ClaimForm({
if (matchedStaff) setStaff(matchedStaff);
}
// Restore NPI provider selection
if ((claim as any).npiProviderId && npiProviders.length > 0) {
// Restore NPI provider selection — chatbot override takes priority
if ((claim as any).npiProviderId && npiProviders.length > 0 && !chatbotRenderingProvider) {
const matchedNpi = npiProviders.find(
(p) => Number(p.id) === Number((claim as any).npiProviderId),
);
@@ -466,8 +489,8 @@ export function ClaimForm({
: {}),
}));
// Restore NPI provider from saved procedures
if (data.npiProviderId) {
// Restore NPI provider from saved procedures — chatbot override takes priority
if (data.npiProviderId && !chatbotRenderingProvider) {
const npiId = Number(data.npiProviderId);
setSavedProcNpiId(npiId);
// Apply immediately if providers are already loaded
@@ -503,9 +526,10 @@ export function ClaimForm({
if (!raw) return;
try {
const { codes, serviceDate } = JSON.parse(raw) as {
const { codes, serviceDate, renderingProvider } = JSON.parse(raw) as {
codes: { code: string; description: string; toothNumber?: string; toothSurface?: string }[];
serviceDate?: string;
renderingProvider?: string | null;
};
sessionStorage.removeItem("chatbot_claim_prefill");
if (!codes?.length) return;
@@ -879,7 +903,12 @@ export function ClaimForm({
const appointmentData = {
patientId: patientId,
date: serviceDate,
staffId: appointmentStaffId ?? staff?.id,
staffId: appointmentStaffId ?? staff?.id ?? 1,
title: serviceDate,
startTime: "09:00",
endTime: "09:30",
type: "recall",
status: "scheduled",
};
const created = await onHandleAppointmentSubmit(appointmentData);
@@ -1033,7 +1062,12 @@ export function ClaimForm({
const created = await onHandleAppointmentSubmit({
patientId,
date: serviceDate,
staffId: appointmentStaffId ?? staff?.id,
staffId: appointmentStaffId ?? staff?.id ?? 1,
title: serviceDate,
startTime: "09:00",
endTime: "09:30",
type: "recall",
status: "scheduled",
});
if (typeof created === "number" && created > 0) {
appointmentIdToUse = created;
@@ -1112,7 +1146,12 @@ export function ClaimForm({
const created = await onHandleAppointmentSubmit({
patientId,
date: serviceDate,
staffId: appointmentStaffId ?? staff?.id,
staffId: appointmentStaffId ?? staff?.id ?? 1,
title: serviceDate,
startTime: "09:00",
endTime: "09:30",
type: "recall",
status: "scheduled",
});
if (typeof created === "number" && created > 0) {
appointmentIdToUse = created;
@@ -1191,7 +1230,12 @@ export function ClaimForm({
const created = await onHandleAppointmentSubmit({
patientId,
date: serviceDate,
staffId: appointmentStaffId ?? staff?.id,
staffId: appointmentStaffId ?? staff?.id ?? 1,
title: serviceDate,
startTime: "09:00",
endTime: "09:30",
type: "recall",
status: "scheduled",
});
if (typeof created === "number" && created > 0) {
appointmentIdToUse = created;
@@ -1269,7 +1313,12 @@ export function ClaimForm({
const created = await onHandleAppointmentSubmit({
patientId,
date: serviceDate,
staffId: appointmentStaffId ?? staff?.id,
staffId: appointmentStaffId ?? staff?.id ?? 1,
title: serviceDate,
startTime: "09:00",
endTime: "09:30",
type: "recall",
status: "scheduled",
});
if (typeof created === "number" && created > 0) {
appointmentIdToUse = created;
@@ -1463,7 +1512,12 @@ export function ClaimForm({
const appointmentData = {
patientId: patientId,
date: serviceDate,
staffId: appointmentStaffId ?? staff?.id,
staffId: appointmentStaffId ?? staff?.id ?? 1,
title: serviceDate,
startTime: "09:00",
endTime: "09:30",
type: "recall",
status: "scheduled",
};
const created = await onHandleAppointmentSubmit(appointmentData);
if (typeof created === "number" && created > 0) {
@@ -1613,7 +1667,12 @@ export function ClaimForm({
const created = await onHandleAppointmentSubmit({
patientId: patientId,
date: serviceDate,
staffId: appointmentStaffId ?? staff?.id,
staffId: appointmentStaffId ?? staff?.id ?? 1,
title: serviceDate,
startTime: "09:00",
endTime: "09:30",
type: "recall",
status: "scheduled",
});
if (typeof created === "number" && created > 0) {
appointmentIdToUse = created;
@@ -1690,6 +1749,7 @@ export function ClaimForm({
!!form.memberId?.trim() &&
!!form.dateOfBirth?.trim() &&
!!form.patientName?.trim() &&
!!form.npiProvider?.npiNumber &&
Array.isArray(form.serviceLines) &&
form.serviceLines.some(
(l) => l.procedureCode && l.procedureCode.trim() !== "",
@@ -1700,6 +1760,7 @@ export function ClaimForm({
form.memberId,
form.dateOfBirth,
form.patientName,
form.npiProvider,
form.serviceLines,
]);