feat: AI chat Eligibility & Appointment button with 15-min slots and Column B fallback
- 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>
This commit is contained in:
@@ -593,7 +593,7 @@ async function handleClaimOnly(
|
||||
// ─── schedule_appointment ─────────────────────────────────────────────────────
|
||||
|
||||
const DEFAULT_STAFF_ID = 1; // Column A
|
||||
const SLOT_DURATION = 30; // minutes
|
||||
const SLOT_DURATION = 15; // minutes
|
||||
|
||||
/** Convert "HH:MM" to total minutes since midnight */
|
||||
function toMinutes(t: string): number {
|
||||
@@ -736,6 +736,68 @@ async function handleScheduleAppointment(
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Standalone helper: create today's appointment for a known patientId ─────
|
||||
|
||||
export async function createAppointmentToday(
|
||||
patientId: number,
|
||||
userId: number,
|
||||
storage: StorageLike
|
||||
): Promise<{ startTime: string; endTime: string; dateStr: string; dateLabel: string; column: string } | { error: string }> {
|
||||
const today = new Date();
|
||||
const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
|
||||
const dateLabel = today.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
||||
const localDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
||||
|
||||
const dayNames = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
||||
const dayName = dayNames[today.getDay()]!;
|
||||
|
||||
const officeHours = await storage.getOfficeHours(userId);
|
||||
const dayHours = officeHours?.data?.doctors?.[dayName] ?? {
|
||||
amStart: "09:00", amEnd: "12:00", pmStart: "13:00", pmEnd: "17:00", enabled: true,
|
||||
};
|
||||
|
||||
if (!dayHours.enabled) {
|
||||
return { error: `The office is closed today (${dayName}). Cannot create appointment.` };
|
||||
}
|
||||
|
||||
const allSlots = buildSlots(dayHours);
|
||||
const allAppointments = await storage.getAppointmentsByDateForUser(dateStr, userId);
|
||||
|
||||
for (const { staffId, label } of [
|
||||
{ staffId: DEFAULT_STAFF_ID, label: "Column A" },
|
||||
{ staffId: 2, label: "Column B" },
|
||||
]) {
|
||||
const booked = allAppointments
|
||||
.filter((a: any) => a.staffId === staffId)
|
||||
.map((a: any) => ({ start: toMinutes(a.startTime), end: toMinutes(a.endTime) }));
|
||||
|
||||
const availableStart = allSlots.find((slotStart) => {
|
||||
const slotEnd = slotStart + SLOT_DURATION;
|
||||
return !booked.some((b) => slotStart < b.end && slotEnd > b.start);
|
||||
});
|
||||
|
||||
if (availableStart !== undefined) {
|
||||
const startTime = fromMinutes(availableStart);
|
||||
const endTime = fromMinutes(availableStart + SLOT_DURATION);
|
||||
await storage.createAppointment({
|
||||
patientId,
|
||||
userId,
|
||||
staffId,
|
||||
title: dateLabel,
|
||||
date: localDate,
|
||||
startTime,
|
||||
endTime,
|
||||
type: "recall",
|
||||
status: "scheduled",
|
||||
movedByAi: true,
|
||||
});
|
||||
return { startTime, endTime, dateStr, dateLabel, column: label };
|
||||
}
|
||||
}
|
||||
|
||||
return { error: `Both Column A and Column B are fully booked today. Please add the appointment manually.` };
|
||||
}
|
||||
|
||||
// ─── Insurance resolution helper ──────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import express, { Request, Response } from "express";
|
||||
import { storage } from "../storage";
|
||||
import { classifyInternalChat } from "../ai/internal-chat-graph";
|
||||
import { runInternalChatWorkflow } from "../ai/internal-chat-workflow";
|
||||
import { runInternalChatWorkflow, createAppointmentToday } from "../ai/internal-chat-workflow";
|
||||
import { resolveAiProvider } from "../ai/llm-factory";
|
||||
|
||||
const router = express.Router();
|
||||
@@ -309,4 +309,18 @@ router.post("/internal-chat", async (req: Request, res: Response): Promise<any>
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/create-appointment-today", async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) return res.status(401).json({ message: "Unauthorized" });
|
||||
const { patientId } = req.body;
|
||||
if (!patientId) return res.status(400).json({ message: "patientId is required" });
|
||||
const result = await createAppointmentToday(Number(patientId), userId, storage);
|
||||
if ("error" in result) return res.status(409).json({ message: result.error });
|
||||
return res.status(200).json(result);
|
||||
} catch (err) {
|
||||
return res.status(500).json({ error: "Failed to create appointment", details: String(err) });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user