Compare commits
19
Commits
ed5a8af835
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b899659e13 | ||
|
|
52ddfd9f57 | ||
|
|
f8235787db | ||
|
|
b860fd213f | ||
|
|
116889dd4e | ||
|
|
ad9d311a99 | ||
|
|
374f96399e | ||
|
|
e934d6bce5 | ||
|
|
304441d95a | ||
|
|
5044fecb07 | ||
|
|
efb85d0b92 | ||
|
|
41bed21302 | ||
|
|
67e71a02e4 | ||
|
|
ff79cd1643 | ||
|
|
0cd095330a | ||
|
|
55ce52f276 | ||
|
|
cef928be97 | ||
|
|
06dc63c53e | ||
|
|
089e94ba88 |
@@ -81,6 +81,43 @@ Reboot to confirm the popup no longer appears.
|
|||||||
|
|
||||||
> Trade-off: Chrome saved passwords/cookie encryption falls back to its weaker "Basic" store, and NetworkManager Wi-Fi passwords may need to be re-entered once. If a future setup needs the keyring kept, unlock it silently instead by opening **Passwords and Keys** (`seahorse`) → right-click **Login** → **Change Password** → set both new-password fields blank.
|
> Trade-off: Chrome saved passwords/cookie encryption falls back to its weaker "Basic" store, and NetworkManager Wi-Fi passwords may need to be re-entered once. If a future setup needs the keyring kept, unlock it silently instead by opening **Passwords and Keys** (`seahorse`) → right-click **Login** → **Change Password** → set both new-password fields blank.
|
||||||
|
|
||||||
|
#### If the popup (or a "choose a new password for keyring" prompt) still appears after a reboot
|
||||||
|
|
||||||
|
The `apt purge` above removes the package, but if something reinstalls `gnome-keyring`/`libpam-gnome-keyring` as a dependency later (e.g. installing another desktop app), it comes back. Deleting just the keyring files under `~/.local/share/keyrings/` doesn't fix it either — a new keyring gets created on the next login/app request and prompts for a password again.
|
||||||
|
|
||||||
|
Do this instead, which disables it at every level (PAM login hook, session autostart, and D-Bus on-demand activation) without needing to uninstall the package:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 1. Disable PAM hooks (login + password-sync)
|
||||||
|
sudo sed -i \
|
||||||
|
-e 's/^-auth optional pam_gnome_keyring.so/#-auth optional pam_gnome_keyring.so/' \
|
||||||
|
-e 's/^-session optional pam_gnome_keyring.so auto_start/#-session optional pam_gnome_keyring.so auto_start/' \
|
||||||
|
/etc/pam.d/sddm
|
||||||
|
sudo sed -i 's/^password\toptional\tpam_gnome_keyring.so/#&/' /etc/pam.d/common-password
|
||||||
|
|
||||||
|
# 2. Stop the keyring daemon components from autostarting in the session
|
||||||
|
mkdir -p ~/.config/autostart
|
||||||
|
for f in gnome-keyring-pkcs11 gnome-keyring-secrets gnome-keyring-ssh; do
|
||||||
|
cp /etc/xdg/autostart/$f.desktop ~/.config/autostart/$f.desktop 2>/dev/null
|
||||||
|
echo "Hidden=true" >> ~/.config/autostart/$f.desktop
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3. Mask D-Bus service activation (the part that keeps bringing it back —
|
||||||
|
# any app calling the Secret Service API, e.g. Chrome, spawns the daemon
|
||||||
|
# on demand even with PAM and autostart disabled)
|
||||||
|
sudo mkdir -p /usr/share/dbus-1/services-disabled
|
||||||
|
sudo mv /usr/share/dbus-1/services/org.freedesktop.secrets.service \
|
||||||
|
/usr/share/dbus-1/services/org.gnome.keyring.service \
|
||||||
|
/usr/share/dbus-1/services/org.freedesktop.impl.portal.Secret.service \
|
||||||
|
/usr/share/dbus-1/services-disabled/ 2>/dev/null
|
||||||
|
|
||||||
|
# 4. Kill the running daemon and clear existing keyring files
|
||||||
|
pkill -f gnome-keyring-daemon
|
||||||
|
rm -f ~/.local/share/keyrings/*
|
||||||
|
```
|
||||||
|
|
||||||
|
Reboot to confirm. Same trade-off as above: Chrome falls back to storing saved passwords unencrypted rather than via the Secret Service.
|
||||||
|
|
||||||
### Step 2 — Install Git
|
### Step 2 — Install Git
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -1064,4 +1101,26 @@ mkdir -p /home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/
|
|||||||
cp -r /media/usb/claude-memory-backup/* /home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/
|
cp -r /media/usb/claude-memory-backup/* /home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ngrok vs. Cloudflare Tunnel (for exposing Twilio webhooks/streams)
|
||||||
|
|
||||||
|
Twilio just needs a public HTTPS/WSS endpoint to reach — either tunnel provider works, and neither
|
||||||
|
is required by name. Trade-offs when choosing between them for the public tunnel (`CLOUDFLARE_HOST`
|
||||||
|
today; the code doesn't actually validate it's a Cloudflare domain, it's just named that way):
|
||||||
|
|
||||||
|
| | Cloudflare Tunnel (current setup) | ngrok |
|
||||||
|
|---|---|---|
|
||||||
|
| **Fixed hostname** | Yes — same subdomain forever, survives reboots | Only on a paid static domain; **free tier assigns a new random subdomain every time the `ngrok` process restarts** (machine reboot, crash, manual relaunch) |
|
||||||
|
| **Auto-start on boot** | Already set up via `systemctl enable cloudflared` | Possible (systemd service), but only fixes the tunnel coming back up — it does **not** stop the free-tier URL from changing on each restart |
|
||||||
|
| **After a URL change** | N/A — doesn't happen | Must update `CLOUDFLARE_HOST` in `.env`, restart the backend, and update the URL(s) configured in the Twilio Console |
|
||||||
|
| **Setup cost** | One-time per office (already documented above) | Quick to spin up for local dev/testing |
|
||||||
|
| **Latency to Twilio** | No reliable general winner — depends on actual network path (nearest PoP, peering) for this specific office, not the provider's name. Cloudflare's edge network is larger/more distributed in general, but the only way to know for a given office is to measure real round-trip time, not assume | |
|
||||||
|
| **Best for** | Production / long-running office deployments | Local dev/testing, or short-lived debugging sessions |
|
||||||
|
|
||||||
|
**Bottom line:** ngrok is fine for local development, but for an office's live Twilio integration
|
||||||
|
(webhooks and any Media Stream WebSocket), the Cloudflare Tunnel setup above is preferred because
|
||||||
|
its hostname doesn't change across reboots — avoiding the reconfigure-`.env`-and-Twilio-Console
|
||||||
|
cycle that free-tier ngrok requires every time the tunnel restarts.
|
||||||
|
|
||||||
The memory is plain markdown files and can also be copied manually via a USB drive or file manager. Enable "show hidden files" (Ctrl+H) in the file manager to see the `.claude` folder.
|
The memory is plain markdown files and can also be copied manually via a USB drive or file manager. Enable "show hidden files" (Ctrl+H) in the file manager to see the `.claude` folder.
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { prisma as db } from "@repo/db/client";
|
|||||||
export type ConversationStage =
|
export type ConversationStage =
|
||||||
| "initial"
|
| "initial"
|
||||||
| "reminder_initial"
|
| "reminder_initial"
|
||||||
|
| "recall_initial"
|
||||||
|
| "recall_asked_datetime"
|
||||||
|
| "recall_asked_time_for_date"
|
||||||
| "greeted"
|
| "greeted"
|
||||||
| "done"
|
| "done"
|
||||||
| "new_patient_greeted"
|
| "new_patient_greeted"
|
||||||
@@ -81,6 +84,12 @@ export async function startRescheduleConversation(userId: number, patientId: num
|
|||||||
await setStage(userId, patientId, "asked_reschedule_confirm");
|
await setStage(userId, patientId, "asked_reschedule_confirm");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recall: office proactively calls/texts an existing patient to offer a
|
||||||
|
// routine exam & cleaning (typically covered at no cost by insurance).
|
||||||
|
export async function startRecallConversation(userId: number, patientId: number): Promise<void> {
|
||||||
|
await setStage(userId, patientId, "recall_initial");
|
||||||
|
}
|
||||||
|
|
||||||
// ── Pending reschedule (in-memory — seconds-lived within a single exchange) ───
|
// ── Pending reschedule (in-memory — seconds-lived within a single exchange) ───
|
||||||
|
|
||||||
interface PendingReschedule {
|
interface PendingReschedule {
|
||||||
|
|||||||
@@ -386,6 +386,48 @@ function parseRctCode(input: string): CdtMatch | null {
|
|||||||
return { code, description: row?.Description ?? code, input, toothNumber: String(toothNum) };
|
return { code, description: row?.Description ?? code, input, toothNumber: String(toothNum) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Levenshtein edit distance between two strings.
|
||||||
|
*/
|
||||||
|
function editDistance(a: string, b: string): number {
|
||||||
|
const dp: number[][] = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
||||||
|
for (let i = 0; i <= a.length; i++) dp[i]![0] = i;
|
||||||
|
for (let j = 0; j <= b.length; j++) dp[0]![j] = j;
|
||||||
|
for (let i = 1; i <= a.length; i++) {
|
||||||
|
for (let j = 1; j <= b.length; j++) {
|
||||||
|
dp[i]![j] = a[i - 1] === b[j - 1]
|
||||||
|
? dp[i - 1]![j - 1]!
|
||||||
|
: 1 + Math.min(dp[i - 1]![j]!, dp[i]![j - 1]!, dp[i - 1]![j - 1]!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[a.length]![b.length]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if two words are the same or a plausible typo of each other
|
||||||
|
* (one edit, only for words long enough that a 1-char slip is unambiguous).
|
||||||
|
*/
|
||||||
|
function wordsMatch(a: string, b: string): boolean {
|
||||||
|
if (a === b) return true;
|
||||||
|
if (a.length < 5 || b.length < 5) return false;
|
||||||
|
return editDistance(a, b) <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fuzzy lookup into ALIAS_MAP: tolerates a single typo per word
|
||||||
|
* (e.g. "comprehensiv exam" → "comprehensive exam").
|
||||||
|
*/
|
||||||
|
function fuzzyAliasLookup(cleaned: string): string | undefined {
|
||||||
|
if (ALIAS_MAP[cleaned]) return ALIAS_MAP[cleaned];
|
||||||
|
const queryWords = cleaned.split(/\s+/).filter(Boolean);
|
||||||
|
for (const key of Object.keys(ALIAS_MAP)) {
|
||||||
|
const keyWords = key.split(/\s+/).filter(Boolean);
|
||||||
|
if (keyWords.length !== queryWords.length) continue;
|
||||||
|
if (keyWords.every((w, i) => wordsMatch(w, queryWords[i]!))) return ALIAS_MAP[key];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Score how well a set of query tokens matches a code's description tokens.
|
* Score how well a set of query tokens matches a code's description tokens.
|
||||||
* Each matched token contributes 1 point; shorter descriptions get a bonus
|
* Each matched token contributes 1 point; shorter descriptions get a bonus
|
||||||
@@ -394,7 +436,14 @@ function parseRctCode(input: string): CdtMatch | null {
|
|||||||
function score(queryTokens: string[], entry: { tokens: Set<string>; description: string }): number {
|
function score(queryTokens: string[], entry: { tokens: Set<string>; description: string }): number {
|
||||||
let hits = 0;
|
let hits = 0;
|
||||||
for (const t of queryTokens) {
|
for (const t of queryTokens) {
|
||||||
if (entry.tokens.has(t)) hits++;
|
if (entry.tokens.has(t)) {
|
||||||
|
hits++;
|
||||||
|
} else if (t.length >= 5) {
|
||||||
|
// Tolerate a single typo (e.g. "comprehensiv" → "comprehensive")
|
||||||
|
for (const dt of entry.tokens) {
|
||||||
|
if (wordsMatch(t, dt)) { hits++; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (hits === 0) return 0;
|
if (hits === 0) return 0;
|
||||||
// Tie-break: prefer shorter descriptions (more specific match)
|
// Tie-break: prefer shorter descriptions (more specific match)
|
||||||
@@ -425,8 +474,8 @@ function matchOne(input: string): CdtMatch | null {
|
|||||||
return { code, description: row?.Description ?? code, input };
|
return { code, description: row?.Description ?? code, input };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply alias before tokenizing
|
// Apply alias before tokenizing (fuzzy-tolerant of single-letter typos)
|
||||||
const normalized = ALIAS_MAP[cleaned] ?? cleaned;
|
const normalized = fuzzyAliasLookup(cleaned) ?? cleaned;
|
||||||
const queryTokens = normalized
|
const queryTokens = normalized
|
||||||
.replace(/[^a-z0-9\s]/g, " ")
|
.replace(/[^a-z0-9\s]/g, " ")
|
||||||
.split(/\s+/)
|
.split(/\s+/)
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import twilio from "twilio";
|
||||||
|
import { prisma as db } from "@repo/db/client";
|
||||||
|
import { storage } from "../storage";
|
||||||
|
|
||||||
|
const CONFIRMATION_FALLBACKS: Record<string, (dateLabel: string, time: string) => string> = {
|
||||||
|
English: (d, t) => `Confirmed! Your appointment is on ${d} at ${t}. We look forward to seeing you.`,
|
||||||
|
Spanish: (d, t) => `¡Confirmado! Su cita es el ${d} a las ${t}. Le esperamos.`,
|
||||||
|
Portuguese: (d, t) => `Confirmado! Sua consulta é em ${d} às ${t}. Aguardamos sua visita.`,
|
||||||
|
Mandarin: (d, t) => `已确认!您的预约在 ${d} ${t}。期待您的光临。`,
|
||||||
|
Cantonese: (d, t) => `已確認!您的預約喺 ${d} ${t}。期待見到您。`,
|
||||||
|
Arabic: (d, t) => `تم التأكيد! موعدك في ${d} الساعة ${t}. نتطلع لرؤيتك.`,
|
||||||
|
"Haitian Creole": (d, t) => `Konfime! Randevou ou se ${d} a ${t}. N'ap tann ou.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a standalone appointment-confirmation text, independent of whichever
|
||||||
|
* channel (SMS chat or AI phone call) the booking conversation happened on —
|
||||||
|
* a voice-booked appointment would otherwise leave the patient with nothing
|
||||||
|
* to refer back to once the call ends. Swallows all errors so a notification
|
||||||
|
* failure never breaks the booking flow that already succeeded.
|
||||||
|
*/
|
||||||
|
export async function sendAppointmentConfirmationSms(
|
||||||
|
userId: number,
|
||||||
|
patientId: number,
|
||||||
|
dateLabel: string,
|
||||||
|
time: string,
|
||||||
|
lang: string,
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const [twilioSettings, patient] = await Promise.all([
|
||||||
|
storage.getTwilioSettings(userId),
|
||||||
|
db.patient.findUnique({ where: { id: patientId }, select: { phone: true } }),
|
||||||
|
]);
|
||||||
|
if (!twilioSettings || !patient?.phone) return;
|
||||||
|
|
||||||
|
const build = CONFIRMATION_FALLBACKS[lang] ?? CONFIRMATION_FALLBACKS["English"]!;
|
||||||
|
const body = build(dateLabel, time);
|
||||||
|
|
||||||
|
const client = twilio(twilioSettings.accountSid, twilioSettings.authToken);
|
||||||
|
await client.messages.create({ body, from: twilioSettings.phoneNumber, to: patient.phone });
|
||||||
|
|
||||||
|
await storage.createCommunication({
|
||||||
|
patientId, userId, channel: "sms", direction: "outbound", status: "sent", body,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Silent — don't crash the booking flow over a notification failure
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,689 @@
|
|||||||
|
import twilio from "twilio";
|
||||||
|
import { prisma as db } from "@repo/db/client";
|
||||||
|
import { storage } from "../storage";
|
||||||
|
import { runReminderGraph } from "./reminder-graph";
|
||||||
|
import { runRecallGraph, runRecallBookingStep, hasDateOrTimeSignal } from "./recall-graph";
|
||||||
|
import { runNewPatientStep } from "./new-patient-graph";
|
||||||
|
import {
|
||||||
|
runRescheduleStep,
|
||||||
|
parseDateOnlyFromMessage,
|
||||||
|
parseTime,
|
||||||
|
isOfficeDayOpen,
|
||||||
|
isWithinOfficeHours,
|
||||||
|
getOfficeHoursDisplay,
|
||||||
|
timeLabel,
|
||||||
|
} from "./reschedule-graph";
|
||||||
|
import { getLlm, type AiProvider } from "./llm-factory";
|
||||||
|
import { runEligibilityProcessor } from "../queue/processors/eligibilityProcessor";
|
||||||
|
import { sendAppointmentConfirmationSms } from "./confirmationSms";
|
||||||
|
import {
|
||||||
|
getPendingReschedule, setPendingReschedule, clearPendingReschedule,
|
||||||
|
setStage, type ConversationStage,
|
||||||
|
} from "./aiHandoffStore";
|
||||||
|
|
||||||
|
// Shared, channel-agnostic implementation of the patient conversation rules
|
||||||
|
// (reminder / reschedule / new-patient graphs, plus the inline MassHealth and
|
||||||
|
// appointment-booking stages) used by both the SMS webhook and the AI voice
|
||||||
|
// call webhook, so a patient gets identical guardrails and outcomes
|
||||||
|
// regardless of channel.
|
||||||
|
|
||||||
|
export interface RoutedPatient {
|
||||||
|
id: number;
|
||||||
|
userId: number;
|
||||||
|
phone: string | null;
|
||||||
|
preferredLanguage: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RouteTurnResult {
|
||||||
|
segments: string[];
|
||||||
|
nextStage: ConversationStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the patient's next scheduled appointment as a human-readable string. */
|
||||||
|
export async function getAppointmentDatetime(patientId: number): Promise<string> {
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
const appt = await db.appointment.findFirst({
|
||||||
|
where: { patientId, status: "scheduled", date: { gte: today } },
|
||||||
|
orderBy: { date: "asc" },
|
||||||
|
});
|
||||||
|
if (!appt) return "";
|
||||||
|
const months = ["January","February","March","April","May","June",
|
||||||
|
"July","August","September","October","November","December"];
|
||||||
|
const d = new Date(appt.date);
|
||||||
|
return `${months[d.getUTCMonth()]} ${d.getUTCDate()}, ${d.getUTCFullYear()} at ${appt.startTime}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Substitute {officeName} in a template string. */
|
||||||
|
export function applyOfficeName(template: string, name: string): string {
|
||||||
|
return template.replace(/\{officeName\}/g, name || "our dental office");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Save an outbound SMS message (always sms channel — used for async follow-ups). */
|
||||||
|
async function saveOutboundSms(patientId: number, body: string): Promise<void> {
|
||||||
|
await storage.createCommunication({
|
||||||
|
patientId, channel: "sms", direction: "outbound", status: "sent", body,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Normalize a DOB string to zero-padded MM/DD/YYYY required by MassHealth. */
|
||||||
|
function normalizeDob(raw: string): string {
|
||||||
|
const parts = raw.split(/[\/\-\.]/);
|
||||||
|
if (parts.length !== 3) return raw;
|
||||||
|
const [m, d, y] = parts;
|
||||||
|
const mm = String(parseInt(m!, 10)).padStart(2, "0");
|
||||||
|
const dd = String(parseInt(d!, 10)).padStart(2, "0");
|
||||||
|
const yyyy = y!.length === 2 ? `20${y}` : y!;
|
||||||
|
return `${mm}/${dd}/${yyyy}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract MassHealth Member ID and date of birth from free text.
|
||||||
|
* Tries regex first, falls back to LLM extraction.
|
||||||
|
*/
|
||||||
|
async function parseMassHealthInfo(
|
||||||
|
message: string,
|
||||||
|
apiKey: string,
|
||||||
|
provider: AiProvider = "google",
|
||||||
|
model?: string
|
||||||
|
): Promise<{ memberId: string | null; dob: string | null }> {
|
||||||
|
const idMatch = message.match(/\b(\d{8,12})\b/);
|
||||||
|
const dobMatch = message.match(/\b(\d{1,2})[\/\-\.](\d{1,2})[\/\-\.](\d{2,4})\b/);
|
||||||
|
|
||||||
|
if (idMatch && dobMatch) {
|
||||||
|
const [, m, d, y] = dobMatch;
|
||||||
|
const year = y!.length === 2 ? `20${y}` : y;
|
||||||
|
return { memberId: idMatch[1]!, dob: normalizeDob(`${m}/${d}/${year}`) };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const llm = getLlm(provider, apiKey, model);
|
||||||
|
const res = await llm.invoke([
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content:
|
||||||
|
'Extract the insurance member ID and date of birth from the patient message. ' +
|
||||||
|
'Return ONLY valid JSON: {"memberId":"...","dob":"MM/DD/YYYY"}. Use null for missing fields.',
|
||||||
|
},
|
||||||
|
{ role: "user", content: message },
|
||||||
|
]);
|
||||||
|
const raw = String(res.content).replace(/```json|```/g, "").trim();
|
||||||
|
const json = JSON.parse(raw);
|
||||||
|
const dob = json.dob ? normalizeDob(String(json.dob)) : null;
|
||||||
|
return { memberId: json.memberId ?? null, dob };
|
||||||
|
} catch {
|
||||||
|
return { memberId: null, dob: null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run MassHealth eligibility check in the background (after replying to the
|
||||||
|
* patient) and send the result as a follow-up SMS, regardless of which
|
||||||
|
* channel (SMS or voice call) triggered the check.
|
||||||
|
*/
|
||||||
|
async function runMassHealthCheckAndNotify(
|
||||||
|
patient: RoutedPatient,
|
||||||
|
memberId: string,
|
||||||
|
dob: string,
|
||||||
|
apiKey: string,
|
||||||
|
isExistingPatient = false
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(patient.userId, "MH");
|
||||||
|
if (!credentials) return;
|
||||||
|
|
||||||
|
const twilioSettings = await storage.getTwilioSettings(patient.userId);
|
||||||
|
if (!twilioSettings || !patient.phone) return;
|
||||||
|
|
||||||
|
await runEligibilityProcessor({
|
||||||
|
userId: patient.userId,
|
||||||
|
insuranceId: memberId,
|
||||||
|
formDob: dob,
|
||||||
|
enrichedPayload: {
|
||||||
|
memberId,
|
||||||
|
dateOfBirth: dob,
|
||||||
|
insuranceSiteKey: "MH",
|
||||||
|
massdhpUsername: credentials.username,
|
||||||
|
massdhpPassword: credentials.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updated = await db.patient.findUnique({
|
||||||
|
where: { id: patient.id },
|
||||||
|
select: { status: true, firstName: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const lang = patient.preferredLanguage || "English";
|
||||||
|
const active = updated?.status === "ACTIVE";
|
||||||
|
|
||||||
|
const activeMessages: Record<string, string> = {
|
||||||
|
English: "Great news! Your MassHealth coverage is active. We can schedule an appointment for you! What date and time would you prefer?",
|
||||||
|
Spanish: "¡Buenas noticias! Su cobertura de MassHealth está activa. ¡Podemos programar una cita para usted! ¿Qué fecha y hora prefiere?",
|
||||||
|
Portuguese: "Ótimas notícias! Sua cobertura MassHealth está ativa. Podemos agendar uma consulta para você! Qual data e horário prefere?",
|
||||||
|
Mandarin: "好消息!您的MassHealth保险有效。我们可以为您安排预约!您希望什么日期和时间?",
|
||||||
|
Cantonese: "好消息!您的MassHealth保險有效。我們可以為您安排預約!您希望什麼日期和時間?",
|
||||||
|
Arabic: "أخبار رائعة! تغطيتك من MassHealth نشطة. يمكننا تحديد موعد لك! ما التاريخ والوقت المفضل لديك؟",
|
||||||
|
"Haitian Creole": "Bon nouvèl! Asirans MassHealth ou aktif. Nou ka planifye yon randevou pou ou! Ki dat ak lè ou prefere?",
|
||||||
|
};
|
||||||
|
|
||||||
|
const inactiveMessagesNew: Record<string, string> = {
|
||||||
|
English: "Unfortunately, your MassHealth coverage appears to be inactive. Do you have any other insurance?",
|
||||||
|
Spanish: "Lamentablemente, su cobertura de MassHealth parece estar inactiva. ¿Tiene algún otro seguro?",
|
||||||
|
Portuguese: "Infelizmente, sua cobertura MassHealth parece estar inativa. Você tem algum outro plano de saúde?",
|
||||||
|
Mandarin: "很遗憾,您的MassHealth保险似乎无效。您还有其他保险吗?",
|
||||||
|
Cantonese: "很遺憾,您的MassHealth保險似乎無效。您還有其他保險嗎?",
|
||||||
|
Arabic: "للأسف، تغطيتك من MassHealth تبدو غير نشطة. هل لديك أي تأمين آخر؟",
|
||||||
|
"Haitian Creole": "Malerezman, kouvèti MassHealth ou parèt inaktif. Èske ou gen yon lòt asirans?",
|
||||||
|
};
|
||||||
|
|
||||||
|
const inactiveMessagesExisting: Record<string, string> = {
|
||||||
|
English: "We checked your MassHealth coverage. Unfortunately the plan appears inactive or could not be verified. Would you still like to schedule an examination appointment as a self-pay patient?",
|
||||||
|
Spanish: "Verificamos su cobertura de MassHealth. Lamentablemente el plan aparece inactivo o no pudo ser verificado. ¿Le gustaría programar una cita de examen como paciente de pago particular?",
|
||||||
|
Portuguese: "Verificamos sua cobertura MassHealth. Infelizmente o plano parece inativo ou não pôde ser verificado. Gostaria de agendar uma consulta de exame como paciente particular?",
|
||||||
|
Mandarin: "我们查看了您的MassHealth保险。遗憾的是,保险似乎无效或无法验证。您仍然希望以自费方式预约检查吗?",
|
||||||
|
Cantonese: "我們查看了您的MassHealth保險。遺憾地,保險似乎無效或無法核實。您仍然希望以自費方式預約檢查嗎?",
|
||||||
|
Arabic: "تحققنا من تغطيتك من MassHealth. للأسف يبدو أن الخطة غير نشطة أو لا يمكن التحقق منها. هل تودّ تحديد موعد فحص كمريض يدفع من حسابه الخاص؟",
|
||||||
|
"Haitian Creole": "Nou te verifye kouvèti MassHealth ou. Malerezman plan an sanble inaktif oswa pa ka verifye. Èske ou ta renmen pran yon randevou egzamen kòm pasyan ki peye poukont li?",
|
||||||
|
};
|
||||||
|
|
||||||
|
const resultText = active
|
||||||
|
? (activeMessages[lang] ?? activeMessages["English"]!)
|
||||||
|
: isExistingPatient
|
||||||
|
? (inactiveMessagesExisting[lang] ?? inactiveMessagesExisting["English"]!)
|
||||||
|
: (inactiveMessagesNew[lang] ?? inactiveMessagesNew["English"]!);
|
||||||
|
|
||||||
|
const nextStage: ConversationStage = active
|
||||||
|
? "asked_appointment_time"
|
||||||
|
: isExistingPatient
|
||||||
|
? "asked_self_pay"
|
||||||
|
: "asked_other_insurance_after_inactive";
|
||||||
|
|
||||||
|
const client = twilio(twilioSettings.accountSid, twilioSettings.authToken);
|
||||||
|
await client.messages.create({
|
||||||
|
body: resultText,
|
||||||
|
from: twilioSettings.phoneNumber,
|
||||||
|
to: patient.phone,
|
||||||
|
});
|
||||||
|
|
||||||
|
await saveOutboundSms(patient.id, resultText);
|
||||||
|
await setStage(patient.userId, patient.id, nextStage);
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
// Silent — don't crash the caller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function finish(
|
||||||
|
userId: number, patientId: number,
|
||||||
|
segments: string[], nextStage: ConversationStage
|
||||||
|
): Promise<RouteTurnResult> {
|
||||||
|
await setStage(userId, patientId, nextStage);
|
||||||
|
return { segments, nextStage };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process one patient message (SMS body or a transcribed spoken turn) against
|
||||||
|
* the current ConversationStage and return what to say next plus the stage
|
||||||
|
* to advance to. Persists the resulting stage itself. Does NOT log the
|
||||||
|
* Communication row for the reply — callers do that per-channel.
|
||||||
|
*/
|
||||||
|
export async function routePatientTurn(params: {
|
||||||
|
patient: RoutedPatient;
|
||||||
|
stage: ConversationStage;
|
||||||
|
message: string;
|
||||||
|
activeAi: { provider: AiProvider; key: string; model: string };
|
||||||
|
chatTemplates: Record<string, string>;
|
||||||
|
officeName: string;
|
||||||
|
}): Promise<RouteTurnResult> {
|
||||||
|
const { patient, stage, message, activeAi, chatTemplates, officeName } = params;
|
||||||
|
const language = patient.preferredLanguage || "English";
|
||||||
|
|
||||||
|
// ── Stage: reminder_initial → self-intro + classify yes/no/other ────────
|
||||||
|
if (stage === "reminder_initial") {
|
||||||
|
const rawGreeting = chatTemplates.reminderGreeting ||
|
||||||
|
`Hi! My name is Lisa, the dedicated AI assistant at {officeName}. I can confirm or reschedule your appointment and answer general questions 24/7.`;
|
||||||
|
const introText = applyOfficeName(rawGreeting, officeName);
|
||||||
|
|
||||||
|
const apptDatetime = await getAppointmentDatetime(patient.id);
|
||||||
|
const { reply: intentReply, intent } = await runReminderGraph(
|
||||||
|
message, activeAi.key, language, apptDatetime,
|
||||||
|
chatTemplates.rescheduleGreeting, chatTemplates.generalFallback, activeAi.provider, activeAi.model
|
||||||
|
);
|
||||||
|
|
||||||
|
if (intentReply) {
|
||||||
|
let nextStage: ConversationStage;
|
||||||
|
if (intent === "no") nextStage = "asked_reschedule_datetime";
|
||||||
|
else if (intent === "wants_appointment") nextStage = "asked_new_or_existing";
|
||||||
|
else nextStage = "done";
|
||||||
|
|
||||||
|
if (intent === "no") {
|
||||||
|
const hasDateInMessage =
|
||||||
|
/\b\d{1,2}[\/\-]\d{1,2}\b/.test(message) ||
|
||||||
|
/\b(monday|tuesday|wednesday|thursday|friday|saturday|sunday|tomorrow|next week)\b/i.test(message);
|
||||||
|
if (hasDateInMessage) {
|
||||||
|
const { reply: rescheduleReply, nextStage: rescheduleNextStage } = await runRescheduleStep(
|
||||||
|
message, "asked_reschedule_datetime", language, patient.id, activeAi.key, patient.userId, activeAi.provider, activeAi.model
|
||||||
|
);
|
||||||
|
return finish(patient.userId, patient.id, [introText, rescheduleReply], rescheduleNextStage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return finish(patient.userId, patient.id, [introText, intentReply], nextStage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No clear intent detected — send only the intro and wait for next reply
|
||||||
|
return finish(patient.userId, patient.id, [introText], "greeted");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stage: recall_initial → office proactively offered an existing patient
|
||||||
|
// a routine exam & cleaning (usually covered at no cost by insurance); the
|
||||||
|
// pitch itself was already spoken/sent as the opening greeting, so this
|
||||||
|
// just classifies the patient's yes/no. On yes, it books directly onto the
|
||||||
|
// schedule (Column A, 30-minute slots) via runRecallBookingStep — unlike
|
||||||
|
// reschedule, a recall patient has no existing appointment to move, so this
|
||||||
|
// is a real create-appointment path, not a shortcut into the reschedule flow.
|
||||||
|
if (stage === "recall_initial") {
|
||||||
|
const { reply: intentReply, intent } = await runRecallGraph(
|
||||||
|
message, activeAi.key, language, chatTemplates.generalFallback, activeAi.provider, activeAi.model
|
||||||
|
);
|
||||||
|
|
||||||
|
if (intentReply) {
|
||||||
|
if (intent === "yes") {
|
||||||
|
// Patient may have already given a date (or date + time) in this same
|
||||||
|
// reply ("Tuesday 1pm") — try to book right away instead of asking again.
|
||||||
|
// A bare "yes"/"ok" has nothing to parse, so skip straight to asking for
|
||||||
|
// day + time instead of wasting a parse call that used to come back
|
||||||
|
// empty and send a mismatched "Sorry, I didn't catch a day" reply.
|
||||||
|
if (hasDateOrTimeSignal(message)) {
|
||||||
|
const { reply: bookingReply, nextStage: bookingNextStage } = await runRecallBookingStep(
|
||||||
|
message, "recall_asked_datetime", language, patient.id, patient.userId, activeAi.key, activeAi.provider, activeAi.model
|
||||||
|
);
|
||||||
|
return finish(patient.userId, patient.id, [bookingReply], bookingNextStage);
|
||||||
|
}
|
||||||
|
return finish(patient.userId, patient.id, [intentReply], "recall_asked_datetime");
|
||||||
|
}
|
||||||
|
if (intent === "no") {
|
||||||
|
return finish(patient.userId, patient.id, [intentReply], "done");
|
||||||
|
}
|
||||||
|
return finish(patient.userId, patient.id, [intentReply], "recall_initial");
|
||||||
|
}
|
||||||
|
|
||||||
|
return finish(patient.userId, patient.id, [chatTemplates.generalFallback || "Thank you for your message!"], "recall_initial");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Recall booking flow — collecting/confirming the day & time, then
|
||||||
|
// creating the appointment (see recall-graph.ts: runRecallBookingStep) ──
|
||||||
|
const recallBookingStages: ConversationStage[] = ["recall_asked_datetime", "recall_asked_time_for_date"];
|
||||||
|
if (recallBookingStages.includes(stage)) {
|
||||||
|
const { reply: aiReply, nextStage } = await runRecallBookingStep(
|
||||||
|
message, stage, language, patient.id, patient.userId, activeAi.key, activeAi.provider, activeAi.model
|
||||||
|
);
|
||||||
|
return finish(patient.userId, patient.id, [aiReply], nextStage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stage: greeted → classify yes/no for appointment reminder ──────────
|
||||||
|
if (stage === "greeted") {
|
||||||
|
const apptDatetime = await getAppointmentDatetime(patient.id);
|
||||||
|
const { reply: aiReply, intent } = await runReminderGraph(
|
||||||
|
message, activeAi.key, language, apptDatetime,
|
||||||
|
chatTemplates.rescheduleGreeting, chatTemplates.generalFallback, activeAi.provider, activeAi.model
|
||||||
|
);
|
||||||
|
if (aiReply) {
|
||||||
|
let nextStage: ConversationStage;
|
||||||
|
if (intent === "no") nextStage = "asked_reschedule_datetime";
|
||||||
|
else if (intent === "wants_appointment") nextStage = "asked_new_or_existing";
|
||||||
|
else nextStage = "done";
|
||||||
|
|
||||||
|
if (intent === "no") {
|
||||||
|
const hasDateInMessage =
|
||||||
|
/\b\d{1,2}[\/\-]\d{1,2}\b/.test(message) ||
|
||||||
|
/\b(monday|tuesday|wednesday|thursday|friday|saturday|sunday|tomorrow|next week)\b/i.test(message);
|
||||||
|
if (hasDateInMessage) {
|
||||||
|
const { reply: rescheduleReply, nextStage: rescheduleNextStage } = await runRescheduleStep(
|
||||||
|
message, "asked_reschedule_datetime", language, patient.id, activeAi.key, patient.userId, activeAi.provider, activeAi.model
|
||||||
|
);
|
||||||
|
return finish(patient.userId, patient.id, [rescheduleReply], rescheduleNextStage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return finish(patient.userId, patient.id, [aiReply], nextStage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rescheduling flow stages ────────────────────────────────────────────
|
||||||
|
const rescheduleStages: ConversationStage[] = [
|
||||||
|
"asked_reschedule_confirm", "asked_reschedule_preference",
|
||||||
|
"asked_reschedule_asap", "asked_reschedule_next_week",
|
||||||
|
"asked_reschedule_time", "asked_reschedule_datetime",
|
||||||
|
"asked_reschedule_time_for_date", "asked_reschedule_confirm_datetime",
|
||||||
|
];
|
||||||
|
if (rescheduleStages.includes(stage)) {
|
||||||
|
const { reply: aiReply, nextStage } = await runRescheduleStep(
|
||||||
|
message, stage, language, patient.id, activeAi.key, patient.userId, activeAi.provider, activeAi.model
|
||||||
|
);
|
||||||
|
return finish(patient.userId, patient.id, [aiReply], nextStage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stage: awaiting MassHealth member ID + DOB ─────────────────────────
|
||||||
|
if (stage === "awaiting_masshealth_info") {
|
||||||
|
const { memberId, dob } = await parseMassHealthInfo(message, activeAi.key, activeAi.provider, activeAi.model);
|
||||||
|
|
||||||
|
if (!memberId || !dob) {
|
||||||
|
const retryMessages: Record<string, string> = {
|
||||||
|
English: "I couldn't read your Member ID and date of birth. Please reply in this format: Member ID: 12345678 DOB: 01/01/1990",
|
||||||
|
Spanish: "No pude leer su número de miembro y fecha de nacimiento. Por favor responda así: ID: 12345678 Fecha: 01/01/1990",
|
||||||
|
Portuguese: "Não consegui ler seu número de membro e data de nascimento. Por favor responda assim: ID: 12345678 Data: 01/01/1990",
|
||||||
|
Mandarin: "我无法读取您的会员ID和出生日期。请按以下格式回复:ID: 12345678 生日: 01/01/1990",
|
||||||
|
Cantonese: "我無法讀取您的會員ID和出生日期。請按以下格式回覆:ID: 12345678 生日: 01/01/1990",
|
||||||
|
Arabic: "لم أتمكن من قراءة رقم العضوية وتاريخ الميلاد. يرجى الرد بالصيغة التالية: ID: 12345678 DOB: 01/01/1990",
|
||||||
|
"Haitian Creole": "Mwen pa t ka li ID manm ou ak dat nesans. Tanpri reponn konsa: ID: 12345678 DOB: 01/01/1990",
|
||||||
|
};
|
||||||
|
return finish(patient.userId, patient.id, [retryMessages[language] ?? retryMessages["English"]!], "awaiting_masshealth_info");
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkingMessages: Record<string, string> = {
|
||||||
|
English: "Thank you! I'm checking your MassHealth eligibility now. I'll send you the result in a moment.",
|
||||||
|
Spanish: "¡Gracias! Estoy verificando su elegibilidad de MassHealth ahora. Le enviaré el resultado en un momento.",
|
||||||
|
Portuguese: "Obrigado! Estou verificando sua elegibilidade MassHealth agora. Enviarei o resultado em instantes.",
|
||||||
|
Mandarin: "谢谢!我正在查询您的MassHealth资格。稍后我会发送结果给您。",
|
||||||
|
Cantonese: "多謝!我正在查詢您的MassHealth資格。稍後我會發送結果給您。",
|
||||||
|
Arabic: "شكراً! أقوم بالتحقق من أهليتك في MassHealth الآن. سأرسل لك النتيجة قريباً.",
|
||||||
|
"Haitian Creole": "Mèsi! Mwen ap verifye kalifikasyon MassHealth ou kounye a. M ap voye rezilta a nan yon ti moman.",
|
||||||
|
};
|
||||||
|
const checkingMsg = checkingMessages[language] ?? checkingMessages["English"]!;
|
||||||
|
|
||||||
|
runMassHealthCheckAndNotify(patient, memberId, dob, activeAi.key).catch(() => {});
|
||||||
|
return finish(patient.userId, patient.id, [checkingMsg], "done");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stage: existing patient said YES to same insurance ──────────────────
|
||||||
|
if (stage === "asked_existing_insurance") {
|
||||||
|
const saysYes = /yes|same|still have|haven't changed|no change|yep|yeah|sí|si|sim|好的|نعم|wi/i.test(message);
|
||||||
|
|
||||||
|
if (saysYes) {
|
||||||
|
const patientRecord = await db.patient.findUnique({
|
||||||
|
where: { id: patient.id },
|
||||||
|
select: { insuranceProvider: true, insuranceId: true, dateOfBirth: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const isMassHealth = /masshealth|mass health|masscare|medicaid/i.test(
|
||||||
|
patientRecord?.insuranceProvider ?? ""
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isMassHealth && patientRecord?.insuranceId) {
|
||||||
|
let dobStr = "";
|
||||||
|
if (patientRecord.dateOfBirth) {
|
||||||
|
const d = new Date(patientRecord.dateOfBirth);
|
||||||
|
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
||||||
|
const dd = String(d.getUTCDate()).padStart(2, "0");
|
||||||
|
const yy = d.getUTCFullYear();
|
||||||
|
dobStr = `${mm}/${dd}/${yy}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkingMessages: Record<string, string> = {
|
||||||
|
English: "Please wait about 30-60 seconds! I'm double-checking your MassHealth coverage right now.",
|
||||||
|
Spanish: "¡Por favor espere unos 30-60 segundos! Estoy verificando su cobertura de MassHealth ahora mismo.",
|
||||||
|
Portuguese: "Por favor aguarde cerca de 30-60 segundos! Estou verificando sua cobertura MassHealth agora.",
|
||||||
|
Mandarin: "请等待约30-60秒!我现在正在为您核查MassHealth保险。",
|
||||||
|
Cantonese: "請等待約30-60秒!我現在正在為您核查MassHealth保險。",
|
||||||
|
Arabic: "يرجى الانتظار حوالي 30-60 ثانية! أقوم الآن بالتحقق من تغطيتك في MassHealth.",
|
||||||
|
"Haitian Creole": "Tanpri tann anviwon 30-60 segonn! Mwen ap verifye kouvèti MassHealth ou kounye a.",
|
||||||
|
};
|
||||||
|
const checkingMsg = checkingMessages[language] ?? checkingMessages["English"]!;
|
||||||
|
|
||||||
|
runMassHealthCheckAndNotify(patient, patientRecord.insuranceId, dobStr, activeAi.key, true).catch(() => {});
|
||||||
|
return finish(patient.userId, patient.id, [checkingMsg], "done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Not MassHealth or said NO — fall through to normal graph handling
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stage: asked_new_or_reschedule ───────────────────────────────────────
|
||||||
|
if (stage === "asked_new_or_reschedule") {
|
||||||
|
const isReschedule = /reschedule|rescheduler|change|modify|move|different|reprogramar|reagendar|cambiar|mudar|\b2\b/i.test(message);
|
||||||
|
const isNew = /new.*appoint|make.*appoint|book.*appoint|new patient|first.*time|nueva cita|nova consulta|\b1\b/i.test(message);
|
||||||
|
|
||||||
|
if (isReschedule) {
|
||||||
|
const apptDatetime = await getAppointmentDatetime(patient.id);
|
||||||
|
if (apptDatetime) {
|
||||||
|
const foundMsgs: Record<string, string> = {
|
||||||
|
English: `Ok. Just to confirm, your current appointment is on ${apptDatetime}. When would you like to reschedule?`,
|
||||||
|
Spanish: `De acuerdo. Solo para confirmar, su cita actual es el ${apptDatetime}. ¿Cuándo le gustaría reprogramarla?`,
|
||||||
|
Portuguese: `Ok. Só para confirmar, sua consulta atual é em ${apptDatetime}. Quando você gostaria de reagendar?`,
|
||||||
|
Mandarin: `好的。请确认一下,您当前的预约是 ${apptDatetime}。您想重新安排到什么时候?`,
|
||||||
|
Cantonese: `好的。請確認一下,您目前的預約是 ${apptDatetime}。您想重新安排到什麼時候?`,
|
||||||
|
Arabic: `حسناً. فقط للتأكيد، موعدك الحالي في ${apptDatetime}. متى تريد إعادة الجدولة؟`,
|
||||||
|
"Haitian Creole": `Oke. Jis pou konfime, randevou aktyèl ou a se ${apptDatetime}. Ki lè ou ta renmen reprogramè?`,
|
||||||
|
};
|
||||||
|
return finish(patient.userId, patient.id, [foundMsgs[language] ?? foundMsgs["English"]!], "asked_reschedule_datetime");
|
||||||
|
} else {
|
||||||
|
const notFoundMsgs: Record<string, string> = {
|
||||||
|
English: "Ok. I could not find your current appointment. Please tell me when you'd like to reschedule and our receptionist will contact you as soon as possible.",
|
||||||
|
Spanish: "De acuerdo. No pude encontrar su cita actual. Por favor díganos cuándo le gustaría reprogramar y nuestra recepcionista le contactará lo antes posible.",
|
||||||
|
Portuguese: "Ok. Não consegui encontrar sua consulta atual. Por favor diga-nos quando você gostaria de reagendar e nossa recepcionista entrará em contato o mais breve possível.",
|
||||||
|
Mandarin: "好的。我找不到您当前的预约。请告诉我您希望重新安排的时间,我们的前台将尽快与您联系。",
|
||||||
|
Cantonese: "好的。我找不到您目前的預約。請告訴我您希望重新安排的時間,我們的接待員將盡快與您聯絡。",
|
||||||
|
Arabic: "حسناً. لم أجد موعدك الحالي. يرجى إخبارنا بالوقت الذي تريد إعادة الجدولة إليه وسيتصل بك موظف الاستقبال في أقرب وقت ممكن.",
|
||||||
|
"Haitian Creole": "Oke. Mwen pa t jwenn randevou aktyèl ou. Tanpri di nou ki lè ou ta renmen reprogramè epi resepsyonis nou an pral kontakte ou pi vit posib.",
|
||||||
|
};
|
||||||
|
return finish(patient.userId, patient.id, [notFoundMsgs[language] ?? notFoundMsgs["English"]!], "done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void isNew; // ambiguous or explicit "new" both fall through to the same prompt
|
||||||
|
const newApptMsgs: Record<string, string> = {
|
||||||
|
English: "No problem! To get started, are you an existing patient or a new patient?",
|
||||||
|
Spanish: "¡Sin problema! Para comenzar, ¿es usted un paciente existente o un paciente nuevo?",
|
||||||
|
Portuguese: "Sem problema! Para começar, você é um paciente existente ou um novo paciente?",
|
||||||
|
Mandarin: "没问题!请问您是现有患者还是新患者?",
|
||||||
|
Cantonese: "沒問題!請問您是現有病人還是新病人?",
|
||||||
|
Arabic: "لا مشكلة! للبدء، هل أنت مريض حالي أم مريض جديد؟",
|
||||||
|
"Haitian Creole": "Pa gen pwoblèm! Pou kòmanse, èske ou se yon pasyan egzistan oswa yon nouvo pasyan?",
|
||||||
|
};
|
||||||
|
return finish(patient.userId, patient.id, [newApptMsgs[language] ?? newApptMsgs["English"]!], "asked_new_or_existing");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stage: asked_appointment_time → parse date, check office hours ─────
|
||||||
|
if (stage === "asked_appointment_time") {
|
||||||
|
const parsedDate = await parseDateOnlyFromMessage(message, activeAi.key, activeAi.provider, activeAi.model);
|
||||||
|
if (!parsedDate) {
|
||||||
|
const msgs: Record<string, string> = {
|
||||||
|
English: "I didn't catch that. What day would you prefer? For example: 'May 28', 'next Monday', or '5/28'.",
|
||||||
|
Spanish: "No entendí. ¿Qué día prefiere? Por ejemplo: '28 de mayo', 'próximo lunes' o '28/5'.",
|
||||||
|
Portuguese: "Não entendi. Que dia você prefere? Por exemplo: '28 de maio', 'próxima segunda' ou '28/5'.",
|
||||||
|
Mandarin: "我没听清。您希望哪天?例如:'5月28日'、'下周一'或'5/28'。",
|
||||||
|
Cantonese: "我沒聽清。您希望哪天?例如:'5月28日'、'下週一'或'5/28'。",
|
||||||
|
Arabic: "لم أفهم. ما اليوم الذي تفضله؟ مثلاً: '28 مايو' أو '5/28'.",
|
||||||
|
"Haitian Creole": "Mwen pa konprann. Ki jou ou prefere? Pa egzanp: '28 me', 'Lendi pwochèn', oswa '5/28'.",
|
||||||
|
};
|
||||||
|
return finish(patient.userId, patient.id, [msgs[language] ?? msgs["English"]!], "asked_appointment_time");
|
||||||
|
}
|
||||||
|
const { date, dateLabel } = parsedDate;
|
||||||
|
const dayCheck = await isOfficeDayOpen(date, patient.userId);
|
||||||
|
if (!dayCheck.open) {
|
||||||
|
const msgs: Record<string, string> = {
|
||||||
|
English: `Our office is closed on ${dateLabel} (${dayCheck.displayDay}). Can you please choose another day?`,
|
||||||
|
Spanish: `Nuestra oficina está cerrada el ${dateLabel} (${dayCheck.displayDay}). ¿Puede elegir otro día?`,
|
||||||
|
Portuguese: `Nosso consultório está fechado em ${dateLabel} (${dayCheck.displayDay}). Pode escolher outro dia?`,
|
||||||
|
Mandarin: `我们诊所在 ${dateLabel}(${dayCheck.displayDay})不开放。请您选择另一天好吗?`,
|
||||||
|
Cantonese: `我們診所在 ${dateLabel}(${dayCheck.displayDay})不開放。請您選擇另一天好嗎?`,
|
||||||
|
Arabic: `مكتبنا مغلق في ${dateLabel} (${dayCheck.displayDay}). هل يمكنك اختيار يوم آخر؟`,
|
||||||
|
"Haitian Creole": `Biwo nou fèmen nan ${dateLabel} (${dayCheck.displayDay}). Tanpri chwazi yon lòt jou?`,
|
||||||
|
};
|
||||||
|
return finish(patient.userId, patient.id, [msgs[language] ?? msgs["English"]!], "asked_appointment_time");
|
||||||
|
}
|
||||||
|
setPendingReschedule(patient.userId, patient.id, { newDate: date, dayLabel: dateLabel });
|
||||||
|
const askTimeMsgs: Record<string, string> = {
|
||||||
|
English: `What time do you prefer on ${dateLabel}?`,
|
||||||
|
Spanish: `¿A qué hora prefiere el ${dateLabel}?`,
|
||||||
|
Portuguese: `Que horário você prefere em ${dateLabel}?`,
|
||||||
|
Mandarin: `您希望在 ${dateLabel} 几点?`,
|
||||||
|
Cantonese: `您希望在 ${dateLabel} 幾點?`,
|
||||||
|
Arabic: `ما الوقت الذي تفضله في ${dateLabel}؟`,
|
||||||
|
"Haitian Creole": `Ki lè ou prefere nan ${dateLabel}?`,
|
||||||
|
};
|
||||||
|
return finish(patient.userId, patient.id, [askTimeMsgs[language] ?? askTimeMsgs["English"]!], "asked_new_appt_time_for_date");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stage: asked_new_appt_time_for_date → parse time, check hours ──────
|
||||||
|
if (stage === "asked_new_appt_time_for_date") {
|
||||||
|
const pending = getPendingReschedule(patient.userId, patient.id);
|
||||||
|
if (!pending) {
|
||||||
|
return finish(patient.userId, patient.id, ["I lost track of the date. What day would you prefer?"], "asked_appointment_time");
|
||||||
|
}
|
||||||
|
const startTime = await parseTime(message, activeAi.key, activeAi.provider, activeAi.model);
|
||||||
|
if (!startTime) {
|
||||||
|
const msgs: Record<string, string> = {
|
||||||
|
English: `I didn't catch the time. What time do you prefer on ${pending.dayLabel}? For example: '10am' or '2pm'.`,
|
||||||
|
Spanish: `No entendí la hora. ¿Qué hora prefiere el ${pending.dayLabel}? Por ejemplo: '10am' o '2pm'.`,
|
||||||
|
Portuguese: `Não entendi o horário. Que hora você prefere em ${pending.dayLabel}? Por exemplo: '10h' ou '14h'.`,
|
||||||
|
Mandarin: `我没听清时间。您在 ${pending.dayLabel} 几点?例如:上午10点或下午2点。`,
|
||||||
|
Cantonese: `我沒聽清時間。您在 ${pending.dayLabel} 幾點?例如:上午10點或下午2點。`,
|
||||||
|
Arabic: `لم أفهم الوقت. ما الوقت الذي تفضله في ${pending.dayLabel}؟ مثلاً: 10 صباحاً أو 2 مساءً.`,
|
||||||
|
"Haitian Creole": `Mwen pa konprann lè a. Ki lè ou prefere nan ${pending.dayLabel}? Pa egzanp: 10am oswa 2pm.`,
|
||||||
|
};
|
||||||
|
return finish(patient.userId, patient.id, [msgs[language] ?? msgs["English"]!], "asked_new_appt_time_for_date");
|
||||||
|
}
|
||||||
|
const withinHours = await isWithinOfficeHours(pending.newDate, startTime, patient.userId);
|
||||||
|
if (!withinHours) {
|
||||||
|
const hoursDisplay = await getOfficeHoursDisplay(pending.newDate, patient.userId);
|
||||||
|
const timeLbl = timeLabel(startTime);
|
||||||
|
const msgs: Record<string, string> = {
|
||||||
|
English: hoursDisplay
|
||||||
|
? `Our office is not available at ${timeLbl} on ${pending.dayLabel}. Our hours are ${hoursDisplay}. What other time do you prefer?`
|
||||||
|
: `Our office is not available at ${timeLbl} on ${pending.dayLabel}. What other time do you prefer?`,
|
||||||
|
Spanish: hoursDisplay
|
||||||
|
? `Nuestra oficina no está disponible a las ${timeLbl} el ${pending.dayLabel}. Nuestro horario es ${hoursDisplay}. ¿Qué otro horario prefiere?`
|
||||||
|
: `Nuestra oficina no está disponible a las ${timeLbl} el ${pending.dayLabel}. ¿Qué otro horario prefiere?`,
|
||||||
|
Portuguese: hoursDisplay
|
||||||
|
? `Nosso consultório não está disponível às ${timeLbl} em ${pending.dayLabel}. Nosso horário é ${hoursDisplay}. Que outro horário você prefere?`
|
||||||
|
: `Nosso consultório não está disponível às ${timeLbl} em ${pending.dayLabel}. Que outro horário você prefere?`,
|
||||||
|
Mandarin: hoursDisplay
|
||||||
|
? `我们诊所在 ${pending.dayLabel} ${timeLbl} 不开放。工作时间是 ${hoursDisplay}。您希望改什么时间?`
|
||||||
|
: `我们诊所在 ${pending.dayLabel} ${timeLbl} 不开放。您希望改什么时间?`,
|
||||||
|
Cantonese: hoursDisplay
|
||||||
|
? `我們診所在 ${pending.dayLabel} ${timeLbl} 不開放。工作時間是 ${hoursDisplay}。您希望改什麼時間?`
|
||||||
|
: `我們診所在 ${pending.dayLabel} ${timeLbl} 不開放。您希望改什麼時間?`,
|
||||||
|
Arabic: hoursDisplay
|
||||||
|
? `مكتبنا غير متاح في ${timeLbl} يوم ${pending.dayLabel}. ساعات العمل: ${hoursDisplay}. ما وقت آخر تفضله؟`
|
||||||
|
: `مكتبنا غير متاح في ${timeLbl} يوم ${pending.dayLabel}. ما وقت آخر تفضله؟`,
|
||||||
|
"Haitian Creole": hoursDisplay
|
||||||
|
? `Biwo nou pa disponib a ${timeLbl} nan ${pending.dayLabel}. Orè nou se ${hoursDisplay}. Ki lòt lè ou prefere?`
|
||||||
|
: `Biwo nou pa disponib a ${timeLbl} nan ${pending.dayLabel}. Ki lòt lè ou prefere?`,
|
||||||
|
};
|
||||||
|
return finish(patient.userId, patient.id, [msgs[language] ?? msgs["English"]!], "asked_new_appt_time_for_date");
|
||||||
|
}
|
||||||
|
clearPendingReschedule(patient.userId, patient.id);
|
||||||
|
const apptLabel = `${pending.dayLabel} at ${timeLabel(startTime)}`;
|
||||||
|
|
||||||
|
const [nh, nm] = startTime.split(":").map(Number);
|
||||||
|
const endTotal = nh! * 60 + nm! + 60;
|
||||||
|
const endTime = `${String(Math.floor(endTotal / 60)).padStart(2, "0")}:${String(endTotal % 60).padStart(2, "0")}`;
|
||||||
|
|
||||||
|
const [patientRecord, firstStaff] = await Promise.all([
|
||||||
|
db.patient.findUnique({
|
||||||
|
where: { id: patient.id },
|
||||||
|
select: { firstName: true, lastName: true },
|
||||||
|
}),
|
||||||
|
db.staff.findFirst({
|
||||||
|
where: { userId: patient.userId },
|
||||||
|
orderBy: { id: "asc" },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (firstStaff) {
|
||||||
|
try {
|
||||||
|
await storage.createAppointment({
|
||||||
|
patientId: patient.id,
|
||||||
|
userId: patient.userId,
|
||||||
|
staffId: firstStaff.id,
|
||||||
|
title: `AI Scheduled - ${(patientRecord?.firstName ?? "") + " " + (patientRecord?.lastName ?? "")}`.trim(),
|
||||||
|
date: pending.newDate,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
type: "checkup",
|
||||||
|
status: "scheduled",
|
||||||
|
movedByAi: true,
|
||||||
|
} as any);
|
||||||
|
await sendAppointmentConfirmationSms(patient.userId, patient.id, pending.dayLabel, timeLabel(startTime), language);
|
||||||
|
} catch { /* silent — message still sent, staff can create manually */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmMsgs: Record<string, string> = {
|
||||||
|
English: `Thank you! Your preferred appointment at ${apptLabel} was scheduled. Our receptionist will confirm it with you shortly.`,
|
||||||
|
Spanish: `¡Gracias! Su cita preferida el ${apptLabel} fue programada. Nuestra recepcionista lo confirmará con usted en breve.`,
|
||||||
|
Portuguese: `Obrigado! Sua consulta preferida em ${apptLabel} foi agendada. Nossa recepcionista confirmará com você em breve.`,
|
||||||
|
Mandarin: `谢谢!您在 ${apptLabel} 的预约已安排。我们的前台将很快与您确认。`,
|
||||||
|
Cantonese: `多謝!您在 ${apptLabel} 的預約已安排。我們的接待員將很快與您確認。`,
|
||||||
|
Arabic: `شكراً! تم جدولة موعدك في ${apptLabel}. ستتواصل معك موظفة الاستقبال قريباً لتأكيده.`,
|
||||||
|
"Haitian Creole": `Mèsi! Randevou ou a nan ${apptLabel} pwograme. Resepsyonis nou an pral konfime li ak ou byento.`,
|
||||||
|
};
|
||||||
|
return finish(patient.userId, patient.id, [confirmMsgs[language] ?? confirmMsgs["English"]!], "done");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stage: new_patient_greeted + multi-step new patient stages ──────────
|
||||||
|
const newPatientStages: ConversationStage[] = [
|
||||||
|
"new_patient_greeted", "asked_new_or_existing",
|
||||||
|
"asked_new_patient_insurance", "asked_insurance_type",
|
||||||
|
"asked_masshealth_check_consent", "asked_existing_insurance",
|
||||||
|
"asked_appointment_preference",
|
||||||
|
"asked_self_pay", "asked_other_insurance_after_inactive",
|
||||||
|
"collecting_contact_info",
|
||||||
|
];
|
||||||
|
if (newPatientStages.includes(stage)) {
|
||||||
|
const { reply: aiReply, nextStage } = await runNewPatientStep(
|
||||||
|
message, stage, language, activeAi.key, chatTemplates.generalFallback, activeAi.provider, activeAi.model
|
||||||
|
);
|
||||||
|
return finish(patient.userId, patient.id, [aiReply], nextStage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stage: done → closing thank-you reply ────────────────────────────────
|
||||||
|
if (stage === "done") {
|
||||||
|
const isThanks = /\b(thank|thanks|thank you|ty|ok|okay|great|perfect|sounds good|got it|understood|alright|appreciate|wonderful|excellent|awesome|cool|nice|good)\b/i.test(message);
|
||||||
|
if (isThanks) {
|
||||||
|
const apptDatetime = await getAppointmentDatetime(patient.id);
|
||||||
|
const CLOSING: Record<string, string> = {
|
||||||
|
English: apptDatetime
|
||||||
|
? `Thank you for choosing our office! We look forward to seeing you on ${apptDatetime}.`
|
||||||
|
: `Thank you for choosing our office! We look forward to seeing you soon.`,
|
||||||
|
Spanish: apptDatetime
|
||||||
|
? `¡Gracias por elegirnos! Le esperamos el ${apptDatetime}.`
|
||||||
|
: `¡Gracias por elegirnos! Le esperamos pronto.`,
|
||||||
|
Portuguese: apptDatetime
|
||||||
|
? `Obrigado por nos escolher! Aguardamos sua visita em ${apptDatetime}.`
|
||||||
|
: `Obrigado por nos escolher! Aguardamos sua visita em breve.`,
|
||||||
|
Mandarin: apptDatetime
|
||||||
|
? `感谢您选择我们!期待在 ${apptDatetime} 见到您。`
|
||||||
|
: `感谢您选择我们!期待很快见到您。`,
|
||||||
|
Cantonese: apptDatetime
|
||||||
|
? `感謝您選擇我們!期待在 ${apptDatetime} 見到您。`
|
||||||
|
: `感謝您選擇我們!期待很快見到您。`,
|
||||||
|
Arabic: apptDatetime
|
||||||
|
? `شكراً لاختيارك عيادتنا! نتطلع إلى رؤيتك في ${apptDatetime}.`
|
||||||
|
: `شكراً لاختيارك عيادتنا! نتطلع إلى رؤيتك قريباً.`,
|
||||||
|
"Haitian Creole": apptDatetime
|
||||||
|
? `Mèsi dèske ou chwazi nou! N'ap tann ou ${apptDatetime}.`
|
||||||
|
: `Mèsi dèske ou chwazi nou! N'ap tann ou byento.`,
|
||||||
|
};
|
||||||
|
const fallback = CLOSING[language] ?? CLOSING["English"]!;
|
||||||
|
if (apptDatetime) {
|
||||||
|
try {
|
||||||
|
const llm = getLlm(activeAi.provider, activeAi.key, activeAi.model);
|
||||||
|
const res = await llm.invoke([
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: `You are a friendly dental office AI assistant. The patient just said "${message}" after completing a conversation. Reply warmly in ${language}, thanking them for choosing the office and reminding them of their upcoming appointment on ${apptDatetime}. 1-2 sentences, no formatting.`,
|
||||||
|
},
|
||||||
|
{ role: "user", content: message },
|
||||||
|
]);
|
||||||
|
const aiMsg = String(res.content).trim();
|
||||||
|
if (aiMsg) return finish(patient.userId, patient.id, [aiMsg], "done");
|
||||||
|
} catch { /* fall through to fallback */ }
|
||||||
|
}
|
||||||
|
return finish(patient.userId, patient.id, [fallback], "done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matching stage/branch — hold stage steady with no reply.
|
||||||
|
return { segments: [], nextStage: stage };
|
||||||
|
}
|
||||||
@@ -0,0 +1,486 @@
|
|||||||
|
import { StateGraph, END, START, Annotation } from "@langchain/langgraph";
|
||||||
|
import { getLlm, type AiProvider } from "./llm-factory";
|
||||||
|
import { prisma as db } from "@repo/db/client";
|
||||||
|
import { storage } from "../storage";
|
||||||
|
import {
|
||||||
|
type ConversationStage,
|
||||||
|
setPendingReschedule, getPendingReschedule, clearPendingReschedule,
|
||||||
|
} from "./aiHandoffStore";
|
||||||
|
import {
|
||||||
|
parseDateOnlyFromMessage, parseTime, isOfficeDayOpen, isWithinOfficeHours,
|
||||||
|
getOfficeHoursDisplay, timeLabel,
|
||||||
|
} from "./reschedule-graph";
|
||||||
|
import { sendAppointmentConfirmationSms } from "./confirmationSms";
|
||||||
|
|
||||||
|
const GraphState = Annotation.Root({
|
||||||
|
message: Annotation<string>(),
|
||||||
|
intent: Annotation<string>(),
|
||||||
|
reply: Annotation<string>(),
|
||||||
|
language: Annotation<string>(),
|
||||||
|
generalFallback: Annotation<string>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type GraphStateType = typeof GraphState.State;
|
||||||
|
|
||||||
|
// ── Intent classifier — multilingual yes/no keywords (shared shape with reminder-graph) ──
|
||||||
|
|
||||||
|
/** True if `text` mentions a concrete day/date/time (e.g. "Tuesday", "3/10", "2pm").
|
||||||
|
* Shared by classifyNode (a bare "yes" vs. "yes, Tuesday at 1pm" both count as
|
||||||
|
* intent "yes") and by the caller in patient-conversation-router.ts, which uses
|
||||||
|
* it to decide whether to attempt an immediate booking parse or just ask for a
|
||||||
|
* day/time first. */
|
||||||
|
export function hasDateOrTimeSignal(text: string): boolean {
|
||||||
|
return (
|
||||||
|
/\b(monday|tuesday|wednesday|thursday|friday|saturday|sunday|today|tomorrow|next week)\b/i.test(text) ||
|
||||||
|
/\b\d{1,2}[\/\-]\d{1,2}\b/.test(text) ||
|
||||||
|
/\b\d{1,2}(:\d{2})?\s*(am|pm|a\.m\.|p\.m\.)\b/i.test(text)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function classifyNode(state: GraphStateType) {
|
||||||
|
const text = state.message.toLowerCase().trim();
|
||||||
|
|
||||||
|
const yesPatterns = /\b(yes|yeah|yep|yup|sure|ok|okay|sounds good|i'?d like (that|to)|let'?s do it|book it|schedule it|i want (an? )?appointment|i want to (come in|schedule|book)|great|perfect|absolutely|definitely|sí|si|claro|por supuesto|de acuerdo|seguro|sim|com certeza|好的|可以|好|明白|نعم|حسنا|موافق|wi|dakò|oke)\b/;
|
||||||
|
const noPatterns = /\b(no|nope|not interested|not right now|no thanks|can't|cannot|won't|not now|not available|busy|sorry|unable|no puedo|no gracias|ahora no|não posso|agora não|não|占线|无法|不用了|不需要|لا|لا أستطيع|pa kapab|pa ka)\b/;
|
||||||
|
|
||||||
|
// A specific day/date/time, or an appointment-request phrase, is itself a
|
||||||
|
// strong signal the patient wants to book — real replies rarely say a bare
|
||||||
|
// "yes", they jump straight to "Tuesday at 1pm" or "I want an appointment".
|
||||||
|
const mentionsDateOrTime = hasDateOrTimeSignal(text);
|
||||||
|
const mentionsAppointment = /\b(appointment|schedule|book|come in|check.?up|cleaning)\b/i.test(text);
|
||||||
|
|
||||||
|
if (noPatterns.test(text) && !mentionsDateOrTime) return { intent: "no" };
|
||||||
|
if (yesPatterns.test(text) || mentionsDateOrTime || mentionsAppointment) return { intent: "yes" };
|
||||||
|
return { intent: "other" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function routeByIntent(state: GraphStateType): string {
|
||||||
|
if (state.intent === "yes") return "wantsBooking";
|
||||||
|
if (state.intent === "no") return "declines";
|
||||||
|
return "other";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Fallbacks ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const WANTS_BOOKING_FALLBACKS: Record<string, string> = {
|
||||||
|
English: "Great! What day and time work best for you?",
|
||||||
|
Spanish: "¡Genial! ¿Qué día y hora le vienen mejor?",
|
||||||
|
Portuguese: "Ótimo! Qual dia e horário são melhores para você?",
|
||||||
|
Mandarin: "太好了!您哪天、几点方便?",
|
||||||
|
Cantonese: "太好喇!您邊日、幾點方便?",
|
||||||
|
Arabic: "رائع! ما هو اليوم والوقت الأنسب لك؟",
|
||||||
|
"Haitian Creole": "Bon! Ki jou ak ki lè ki pi bon pou ou?",
|
||||||
|
};
|
||||||
|
|
||||||
|
const DECLINES_FALLBACKS: Record<string, string> = {
|
||||||
|
English: "No problem at all! Feel free to reach out whenever you're ready. Have a great day!",
|
||||||
|
Spanish: "¡No hay problema! No dude en contactarnos cuando esté listo. ¡Que tenga un buen día!",
|
||||||
|
Portuguese: "Sem problemas! Entre em contato sempre que estiver pronto. Tenha um ótimo dia!",
|
||||||
|
Mandarin: "没问题!您准备好了随时联系我们。祝您愉快!",
|
||||||
|
Cantonese: "冇問題!您準備好隨時聯絡我們。祝您愉快!",
|
||||||
|
Arabic: "لا مشكلة على الإطلاق! لا تتردد في التواصل معنا عندما تكون مستعداً. أتمنى لك يوماً سعيداً!",
|
||||||
|
"Haitian Creole": "Pa gen pwoblèm! Ou ka kontakte nou nenpòt lè ou pare. Pase yon bon jounen!",
|
||||||
|
};
|
||||||
|
|
||||||
|
const GENERAL_FALLBACKS: Record<string, string> = {
|
||||||
|
English: "Thank you for your message! Our office staff will be happy to assist you shortly.",
|
||||||
|
Spanish: "¡Gracias por su mensaje! El personal de nuestra oficina estará encantado de ayudarle en breve.",
|
||||||
|
Portuguese: "Obrigado pela sua mensagem! Nossa equipe terá prazer em ajudá-lo em breve.",
|
||||||
|
Mandarin: "感谢您的留言!我们的办公室工作人员将很快为您提供帮助。",
|
||||||
|
Cantonese: "感謝您的留言!我們的辦公室工作人員將很快為您提供幫助。",
|
||||||
|
Arabic: "شكراً على رسالتك! سيسعد فريق مكتبنا بمساعدتك قريباً.",
|
||||||
|
"Haitian Creole": "Mèsi pou mesaj ou! Ekip biwo nou an pral kontan ede ou byento.",
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── LangGraph nodes ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function wantsBookingNode(state: GraphStateType, config: any) {
|
||||||
|
const apiKey: string | undefined = config?.configurable?.apiKey;
|
||||||
|
const lang = state.language || "English";
|
||||||
|
const fallback = WANTS_BOOKING_FALLBACKS[lang] ?? WANTS_BOOKING_FALLBACKS["English"]!;
|
||||||
|
|
||||||
|
if (!apiKey) return { reply: fallback };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const provider: AiProvider = config?.configurable?.provider ?? "google";
|
||||||
|
const model: string | undefined = config?.configurable?.model;
|
||||||
|
const llm = getLlm(provider, apiKey, model);
|
||||||
|
const response = await llm.invoke([
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content:
|
||||||
|
`You are a friendly dental office assistant. The patient agreed to come in for a routine exam and cleaning. Write a short, warm reply (1-2 sentences max) and ask what day AND time work best for them. Do NOT introduce yourself or say your name — the introduction has already been sent in a separate message. You MUST reply in ${lang}. No formatting, no extra text.`,
|
||||||
|
},
|
||||||
|
{ role: "user", content: `Patient replied: "${state.message}"` },
|
||||||
|
]);
|
||||||
|
return { reply: String(response.content) || fallback };
|
||||||
|
} catch {
|
||||||
|
return { reply: fallback };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function declinesNode(state: GraphStateType, config: any) {
|
||||||
|
const apiKey: string | undefined = config?.configurable?.apiKey;
|
||||||
|
const lang = state.language || "English";
|
||||||
|
const fallback = DECLINES_FALLBACKS[lang] ?? DECLINES_FALLBACKS["English"]!;
|
||||||
|
|
||||||
|
if (!apiKey) return { reply: fallback };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const provider: AiProvider = config?.configurable?.provider ?? "google";
|
||||||
|
const model: string | undefined = config?.configurable?.model;
|
||||||
|
const llm = getLlm(provider, apiKey, model);
|
||||||
|
const response = await llm.invoke([
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content:
|
||||||
|
`You are a friendly dental office assistant. The patient does not want to schedule a checkup right now. Write a short, polite reply (1-2 sentences max) letting them know that's no problem and they can reach out whenever they're ready. Do NOT introduce yourself or say your name. You MUST reply in ${lang}. No formatting, no extra text.`,
|
||||||
|
},
|
||||||
|
{ role: "user", content: `Patient replied: "${state.message}"` },
|
||||||
|
]);
|
||||||
|
return { reply: String(response.content) || fallback };
|
||||||
|
} catch {
|
||||||
|
return { reply: fallback };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function otherNode(state: GraphStateType, config: any) {
|
||||||
|
const apiKey: string | undefined = config?.configurable?.apiKey;
|
||||||
|
const lang = state.language || "English";
|
||||||
|
const fallback = state.generalFallback || (GENERAL_FALLBACKS[lang] ?? GENERAL_FALLBACKS["English"]!);
|
||||||
|
|
||||||
|
if (!apiKey) return { reply: fallback };
|
||||||
|
try {
|
||||||
|
const provider: AiProvider = config?.configurable?.provider ?? "google";
|
||||||
|
const model: string | undefined = config?.configurable?.model;
|
||||||
|
const llm = getLlm(provider, apiKey, model);
|
||||||
|
const response = await llm.invoke([
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: `You are a friendly dental office AI assistant. Respond helpfully to the patient's message in ${lang}. Keep it to 1-2 sentences, no formatting. For non-dental questions, let them know our office staff can assist.`,
|
||||||
|
},
|
||||||
|
{ role: "user", content: `Patient said: "${state.message}"` },
|
||||||
|
]);
|
||||||
|
return { reply: String(response.content) || fallback };
|
||||||
|
} catch {
|
||||||
|
return { reply: fallback };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Graph ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const graph = new StateGraph(GraphState)
|
||||||
|
.addNode("classify", classifyNode)
|
||||||
|
.addNode("wantsBooking", wantsBookingNode)
|
||||||
|
.addNode("declines", declinesNode)
|
||||||
|
.addNode("other", otherNode)
|
||||||
|
.addEdge(START, "classify")
|
||||||
|
.addConditionalEdges("classify", routeByIntent, {
|
||||||
|
wantsBooking: "wantsBooking",
|
||||||
|
declines: "declines",
|
||||||
|
other: "other",
|
||||||
|
})
|
||||||
|
.addEdge("wantsBooking", END)
|
||||||
|
.addEdge("declines", END)
|
||||||
|
.addEdge("other", END)
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
export async function runRecallGraph(
|
||||||
|
patientMessage: string,
|
||||||
|
apiKey: string,
|
||||||
|
language = "English",
|
||||||
|
generalFallback = "",
|
||||||
|
provider: AiProvider = "google",
|
||||||
|
model?: string
|
||||||
|
): Promise<{ reply: string | null; intent: string | null }> {
|
||||||
|
const result = await graph.invoke(
|
||||||
|
{ message: patientMessage, intent: "", reply: "", language, generalFallback },
|
||||||
|
{ configurable: { apiKey, provider, model } }
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
reply: result.reply || null,
|
||||||
|
intent: result.intent || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Recall booking — actually reserves a slot, unlike the reschedule flow ──
|
||||||
|
// (which only MOVES an existing appointment). A recall patient has none, so
|
||||||
|
// this creates a new one directly on "Column A" (the office's first staff
|
||||||
|
// column, matching the convention already used in internal-chat-workflow.ts),
|
||||||
|
// in fixed 30-minute slots.
|
||||||
|
|
||||||
|
const RECALL_SLOT_MINUTES = 30;
|
||||||
|
|
||||||
|
function toMinutes(t: string): number {
|
||||||
|
const [h, m] = t.split(":").map(Number);
|
||||||
|
return (h ?? 0) * 60 + (m ?? 0);
|
||||||
|
}
|
||||||
|
function fromMinutes(m: number): string {
|
||||||
|
return `${String(Math.floor(m / 60)).padStart(2, "0")}:${String(m % 60).padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** "Column A" = the office's first staff record by id, per the same convention
|
||||||
|
* used for AI-booked appointments elsewhere (see internal-chat-workflow.ts). */
|
||||||
|
async function getColumnAStaffId(userId: number): Promise<number | null> {
|
||||||
|
const staff = await db.staff.findFirst({ where: { userId }, orderBy: { id: "asc" } });
|
||||||
|
return staff?.id ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a free RECALL_SLOT_MINUTES slot for `staffId` on `date`. Prefers
|
||||||
|
* `requestedStartTime` if it's free; otherwise falls back to the earliest
|
||||||
|
* open slot that day. Returns null if the whole day is fully booked.
|
||||||
|
*/
|
||||||
|
async function findRecallSlot(
|
||||||
|
userId: number, staffId: number, date: Date, requestedStartTime?: string | null,
|
||||||
|
): Promise<{ time: string; matchedRequest: boolean } | null> {
|
||||||
|
const officeHours = await storage.getOfficeHours(userId);
|
||||||
|
const data = (officeHours as any)?.data;
|
||||||
|
const dayNames = ["sunday","monday","tuesday","wednesday","thursday","friday","saturday"];
|
||||||
|
const dayName = dayNames[date.getUTCDay()]!;
|
||||||
|
|
||||||
|
// Union of doctors' + hygienists' windows that day — matches the "open if
|
||||||
|
// within either group's hours" tolerance isWithinOfficeHours() already uses.
|
||||||
|
const windows: [number, number][] = [];
|
||||||
|
for (const group of [data?.doctors, data?.hygienists]) {
|
||||||
|
const slot = group?.[dayName];
|
||||||
|
if (slot?.enabled) {
|
||||||
|
windows.push([toMinutes(slot.amStart), toMinutes(slot.amEnd)]);
|
||||||
|
windows.push([toMinutes(slot.pmStart), toMinutes(slot.pmEnd)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (windows.length === 0) {
|
||||||
|
windows.push([toMinutes("09:00"), toMinutes("12:00")], [toMinutes("13:00"), toMinutes("17:00")]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allSlots = new Set<number>();
|
||||||
|
for (const [start, end] of windows) {
|
||||||
|
for (let t = start; t + RECALL_SLOT_MINUTES <= end; t += RECALL_SLOT_MINUTES) allSlots.add(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateStr = date.toISOString().split("T")[0]!;
|
||||||
|
const existing = await storage.getAppointmentsByDateForUser(dateStr, userId);
|
||||||
|
const booked = existing
|
||||||
|
.filter((a: any) => a.staffId === staffId && a.status !== "cancelled")
|
||||||
|
.map((a: any) => ({ start: toMinutes(a.startTime), end: toMinutes(a.endTime) }));
|
||||||
|
|
||||||
|
const isFree = (slotStart: number) => {
|
||||||
|
const slotEnd = slotStart + RECALL_SLOT_MINUTES;
|
||||||
|
return !booked.some((b) => slotStart < b.end && slotEnd > b.start);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (requestedStartTime) {
|
||||||
|
const reqStart = toMinutes(requestedStartTime);
|
||||||
|
if (allSlots.has(reqStart) && isFree(reqStart)) return { time: requestedStartTime, matchedRequest: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = Array.from(allSlots).sort((a, b) => a - b);
|
||||||
|
const fallback = sorted.find(isFree);
|
||||||
|
return fallback === undefined ? null : { time: fromMinutes(fallback), matchedRequest: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const DAY_FULL_FALLBACKS: Record<string, (dateLabel: string) => string> = {
|
||||||
|
English: (d) => `I'm sorry, ${d} is fully booked. Could you please choose another day?`,
|
||||||
|
Spanish: (d) => `Lo sentimos, el ${d} ya está completo. ¿Podría elegir otro día?`,
|
||||||
|
Portuguese: (d) => `Sinto muito, ${d} está totalmente reservado. Poderia escolher outro dia?`,
|
||||||
|
Mandarin: (d) => `很抱歉,${d} 的预约已满。您能选择其他日期吗?`,
|
||||||
|
Cantonese: (d) => `好抱歉,${d} 已經約滿。您可以選擇其他日子嗎?`,
|
||||||
|
Arabic: (d) => `عذراً، ${d} محجوز بالكامل. هل يمكنك اختيار يوم آخر؟`,
|
||||||
|
"Haitian Creole": (d) => `Padon, ${d} konplè. Èske ou ka chwazi yon lòt jou?`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const NO_STAFF_FALLBACK: Record<string, string> = {
|
||||||
|
English: "Thanks! Our office staff will reach out shortly to confirm a time that works.",
|
||||||
|
Spanish: "¡Gracias! Nuestro personal se pondrá en contacto en breve para confirmar un horario.",
|
||||||
|
Portuguese: "Obrigado! Nossa equipe entrará em contato em breve para confirmar um horário.",
|
||||||
|
Mandarin: "谢谢!我们的工作人员将很快与您联系以确认时间。",
|
||||||
|
Cantonese: "多謝!我們的職員好快會聯絡您確認時間。",
|
||||||
|
Arabic: "شكراً! سيتواصل معك فريقنا قريباً لتأكيد الموعد.",
|
||||||
|
"Haitian Creole": "Mèsi! Ekip biwo nou an pral kontakte ou byento pou konfime yon lè.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const BOOKED_EXACT_FALLBACKS: Record<string, (dateLabel: string, time: string) => string> = {
|
||||||
|
English: (d, t) => `You're all set for ${d} at ${t}! We look forward to seeing you.`,
|
||||||
|
Spanish: (d, t) => `¡Todo listo para el ${d} a las ${t}! Le esperamos.`,
|
||||||
|
Portuguese: (d, t) => `Tudo pronto para ${d} às ${t}! Aguardamos sua visita.`,
|
||||||
|
Mandarin: (d, t) => `已为您安排在 ${d} ${t}!期待您的光临。`,
|
||||||
|
Cantonese: (d, t) => `已幫您安排喺 ${d} ${t}!期待見到您。`,
|
||||||
|
Arabic: (d, t) => `تم الحجز في ${d} الساعة ${t}! نتطلع لرؤيتك.`,
|
||||||
|
"Haitian Creole": (d, t) => `Ou pare pou ${d} a ${t}! N'ap tann ou.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const BOOKED_ALT_FALLBACKS: Record<string, (dateLabel: string, time: string) => string> = {
|
||||||
|
English: (d, t) => `That exact time was just taken, so I booked you for ${t} on ${d} instead. We look forward to seeing you!`,
|
||||||
|
Spanish: (d, t) => `Esa hora ya no estaba disponible, así que le reservé a las ${t} el ${d}. ¡Le esperamos!`,
|
||||||
|
Portuguese: (d, t) => `Esse horário exato já foi reservado, então marquei ${t} em ${d}. Aguardamos sua visita!`,
|
||||||
|
Mandarin: (d, t) => `那个时间刚被预订了,我已为您安排在 ${d} ${t}。期待您的光临!`,
|
||||||
|
Cantonese: (d, t) => `嗰個時間啱啱被人訂咗,我已經幫您安排喺 ${d} ${t}。期待見到您!`,
|
||||||
|
Arabic: (d, t) => `تم حجز ذلك الوقت للتو، لذا حجزت لك موعداً في ${d} الساعة ${t}. نتطلع لرؤيتك!`,
|
||||||
|
"Haitian Creole": (d, t) => `Lè sa a fèk pran, kidonk mwen bwouke ou pou ${t} nan ${d}. N'ap tann ou!`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ASK_TIME_FALLBACKS: Record<string, (dateLabel: string) => string> = {
|
||||||
|
English: (d) => `Great, what time on ${d} works for you?`,
|
||||||
|
Spanish: (d) => `Genial, ¿qué hora le viene bien el ${d}?`,
|
||||||
|
Portuguese: (d) => `Ótimo, que horário funciona para você em ${d}?`,
|
||||||
|
Mandarin: (d) => `太好了,您 ${d} 哪个时间方便?`,
|
||||||
|
Cantonese: (d) => `太好喇,您 ${d} 邊個時間方便?`,
|
||||||
|
Arabic: (d) => `رائع، ما الوقت المناسب لك في ${d}؟`,
|
||||||
|
"Haitian Creole": (d) => `Bon, ki lè nan ${d} ki bon pou ou?`,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Same follow-up question as ASK_TIME_FALLBACKS, without the leading "Great,"
|
||||||
|
// greeting — used when it directly follows a rejection sentence, where the
|
||||||
|
// greeting would read as redundant filler.
|
||||||
|
const ASK_TIME_PLAIN_FALLBACKS: Record<string, (dateLabel: string) => string> = {
|
||||||
|
English: (d) => `What time on ${d} works for you?`,
|
||||||
|
Spanish: (d) => `¿Qué hora le viene bien el ${d}?`,
|
||||||
|
Portuguese: (d) => `Que horário funciona para você em ${d}?`,
|
||||||
|
Mandarin: (d) => `您 ${d} 哪个时间方便?`,
|
||||||
|
Cantonese: (d) => `您 ${d} 邊個時間方便?`,
|
||||||
|
Arabic: (d) => `ما الوقت المناسب لك في ${d}؟`,
|
||||||
|
"Haitian Creole": (d) => `Ki lè nan ${d} ki bon pou ou?`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const OFFICE_HOURS_ARE_FALLBACKS: Record<string, string> = {
|
||||||
|
English: "Our office hours are",
|
||||||
|
Spanish: "Nuestro horario de atención es",
|
||||||
|
Portuguese: "Nosso horário de atendimento é",
|
||||||
|
Mandarin: "我们的营业时间是",
|
||||||
|
Cantonese: "我們嘅營業時間係",
|
||||||
|
Arabic: "ساعات عملنا هي",
|
||||||
|
"Haitian Creole": "Orè biwo nou se",
|
||||||
|
};
|
||||||
|
|
||||||
|
const CLOSED_DAY_FALLBACKS: Record<string, (day: string) => string> = {
|
||||||
|
English: (day) => `We're closed on ${day}s. What other day works for you?`,
|
||||||
|
Spanish: (day) => `Estamos cerrados los ${day}. ¿Qué otro día le viene bien?`,
|
||||||
|
Portuguese: (day) => `Estamos fechados aos ${day}. Que outro dia funciona para você?`,
|
||||||
|
Mandarin: (day) => `我们${day}休息。您方便其他哪天?`,
|
||||||
|
Cantonese: (day) => `我們${day}休息。您方便其他邊日?`,
|
||||||
|
Arabic: (day) => `نحن مغلقون أيام ${day}. ما يوم آخر يناسبك؟`,
|
||||||
|
"Haitian Creole": (day) => `Nou fèmen jou ${day}. Ki lòt jou ki bon pou ou?`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const NO_DATE_FALLBACKS: Record<string, string> = {
|
||||||
|
English: "Sorry, I didn't catch a day — what day would you like to come in?",
|
||||||
|
Spanish: "Disculpe, no entendí el día — ¿qué día le gustaría venir?",
|
||||||
|
Portuguese: "Desculpe, não entendi o dia — que dia você gostaria de vir?",
|
||||||
|
Mandarin: "抱歉,我没听清日期——您想哪天过来?",
|
||||||
|
Cantonese: "抱歉,我冇聽清楚日期——您想邊日嚟?",
|
||||||
|
Arabic: "عذراً، لم أفهم اليوم — ما اليوم الذي تود الحضور فيه؟",
|
||||||
|
"Haitian Creole": "Padon, mwen pa konprann jou a — ki jou ou ta renmen vini?",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Books the recall appointment (or reports the day is full) and returns the reply + next stage. */
|
||||||
|
async function bookRecallSlot(
|
||||||
|
patientId: number, userId: number, date: Date, dateLabel: string, requestedStartTime: string | null, lang: string,
|
||||||
|
): Promise<{ reply: string; nextStage: ConversationStage }> {
|
||||||
|
const staffId = await getColumnAStaffId(userId);
|
||||||
|
if (staffId === null) {
|
||||||
|
return { reply: NO_STAFF_FALLBACK[lang] ?? NO_STAFF_FALLBACK["English"]!, nextStage: "done" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const slot = await findRecallSlot(userId, staffId, date, requestedStartTime);
|
||||||
|
if (!slot) {
|
||||||
|
const build = DAY_FULL_FALLBACKS[lang] ?? DAY_FULL_FALLBACKS["English"]!;
|
||||||
|
return { reply: build(dateLabel), nextStage: "recall_asked_datetime" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = fromMinutes(toMinutes(slot.time) + RECALL_SLOT_MINUTES);
|
||||||
|
await storage.createAppointment({
|
||||||
|
patientId, userId, staffId,
|
||||||
|
title: "Routine Exam & Cleaning",
|
||||||
|
date, startTime: slot.time, endTime,
|
||||||
|
type: "recall",
|
||||||
|
status: "scheduled",
|
||||||
|
movedByAi: true,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
const timeLbl = timeLabel(slot.time);
|
||||||
|
const build = slot.matchedRequest
|
||||||
|
? (BOOKED_EXACT_FALLBACKS[lang] ?? BOOKED_EXACT_FALLBACKS["English"]!)
|
||||||
|
: (BOOKED_ALT_FALLBACKS[lang] ?? BOOKED_ALT_FALLBACKS["English"]!);
|
||||||
|
await sendAppointmentConfirmationSms(userId, patientId, dateLabel, timeLbl, lang);
|
||||||
|
return { reply: build(dateLabel, timeLbl), nextStage: "done" };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drives the recall_asked_datetime / recall_asked_time_for_date stages:
|
||||||
|
* parses a date (and optionally a time) out of the patient's reply, checks
|
||||||
|
* office hours, then books via bookRecallSlot() once both pieces are known.
|
||||||
|
*/
|
||||||
|
export async function runRecallBookingStep(
|
||||||
|
message: string,
|
||||||
|
stage: ConversationStage,
|
||||||
|
language: string,
|
||||||
|
patientId: number,
|
||||||
|
userId: number,
|
||||||
|
apiKey: string,
|
||||||
|
provider: AiProvider = "google",
|
||||||
|
model?: string
|
||||||
|
): Promise<{ reply: string; nextStage: ConversationStage }> {
|
||||||
|
const lang = language || "English";
|
||||||
|
|
||||||
|
// ── Waiting on a time only (date was already captured last turn) ─────────
|
||||||
|
if (stage === "recall_asked_time_for_date") {
|
||||||
|
const pending = getPendingReschedule(userId, patientId);
|
||||||
|
if (!pending) {
|
||||||
|
return { reply: NO_DATE_FALLBACKS[lang] ?? NO_DATE_FALLBACKS["English"]!, nextStage: "recall_asked_datetime" };
|
||||||
|
}
|
||||||
|
const time = await parseTime(message, apiKey, provider, model);
|
||||||
|
if (!time) {
|
||||||
|
const build = ASK_TIME_FALLBACKS[lang] ?? ASK_TIME_FALLBACKS["English"]!;
|
||||||
|
return { reply: build(pending.dayLabel), nextStage: "recall_asked_time_for_date" };
|
||||||
|
}
|
||||||
|
return await resolveDateTime(patientId, userId, pending.newDate, pending.dayLabel, time, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── recall_initial (yes, with a date already in the same message) or
|
||||||
|
// recall_asked_datetime — parse a date out of the message ────────────────
|
||||||
|
const parsedDate = await parseDateOnlyFromMessage(message, apiKey, provider, model);
|
||||||
|
if (!parsedDate) {
|
||||||
|
return { reply: NO_DATE_FALLBACKS[lang] ?? NO_DATE_FALLBACKS["English"]!, nextStage: "recall_asked_datetime" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const dayCheck = await isOfficeDayOpen(parsedDate.date, userId);
|
||||||
|
if (!dayCheck.open) {
|
||||||
|
const build = CLOSED_DAY_FALLBACKS[lang] ?? CLOSED_DAY_FALLBACKS["English"]!;
|
||||||
|
return { reply: build(dayCheck.displayDay), nextStage: "recall_asked_datetime" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const time = await parseTime(message, apiKey, provider, model);
|
||||||
|
if (!time) {
|
||||||
|
setPendingReschedule(userId, patientId, { newDate: parsedDate.date, dayLabel: parsedDate.dateLabel });
|
||||||
|
const build = ASK_TIME_FALLBACKS[lang] ?? ASK_TIME_FALLBACKS["English"]!;
|
||||||
|
return { reply: build(parsedDate.dateLabel), nextStage: "recall_asked_time_for_date" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return await resolveDateTime(patientId, userId, parsedDate.date, parsedDate.dateLabel, time, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveDateTime(
|
||||||
|
patientId: number, userId: number, date: Date, dateLabel: string, time: string, lang: string,
|
||||||
|
): Promise<{ reply: string; nextStage: ConversationStage }> {
|
||||||
|
const withinHours = await isWithinOfficeHours(date, time, userId);
|
||||||
|
if (!withinHours) {
|
||||||
|
const hoursDisplay = await getOfficeHoursDisplay(date, userId);
|
||||||
|
setPendingReschedule(userId, patientId, { newDate: date, dayLabel: dateLabel });
|
||||||
|
const base: Record<string, string> = {
|
||||||
|
English: `We're not available at that time on ${dateLabel}.`,
|
||||||
|
Spanish: `No estamos disponibles a esa hora el ${dateLabel}.`,
|
||||||
|
Portuguese: `Não estamos disponíveis nesse horário em ${dateLabel}.`,
|
||||||
|
Mandarin: `我们在 ${dateLabel} 的那个时间不营业。`,
|
||||||
|
Cantonese: `我們喺 ${dateLabel} 嗰個時間唔開放。`,
|
||||||
|
Arabic: `نحن غير متاحين في ذلك الوقت يوم ${dateLabel}.`,
|
||||||
|
"Haitian Creole": `Nou pa disponib nan lè sa a nan ${dateLabel}.`,
|
||||||
|
};
|
||||||
|
const askTime = (ASK_TIME_PLAIN_FALLBACKS[lang] ?? ASK_TIME_PLAIN_FALLBACKS["English"]!)(dateLabel);
|
||||||
|
const officeHoursAre = OFFICE_HOURS_ARE_FALLBACKS[lang] ?? OFFICE_HOURS_ARE_FALLBACKS["English"]!;
|
||||||
|
const hours = hoursDisplay ? ` ${officeHoursAre} ${hoursDisplay}.` : "";
|
||||||
|
return { reply: `${base[lang] ?? base["English"]}${hours} ${askTime}`, nextStage: "recall_asked_time_for_date" };
|
||||||
|
}
|
||||||
|
|
||||||
|
clearPendingReschedule(userId, patientId);
|
||||||
|
return await bookRecallSlot(patientId, userId, date, dateLabel, time, lang);
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
getPendingReschedule,
|
getPendingReschedule,
|
||||||
clearPendingReschedule,
|
clearPendingReschedule,
|
||||||
} from "./aiHandoffStore";
|
} from "./aiHandoffStore";
|
||||||
|
import { sendAppointmentConfirmationSms } from "./confirmationSms";
|
||||||
|
|
||||||
// ── Date helpers ──────────────────────────────────────────────────────────────
|
// ── Date helpers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -392,18 +393,35 @@ async function isSlotAvailable(
|
|||||||
// ── Time parsing (legacy) ─────────────────────────────────────────────────────
|
// ── Time parsing (legacy) ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function parseTime(message: string, apiKey: string, provider: AiProvider = "google", model?: string): Promise<string | null> {
|
export async function parseTime(message: string, apiKey: string, provider: AiProvider = "google", model?: string): Promise<string | null> {
|
||||||
const t = message.toLowerCase();
|
// Twilio speech-to-text usually renders "pm"/"am" with periods ("p.m."),
|
||||||
|
// and often drops the colon in a spoken time too ("1:00pm" -> "100 pm",
|
||||||
|
// "12:30pm" -> "1230 pm") — normalize both before matching.
|
||||||
|
const t = message.toLowerCase()
|
||||||
|
.replace(/\ba\.m\.?/g, "am")
|
||||||
|
.replace(/\bp\.m\.?/g, "pm");
|
||||||
if (/\bmorning\b|mañana|manhã|上午|صباح|maten/i.test(t)) return "09:00";
|
if (/\bmorning\b|mañana|manhã|上午|صباح|maten/i.test(t)) return "09:00";
|
||||||
if (/\bafternoon\b|tarde|après-midi|下午|مساء|aprèmidi/i.test(t)) return "13:00";
|
if (/\bafternoon\b|tarde|après-midi|下午|مساء|aprèmidi/i.test(t)) return "13:00";
|
||||||
const ampm = t.match(/\b(\d{1,2})(?::(\d{2}))?\s*(am|pm)\b/);
|
const ampm = t.match(/\b(\d{1,2}):([0-5]\d)\s*(am|pm)\b/);
|
||||||
|
const glued = !ampm ? t.match(/\b(\d{1,4})\s*(am|pm)\b/) : null;
|
||||||
const clock = t.match(/\b([01]?\d|2[0-3]):([0-5]\d)\b/);
|
const clock = t.match(/\b([01]?\d|2[0-3]):([0-5]\d)\b/);
|
||||||
if (ampm) {
|
if (ampm) {
|
||||||
let h = parseInt(ampm[1]!);
|
let h = parseInt(ampm[1]!);
|
||||||
const m = ampm[2] ? parseInt(ampm[2]) : 0;
|
const m = parseInt(ampm[2]!);
|
||||||
if (ampm[3] === "pm" && h < 12) h += 12;
|
if (ampm[3] === "pm" && h < 12) h += 12;
|
||||||
if (ampm[3] === "am" && h === 12) h = 0;
|
if (ampm[3] === "am" && h === 12) h = 0;
|
||||||
return `${String(h).padStart(2,"0")}:${String(m).padStart(2,"0")}`;
|
return `${String(h).padStart(2,"0")}:${String(m).padStart(2,"0")}`;
|
||||||
}
|
}
|
||||||
|
if (glued) {
|
||||||
|
const digits = glued[1]!;
|
||||||
|
let h: number, m: number;
|
||||||
|
if (digits.length <= 2) { h = parseInt(digits); m = 0; }
|
||||||
|
else { h = parseInt(digits.slice(0, -2)); m = parseInt(digits.slice(-2)); }
|
||||||
|
if (h >= 1 && h <= 12 && m <= 59) {
|
||||||
|
if (glued[2] === "pm" && h < 12) h += 12;
|
||||||
|
if (glued[2] === "am" && h === 12) h = 0;
|
||||||
|
return `${String(h).padStart(2,"0")}:${String(m).padStart(2,"0")}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (clock) return clock[0]!;
|
if (clock) return clock[0]!;
|
||||||
try {
|
try {
|
||||||
const llm = getLlm(provider, apiKey, model);
|
const llm = getLlm(provider, apiKey, model);
|
||||||
@@ -820,6 +838,8 @@ export async function runRescheduleStep(
|
|||||||
fallback,
|
fallback,
|
||||||
apiKey, provider, model
|
apiKey, provider, model
|
||||||
);
|
);
|
||||||
|
const datePart = displayLabel.split(" at ")[0] ?? displayLabel;
|
||||||
|
await sendAppointmentConfirmationSms(userId, patientId, datePart, timeLabel(startTime), lang);
|
||||||
return { reply, nextStage: "done" };
|
return { reply, nextStage: "done" };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1109,6 +1129,7 @@ export async function runRescheduleStep(
|
|||||||
`You are a friendly dental office assistant. The patient's appointment has been rescheduled to ${apptLabel}. Confirm in ${lang}: say the appointment is moved to ${apptLabel} and that our dental receptionist will confirm it with them tomorrow. 2 sentences, no formatting.`,
|
`You are a friendly dental office assistant. The patient's appointment has been rescheduled to ${apptLabel}. Confirm in ${lang}: say the appointment is moved to ${apptLabel} and that our dental receptionist will confirm it with them tomorrow. 2 sentences, no formatting.`,
|
||||||
`Appointment moved to ${apptLabel}.`, fallback, apiKey, provider, model
|
`Appointment moved to ${apptLabel}.`, fallback, apiKey, provider, model
|
||||||
);
|
);
|
||||||
|
await sendAppointmentConfirmationSms(userId, patientId, pending.dayLabel, timeLabel(startTime), lang);
|
||||||
return { reply, nextStage: "done" };
|
return { reply, nextStage: "done" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const CLOSING_PATTERNS =
|
||||||
|
/\b(bye|goodbye|good bye|that'?s all|nothing else|no thanks|no thank you|i'?m good|that'?s it|hang up)\b/i;
|
||||||
|
|
||||||
|
export function soundsLikeGoodbye(text: string): boolean {
|
||||||
|
return CLOSING_PATTERNS.test(text.trim());
|
||||||
|
}
|
||||||
@@ -34,6 +34,8 @@ export interface CmspEligibilityHistoryRemainingProcessorResult {
|
|||||||
historyPdfFilename?: string | null;
|
historyPdfFilename?: string | null;
|
||||||
accumulatorPdfFileId?: number | string | null;
|
accumulatorPdfFileId?: number | string | null;
|
||||||
accumulatorPdfFilename?: string | null;
|
accumulatorPdfFilename?: string | null;
|
||||||
|
remainingBenefit?: string | null;
|
||||||
|
thirdPartyInsurance?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runCmspEligibilityHistoryRemainingProcessor(
|
export async function runCmspEligibilityHistoryRemainingProcessor(
|
||||||
@@ -105,8 +107,21 @@ export async function runCmspEligibilityHistoryRemainingProcessor(
|
|||||||
|
|
||||||
const updates: any = { status: newStatus };
|
const updates: any = { status: newStatus };
|
||||||
if (seleniumInsurance) updates.insuranceProvider = seleniumInsurance;
|
if (seleniumInsurance) updates.insuranceProvider = seleniumInsurance;
|
||||||
|
|
||||||
|
const remainingBenefit = seleniumResult.remaining_amount?.trim() || null;
|
||||||
|
if (remainingBenefit) updates.remainingBenefit = remainingBenefit;
|
||||||
|
|
||||||
|
// third_party_insurance is present (even as null) whenever the Third Party
|
||||||
|
// Liability table was found — null means the member has no 2nd insurance,
|
||||||
|
// so we still write it to clear any stale value from a prior check.
|
||||||
|
if (Object.prototype.hasOwnProperty.call(seleniumResult, "third_party_insurance")) {
|
||||||
|
updates.thirdPartyInsurance = seleniumResult.third_party_insurance?.trim() || null;
|
||||||
|
}
|
||||||
|
|
||||||
await storage.updatePatient(patient.id, updates);
|
await storage.updatePatient(patient.id, updates);
|
||||||
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
||||||
|
outputResult.remainingBenefit = remainingBenefit;
|
||||||
|
outputResult.thirdPartyInsurance = updates.thirdPartyInsurance ?? null;
|
||||||
|
|
||||||
// Helper: save a single PDF to the patient's Cloud Storage Eligibility folder
|
// Helper: save a single PDF to the patient's Cloud Storage Eligibility folder
|
||||||
const patientName = `${patient!.firstName ?? ""} ${patient!.lastName ?? ""}`.trim() || `Patient ${patient!.id}`;
|
const patientName = `${patient!.firstName ?? ""} ${patient!.lastName ?? ""}`.trim() || `Patient ${patient!.id}`;
|
||||||
|
|||||||
@@ -138,8 +138,9 @@ async function processDdmaResult(
|
|||||||
|
|
||||||
// 4) Determine and update eligibility status + insurance provider name
|
// 4) Determine and update eligibility status + insurance provider name
|
||||||
const eligStatus = (seleniumResult?.eligibility ?? "").toLowerCase();
|
const eligStatus = (seleniumResult?.eligibility ?? "").toLowerCase();
|
||||||
const newStatus =
|
let newStatus = "UNKNOWN";
|
||||||
eligStatus === "active" || eligStatus === "y" ? "ACTIVE" : "INACTIVE";
|
if (eligStatus === "active" || eligStatus === "y") newStatus = "ACTIVE";
|
||||||
|
else if (eligStatus === "inactive" || eligStatus === "n" || eligStatus === "termed") newStatus = "INACTIVE";
|
||||||
await storage.updatePatient(patient.id, {
|
await storage.updatePatient(patient.id, {
|
||||||
status: newStatus,
|
status: newStatus,
|
||||||
insuranceProvider: "Delta Dental MA",
|
insuranceProvider: "Delta Dental MA",
|
||||||
|
|||||||
@@ -223,6 +223,32 @@ router.put("/chat-templates", async (req: Request, res: Response): Promise<any>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/ai/call-templates
|
||||||
|
router.get("/call-templates", async (req: Request, res: Response): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const userId = req.user?.id;
|
||||||
|
if (!userId) return res.status(401).json({ message: "Unauthorized" });
|
||||||
|
const templates = await storage.getAiCallTemplates(userId);
|
||||||
|
return res.status(200).json(templates);
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(500).json({ error: "Failed to fetch AI call templates", details: String(err) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PUT /api/ai/call-templates
|
||||||
|
router.put("/call-templates", async (req: Request, res: Response): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const userId = req.user?.id;
|
||||||
|
if (!userId) return res.status(401).json({ message: "Unauthorized" });
|
||||||
|
const { greeting, reminderGreeting, newPatientGreeting, generalFallback, recallGreeting } = req.body;
|
||||||
|
await storage.saveAiCallTemplates(userId, { greeting, reminderGreeting, newPatientGreeting, generalFallback, recallGreeting });
|
||||||
|
const updated = await storage.getAiCallTemplates(userId);
|
||||||
|
return res.status(200).json(updated);
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(500).json({ error: "Failed to save AI call templates", details: String(err) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// GET /api/ai/internal-chat-settings
|
// GET /api/ai/internal-chat-settings
|
||||||
router.get("/internal-chat-settings", async (req: Request, res: Response): Promise<any> => {
|
router.get("/internal-chat-settings", async (req: Request, res: Response): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,8 @@ import twilio from "twilio";
|
|||||||
import { storage } from "../storage";
|
import { storage } from "../storage";
|
||||||
import { prisma as db } from "@repo/db/client";
|
import { prisma as db } from "@repo/db/client";
|
||||||
import { getHandoff, setHandoff, resetConversation, startNewPatientConversation, startRescheduleConversation, getAfterHoursHandoff, setAfterHoursHandoff } from "../ai/aiHandoffStore";
|
import { getHandoff, setHandoff, resetConversation, startNewPatientConversation, startRescheduleConversation, getAfterHoursHandoff, setAfterHoursHandoff } from "../ai/aiHandoffStore";
|
||||||
|
import { resolveAiProvider } from "../ai/llm-factory";
|
||||||
|
import { getTwilioPublicBaseUrl } from "../utils/publicUrl";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -117,6 +119,53 @@ router.post("/send-sms", async (req: Request, res: Response): Promise<any> => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// POST /api/twilio/make-ai-call
|
||||||
|
// Places an outbound call where Lisa (the AI assistant) conducts the whole
|
||||||
|
// conversation live over the phone — see /webhook/ai-voice for the loop.
|
||||||
|
router.post("/make-ai-call", async (req: Request, res: Response): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const userId = req.user?.id;
|
||||||
|
if (!userId) return res.status(401).json({ message: "Unauthorized" });
|
||||||
|
|
||||||
|
const { to, patientId, purpose } = req.body;
|
||||||
|
if (!to || !patientId) return res.status(400).json({ message: "to and patientId are required" });
|
||||||
|
|
||||||
|
const settings = await storage.getTwilioSettings(userId);
|
||||||
|
if (!settings) {
|
||||||
|
return res.status(400).json({ message: "Twilio is not configured. Please add your Twilio credentials in Settings." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const aiSettings = await storage.getAiSettings(userId);
|
||||||
|
if (!resolveAiProvider(aiSettings ?? {})) {
|
||||||
|
return res.status(400).json({ message: "AI is not configured. Please add an AI provider key in Settings before placing an AI call." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const patient = await db.patient.findFirst({ where: { id: Number(patientId), userId } });
|
||||||
|
if (!patient) return res.status(404).json({ message: "Patient not found" });
|
||||||
|
|
||||||
|
let publicBaseUrl: string;
|
||||||
|
try {
|
||||||
|
publicBaseUrl = getTwilioPublicBaseUrl();
|
||||||
|
} catch (err: any) {
|
||||||
|
return res.status(500).json({ message: err.message });
|
||||||
|
}
|
||||||
|
const callPurpose = purpose === "recall" ? "recall" : "reminder";
|
||||||
|
const webhookUrl = `${publicBaseUrl}/api/twilio/webhook/ai-voice?patientId=${patient.id}&purpose=${callPurpose}`;
|
||||||
|
|
||||||
|
const client = getTwilioClient(settings.accountSid, settings.authToken);
|
||||||
|
const call = await client.calls.create({
|
||||||
|
url: webhookUrl,
|
||||||
|
method: "POST",
|
||||||
|
from: settings.phoneNumber,
|
||||||
|
to,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ sid: call.sid, status: call.status });
|
||||||
|
} catch (err: any) {
|
||||||
|
return res.status(500).json({ error: err.message || "Failed to place AI call" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// POST /api/twilio/send-reminders-batch
|
// POST /api/twilio/send-reminders-batch
|
||||||
router.post("/send-reminders-batch", async (req: Request, res: Response): Promise<any> => {
|
router.post("/send-reminders-batch", async (req: Request, res: Response): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -46,9 +46,16 @@ export async function startWebDavServer(): Promise<void> {
|
|||||||
|
|
||||||
const config = readRcloneConfig();
|
const config = readRcloneConfig();
|
||||||
|
|
||||||
|
// Serve only the backups folder — never the app root, which contains
|
||||||
|
// .env (DB/Twilio/AI credentials) and the full source tree.
|
||||||
|
const backupsDir = path.join(APP_ROOT, "backups");
|
||||||
|
if (!fs.existsSync(backupsDir)) {
|
||||||
|
fs.mkdirSync(backupsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
"serve", "webdav",
|
"serve", "webdav",
|
||||||
APP_ROOT,
|
backupsDir,
|
||||||
"--addr", `:${config.serverPort}`,
|
"--addr", `:${config.serverPort}`,
|
||||||
"--user", RCLONE_USER,
|
"--user", RCLONE_USER,
|
||||||
"--pass", RCLONE_PASS,
|
"--pass", RCLONE_PASS,
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { prisma as db } from "@repo/db/client";
|
import { prisma as db } from "@repo/db/client";
|
||||||
|
|
||||||
|
// Exported so twilio-webhooks.ts can detect an admin-customized template
|
||||||
|
// (vs. this untouched default) and swap in a translated greeting instead.
|
||||||
|
export const DEFAULT_GREETING =
|
||||||
|
"Hi {firstName}, this is Lisa, the AI assistant at {officeName}. How can I help you today?";
|
||||||
|
export const DEFAULT_RECALL_GREETING =
|
||||||
|
"Hi {firstName}, this is Lisa from {officeName}. We wanted to check in — you're due for a routine exam and cleaning. Would you like to make an appointment because your insurance covers it?";
|
||||||
|
|
||||||
export type TwilioSettingsData = {
|
export type TwilioSettingsData = {
|
||||||
accountSid: string;
|
accountSid: string;
|
||||||
authToken: string;
|
authToken: string;
|
||||||
@@ -96,6 +103,38 @@ export const twilioStorage = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── Call Templates — wording spoken during live AI phone calls, kept
|
||||||
|
// separate from the SMS wording above (routePatientTurn() consumes
|
||||||
|
// whichever set the caller passes in). ────────────────────────────────────
|
||||||
|
|
||||||
|
async getAiCallTemplates(userId: number): Promise<Record<string, string>> {
|
||||||
|
const settings = await db.twilioSettings.findUnique({ where: { userId } });
|
||||||
|
const all = (settings?.templates as Record<string, string>) || {};
|
||||||
|
return {
|
||||||
|
greeting: all["_ai_call_greeting"] ?? all["_ai_chat_call_template"] ?? DEFAULT_GREETING,
|
||||||
|
reminderGreeting: all["_ai_call_reminder_greeting"] ?? "Sure, I can help with that.",
|
||||||
|
newPatientGreeting: all["_ai_call_new_patient_greeting"] ?? "No problem, let's get you set up.",
|
||||||
|
generalFallback: all["_ai_call_general_fallback"] ?? "Sure, how can I help?",
|
||||||
|
recallGreeting: all["_ai_call_recall_greeting"] ?? DEFAULT_RECALL_GREETING,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveAiCallTemplates(userId: number, templates: { greeting?: string; reminderGreeting?: string; newPatientGreeting?: string; generalFallback?: string; recallGreeting?: string }) {
|
||||||
|
const settings = await db.twilioSettings.findUnique({ where: { userId } });
|
||||||
|
const existing = (settings?.templates as Record<string, string>) || {};
|
||||||
|
const updated: Record<string, string> = { ...existing };
|
||||||
|
if (templates.greeting !== undefined) updated["_ai_call_greeting"] = templates.greeting;
|
||||||
|
if (templates.reminderGreeting !== undefined) updated["_ai_call_reminder_greeting"] = templates.reminderGreeting;
|
||||||
|
if (templates.newPatientGreeting !== undefined) updated["_ai_call_new_patient_greeting"] = templates.newPatientGreeting;
|
||||||
|
if (templates.generalFallback !== undefined) updated["_ai_call_general_fallback"] = templates.generalFallback;
|
||||||
|
if (templates.recallGreeting !== undefined) updated["_ai_call_recall_greeting"] = templates.recallGreeting;
|
||||||
|
return db.twilioSettings.upsert({
|
||||||
|
where: { userId },
|
||||||
|
update: { templates: updated },
|
||||||
|
create: { userId, accountSid: "", authToken: "", phoneNumber: "", templates: updated },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
async getSmsTemplateList(userId: number): Promise<{ id: string; name: string; body: string }[]> {
|
async getSmsTemplateList(userId: number): Promise<{ id: string; name: string; body: string }[]> {
|
||||||
const settings = await db.twilioSettings.findUnique({ where: { userId } });
|
const settings = await db.twilioSettings.findUnique({ where: { userId } });
|
||||||
const all = (settings?.templates as Record<string, string>) || {};
|
const all = (settings?.templates as Record<string, string>) || {};
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Request } from "express";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public HTTPS origin this request arrived on (e.g. https://broadwaydental.mydentalofficemanagement.com).
|
||||||
|
* Derived from the Host header rather than an env var so it's automatically correct
|
||||||
|
* per-office — each office's nginx forwards its own public hostname in Host,
|
||||||
|
* and Cloudflare/Let's Encrypt terminate TLS in front of it either way.
|
||||||
|
*
|
||||||
|
* Only valid for requests that actually arrived via the public tunnel (i.e. inside
|
||||||
|
* a Twilio webhook handler). For anything triggered from the staff browser — which
|
||||||
|
* reaches the backend over the LAN-only local-* hostname — use getTwilioPublicBaseUrl()
|
||||||
|
* instead, since Twilio can never reach that LAN hostname to fetch a callback URL.
|
||||||
|
*/
|
||||||
|
export function getPublicBaseUrl(req: Request): string {
|
||||||
|
return `https://${req.get("host")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixed public origin Twilio must use to call back into this office's server
|
||||||
|
* (e.g. for outbound-call TwiML webhooks). Comes from CLOUDFLARE_HOST, the
|
||||||
|
* per-office env var already reserved for this — not derived from the
|
||||||
|
* triggering request, since that request may have arrived over the LAN-only
|
||||||
|
* hostname, which Twilio cannot reach.
|
||||||
|
*/
|
||||||
|
export function getTwilioPublicBaseUrl(): string {
|
||||||
|
const host = process.env.CLOUDFLARE_HOST?.trim();
|
||||||
|
if (!host) {
|
||||||
|
throw new Error(
|
||||||
|
"CLOUDFLARE_HOST is not set — required to build a Twilio-reachable callback URL. Set it in .env to this office's public tunnel hostname."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return `https://${host}`;
|
||||||
|
}
|
||||||
@@ -791,13 +791,12 @@ export function ClaimForm({
|
|||||||
const deriveInsuranceSiteKey = (provider: string | null | undefined): string => {
|
const deriveInsuranceSiteKey = (provider: string | null | undefined): string => {
|
||||||
const p = (provider || "").toLowerCase().trim();
|
const p = (provider || "").toLowerCase().trim();
|
||||||
if (!p) return "";
|
if (!p) return "";
|
||||||
if (p.includes("masshealth") || p === "mh" || p === "mass health") return "MH";
|
if (p.includes("masshealth") || p === "mh" || p === "mass health" || p.includes("cmsp")) return "MH";
|
||||||
if (p.includes("commonwealth care alliance") || p === "cca") return "CCA";
|
if (p.includes("commonwealth care alliance") || p === "cca") return "CCA";
|
||||||
if (p.includes("ddma") || p.includes("delta dental ma")) return "DDMA";
|
if (p.includes("ddma") || p.includes("delta dental ma")) return "DDMA";
|
||||||
if (p.includes("delta ins") || p === "deltains") return "DeltaIns";
|
if (p.includes("delta ins") || p === "deltains") return "DeltaIns";
|
||||||
if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TuftsSCO";
|
if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TuftsSCO";
|
||||||
if ((p.includes("united") && p.includes("sco")) || p === "unitedsco") return "UnitedSCO";
|
if ((p.includes("united") && p.includes("sco")) || p === "unitedsco") return "UnitedSCO";
|
||||||
if (p.includes("cmsp")) return "CMSP";
|
|
||||||
if (p.includes("bcbs") || p.includes("blue cross")) return "BCBS";
|
if (p.includes("bcbs") || p.includes("blue cross")) return "BCBS";
|
||||||
if (p.includes("united aapr") || p === "unitedaapr") return "UnitedAAPR";
|
if (p.includes("united aapr") || p === "unitedaapr") return "UnitedAAPR";
|
||||||
if (p.includes("aetna")) return "Aetna";
|
if (p.includes("aetna")) return "Aetna";
|
||||||
@@ -2008,7 +2007,6 @@ export function ClaimForm({
|
|||||||
<SelectItem value="DeltaIns">Delta Ins</SelectItem>
|
<SelectItem value="DeltaIns">Delta Ins</SelectItem>
|
||||||
<SelectItem value="TuftsSCO">Tufts SCO</SelectItem>
|
<SelectItem value="TuftsSCO">Tufts SCO</SelectItem>
|
||||||
<SelectItem value="UnitedSCO">United SCO</SelectItem>
|
<SelectItem value="UnitedSCO">United SCO</SelectItem>
|
||||||
<SelectItem value="CMSP">CMSP</SelectItem>
|
|
||||||
<SelectItem value="BCBS">BCBS</SelectItem>
|
<SelectItem value="BCBS">BCBS</SelectItem>
|
||||||
<SelectItem value="UnitedAAPR">United AAPR</SelectItem>
|
<SelectItem value="UnitedAAPR">United AAPR</SelectItem>
|
||||||
<SelectItem value="Aetna">Aetna</SelectItem>
|
<SelectItem value="Aetna">Aetna</SelectItem>
|
||||||
@@ -2596,7 +2594,6 @@ export function ClaimForm({
|
|||||||
<SelectItem value="DeltaIns">Delta Ins</SelectItem>
|
<SelectItem value="DeltaIns">Delta Ins</SelectItem>
|
||||||
<SelectItem value="TuftsSCO">Tufts SCO</SelectItem>
|
<SelectItem value="TuftsSCO">Tufts SCO</SelectItem>
|
||||||
<SelectItem value="UnitedSCO">United SCO</SelectItem>
|
<SelectItem value="UnitedSCO">United SCO</SelectItem>
|
||||||
<SelectItem value="CMSP">CMSP</SelectItem>
|
|
||||||
<SelectItem value="BCBS">BCBS</SelectItem>
|
<SelectItem value="BCBS">BCBS</SelectItem>
|
||||||
<SelectItem value="UnitedAAPR">United AAPR</SelectItem>
|
<SelectItem value="UnitedAAPR">United AAPR</SelectItem>
|
||||||
<SelectItem value="Aetna">Aetna</SelectItem>
|
<SelectItem value="Aetna">Aetna</SelectItem>
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ function mdyToISO(mdy: string): string {
|
|||||||
|
|
||||||
const INSURANCE_TYPE_OPTIONS: { value: string; label: string }[] = [
|
const INSURANCE_TYPE_OPTIONS: { value: string; label: string }[] = [
|
||||||
{ value: "mh", label: "MassHealth" },
|
{ value: "mh", label: "MassHealth" },
|
||||||
{ value: "cmsp", label: "CMSP" },
|
|
||||||
{ value: "ddma", label: "DDMA" },
|
{ value: "ddma", label: "DDMA" },
|
||||||
{ value: "delta-ins", label: "Delta Dental" },
|
{ value: "delta-ins", label: "Delta Dental" },
|
||||||
{ value: "united-sco", label: "United SCO" },
|
{ value: "united-sco", label: "United SCO" },
|
||||||
@@ -143,13 +142,10 @@ const INSURANCE_TYPE_OPTIONS: { value: string; label: string }[] = [
|
|||||||
{ value: "cca", label: "CCA" },
|
{ value: "cca", label: "CCA" },
|
||||||
];
|
];
|
||||||
|
|
||||||
function getAutoCheck(dobISO: string): "mh" | "cmsp" {
|
function getAutoCheck(_dobISO: string): "mh" {
|
||||||
const [y, m, d] = dobISO.split("-").map(Number);
|
// CMSP and MassHealth eligibility checks now run through the same MH worker,
|
||||||
const today = new Date();
|
// so there's no longer an age-based split — everyone is "mh".
|
||||||
let age = today.getFullYear() - (y ?? 0);
|
return "mh";
|
||||||
const monthDiff = today.getMonth() + 1 - (m ?? 0);
|
|
||||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < (d ?? 0))) age--;
|
|
||||||
return age >= 21 ? "mh" : "cmsp";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseEligibilityInput(
|
function parseEligibilityInput(
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ export function Sidebar() {
|
|||||||
icon: <Bot className="h-4 w-4 text-gray-400" />,
|
icon: <Bot className="h-4 w-4 text-gray-400" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AI Chat Settings",
|
name: "AI Chat/Call Settings",
|
||||||
path: "/settings/aichat",
|
path: "/settings/aichat",
|
||||||
icon: <Bot className="h-4 w-4 text-gray-400" />,
|
icon: <Bot className="h-4 w-4 text-gray-400" />,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { Send, ArrowLeft, FileText, Globe, Bot, UserPlus, CalendarX } from "lucide-react";
|
import { Send, ArrowLeft, FileText, Globe, Bot, UserPlus, CalendarX, Phone } from "lucide-react";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import type { Patient, Communication } from "@repo/db/types";
|
import type { Patient, Communication } from "@repo/db/types";
|
||||||
import { format, isToday, isYesterday, parseISO } from "date-fns";
|
import { format, isToday, isYesterday, parseISO } from "date-fns";
|
||||||
@@ -534,6 +534,12 @@ export function MessageThread({ patient, onBack, appointmentInfo }: MessageThrea
|
|||||||
{patient.firstName[0]}{patient.lastName[0]}
|
{patient.firstName[0]}{patient.lastName[0]}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
{comm.channel === "voice" && (
|
||||||
|
<div className="flex items-center gap-1 mb-1 text-[10px] font-medium text-muted-foreground">
|
||||||
|
<Phone className="h-3 w-3" />
|
||||||
|
Voice call
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="p-3 rounded-2xl bg-gray-100 text-gray-900 rounded-tl-md">
|
<div className="p-3 rounded-2xl bg-gray-100 text-gray-900 rounded-tl-md">
|
||||||
<p className="text-sm whitespace-pre-wrap break-words">{comm.body}</p>
|
<p className="text-sm whitespace-pre-wrap break-words">{comm.body}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -545,6 +551,12 @@ export function MessageThread({ patient, onBack, appointmentInfo }: MessageThrea
|
|||||||
)}
|
)}
|
||||||
{comm.direction === "outbound" && (
|
{comm.direction === "outbound" && (
|
||||||
<div>
|
<div>
|
||||||
|
{comm.channel === "voice" && (
|
||||||
|
<div className="flex items-center justify-end gap-1 mb-1 text-[10px] font-medium text-muted-foreground">
|
||||||
|
<Phone className="h-3 w-3" />
|
||||||
|
Voice call
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="p-3 rounded-2xl bg-primary text-primary-foreground rounded-tr-md">
|
<div className="p-3 rounded-2xl bg-primary text-primary-foreground rounded-tr-md">
|
||||||
<p className="text-sm whitespace-pre-wrap break-words">{comm.body}</p>
|
<p className="text-sm whitespace-pre-wrap break-words">{comm.body}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -50,6 +51,7 @@ export type PatientFormRef = {
|
|||||||
export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
||||||
({ patient, extractedInfo, onSubmit }, ref) => {
|
({ patient, extractedInfo, onSubmit }, ref) => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const { toast } = useToast();
|
||||||
const isEditing = !!patient;
|
const isEditing = !!patient;
|
||||||
|
|
||||||
const schema = useMemo(
|
const schema = useMemo(
|
||||||
@@ -62,13 +64,12 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
|
|
||||||
const normalizeInsuranceProvider = (val: string | null | undefined): string => {
|
const normalizeInsuranceProvider = (val: string | null | undefined): string => {
|
||||||
const p = (val || "").toLowerCase().trim();
|
const p = (val || "").toLowerCase().trim();
|
||||||
if (p.includes("masshealth") || p === "mh" || p === "mass health") return "MassHealth";
|
if (p.includes("masshealth") || p === "mh" || p === "mass health" || p.includes("cmsp")) return "MassHealth";
|
||||||
if (p.includes("commonwealth care alliance") || p === "cca") return "CCA";
|
if (p.includes("commonwealth care alliance") || p === "cca") return "CCA";
|
||||||
if (p.includes("ddma")) return "DDMA";
|
if (p.includes("ddma")) return "DDMA";
|
||||||
if (p.includes("delta dental") || p.includes("delta ins") || p === "deltains") return "DeltaIns";
|
if (p.includes("delta dental") || p.includes("delta ins") || p === "deltains") return "DeltaIns";
|
||||||
if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TuftsSCO";
|
if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TuftsSCO";
|
||||||
if (p.includes("united sco") || p === "unitedsco") return "UnitedSCO";
|
if (p.includes("united sco") || p === "unitedsco") return "UnitedSCO";
|
||||||
if (p.includes("cmsp")) return "CMSP";
|
|
||||||
if (p.includes("bcbs") || p.includes("blue cross")) return "BCBS";
|
if (p.includes("bcbs") || p.includes("blue cross")) return "BCBS";
|
||||||
if (p.includes("united aapr") || p === "unitedaapr") return "UnitedAAPR";
|
if (p.includes("united aapr") || p === "unitedaapr") return "UnitedAAPR";
|
||||||
if (p.includes("aetna")) return "Aetna";
|
if (p.includes("aetna")) return "Aetna";
|
||||||
@@ -83,7 +84,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
|
|
||||||
const computedDefaultValues = useMemo(() => {
|
const computedDefaultValues = useMemo(() => {
|
||||||
if (isEditing && patient) {
|
if (isEditing && patient) {
|
||||||
const { id, userId, createdAt, ...sanitizedPatient } = patient;
|
const { id, userId, createdAt, updatedAt, ...sanitizedPatient } = patient;
|
||||||
return {
|
return {
|
||||||
...sanitizedPatient,
|
...sanitizedPatient,
|
||||||
dateOfBirth: patient.dateOfBirth
|
dateOfBirth: patient.dateOfBirth
|
||||||
@@ -137,7 +138,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (patient) {
|
if (patient) {
|
||||||
const { id, userId, createdAt, ...sanitizedPatient } = patient;
|
const { id, userId, createdAt, updatedAt, ...sanitizedPatient } = patient;
|
||||||
const normalized = normalizeInsuranceProvider(patient.insuranceProvider);
|
const normalized = normalizeInsuranceProvider(patient.insuranceProvider);
|
||||||
const resetValues: Partial<Patient> = {
|
const resetValues: Partial<Patient> = {
|
||||||
...sanitizedPatient,
|
...sanitizedPatient,
|
||||||
@@ -164,9 +165,19 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
id="patient-form"
|
id="patient-form"
|
||||||
key={patient?.id || "new"}
|
key={patient?.id || "new"}
|
||||||
noValidate
|
noValidate
|
||||||
onSubmit={form.handleSubmit((data) => {
|
onSubmit={form.handleSubmit(
|
||||||
handleSubmit2(data);
|
(data) => {
|
||||||
})}
|
handleSubmit2(data);
|
||||||
|
},
|
||||||
|
(errors) => {
|
||||||
|
console.log("❌ Form validation errors:", errors);
|
||||||
|
toast({
|
||||||
|
title: "Could not save patient",
|
||||||
|
description: "Please check the highlighted fields and try again.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)}
|
||||||
className="space-y-6"
|
className="space-y-6"
|
||||||
>
|
>
|
||||||
{/* Personal Information */}
|
{/* Personal Information */}
|
||||||
@@ -439,7 +450,6 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
<SelectItem value="DeltaIns">Delta Ins</SelectItem>
|
<SelectItem value="DeltaIns">Delta Ins</SelectItem>
|
||||||
<SelectItem value="TuftsSCO">Tufts SCO</SelectItem>
|
<SelectItem value="TuftsSCO">Tufts SCO</SelectItem>
|
||||||
<SelectItem value="UnitedSCO">United SCO</SelectItem>
|
<SelectItem value="UnitedSCO">United SCO</SelectItem>
|
||||||
<SelectItem value="CMSP">CMSP</SelectItem>
|
|
||||||
<SelectItem value="BCBS">BCBS</SelectItem>
|
<SelectItem value="BCBS">BCBS</SelectItem>
|
||||||
<SelectItem value="UnitedAAPR">United AAPR</SelectItem>
|
<SelectItem value="UnitedAAPR">United AAPR</SelectItem>
|
||||||
<SelectItem value="Aetna">Aetna</SelectItem>
|
<SelectItem value="Aetna">Aetna</SelectItem>
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ interface PatientTableProps {
|
|||||||
allowCheckbox?: boolean;
|
allowCheckbox?: boolean;
|
||||||
allowNewClaim?: boolean;
|
allowNewClaim?: boolean;
|
||||||
allowFinancial?: boolean;
|
allowFinancial?: boolean;
|
||||||
|
showEligibilityColumns?: boolean;
|
||||||
onNewClaim?: (patientId: number) => void;
|
onNewClaim?: (patientId: number) => void;
|
||||||
onSelectPatient?: (patient: Patient | null) => void;
|
onSelectPatient?: (patient: Patient | null) => void;
|
||||||
onPageChange?: (page: number) => void;
|
onPageChange?: (page: number) => void;
|
||||||
@@ -82,6 +83,7 @@ export function PatientTable({
|
|||||||
allowCheckbox,
|
allowCheckbox,
|
||||||
allowNewClaim,
|
allowNewClaim,
|
||||||
allowFinancial,
|
allowFinancial,
|
||||||
|
showEligibilityColumns,
|
||||||
onNewClaim,
|
onNewClaim,
|
||||||
onSelectPatient,
|
onSelectPatient,
|
||||||
onPageChange,
|
onPageChange,
|
||||||
@@ -976,6 +978,12 @@ export function PatientTable({
|
|||||||
{/*<TableHead>Contact</TableHead>*/}
|
{/*<TableHead>Contact</TableHead>*/}
|
||||||
<TableHead>Insurance</TableHead>
|
<TableHead>Insurance</TableHead>
|
||||||
<TableHead>Status</TableHead>
|
<TableHead>Status</TableHead>
|
||||||
|
{showEligibilityColumns && (
|
||||||
|
<>
|
||||||
|
<TableHead>Remaining</TableHead>
|
||||||
|
<TableHead>Third Party/2nd Insurance</TableHead>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@@ -983,7 +991,7 @@ export function PatientTable({
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell
|
<TableCell
|
||||||
colSpan={6}
|
colSpan={showEligibilityColumns ? 8 : 6}
|
||||||
className="text-center py-8 text-muted-foreground"
|
className="text-center py-8 text-muted-foreground"
|
||||||
>
|
>
|
||||||
<LoadingScreen />
|
<LoadingScreen />
|
||||||
@@ -992,7 +1000,7 @@ export function PatientTable({
|
|||||||
) : isError ? (
|
) : isError ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell
|
<TableCell
|
||||||
colSpan={6}
|
colSpan={showEligibilityColumns ? 8 : 6}
|
||||||
className="text-center py-8 text-red-500"
|
className="text-center py-8 text-red-500"
|
||||||
>
|
>
|
||||||
Error loading patients.
|
Error loading patients.
|
||||||
@@ -1001,7 +1009,7 @@ export function PatientTable({
|
|||||||
) : patientsData?.patients.length === 0 ? (
|
) : patientsData?.patients.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell
|
<TableCell
|
||||||
colSpan={6}
|
colSpan={showEligibilityColumns ? 8 : 6}
|
||||||
className="text-center py-8 text-muted-foreground"
|
className="text-center py-8 text-muted-foreground"
|
||||||
>
|
>
|
||||||
No patients found.
|
No patients found.
|
||||||
@@ -1088,8 +1096,28 @@ export function PatientTable({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{patient.updatedAt && (
|
||||||
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
|
Updated: {formatDateToHumanReadable(patient.updatedAt)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
|
{showEligibilityColumns && (
|
||||||
|
<>
|
||||||
|
<TableCell>
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{patient.remainingBenefit || "—"}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{patient.thirdPartyInsurance || "—"}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
{allowDelete && isAdmin && (
|
{allowDelete && isAdmin && (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { Bot, CalendarCheck, UserPlus, MessageCircle, Info, GitFork, MessageSquare, Trash2, Plus, Zap, SlidersHorizontal, BookMarked, ChevronDown, ChevronUp } from "lucide-react";
|
import { Bot, CalendarCheck, UserPlus, MessageCircle, Info, GitFork, MessageSquare, Trash2, Plus, Zap, SlidersHorizontal, BookMarked, ChevronDown, ChevronUp, PhoneCall, Stethoscope } from "lucide-react";
|
||||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
@@ -20,6 +20,14 @@ type AiChatTemplates = {
|
|||||||
generalFallback: string;
|
generalFallback: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AiCallTemplates = {
|
||||||
|
greeting: string;
|
||||||
|
reminderGreeting: string;
|
||||||
|
newPatientGreeting: string;
|
||||||
|
generalFallback: string;
|
||||||
|
recallGreeting: string;
|
||||||
|
};
|
||||||
|
|
||||||
type OfficeContact = {
|
type OfficeContact = {
|
||||||
officeName?: string | null;
|
officeName?: string | null;
|
||||||
};
|
};
|
||||||
@@ -38,6 +46,16 @@ const DEFAULTS = {
|
|||||||
"Hi! My name is Lisa, the dedicated AI assistant at {officeName}. How can I help you today?",
|
"Hi! My name is Lisa, the dedicated AI assistant at {officeName}. How can I help you today?",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CALL_DEFAULTS = {
|
||||||
|
greeting:
|
||||||
|
"Hi {firstName}, this is Lisa, the AI assistant at {officeName}. How can I help you today?",
|
||||||
|
reminderGreeting: "Sure, I can help with that.",
|
||||||
|
newPatientGreeting: "No problem, let's get you set up.",
|
||||||
|
generalFallback: "Sure, how can I help?",
|
||||||
|
recallGreeting:
|
||||||
|
"Hi {firstName}, this is Lisa from {officeName}. We wanted to check in — you're due for a routine exam and cleaning. Would you like to make an appointment because your insurance covers it?",
|
||||||
|
};
|
||||||
|
|
||||||
const DEFAULT_SMS_TEMPLATES = [
|
const DEFAULT_SMS_TEMPLATES = [
|
||||||
{
|
{
|
||||||
id: "default-appt-reminder",
|
id: "default-appt-reminder",
|
||||||
@@ -67,18 +85,19 @@ function LangGraphFlow() {
|
|||||||
const nW = 210;
|
const nW = 210;
|
||||||
const nx = cx - nW / 2; // 245
|
const nx = cx - nW / 2; // 245
|
||||||
|
|
||||||
// ── Dual entry nodes ──────────────────────────────────────────────────────
|
// ── Triple entry nodes ─────────────────────────────────────────────────────
|
||||||
const entryW = 192;
|
const entryW = 150;
|
||||||
const lEntryCx = 155;
|
const lEntryCx = 130;
|
||||||
const rEntryCx = 545;
|
const mEntryCx = cx; // 350
|
||||||
const e1y = 14, e1h = 56;
|
const rEntryCx = 570;
|
||||||
|
const e1y = 14, e1h = 70;
|
||||||
|
|
||||||
// Merge connector (horizontal line where both branches meet)
|
// Merge connector (horizontal line where all three entries meet)
|
||||||
const mergeY = e1y + e1h + 14; // 84
|
const mergeY = e1y + e1h + 14; // 98
|
||||||
|
|
||||||
// ── Center sequence ───────────────────────────────────────────────────────
|
// ── Center sequence ───────────────────────────────────────────────────────
|
||||||
const n2y = mergeY + 14, n2h = 52; // Patient replies y=98
|
const n2y = mergeY + 14, n2h = 52; // Patient replies
|
||||||
const n3y = n2y + n2h + 14, n3h = 84; // AI classifies y=164
|
const n3y = n2y + n2h + 14, n3h = 98; // AI classifies
|
||||||
|
|
||||||
const forkHY = n3y + n3h + 22; // 270
|
const forkHY = n3y + n3h + 22; // 270
|
||||||
|
|
||||||
@@ -88,7 +107,7 @@ function LangGraphFlow() {
|
|||||||
|
|
||||||
// ── YES branch ────────────────────────────────────────────────────────────
|
// ── YES branch ────────────────────────────────────────────────────────────
|
||||||
const yesW = 195;
|
const yesW = 195;
|
||||||
const yes1y = forkHY + 50, yes1h = 88; // y=320
|
const yes1y = forkHY + 50, yes1h = 102; // y=320
|
||||||
const yes2y = yes1y + yes1h + 12, yes2h = 52; // y=460
|
const yes2y = yes1y + yes1h + 12, yes2h = 52; // y=460
|
||||||
|
|
||||||
// ── NO / Reschedule branch ────────────────────────────────────────────────
|
// ── NO / Reschedule branch ────────────────────────────────────────────────
|
||||||
@@ -113,7 +132,7 @@ function LangGraphFlow() {
|
|||||||
viewBox={`0 0 ${W} ${totalH}`}
|
viewBox={`0 0 ${W} ${totalH}`}
|
||||||
className="w-full max-w-2xl mx-auto"
|
className="w-full max-w-2xl mx-auto"
|
||||||
role="img"
|
role="img"
|
||||||
aria-label="LangGraph Reminder and Reschedule conversation flow"
|
aria-label="LangGraph Reminder, Recall, and Reschedule conversation flow"
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<marker id="rg2ah" markerWidth="10" markerHeight="7"
|
<marker id="rg2ah" markerWidth="10" markerHeight="7"
|
||||||
@@ -130,20 +149,29 @@ function LangGraphFlow() {
|
|||||||
</marker>
|
</marker>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
{/* ══ DUAL ENTRY NODES ════════════════════════════════════════════ */}
|
{/* ══ TRIPLE ENTRY NODES ════════════════════════════════════════════ */}
|
||||||
|
|
||||||
{/* Left: Reminder SMS */}
|
{/* Left: Reminder SMS */}
|
||||||
<rect x={lEntryCx-entryW/2} y={e1y} width={entryW} height={e1h} rx={8} fill="#EFF6FF" stroke="#3B82F6" strokeWidth={1.5} />
|
<rect x={lEntryCx-entryW/2} y={e1y} width={entryW} height={e1h} rx={8} fill="#EFF6FF" stroke="#3B82F6" strokeWidth={1.5} />
|
||||||
<text x={lEntryCx} y={e1y+22} textAnchor="middle" fontSize={12} fontWeight="600" fill="#1D4ED8">Office sends Reminder SMS</text>
|
<text x={lEntryCx} y={e1y+22} textAnchor="middle" fontSize={12} fontWeight="600" fill="#1D4ED8">Office sends Reminder</text>
|
||||||
<text x={lEntryCx} y={e1y+40} textAnchor="middle" fontSize={9} fill="#93C5FD">Staff triggers batch send</text>
|
<text x={lEntryCx} y={e1y+38} textAnchor="middle" fontSize={12} fontWeight="600" fill="#1D4ED8">SMS or Call</text>
|
||||||
|
<text x={lEntryCx} y={e1y+56} textAnchor="middle" fontSize={9} fill="#93C5FD">Staff triggers batch send</text>
|
||||||
|
|
||||||
|
{/* Middle: Recall call/SMS for an existing patient */}
|
||||||
|
<rect x={mEntryCx-entryW/2} y={e1y} width={entryW} height={e1h} rx={8} fill="#F5F3FF" stroke="#8B5CF6" strokeWidth={1.5} />
|
||||||
|
<text x={mEntryCx} y={e1y+22} textAnchor="middle" fontSize={12} fontWeight="600" fill="#5B21B6">Office call or SMS for</text>
|
||||||
|
<text x={mEntryCx} y={e1y+38} textAnchor="middle" fontSize={12} fontWeight="600" fill="#5B21B6">an existing patient</text>
|
||||||
|
<text x={mEntryCx} y={e1y+56} textAnchor="middle" fontSize={9} fill="#C4B5FD">Recall: free exam & cleaning</text>
|
||||||
|
|
||||||
{/* Right: Reschedule by Office SMS */}
|
{/* Right: Reschedule by Office SMS */}
|
||||||
<rect x={rEntryCx-entryW/2} y={e1y} width={entryW} height={e1h} rx={8} fill="#FFF7ED" stroke="#F97316" strokeWidth={1.5} />
|
<rect x={rEntryCx-entryW/2} y={e1y} width={entryW} height={e1h} rx={8} fill="#FFF7ED" stroke="#F97316" strokeWidth={1.5} />
|
||||||
<text x={rEntryCx} y={e1y+22} textAnchor="middle" fontSize={12} fontWeight="600" fill="#C2410C">Office sends Reschedule SMS</text>
|
<text x={rEntryCx} y={e1y+22} textAnchor="middle" fontSize={12} fontWeight="600" fill="#C2410C">Office sends Reschedule</text>
|
||||||
<text x={rEntryCx} y={e1y+40} textAnchor="middle" fontSize={9} fill="#FDBA74">"Reschedule by Office" template</text>
|
<text x={rEntryCx} y={e1y+38} textAnchor="middle" fontSize={12} fontWeight="600" fill="#C2410C">SMS</text>
|
||||||
|
<text x={rEntryCx} y={e1y+56} textAnchor="middle" fontSize={9} fill="#FDBA74">"Reschedule by Office" template</text>
|
||||||
|
|
||||||
{/* Converging lines → horizontal merge → Patient replies */}
|
{/* Converging lines → horizontal merge → Patient replies */}
|
||||||
<line x1={lEntryCx} y1={e1y+e1h} x2={lEntryCx} y2={mergeY} stroke="#9CA3AF" strokeWidth={1.5} />
|
<line x1={lEntryCx} y1={e1y+e1h} x2={lEntryCx} y2={mergeY} stroke="#9CA3AF" strokeWidth={1.5} />
|
||||||
|
<line x1={mEntryCx} y1={e1y+e1h} x2={mEntryCx} y2={mergeY} stroke="#9CA3AF" strokeWidth={1.5} />
|
||||||
<line x1={rEntryCx} y1={e1y+e1h} x2={rEntryCx} y2={mergeY} stroke="#9CA3AF" strokeWidth={1.5} />
|
<line x1={rEntryCx} y1={e1y+e1h} x2={rEntryCx} y2={mergeY} stroke="#9CA3AF" strokeWidth={1.5} />
|
||||||
<line x1={lEntryCx} y1={mergeY} x2={rEntryCx} y2={mergeY} stroke="#9CA3AF" strokeWidth={1.5} />
|
<line x1={lEntryCx} y1={mergeY} x2={rEntryCx} y2={mergeY} stroke="#9CA3AF" strokeWidth={1.5} />
|
||||||
<line x1={cx} y1={mergeY} x2={cx} y2={n2y-2} stroke="#9CA3AF" strokeWidth={1.5} markerEnd="url(#rg2ah)" />
|
<line x1={cx} y1={mergeY} x2={cx} y2={n2y-2} stroke="#9CA3AF" strokeWidth={1.5} markerEnd="url(#rg2ah)" />
|
||||||
@@ -158,10 +186,11 @@ function LangGraphFlow() {
|
|||||||
|
|
||||||
{/* N3: AI classifies */}
|
{/* N3: AI classifies */}
|
||||||
<rect x={nx} y={n3y} width={nW} height={n3h} rx={8} fill="#ECFDF5" stroke="#10B981" strokeWidth={1.5} />
|
<rect x={nx} y={n3y} width={nW} height={n3h} rx={8} fill="#ECFDF5" stroke="#10B981" strokeWidth={1.5} />
|
||||||
<text x={cx} y={n3y+20} textAnchor="middle" fontSize={12} fontWeight="700" fill="#065F46">Google AI classifies YES / NO</text>
|
<text x={cx} y={n3y+18} textAnchor="middle" fontSize={12} fontWeight="700" fill="#065F46">Google AI classifies YES / NO</text>
|
||||||
<text x={cx} y={n3y+36} textAnchor="middle" fontSize={10} fontWeight="600" fill="#1D4ED8">Reminder: intro (MSG 1) + reply (MSG 2)</text>
|
<text x={cx} y={n3y+33} textAnchor="middle" fontSize={10} fontWeight="600" fill="#1D4ED8">Reminder: intro (MSG 1) + reply (MSG 2)</text>
|
||||||
<text x={cx} y={n3y+52} textAnchor="middle" fontSize={10} fontWeight="600" fill="#C2410C">Reschedule: reply sent directly</text>
|
<text x={cx} y={n3y+48} textAnchor="middle" fontSize={10} fontWeight="600" fill="#C2410C">Reschedule: reply sent directly</text>
|
||||||
<text x={cx} y={n3y+70} textAnchor="middle" fontSize={9} fill="#6B7280" fontStyle="italic">Date in reply → AI skips to Day Check</text>
|
<text x={cx} y={n3y+63} textAnchor="middle" fontSize={10} fontWeight="600" fill="#5B21B6">Recall: same as Reschedule flow</text>
|
||||||
|
<text x={cx} y={n3y+82} textAnchor="middle" fontSize={9} fill="#6B7280" fontStyle="italic">Date in reply → AI skips to Day Check</text>
|
||||||
|
|
||||||
{/* Fork lines */}
|
{/* Fork lines */}
|
||||||
<line x1={cx} y1={n3y+n3h} x2={cx} y2={forkHY} stroke="#9CA3AF" strokeWidth={1.5} />
|
<line x1={cx} y1={n3y+n3h} x2={cx} y2={forkHY} stroke="#9CA3AF" strokeWidth={1.5} />
|
||||||
@@ -178,10 +207,11 @@ function LangGraphFlow() {
|
|||||||
{/* ══ YES BRANCH ══════════════════════════════════════════════════ */}
|
{/* ══ YES BRANCH ══════════════════════════════════════════════════ */}
|
||||||
|
|
||||||
<rect x={lcx-yesW/2} y={yes1y} width={yesW} height={yes1h} rx={8} fill="#F0FDF4" stroke="#22C55E" strokeWidth={1.5} />
|
<rect x={lcx-yesW/2} y={yes1y} width={yesW} height={yes1h} rx={8} fill="#F0FDF4" stroke="#22C55E" strokeWidth={1.5} />
|
||||||
<text x={lcx} y={yes1y+18} textAnchor="middle" fontSize={10} fontWeight="600" fill="#15803D">Reminder: Thank you for confirming!</text>
|
<text x={lcx} y={yes1y+16} textAnchor="middle" fontSize={10} fontWeight="600" fill="#15803D">Reminder: Thank you for confirming!</text>
|
||||||
<text x={lcx} y={yes1y+34} textAnchor="middle" fontSize={10} fontWeight="600" fill="#C2410C">Reschedule: What day & time?</text>
|
<text x={lcx} y={yes1y+31} textAnchor="middle" fontSize={10} fontWeight="600" fill="#C2410C">Reschedule: What day & time?</text>
|
||||||
<text x={lcx} y={yes1y+52} textAnchor="middle" fontSize={9} fill="#6B7280" fontStyle="italic">"See you on [date & time]"</text>
|
<text x={lcx} y={yes1y+46} textAnchor="middle" fontSize={10} fontWeight="600" fill="#5B21B6">Recall: What day & time?</text>
|
||||||
<text x={lcx} y={yes1y+70} textAnchor="middle" fontSize={8} fill="#047857">date in reply → Day Check ↘</text>
|
<text x={lcx} y={yes1y+64} textAnchor="middle" fontSize={9} fill="#6B7280" fontStyle="italic">"See you on [date & time]"</text>
|
||||||
|
<text x={lcx} y={yes1y+80} textAnchor="middle" fontSize={8} fill="#047857">date in reply → Day Check ↘</text>
|
||||||
<line x1={lcx} y1={yes1y+yes1h} x2={lcx} y2={yes2y-2} stroke="#9CA3AF" strokeWidth={1.5} markerEnd="url(#rg2ah)" />
|
<line x1={lcx} y1={yes1y+yes1h} x2={lcx} y2={yes2y-2} stroke="#9CA3AF" strokeWidth={1.5} markerEnd="url(#rg2ah)" />
|
||||||
|
|
||||||
{/* Yes2: Patient thanks */}
|
{/* Yes2: Patient thanks */}
|
||||||
@@ -191,7 +221,7 @@ function LangGraphFlow() {
|
|||||||
|
|
||||||
{/* Dashed shortcut: YES with date → Day Check */}
|
{/* Dashed shortcut: YES with date → Day Check */}
|
||||||
<line
|
<line
|
||||||
x1={lcx + yesW/2} y1={yes1y + 52}
|
x1={lcx + yesW/2} y1={yes1y + 64}
|
||||||
x2={rcx - noW/2 - 2} y2={no2y + 34}
|
x2={rcx - noW/2 - 2} y2={no2y + 34}
|
||||||
stroke="#047857" strokeWidth={1} strokeDasharray="4 3"
|
stroke="#047857" strokeWidth={1} strokeDasharray="4 3"
|
||||||
markerEnd="url(#rg2short)"
|
markerEnd="url(#rg2short)"
|
||||||
@@ -1011,6 +1041,13 @@ export function AiChatSettingsCard() {
|
|||||||
const [generalFallback, setGeneralFallback] = useState(DEFAULTS.generalFallback);
|
const [generalFallback, setGeneralFallback] = useState(DEFAULTS.generalFallback);
|
||||||
const initialized = useRef(false);
|
const initialized = useRef(false);
|
||||||
|
|
||||||
|
const [callGreeting, setCallGreeting] = useState(CALL_DEFAULTS.greeting);
|
||||||
|
const [callReminderGreeting, setCallReminderGreeting] = useState(CALL_DEFAULTS.reminderGreeting);
|
||||||
|
const [callNewPatientGreeting, setCallNewPatientGreeting] = useState(CALL_DEFAULTS.newPatientGreeting);
|
||||||
|
const [callGeneralFallback, setCallGeneralFallback] = useState(CALL_DEFAULTS.generalFallback);
|
||||||
|
const [callRecallGreeting, setCallRecallGreeting] = useState(CALL_DEFAULTS.recallGreeting);
|
||||||
|
const callInitialized = useRef(false);
|
||||||
|
|
||||||
const [openPhoneReply, setOpenPhoneReply] = useState(false);
|
const [openPhoneReply, setOpenPhoneReply] = useState(false);
|
||||||
|
|
||||||
const { data: advancedSettings } = useQuery<{ openPhoneReply: boolean }>({
|
const { data: advancedSettings } = useQuery<{ openPhoneReply: boolean }>({
|
||||||
@@ -1090,6 +1127,29 @@ export function AiChatSettingsCard() {
|
|||||||
}
|
}
|
||||||
}, [templates]);
|
}, [templates]);
|
||||||
|
|
||||||
|
const { data: callTemplatesData, isLoading: callTemplatesLoading } = useQuery<AiCallTemplates>({
|
||||||
|
queryKey: ["/api/ai/call-templates"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await apiRequest("GET", "/api/ai/call-templates");
|
||||||
|
if (!res.ok) throw new Error("Failed to load call templates");
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
staleTime: Infinity,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Seed AI call templates
|
||||||
|
useEffect(() => {
|
||||||
|
if (callTemplatesData && !callInitialized.current) {
|
||||||
|
callInitialized.current = true;
|
||||||
|
setCallGreeting(callTemplatesData.greeting || CALL_DEFAULTS.greeting);
|
||||||
|
setCallReminderGreeting(callTemplatesData.reminderGreeting || CALL_DEFAULTS.reminderGreeting);
|
||||||
|
setCallNewPatientGreeting(callTemplatesData.newPatientGreeting || CALL_DEFAULTS.newPatientGreeting);
|
||||||
|
setCallGeneralFallback(callTemplatesData.generalFallback || CALL_DEFAULTS.generalFallback);
|
||||||
|
setCallRecallGreeting(callTemplatesData.recallGreeting || CALL_DEFAULTS.recallGreeting);
|
||||||
|
}
|
||||||
|
}, [callTemplatesData]);
|
||||||
|
|
||||||
// Seed SMS template list — fall back to the saved reminderSms if list is empty
|
// Seed SMS template list — fall back to the saved reminderSms if list is empty
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (smsTemplateListData && !smsInitialized.current) {
|
if (smsTemplateListData && !smsInitialized.current) {
|
||||||
@@ -1120,6 +1180,24 @@ export function AiChatSettingsCard() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const saveCallMutation = useMutation({
|
||||||
|
mutationFn: async (data: AiCallTemplates) => {
|
||||||
|
const res = await apiRequest("PUT", "/api/ai/call-templates", data);
|
||||||
|
if (!res.ok) {
|
||||||
|
const err = await res.json().catch(() => null);
|
||||||
|
throw new Error(err?.message || "Failed to save call templates");
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["/api/ai/call-templates"] });
|
||||||
|
toast({ title: "Templates saved", description: "AI call templates have been updated." });
|
||||||
|
},
|
||||||
|
onError: (err: any) => {
|
||||||
|
toast({ title: "Error", description: err?.message, variant: "destructive" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const saveSmsListMutation = useMutation({
|
const saveSmsListMutation = useMutation({
|
||||||
mutationFn: async (list: SmsTemplate[]) => {
|
mutationFn: async (list: SmsTemplate[]) => {
|
||||||
const res = await apiRequest("PUT", "/api/twilio/sms-template-list", list);
|
const res = await apiRequest("PUT", "/api/twilio/sms-template-list", list);
|
||||||
@@ -1147,6 +1225,17 @@ export function AiChatSettingsCard() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCallSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
saveCallMutation.mutate({
|
||||||
|
greeting: callGreeting.trim() || CALL_DEFAULTS.greeting,
|
||||||
|
reminderGreeting: callReminderGreeting.trim() || CALL_DEFAULTS.reminderGreeting,
|
||||||
|
newPatientGreeting: callNewPatientGreeting.trim() || CALL_DEFAULTS.newPatientGreeting,
|
||||||
|
generalFallback: callGeneralFallback.trim() || CALL_DEFAULTS.generalFallback,
|
||||||
|
recallGreeting: callRecallGreeting.trim() || CALL_DEFAULTS.recallGreeting,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const updateSmsTemplate = (idx: number, field: "name" | "body", value: string) => {
|
const updateSmsTemplate = (idx: number, field: "name" | "body", value: string) => {
|
||||||
setSmsTemplates((prev) => prev.map((t, i) => i === idx ? { ...t, [field]: value } : t));
|
setSmsTemplates((prev) => prev.map((t, i) => i === idx ? { ...t, [field]: value } : t));
|
||||||
};
|
};
|
||||||
@@ -1193,6 +1282,54 @@ export function AiChatSettingsCard() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const callTemplateFields = [
|
||||||
|
{
|
||||||
|
key: "callGreeting",
|
||||||
|
icon: <PhoneCall className="h-4 w-4 text-primary" />,
|
||||||
|
label: "Call an existing patient for an appointment",
|
||||||
|
description: "Lisa's opening line when placing an AI Call to a patient — use {firstName} and {officeName} as placeholders.",
|
||||||
|
value: callGreeting,
|
||||||
|
onChange: setCallGreeting,
|
||||||
|
placeholder: CALL_DEFAULTS.greeting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "callRecall",
|
||||||
|
icon: <Stethoscope className="h-4 w-4 text-primary" />,
|
||||||
|
label: "Recall Call: Existing Patient Check-Up Offer",
|
||||||
|
description: "Lisa's opening line for a Patient Connection \"AI Call\" — offers an existing patient a routine exam & cleaning (most insurance covers it at no cost) and asks if they'd like to book. Use {firstName} and {officeName} as placeholders.",
|
||||||
|
value: callRecallGreeting,
|
||||||
|
onChange: setCallRecallGreeting,
|
||||||
|
placeholder: CALL_DEFAULTS.recallGreeting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "callReminder",
|
||||||
|
icon: <CalendarCheck className="h-4 w-4 text-primary" />,
|
||||||
|
label: "Reminder Call Reply",
|
||||||
|
description: "Spoken after the call's opening greeting when the AI is following up on an appointment reminder — keep it short, it's read aloud, not texted.",
|
||||||
|
value: callReminderGreeting,
|
||||||
|
onChange: setCallReminderGreeting,
|
||||||
|
placeholder: CALL_DEFAULTS.reminderGreeting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "callNewPatient",
|
||||||
|
icon: <UserPlus className="h-4 w-4 text-primary" />,
|
||||||
|
label: "New Patient Call Reply",
|
||||||
|
description: "Spoken when the AI is walking a new patient through scheduling on a live call.",
|
||||||
|
value: callNewPatientGreeting,
|
||||||
|
onChange: setCallNewPatientGreeting,
|
||||||
|
placeholder: CALL_DEFAULTS.newPatientGreeting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "callGeneral",
|
||||||
|
icon: <MessageCircle className="h-4 w-4 text-primary" />,
|
||||||
|
label: "General Call Fallback",
|
||||||
|
description: "Spoken when the AI cannot determine the context of what the patient said on a call.",
|
||||||
|
value: callGeneralFallback,
|
||||||
|
onChange: setCallGeneralFallback,
|
||||||
|
placeholder: CALL_DEFAULTS.generalFallback,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|
||||||
@@ -1403,6 +1540,86 @@ export function AiChatSettingsCard() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* ── Section 1b: Call Templates ───────────────────────────── */}
|
||||||
|
<Card>
|
||||||
|
<CardContent className="py-6 space-y-5">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<PhoneCall className="h-5 w-5 text-primary" />
|
||||||
|
<h3 className="text-lg font-semibold">Call Templates</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Customize what Lisa says on live AI phone calls — kept separate from the SMS wording
|
||||||
|
above since spoken conversation reads differently than a text message. Use{" "}
|
||||||
|
<code className="bg-muted px-1 py-0.5 rounded text-xs font-mono">{"{officeName}"}</code>{" "}
|
||||||
|
and{" "}
|
||||||
|
<code className="bg-muted px-1 py-0.5 rounded text-xs font-mono">{"{firstName}"}</code>{" "}
|
||||||
|
as placeholders.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{officeName && (
|
||||||
|
<div className="flex items-center gap-2 text-xs text-muted-foreground bg-muted/50 rounded px-3 py-2">
|
||||||
|
<Info className="h-3.5 w-3.5 flex-shrink-0" />
|
||||||
|
<span>
|
||||||
|
<span className="font-medium">{"{officeName}"}</span> will display as{" "}
|
||||||
|
<span className="font-medium text-foreground">"{officeName}"</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{callTemplatesLoading ? (
|
||||||
|
<p className="text-sm text-muted-foreground">Loading templates...</p>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={handleCallSubmit} className="space-y-5">
|
||||||
|
{callTemplateFields.map((f) => (
|
||||||
|
<div key={f.key} className="space-y-1.5">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{f.icon}
|
||||||
|
<span className="text-sm font-medium">{f.label}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">{f.description}</p>
|
||||||
|
<Textarea
|
||||||
|
value={f.value}
|
||||||
|
onChange={(e) => f.onChange(e.target.value)}
|
||||||
|
placeholder={f.placeholder}
|
||||||
|
rows={3}
|
||||||
|
className="text-sm resize-none"
|
||||||
|
/>
|
||||||
|
{officeName && f.value.includes("{officeName}") && (
|
||||||
|
<p className="text-xs text-muted-foreground italic pl-1">
|
||||||
|
Preview: {previewTemplate(f.value, officeName)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 pt-1">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={saveCallMutation.isPending}
|
||||||
|
className="bg-teal-600 hover:bg-teal-700 text-white"
|
||||||
|
>
|
||||||
|
{saveCallMutation.isPending ? "Saving..." : "Save Templates"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
className="text-xs text-muted-foreground"
|
||||||
|
onClick={() => {
|
||||||
|
setCallGreeting(CALL_DEFAULTS.greeting);
|
||||||
|
setCallReminderGreeting(CALL_DEFAULTS.reminderGreeting);
|
||||||
|
setCallNewPatientGreeting(CALL_DEFAULTS.newPatientGreeting);
|
||||||
|
setCallGeneralFallback(CALL_DEFAULTS.generalFallback);
|
||||||
|
setCallRecallGreeting(CALL_DEFAULTS.recallGreeting);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset to defaults
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* ── Section 2: LangGraph Flow ────────────────────────────── */}
|
{/* ── Section 2: LangGraph Flow ────────────────────────────── */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="py-6 space-y-5">
|
<CardContent className="py-6 space-y-5">
|
||||||
|
|||||||
@@ -114,13 +114,12 @@ interface ScheduledAppointment {
|
|||||||
function deriveInsuranceSiteKey(provider: string | null | undefined): string {
|
function deriveInsuranceSiteKey(provider: string | null | undefined): string {
|
||||||
const p = (provider || "").toLowerCase().trim();
|
const p = (provider || "").toLowerCase().trim();
|
||||||
if (!p) return "";
|
if (!p) return "";
|
||||||
if (p.includes("masshealth") || p === "mh" || p === "mass health") return "MH";
|
if (p.includes("masshealth") || p === "mh" || p === "mass health" || p.includes("cmsp")) return "MH";
|
||||||
if (p.includes("commonwealth care alliance") || p === "cca") return "CCA";
|
if (p.includes("commonwealth care alliance") || p === "cca") return "CCA";
|
||||||
if (p.includes("ddma") || p.includes("delta dental ma")) return "DDMA";
|
if (p.includes("ddma") || p.includes("delta dental ma")) return "DDMA";
|
||||||
if (p.includes("delta ins") || p === "deltains") return "DeltaIns";
|
if (p.includes("delta ins") || p === "deltains") return "DeltaIns";
|
||||||
if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TuftsSCO";
|
if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TuftsSCO";
|
||||||
if ((p.includes("united") && p.includes("sco")) || p === "unitedsco") return "UnitedSCO";
|
if ((p.includes("united") && p.includes("sco")) || p === "unitedsco") return "UnitedSCO";
|
||||||
if (p.includes("cmsp")) return "CMSP";
|
|
||||||
if (p.includes("bcbs") || p.includes("blue cross")) return "BCBS";
|
if (p.includes("bcbs") || p.includes("blue cross")) return "BCBS";
|
||||||
if (p.includes("united aapr") || p === "unitedaapr") return "UnitedAAPR";
|
if (p.includes("united aapr") || p === "unitedaapr") return "UnitedAAPR";
|
||||||
if (p.includes("aetna")) return "Aetna";
|
if (p.includes("aetna")) return "Aetna";
|
||||||
@@ -199,6 +198,11 @@ export default function AppointmentsPage() {
|
|||||||
const [isClaimingAiColumn, setIsClaimingAiColumn] = useState(false);
|
const [isClaimingAiColumn, setIsClaimingAiColumn] = useState(false);
|
||||||
const [autoClaimEnabled, setAutoClaimEnabled] = useState(false);
|
const [autoClaimEnabled, setAutoClaimEnabled] = useState(false);
|
||||||
const [autoClaimTime, setAutoClaimTime] = useState("19:00");
|
const [autoClaimTime, setAutoClaimTime] = useState("19:00");
|
||||||
|
const [autoConfirmEnabled, setAutoConfirmEnabled] = useState(
|
||||||
|
() => localStorage.getItem("ai_claim_auto_confirm") === "1"
|
||||||
|
);
|
||||||
|
// True while the current AI claim run should advance without manual Confirm clicks
|
||||||
|
const [aiClaimAutoRun, setAiClaimAutoRun] = useState(false);
|
||||||
const autoClaimFiredHourRef = useRef<number | null>(null);
|
const autoClaimFiredHourRef = useRef<number | null>(null);
|
||||||
const [aiClaimModalOpen, setAiClaimModalOpen] = useState(false);
|
const [aiClaimModalOpen, setAiClaimModalOpen] = useState(false);
|
||||||
const [aiClaimQueue, setAiClaimQueue] = useState<ScheduledAppointment[]>([]);
|
const [aiClaimQueue, setAiClaimQueue] = useState<ScheduledAppointment[]>([]);
|
||||||
@@ -488,6 +492,7 @@ export default function AppointmentsPage() {
|
|||||||
sessionStorage.setItem("ai_claim_queue", JSON.stringify({ ...parsed, pendingResume: false }));
|
sessionStorage.setItem("ai_claim_queue", JSON.stringify({ ...parsed, pendingResume: false }));
|
||||||
setAiClaimQueue(queue);
|
setAiClaimQueue(queue);
|
||||||
setAiClaimCurrentIndex(0);
|
setAiClaimCurrentIndex(0);
|
||||||
|
setAiClaimAutoRun(!!parsed.autoRun);
|
||||||
setAiClaimModalOpen(true);
|
setAiClaimModalOpen(true);
|
||||||
processAiClaimAtIndex(queue, 0);
|
processAiClaimAtIndex(queue, 0);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -1652,13 +1657,16 @@ export default function AppointmentsPage() {
|
|||||||
toast({ title: "No unclaimed appointments", description: "All appointments in the selected columns are already claimed." });
|
toast({ title: "No unclaimed appointments", description: "All appointments in the selected columns are already claimed." });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const autoRun = autoConfirmEnabled || autoClaimEnabled;
|
||||||
sessionStorage.setItem("ai_claim_queue", JSON.stringify({
|
sessionStorage.setItem("ai_claim_queue", JSON.stringify({
|
||||||
appointments: unclaimed,
|
appointments: unclaimed,
|
||||||
date: formattedSelectedDate,
|
date: formattedSelectedDate,
|
||||||
pendingResume: false,
|
pendingResume: false,
|
||||||
|
autoRun,
|
||||||
}));
|
}));
|
||||||
setAiClaimQueue(unclaimed);
|
setAiClaimQueue(unclaimed);
|
||||||
setAiClaimCurrentIndex(0);
|
setAiClaimCurrentIndex(0);
|
||||||
|
setAiClaimAutoRun(autoRun);
|
||||||
setAiClaimModalOpen(true);
|
setAiClaimModalOpen(true);
|
||||||
await processAiClaimAtIndex(unclaimed, 0);
|
await processAiClaimAtIndex(unclaimed, 0);
|
||||||
};
|
};
|
||||||
@@ -1673,6 +1681,7 @@ export default function AppointmentsPage() {
|
|||||||
appointments: fromCurrent,
|
appointments: fromCurrent,
|
||||||
date: formattedSelectedDate,
|
date: formattedSelectedDate,
|
||||||
pendingResume: true,
|
pendingResume: true,
|
||||||
|
autoRun: aiClaimAutoRun,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
sessionStorage.removeItem("ai_claim_queue");
|
sessionStorage.removeItem("ai_claim_queue");
|
||||||
@@ -1760,6 +1769,28 @@ export default function AppointmentsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Auto mode: advance the AI claim queue without manual Confirm clicks.
|
||||||
|
// Confirms when codes matched, skips otherwise (incl. CDT clarifications, which need human input).
|
||||||
|
useEffect(() => {
|
||||||
|
if (!aiClaimModalOpen || !aiClaimAutoRun || isAiClaimProcessing) return;
|
||||||
|
if (aiClaimCdtClarification) {
|
||||||
|
const t = setTimeout(() => {
|
||||||
|
setAiClaimCdtClarification(null);
|
||||||
|
handleAiClaimSkip();
|
||||||
|
}, 1500);
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}
|
||||||
|
if (!aiClaimCurrentData) return;
|
||||||
|
const t = setTimeout(() => {
|
||||||
|
if (aiClaimCurrentData.matchedCodes.length > 0) {
|
||||||
|
handleAiClaimConfirm();
|
||||||
|
} else {
|
||||||
|
handleAiClaimSkip();
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}, [aiClaimModalOpen, aiClaimAutoRun, isAiClaimProcessing, aiClaimCurrentData, aiClaimCdtClarification]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SeleniumTaskBanner
|
<SeleniumTaskBanner
|
||||||
@@ -1907,6 +1938,19 @@ export default function AppointmentsPage() {
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
|
<div className="flex items-center gap-2 border-l pl-3 ml-1">
|
||||||
|
<Switch
|
||||||
|
id="auto-confirm-toggle"
|
||||||
|
checked={autoConfirmEnabled}
|
||||||
|
onCheckedChange={(v) => {
|
||||||
|
setAutoConfirmEnabled(v);
|
||||||
|
localStorage.setItem("ai_claim_auto_confirm", v ? "1" : "0");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="auto-confirm-toggle" className="text-sm font-medium cursor-pointer whitespace-nowrap">
|
||||||
|
Auto Confirm
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-2 border-l pl-3 ml-1">
|
<div className="flex items-center gap-2 border-l pl-3 ml-1">
|
||||||
<Switch
|
<Switch
|
||||||
id="auto-claim-toggle"
|
id="auto-claim-toggle"
|
||||||
@@ -2485,6 +2529,9 @@ export default function AppointmentsPage() {
|
|||||||
AI Claim Queue
|
AI Claim Queue
|
||||||
</h3>
|
</h3>
|
||||||
<span className="text-xs text-gray-500 font-medium">
|
<span className="text-xs text-gray-500 font-medium">
|
||||||
|
{aiClaimAutoRun && (
|
||||||
|
<span className="text-teal-600 mr-2">Auto</span>
|
||||||
|
)}
|
||||||
{aiClaimCurrentIndex + 1} / {aiClaimQueue.length}
|
{aiClaimCurrentIndex + 1} / {aiClaimQueue.length}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -122,6 +123,7 @@ export default function InsuranceStatusPage() {
|
|||||||
const [isCheckingEligibilityHistory, setIsCheckingEligibilityHistory] =
|
const [isCheckingEligibilityHistory, setIsCheckingEligibilityHistory] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const [isCheckingCMSP, setIsCheckingCMSP] = useState(false);
|
const [isCheckingCMSP, setIsCheckingCMSP] = useState(false);
|
||||||
|
const [showPdfViewerOnComplete, setShowPdfViewerOnComplete] = useState(false);
|
||||||
|
|
||||||
// AI Call Insurance section
|
// AI Call Insurance section
|
||||||
const [aiCallOpen, setAiCallOpen] = useState(false);
|
const [aiCallOpen, setAiCallOpen] = useState(false);
|
||||||
@@ -710,7 +712,7 @@ export default function InsuranceStatusPage() {
|
|||||||
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: msg }))
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: msg }))
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "success", message: "CMSP PDFs saved to Documents." }));
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "success", message: "MH PDFs saved to Documents." }));
|
||||||
toast({
|
toast({
|
||||||
title: "Done",
|
title: "Done",
|
||||||
description: "Eligibility, history, and accumulator PDFs saved.",
|
description: "Eligibility, history, and accumulator PDFs saved.",
|
||||||
@@ -727,14 +729,14 @@ export default function InsuranceStatusPage() {
|
|||||||
// Open 4-panel modal
|
// Open 4-panel modal
|
||||||
if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId || jobResult.accumulatorPdfFileId) {
|
if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId || jobResult.accumulatorPdfFileId) {
|
||||||
setCmspEligibilityPdfId(jobResult.pdfFileId ?? null);
|
setCmspEligibilityPdfId(jobResult.pdfFileId ?? null);
|
||||||
setCmspEligibilityFilename(jobResult.pdfFilename ?? `cmsp_eligibility_${memberId}.pdf`);
|
setCmspEligibilityFilename(jobResult.pdfFilename ?? `mh_eligibility_${memberId}.pdf`);
|
||||||
setCmspMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ?? null);
|
setCmspMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ?? null);
|
||||||
setCmspMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `cmsp_member_details_${memberId}.pdf`);
|
setCmspMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `mh_member_details_${memberId}.pdf`);
|
||||||
setCmspHistoryPdfId(jobResult.historyPdfFileId ?? null);
|
setCmspHistoryPdfId(jobResult.historyPdfFileId ?? null);
|
||||||
setCmspHistoryFilename(jobResult.historyPdfFilename ?? `cmsp_history_${memberId}.pdf`);
|
setCmspHistoryFilename(jobResult.historyPdfFilename ?? `mh_history_${memberId}.pdf`);
|
||||||
setCmspAccumulatorPdfId(jobResult.accumulatorPdfFileId ?? null);
|
setCmspAccumulatorPdfId(jobResult.accumulatorPdfFileId ?? null);
|
||||||
setCmspAccumulatorFilename(jobResult.accumulatorPdfFilename ?? `cmsp_accumulator_${memberId}.pdf`);
|
setCmspAccumulatorFilename(jobResult.accumulatorPdfFilename ?? `mh_accumulator_${memberId}.pdf`);
|
||||||
setCmspPreviewOpen(true);
|
if (showPdfViewerOnComplete) setCmspPreviewOpen(true);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
|
||||||
@@ -750,11 +752,9 @@ export default function InsuranceStatusPage() {
|
|||||||
const check = pendingAutoCheck.current;
|
const check = pendingAutoCheck.current;
|
||||||
pendingAutoCheck.current = null;
|
pendingAutoCheck.current = null;
|
||||||
if (check === "mh" || check === "mh-history") {
|
if (check === "mh" || check === "mh-history") {
|
||||||
if (isUnder14(dateOfBirth)) {
|
// Temporarily disabled: selenium_MH_eligibilityHistoryCheckWorker.py is no
|
||||||
handleCMSPButton();
|
// longer used — all MH eligibility checks route through the CMSP/MH worker.
|
||||||
} else {
|
handleCMSPButton();
|
||||||
handleMHEligibilityHistoryButton();
|
|
||||||
}
|
|
||||||
} else if (check === "cmsp") {
|
} else if (check === "cmsp") {
|
||||||
handleCMSPButton();
|
handleCMSPButton();
|
||||||
} else {
|
} else {
|
||||||
@@ -842,12 +842,13 @@ export default function InsuranceStatusPage() {
|
|||||||
const patient = await lookupRes.json();
|
const patient = await lookupRes.json();
|
||||||
if (!patient?.id) return;
|
if (!patient?.id) return;
|
||||||
|
|
||||||
// The eligibility check that just ran saved the result to patient.status — only book
|
// The eligibility check that just ran saved the result to patient.status — only skip
|
||||||
// the appointment when insurance came back ACTIVE.
|
// booking when insurance came back explicitly INACTIVE. ACTIVE and UNKNOWN (e.g. a
|
||||||
if (patient.status !== "ACTIVE") {
|
// scrape that couldn't confirm status but didn't find an ineligible record) still book.
|
||||||
|
if (patient.status === "INACTIVE") {
|
||||||
toast({
|
toast({
|
||||||
title: "No appointment created",
|
title: "No appointment created",
|
||||||
description: `${patient.firstName ?? ""} ${patient.lastName ?? ""} insurance is ${patient.status === "INACTIVE" ? "inactive" : "not active"} — please add manually if needed.`.trim(),
|
description: `${patient.firstName ?? ""} ${patient.lastName ?? ""} insurance is inactive — please add manually if needed.`.trim(),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -1020,82 +1021,7 @@ export default function InsuranceStatusPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col-2 gap-4">
|
|
||||||
<Button
|
|
||||||
onClick={() => handleMHEligibilityButton()}
|
|
||||||
className="w-full"
|
|
||||||
disabled={isCheckingEligibilityStatus}
|
|
||||||
>
|
|
||||||
{isCheckingEligibilityStatus ? (
|
|
||||||
<>
|
|
||||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
|
||||||
Processing...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<CheckCircle className="h-4 w-4 mr-2" />
|
|
||||||
MH Eligibility
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={() => handleMHEligibilityAppointmentButton()}
|
|
||||||
className="w-full"
|
|
||||||
disabled={isCheckingEligibilityAppointment}
|
|
||||||
>
|
|
||||||
{isCheckingEligibilityAppointment ? (
|
|
||||||
<>
|
|
||||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
|
||||||
Processing...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<CheckCircle className="h-4 w-4 mr-2" />
|
|
||||||
MH Eligibility&Appointment
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={() => handleMHEligibilityClaimsPreAuthButton()}
|
|
||||||
className="w-full"
|
|
||||||
disabled={isCheckingEligibilityClaimsPreAuth}
|
|
||||||
>
|
|
||||||
{isCheckingEligibilityClaimsPreAuth ? (
|
|
||||||
<>
|
|
||||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
|
||||||
Processing...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<CheckCircle className="h-4 w-4 mr-2" />
|
|
||||||
MH Eligibility&Claims/PreAuth
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col-2 gap-4 mt-4">
|
<div className="flex flex-col-2 gap-4 mt-4">
|
||||||
<Button
|
|
||||||
id="eligibility-mh-history"
|
|
||||||
className="w-full"
|
|
||||||
disabled={isCheckingEligibilityHistory}
|
|
||||||
onClick={() => handleMHEligibilityHistoryButton()}
|
|
||||||
>
|
|
||||||
{isCheckingEligibilityHistory ? (
|
|
||||||
<>
|
|
||||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
|
||||||
Processing...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<CheckCircle className="h-4 w-4 mr-2" />
|
|
||||||
MH Eligibility & History
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
id="eligibility-cmsp"
|
id="eligibility-cmsp"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
@@ -1110,12 +1036,23 @@ export default function InsuranceStatusPage() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<CheckCircle className="h-4 w-4 mr-2" />
|
<CheckCircle className="h-4 w-4 mr-2" />
|
||||||
CMSP Eligibility & History & Remaining
|
MH Eligibility & Remaining & History
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 mt-2">
|
||||||
|
<Switch
|
||||||
|
id="mh-show-pdf-viewer"
|
||||||
|
checked={showPdfViewerOnComplete}
|
||||||
|
onCheckedChange={setShowPdfViewerOnComplete}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="mh-show-pdf-viewer" className="text-sm text-muted-foreground font-normal">
|
||||||
|
Show PDF viewer after check completes
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* TEMP PROVIDER BUTTONS */}
|
{/* TEMP PROVIDER BUTTONS */}
|
||||||
<div id="eligibility-other-providers" className="space-y-4 mt-6">
|
<div id="eligibility-other-providers" className="space-y-4 mt-6">
|
||||||
<h3 className="text-sm font-medium text-muted-foreground">
|
<h3 className="text-sm font-medium text-muted-foreground">
|
||||||
@@ -1463,6 +1400,7 @@ export default function InsuranceStatusPage() {
|
|||||||
allowDelete={true}
|
allowDelete={true}
|
||||||
allowCheckbox={true}
|
allowCheckbox={true}
|
||||||
allowEdit={true}
|
allowEdit={true}
|
||||||
|
showEligibilityColumns={true}
|
||||||
onSelectPatient={setSelectedPatient}
|
onSelectPatient={setSelectedPatient}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -1479,7 +1417,6 @@ export default function InsuranceStatusPage() {
|
|||||||
}}
|
}}
|
||||||
pdfId={previewPdfId ?? undefined}
|
pdfId={previewPdfId ?? undefined}
|
||||||
fallbackFilename={previewFallbackFilename ?? undefined}
|
fallbackFilename={previewFallbackFilename ?? undefined}
|
||||||
autoDownload
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 4-panel modal for CMSP — eligibility, member details, history, accumulator */}
|
{/* 4-panel modal for CMSP — eligibility, member details, history, accumulator */}
|
||||||
@@ -1496,13 +1433,12 @@ export default function InsuranceStatusPage() {
|
|||||||
setCmspAccumulatorPdfId(null);
|
setCmspAccumulatorPdfId(null);
|
||||||
setCmspAccumulatorFilename(null);
|
setCmspAccumulatorFilename(null);
|
||||||
}}
|
}}
|
||||||
title="CMSP Eligibility, History & Remaining"
|
title="MH Eligibility, Remaining & History"
|
||||||
panels={[
|
panels={[
|
||||||
{
|
{
|
||||||
pdfId: cmspEligibilityPdfId,
|
pdfId: cmspEligibilityPdfId,
|
||||||
fallbackFilename: cmspEligibilityFilename,
|
fallbackFilename: cmspEligibilityFilename,
|
||||||
label: "Eligibility",
|
label: "Eligibility",
|
||||||
autoDownload: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pdfId: cmspMemberDetailsPdfId,
|
pdfId: cmspMemberDetailsPdfId,
|
||||||
@@ -1540,7 +1476,6 @@ export default function InsuranceStatusPage() {
|
|||||||
pdfId: dualEligibilityPdfId,
|
pdfId: dualEligibilityPdfId,
|
||||||
fallbackFilename: dualEligibilityFilename,
|
fallbackFilename: dualEligibilityFilename,
|
||||||
label: "Eligibility",
|
label: "Eligibility",
|
||||||
autoDownload: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pdfId: dualMemberDetailsPdfId,
|
pdfId: dualMemberDetailsPdfId,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
Send,
|
Send,
|
||||||
X,
|
X,
|
||||||
MoonStar,
|
MoonStar,
|
||||||
|
Bot,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { SmsTemplateDialog } from "@/components/patient-connection/sms-template-diaog";
|
import { SmsTemplateDialog } from "@/components/patient-connection/sms-template-diaog";
|
||||||
import { MessageThread } from "@/components/patient-connection/message-thread";
|
import { MessageThread } from "@/components/patient-connection/message-thread";
|
||||||
@@ -103,6 +104,32 @@ export default function PatientConnectionPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const makeAiCallMutation = useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
to,
|
||||||
|
patientId,
|
||||||
|
}: {
|
||||||
|
to: string;
|
||||||
|
patientId: number;
|
||||||
|
}) => {
|
||||||
|
return apiRequest("POST", "/api/twilio/make-ai-call", { to, patientId, purpose: "recall" });
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
title: "AI Call Started",
|
||||||
|
description: "Lisa is calling the patient now.",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast({
|
||||||
|
title: "AI Call Failed",
|
||||||
|
description:
|
||||||
|
error.message || "Unable to place the AI call. Please try again.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Fetch all patients from database
|
// Fetch all patients from database
|
||||||
const { data: patients = [], isLoading } = useQuery<Patient[]>({
|
const { data: patients = [], isLoading } = useQuery<Patient[]>({
|
||||||
queryKey: ["/api/patients"],
|
queryKey: ["/api/patients"],
|
||||||
@@ -139,6 +166,22 @@ export default function PatientConnectionPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle AI-driven call via Twilio
|
||||||
|
const handleAiCall = (patient: Patient) => {
|
||||||
|
if (!patient.phone?.trim()) {
|
||||||
|
toast({
|
||||||
|
title: "No Phone Number",
|
||||||
|
description: "This patient does not have a phone number on file.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
makeAiCallMutation.mutate({
|
||||||
|
to: patient.phone,
|
||||||
|
patientId: Number(patient.id),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Handle sending SMS
|
// Handle sending SMS
|
||||||
const handleSMS = (patient: Patient) => {
|
const handleSMS = (patient: Patient) => {
|
||||||
setSelectedPatient(patient);
|
setSelectedPatient(patient);
|
||||||
@@ -343,6 +386,15 @@ export default function PatientConnectionPage() {
|
|||||||
<Phone className="h-4 w-4 mr-1" />
|
<Phone className="h-4 w-4 mr-1" />
|
||||||
Call
|
Call
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleAiCall(patient)}
|
||||||
|
data-testid={`button-ai-call-${patient.id}`}
|
||||||
|
>
|
||||||
|
<Bot className="h-4 w-4 mr-1" />
|
||||||
|
AI Call
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
google_credentials.json
|
google_credentials.json
|
||||||
|
astute-office-469222-k3-ba714473050d.json
|
||||||
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ class AutomationCMSPEligibilityHistoryRemainingCheck:
|
|||||||
print("No new tab for eligibility; printing current page directly")
|
print("No new tab for eligibility; printing current page directly")
|
||||||
|
|
||||||
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
|
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
|
||||||
pdf_filename = f"cmsp_eligibility_{safe_member}.pdf"
|
pdf_filename = f"mh_eligibility_{safe_member}.pdf"
|
||||||
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
|
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
|
||||||
pdf_bytes = base64.b64decode(pdf_data["data"])
|
pdf_bytes = base64.b64decode(pdf_data["data"])
|
||||||
pdf_path = os.path.join(self.download_dir, pdf_filename)
|
pdf_path = os.path.join(self.download_dir, pdf_filename)
|
||||||
@@ -279,14 +279,57 @@ class AutomationCMSPEligibilityHistoryRemainingCheck:
|
|||||||
|
|
||||||
# ── step 3b — print member details page as PDF via CDP ───────────────────────
|
# ── step 3b — print member details page as PDF via CDP ───────────────────────
|
||||||
|
|
||||||
|
def extract_third_party_insurance(self):
|
||||||
|
"""Reads the "Third Party Liability" table (Policy Number | Coverage Type |
|
||||||
|
Effective Date | Termination Date | Insurer Name | Insurer Payment Order)
|
||||||
|
and returns the Insurer Name(s), or None if the table has no rows
|
||||||
|
(i.e. the member has no third-party/2nd insurance).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
tables = self.driver.find_elements(By.TAG_NAME, "table")
|
||||||
|
for table in tables:
|
||||||
|
rows = table.find_elements(By.TAG_NAME, "tr")
|
||||||
|
if not rows:
|
||||||
|
continue
|
||||||
|
|
||||||
|
header_cells = [
|
||||||
|
c.text.strip().lower()
|
||||||
|
for c in (rows[0].find_elements(By.TAG_NAME, "th") or rows[0].find_elements(By.TAG_NAME, "td"))
|
||||||
|
]
|
||||||
|
if "insurer name" not in header_cells:
|
||||||
|
continue
|
||||||
|
|
||||||
|
insurer_idx = header_cells.index("insurer name")
|
||||||
|
insurers = []
|
||||||
|
for row in rows[1:]:
|
||||||
|
cells = row.find_elements(By.TAG_NAME, "td")
|
||||||
|
if len(cells) <= insurer_idx:
|
||||||
|
continue
|
||||||
|
name = cells[insurer_idx].text.strip()
|
||||||
|
if name:
|
||||||
|
insurers.append(name)
|
||||||
|
|
||||||
|
third_party = ", ".join(insurers) if insurers else None
|
||||||
|
self.extracted_data["third_party_insurance"] = third_party
|
||||||
|
print(f"[extract_third_party_insurance] {third_party!r}")
|
||||||
|
return third_party
|
||||||
|
|
||||||
|
print("[extract_third_party_insurance] No 'Third Party Liability' table found")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[extract_third_party_insurance] failed: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def step3b_member_details_pdf(self):
|
def step3b_member_details_pdf(self):
|
||||||
wait = WebDriverWait(self.driver, 30)
|
wait = WebDriverWait(self.driver, 30)
|
||||||
try:
|
try:
|
||||||
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
|
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
|
self.extract_third_party_insurance()
|
||||||
|
|
||||||
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
|
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
|
||||||
pdf_filename = f"cmsp_member_details_{safe_member}.pdf"
|
pdf_filename = f"mh_member_details_{safe_member}.pdf"
|
||||||
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
|
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
|
||||||
pdf_bytes = base64.b64decode(pdf_data["data"])
|
pdf_bytes = base64.b64decode(pdf_data["data"])
|
||||||
pdf_path = os.path.join(self.download_dir, pdf_filename)
|
pdf_path = os.path.join(self.download_dir, pdf_filename)
|
||||||
@@ -341,7 +384,7 @@ class AutomationCMSPEligibilityHistoryRemainingCheck:
|
|||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
|
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
|
||||||
pdf_filename = f"cmsp_history_{safe_member}.pdf"
|
pdf_filename = f"mh_history_{safe_member}.pdf"
|
||||||
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
|
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
|
||||||
pdf_bytes = base64.b64decode(pdf_data["data"])
|
pdf_bytes = base64.b64decode(pdf_data["data"])
|
||||||
pdf_path = os.path.join(self.download_dir, pdf_filename)
|
pdf_path = os.path.join(self.download_dir, pdf_filename)
|
||||||
@@ -353,7 +396,68 @@ class AutomationCMSPEligibilityHistoryRemainingCheck:
|
|||||||
print(f"[step5_service_history_pdf] failed: {e}")
|
print(f"[step5_service_history_pdf] failed: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# ── step 6 — Tab B: accumulator → wait for data → CDP print ─────────────────
|
# ── step 6 — Tab B: accumulator → extract remaining → CDP print ─────────────
|
||||||
|
|
||||||
|
def extract_remaining_amount(self):
|
||||||
|
"""Reads the accumulator benefits table and returns the Remaining amount.
|
||||||
|
|
||||||
|
Layout (see sample PDF "Accumulator Details"):
|
||||||
|
Type | Level | Network | Benefit Period | Period | Limit | Applied | Remaining
|
||||||
|
Prefers the Type="Maximum", Level="Individual" row; falls back to the
|
||||||
|
first data row with a non-empty Remaining cell if that combination
|
||||||
|
isn't found (payer may only return one row, or reorder columns).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
wait = WebDriverWait(self.driver, 15)
|
||||||
|
wait.until(EC.presence_of_element_located((By.TAG_NAME, "table")))
|
||||||
|
tables = self.driver.find_elements(By.TAG_NAME, "table")
|
||||||
|
|
||||||
|
for table in tables:
|
||||||
|
rows = table.find_elements(By.TAG_NAME, "tr")
|
||||||
|
if not rows:
|
||||||
|
continue
|
||||||
|
|
||||||
|
header_cells = [
|
||||||
|
c.text.strip().lower()
|
||||||
|
for c in (rows[0].find_elements(By.TAG_NAME, "th") or rows[0].find_elements(By.TAG_NAME, "td"))
|
||||||
|
]
|
||||||
|
if "remaining" not in header_cells:
|
||||||
|
continue
|
||||||
|
|
||||||
|
remaining_idx = header_cells.index("remaining")
|
||||||
|
type_idx = header_cells.index("type") if "type" in header_cells else None
|
||||||
|
level_idx = header_cells.index("level") if "level" in header_cells else None
|
||||||
|
|
||||||
|
fallback_value = None
|
||||||
|
best_value = None
|
||||||
|
for row in rows[1:]:
|
||||||
|
cells = row.find_elements(By.TAG_NAME, "td")
|
||||||
|
if len(cells) <= remaining_idx:
|
||||||
|
continue
|
||||||
|
cell_texts = [c.text.strip() for c in cells]
|
||||||
|
value = cell_texts[remaining_idx]
|
||||||
|
if not value:
|
||||||
|
continue
|
||||||
|
if fallback_value is None:
|
||||||
|
fallback_value = value
|
||||||
|
|
||||||
|
type_ok = type_idx is None or "maximum" in cell_texts[type_idx].lower()
|
||||||
|
level_ok = level_idx is None or "individual" in cell_texts[level_idx].lower()
|
||||||
|
if type_ok and level_ok:
|
||||||
|
best_value = value
|
||||||
|
break
|
||||||
|
|
||||||
|
remaining_value = best_value or fallback_value
|
||||||
|
if remaining_value:
|
||||||
|
self.extracted_data["remaining_amount"] = remaining_value
|
||||||
|
print(f"[extract_remaining_amount] Remaining: {remaining_value}")
|
||||||
|
return remaining_value
|
||||||
|
|
||||||
|
print("[extract_remaining_amount] No accumulator table with a 'Remaining' column found")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[extract_remaining_amount] failed: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def step6_accumulator_pdf(self):
|
def step6_accumulator_pdf(self):
|
||||||
wait = WebDriverWait(self.driver, 30)
|
wait = WebDriverWait(self.driver, 30)
|
||||||
@@ -379,8 +483,10 @@ class AutomationCMSPEligibilityHistoryRemainingCheck:
|
|||||||
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
|
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
|
||||||
time.sleep(4)
|
time.sleep(4)
|
||||||
|
|
||||||
|
self.extract_remaining_amount()
|
||||||
|
|
||||||
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
|
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
|
||||||
pdf_filename = f"cmsp_accumulator_{safe_member}.pdf"
|
pdf_filename = f"mh_accumulator_{safe_member}.pdf"
|
||||||
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
|
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
|
||||||
pdf_bytes = base64.b64decode(pdf_data["data"])
|
pdf_bytes = base64.b64decode(pdf_data["data"])
|
||||||
pdf_path = os.path.join(self.download_dir, pdf_filename)
|
pdf_path = os.path.join(self.download_dir, pdf_filename)
|
||||||
|
|||||||
@@ -366,16 +366,26 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
|||||||
search_btn.click()
|
search_btn.click()
|
||||||
print("[DDMA step1] Clicked Search button")
|
print("[DDMA step1] Clicked Search button")
|
||||||
|
|
||||||
# Wait for the Members results table to render
|
# Wait for either the Members results table to render, or (when the
|
||||||
|
# Subscriber ID + DOB match exactly one family member) DDMA to skip
|
||||||
|
# the results list and navigate straight to the patient detail page.
|
||||||
|
# Note: the detail page URL is nested UNDER the search page path
|
||||||
|
# (".../check-patient-benefits-search/check-patient-benefit-details"),
|
||||||
|
# so "check-patient-benefits-search" alone can't distinguish them —
|
||||||
|
# only the detail page's own "-benefit-details" segment can.
|
||||||
try:
|
try:
|
||||||
WebDriverWait(self.driver, 15).until(
|
WebDriverWait(self.driver, 15).until(
|
||||||
EC.presence_of_element_located((By.XPATH, "//table//tbody//tr"))
|
lambda d: d.find_elements(By.XPATH, "//table//tbody//tr")
|
||||||
|
or "check-patient-benefit-details" in d.current_url.lower()
|
||||||
)
|
)
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
except TimeoutException:
|
except TimeoutException:
|
||||||
print("[DDMA step1] Warning: No results table appeared within timeout")
|
print("[DDMA step1] Warning: No results table appeared within timeout")
|
||||||
return "ERROR: INVALID SEARCH CRITERIA"
|
return "ERROR: INVALID SEARCH CRITERIA"
|
||||||
|
|
||||||
|
if "check-patient-benefit-details" in self.driver.current_url.lower():
|
||||||
|
print(f"[DDMA step1] Search navigated directly to detail page: {self.driver.current_url}")
|
||||||
|
|
||||||
print("[DDMA step1] Search completed successfully")
|
print("[DDMA step1] Search completed successfully")
|
||||||
return "Success"
|
return "Success"
|
||||||
|
|
||||||
@@ -390,26 +400,40 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
|||||||
try:
|
try:
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# Wait for results table, then pause for full render
|
|
||||||
try:
|
|
||||||
WebDriverWait(self.driver, 10).until(
|
|
||||||
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
|
|
||||||
)
|
|
||||||
time.sleep(2) # Let the row content fully render after table appears
|
|
||||||
except TimeoutException:
|
|
||||||
print("[DDMA step2] Warning: Results table not found within timeout")
|
|
||||||
|
|
||||||
eligibilityText = "unknown"
|
eligibilityText = "unknown"
|
||||||
foundMemberId = self.memberId or ""
|
foundMemberId = self.memberId or ""
|
||||||
patientName = ""
|
# If the caller already knows this patient (e.g. an existing patient record
|
||||||
|
# being rechecked), use that name directly — no need to scrape/parse it off
|
||||||
|
# the DDMA page, only the eligibility status is actually needed.
|
||||||
|
patientName = f"{self.firstName} {self.lastName}".strip() if self.firstName and self.lastName else ""
|
||||||
|
known_patient_name = bool(patientName)
|
||||||
|
|
||||||
# The Members table lists the whole family sharing this subscriber ID —
|
# DDMA now sometimes skips the family-members results table entirely and
|
||||||
# find the row matching this patient's DOB (falls back to last name, then first row).
|
# navigates straight to the single-patient detail page (when Subscriber ID +
|
||||||
rows = self.driver.find_elements(By.XPATH, "//table//tbody//tr")
|
# DOB match exactly one person). In that case there's no members table to
|
||||||
print(f"[DDMA step2] Found {len(rows)} family member row(s)")
|
# parse here — skip straight to the detail-page fallback extraction below.
|
||||||
|
already_on_detail_page = "check-patient-benefit-details" in self.driver.current_url.lower()
|
||||||
|
|
||||||
|
rows = []
|
||||||
target_row = None
|
target_row = None
|
||||||
|
|
||||||
|
if not already_on_detail_page:
|
||||||
|
# Wait for results table, then pause for full render
|
||||||
|
try:
|
||||||
|
WebDriverWait(self.driver, 10).until(
|
||||||
|
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
|
||||||
|
)
|
||||||
|
time.sleep(2) # Let the row content fully render after table appears
|
||||||
|
except TimeoutException:
|
||||||
|
print("[DDMA step2] Warning: Results table not found within timeout")
|
||||||
|
|
||||||
|
# The Members table lists the whole family sharing this subscriber ID —
|
||||||
|
# find the row matching this patient's DOB (falls back to last name, then first row).
|
||||||
|
rows = self.driver.find_elements(By.XPATH, "//table//tbody//tr")
|
||||||
|
print(f"[DDMA step2] Found {len(rows)} family member row(s)")
|
||||||
|
else:
|
||||||
|
print(f"[DDMA step2] Already on detail page: {self.driver.current_url}")
|
||||||
|
|
||||||
if self.dateOfBirth and rows:
|
if self.dateOfBirth and rows:
|
||||||
try:
|
try:
|
||||||
dob_parts = self.dateOfBirth.split("-")
|
dob_parts = self.dateOfBirth.split("-")
|
||||||
@@ -436,17 +460,17 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
|||||||
if target_row is not None:
|
if target_row is not None:
|
||||||
try:
|
try:
|
||||||
cells = target_row.find_elements(By.XPATH, "./td")
|
cells = target_row.find_elements(By.XPATH, "./td")
|
||||||
if cells:
|
if cells and not known_patient_name:
|
||||||
patientName = cells[0].text.strip()
|
patientName = cells[0].text.strip()
|
||||||
print(f"[DDMA step2] Extracted patient name from row: '{patientName}'")
|
print(f"[DDMA step2] Extracted patient name from row: '{patientName}'")
|
||||||
if len(cells) >= 2 and cells[1].text.strip():
|
if len(cells) >= 2 and cells[1].text.strip():
|
||||||
foundMemberId = cells[1].text.strip()
|
foundMemberId = cells[1].text.strip()
|
||||||
if len(cells) >= 7:
|
if len(cells) >= 7:
|
||||||
status_text = cells[6].text.strip().lower()
|
status_text = cells[6].text.strip().lower()
|
||||||
if "active" in status_text:
|
if "inactive" in status_text or "termed" in status_text:
|
||||||
eligibilityText = "active"
|
|
||||||
elif "inactive" in status_text or "termed" in status_text:
|
|
||||||
eligibilityText = "inactive"
|
eligibilityText = "inactive"
|
||||||
|
elif "active" in status_text:
|
||||||
|
eligibilityText = "active"
|
||||||
else:
|
else:
|
||||||
eligibilityText = status_text
|
eligibilityText = status_text
|
||||||
print(f"[DDMA step2] Found eligibility status: {eligibilityText}")
|
print(f"[DDMA step2] Found eligibility status: {eligibilityText}")
|
||||||
@@ -463,10 +487,10 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
|||||||
else:
|
else:
|
||||||
print("[DDMA step2] Warning: No family member rows found in search results")
|
print("[DDMA step2] Warning: No family member rows found in search results")
|
||||||
|
|
||||||
# Wait for the detail page to load (URL moves away from the search page)
|
# Wait for the detail page to load (URL gains the "-benefit-details" segment)
|
||||||
try:
|
try:
|
||||||
WebDriverWait(self.driver, 15).until(
|
WebDriverWait(self.driver, 15).until(
|
||||||
lambda d: "check-patient-benefits-search" not in d.current_url
|
lambda d: "check-patient-benefit-details" in d.current_url.lower()
|
||||||
)
|
)
|
||||||
print(f"[DDMA step2] Navigated to detail page: {self.driver.current_url}")
|
print(f"[DDMA step2] Navigated to detail page: {self.driver.current_url}")
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -522,11 +546,13 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
|||||||
# forward past accessibility text to the first plausible name value.
|
# forward past accessibility text to the first plausible name value.
|
||||||
for i, line in enumerate(page_lines):
|
for i, line in enumerate(page_lines):
|
||||||
# Case 1: "Name : Value" or "Name: Value" on the same line
|
# Case 1: "Name : Value" or "Name: Value" on the same line
|
||||||
|
# (DDMA's detail page labels this "Subscriber name:", so that
|
||||||
|
# prefix must be accepted rather than excluded)
|
||||||
same_line = re.match(
|
same_line = re.match(
|
||||||
r'^(?:member\s+)?name\s*[:\-]\s*(.+)$', line, re.IGNORECASE
|
r'^(?:member\s+|subscriber\s+)?name\s*[:\-]\s*(.+)$', line, re.IGNORECASE
|
||||||
)
|
)
|
||||||
if same_line and not re.search(
|
if same_line and not re.search(
|
||||||
r'(provider|group|subscriber|plan)\s+name', line, re.IGNORECASE
|
r'(provider|group|plan)\s+name', line, re.IGNORECASE
|
||||||
):
|
):
|
||||||
candidate = same_line.group(1).strip()
|
candidate = same_line.group(1).strip()
|
||||||
if looks_like_name(candidate):
|
if looks_like_name(candidate):
|
||||||
@@ -535,7 +561,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Case 2: "Name" or "Name:" alone on a line — scan forward for value
|
# Case 2: "Name" or "Name:" alone on a line — scan forward for value
|
||||||
elif re.match(r'^(?:member\s+)?name\s*:?\s*$', line, re.IGNORECASE):
|
elif re.match(r'^(?:member\s+|subscriber\s+)?name\s*:?\s*$', line, re.IGNORECASE):
|
||||||
for j in range(i + 1, min(i + 6, len(page_lines))):
|
for j in range(i + 1, min(i + 6, len(page_lines))):
|
||||||
candidate = page_lines[j].strip()
|
candidate = page_lines[j].strip()
|
||||||
if looks_like_name(candidate):
|
if looks_like_name(candidate):
|
||||||
@@ -554,6 +580,27 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
|||||||
if not patientName:
|
if not patientName:
|
||||||
print("[DDMA step2] Could not extract patient name from detail page")
|
print("[DDMA step2] Could not extract patient name from detail page")
|
||||||
|
|
||||||
|
# Fallback: refine eligibility status from the detail page's "Coverage is:"
|
||||||
|
# label if the family-table row extraction didn't set it (e.g. DDMA
|
||||||
|
# navigated straight to the detail page, skipping the members table).
|
||||||
|
if eligibilityText == "unknown":
|
||||||
|
try:
|
||||||
|
page_text = self.driver.execute_script("return document.body.innerText;")
|
||||||
|
coverage_match = re.search(
|
||||||
|
r'coverage\s+is\s*[:\-]?\s*([A-Za-z]+)', page_text, re.IGNORECASE
|
||||||
|
)
|
||||||
|
if coverage_match:
|
||||||
|
status_text = coverage_match.group(1).strip().lower()
|
||||||
|
if "inactive" in status_text or "termed" in status_text:
|
||||||
|
eligibilityText = "inactive"
|
||||||
|
elif "active" in status_text:
|
||||||
|
eligibilityText = "active"
|
||||||
|
else:
|
||||||
|
eligibilityText = status_text
|
||||||
|
print(f"[DDMA step2] Extracted eligibility from 'Coverage is:' label: '{eligibilityText}'")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DDMA step2] Could not extract eligibility from 'Coverage is:' label: {e}")
|
||||||
|
|
||||||
# Wait for the page to be fully ready before capturing PDF
|
# Wait for the page to be fully ready before capturing PDF
|
||||||
try:
|
try:
|
||||||
WebDriverWait(self.driver, 30).until(
|
WebDriverWait(self.driver, 30).until(
|
||||||
|
|||||||
@@ -1096,11 +1096,16 @@ class AutomationUnitedDHClaimSubmit:
|
|||||||
print(f"[UnitedDH Claim] step7: Could not click caret (section may already be open): {e}")
|
print(f"[UnitedDH Claim] step7: Could not click caret (section may already be open): {e}")
|
||||||
|
|
||||||
attached = 0
|
attached = 0
|
||||||
|
seen_paths = set()
|
||||||
for cf in self.claimFiles:
|
for cf in self.claimFiles:
|
||||||
relative_path = cf.get("filePath") or ""
|
relative_path = cf.get("filePath") or ""
|
||||||
if not relative_path:
|
if not relative_path:
|
||||||
print(f"[UnitedDH Claim] step7: Skipping file with no filePath: {cf}")
|
print(f"[UnitedDH Claim] step7: Skipping file with no filePath: {cf}")
|
||||||
continue
|
continue
|
||||||
|
if relative_path in seen_paths:
|
||||||
|
print(f"[UnitedDH Claim] step7: Skipping duplicate file: {relative_path}")
|
||||||
|
continue
|
||||||
|
seen_paths.add(relative_path)
|
||||||
|
|
||||||
abs_path = os.path.normpath(os.path.join(_BACKEND_CWD, relative_path.lstrip("/")))
|
abs_path = os.path.normpath(os.path.join(_BACKEND_CWD, relative_path.lstrip("/")))
|
||||||
if not os.path.isfile(abs_path):
|
if not os.path.isfile(abs_path):
|
||||||
@@ -1113,6 +1118,10 @@ class AutomationUnitedDHClaimSubmit:
|
|||||||
EC.presence_of_element_located((By.XPATH, "//input[@type='file']"))
|
EC.presence_of_element_located((By.XPATH, "//input[@type='file']"))
|
||||||
)
|
)
|
||||||
self.driver.execute_script("arguments[0].removeAttribute('class');", file_input)
|
self.driver.execute_script("arguments[0].removeAttribute('class');", file_input)
|
||||||
|
# The input is a multi-file input and chromedriver APPENDS on send_keys,
|
||||||
|
# so a leftover selection from the previous iteration re-uploads the
|
||||||
|
# earlier file (2 attachments became 3). Clear it first.
|
||||||
|
self.driver.execute_script("arguments[0].value = '';", file_input)
|
||||||
file_input.send_keys(abs_path)
|
file_input.send_keys(abs_path)
|
||||||
WebDriverWait(self.driver, 60).until(
|
WebDriverWait(self.driver, 60).until(
|
||||||
EC.element_to_be_clickable((By.XPATH,
|
EC.element_to_be_clickable((By.XPATH,
|
||||||
|
|||||||
@@ -1035,11 +1035,16 @@ class AutomationUnitedDHPreAuth:
|
|||||||
print(f"[UnitedDH PreAuth] step7: Could not click caret (section may already be open): {e}")
|
print(f"[UnitedDH PreAuth] step7: Could not click caret (section may already be open): {e}")
|
||||||
|
|
||||||
attached = 0
|
attached = 0
|
||||||
|
seen_paths = set()
|
||||||
for cf in self.claimFiles:
|
for cf in self.claimFiles:
|
||||||
relative_path = cf.get("filePath") or ""
|
relative_path = cf.get("filePath") or ""
|
||||||
if not relative_path:
|
if not relative_path:
|
||||||
print(f"[UnitedDH PreAuth] step7: Skipping file with no filePath: {cf}")
|
print(f"[UnitedDH PreAuth] step7: Skipping file with no filePath: {cf}")
|
||||||
continue
|
continue
|
||||||
|
if relative_path in seen_paths:
|
||||||
|
print(f"[UnitedDH PreAuth] step7: Skipping duplicate file: {relative_path}")
|
||||||
|
continue
|
||||||
|
seen_paths.add(relative_path)
|
||||||
|
|
||||||
abs_path = os.path.normpath(os.path.join(_BACKEND_CWD, relative_path.lstrip("/")))
|
abs_path = os.path.normpath(os.path.join(_BACKEND_CWD, relative_path.lstrip("/")))
|
||||||
if not os.path.isfile(abs_path):
|
if not os.path.isfile(abs_path):
|
||||||
@@ -1052,6 +1057,10 @@ class AutomationUnitedDHPreAuth:
|
|||||||
EC.presence_of_element_located((By.XPATH, "//input[@type='file']"))
|
EC.presence_of_element_located((By.XPATH, "//input[@type='file']"))
|
||||||
)
|
)
|
||||||
self.driver.execute_script("arguments[0].removeAttribute('class');", file_input)
|
self.driver.execute_script("arguments[0].removeAttribute('class');", file_input)
|
||||||
|
# The input is a multi-file input and chromedriver APPENDS on send_keys,
|
||||||
|
# so a leftover selection from the previous iteration re-uploads the
|
||||||
|
# earlier file (2 attachments became 3). Clear it first.
|
||||||
|
self.driver.execute_script("arguments[0].value = '';", file_input)
|
||||||
file_input.send_keys(abs_path)
|
file_input.send_keys(abs_path)
|
||||||
WebDriverWait(self.driver, 60).until(
|
WebDriverWait(self.driver, 60).until(
|
||||||
EC.element_to_be_clickable((By.XPATH,
|
EC.element_to_be_clickable((By.XPATH,
|
||||||
|
|||||||
@@ -694,7 +694,7 @@ class AutomationMassHealthClaimsLogin:
|
|||||||
print("DEBUG: Clicking plus button to add new service line")
|
print("DEBUG: Clicking plus button to add new service line")
|
||||||
|
|
||||||
# Step 1: Wait a moment to ensure the previous service line is fully processed
|
# Step 1: Wait a moment to ensure the previous service line is fully processed
|
||||||
time.sleep(3)
|
time.sleep(1)
|
||||||
|
|
||||||
# Step 2: Try multiple approaches to find the plus button
|
# Step 2: Try multiple approaches to find the plus button
|
||||||
plus_button = None
|
plus_button = None
|
||||||
@@ -768,11 +768,11 @@ class AutomationMassHealthClaimsLogin:
|
|||||||
print(f"DEBUG: JavaScript approach failed: {e}")
|
print(f"DEBUG: JavaScript approach failed: {e}")
|
||||||
raise Exception("Could not find plus button with any method")
|
raise Exception("Could not find plus button with any method")
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(0.5)
|
||||||
|
|
||||||
# Step 3: Scroll into view
|
# Step 3: Scroll into view
|
||||||
self.driver.execute_script("arguments[0].scrollIntoView(true);", plus_button)
|
self.driver.execute_script("arguments[0].scrollIntoView(true);", plus_button)
|
||||||
time.sleep(1)
|
time.sleep(0.5)
|
||||||
|
|
||||||
# Step 4: Check if button is enabled
|
# Step 4: Check if button is enabled
|
||||||
is_disabled = plus_button.get_attribute("disabled")
|
is_disabled = plus_button.get_attribute("disabled")
|
||||||
@@ -804,8 +804,8 @@ class AutomationMassHealthClaimsLogin:
|
|||||||
actions = ActionChains(self.driver)
|
actions = ActionChains(self.driver)
|
||||||
actions.move_to_element(plus_button).click().perform()
|
actions.move_to_element(plus_button).click().perform()
|
||||||
|
|
||||||
time.sleep(2) # Wait for new row to be created
|
time.sleep(1) # Wait for new row to be created
|
||||||
|
|
||||||
print("DEBUG: Successfully clicked plus button")
|
print("DEBUG: Successfully clicked plus button")
|
||||||
return "Success"
|
return "Success"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
+4
-4
@@ -17,7 +17,7 @@ server {
|
|||||||
|
|
||||||
# API requests → backend (Authorization header must be explicit or it gets stripped)
|
# API requests → backend (Authorization header must be explicit or it gets stripped)
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://localhost:5000;
|
proxy_pass http://127.0.0.1:5000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
@@ -27,7 +27,7 @@ server {
|
|||||||
|
|
||||||
# Socket.IO → backend (WebSocket upgrade)
|
# Socket.IO → backend (WebSocket upgrade)
|
||||||
location /socket.io/ {
|
location /socket.io/ {
|
||||||
proxy_pass http://localhost:5000;
|
proxy_pass http://127.0.0.1:5000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
@@ -37,7 +37,7 @@ server {
|
|||||||
|
|
||||||
# Everything else → Vite dev server
|
# Everything else → Vite dev server
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://localhost:3000;
|
proxy_pass http://127.0.0.1:3000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
@@ -57,7 +57,7 @@ server {
|
|||||||
client_max_body_size 50m;
|
client_max_body_size 50m;
|
||||||
|
|
||||||
location /api/twilio/ {
|
location /api/twilio/ {
|
||||||
proxy_pass http://localhost:5000;
|
proxy_pass http://127.0.0.1:5000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -152,6 +152,8 @@ exports.Prisma.PatientScalarFieldEnum = {
|
|||||||
medicalConditions: 'medicalConditions',
|
medicalConditions: 'medicalConditions',
|
||||||
preferredLanguage: 'preferredLanguage',
|
preferredLanguage: 'preferredLanguage',
|
||||||
status: 'status',
|
status: 'status',
|
||||||
|
remainingBenefit: 'remainingBenefit',
|
||||||
|
thirdPartyInsurance: 'thirdPartyInsurance',
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
createdAt: 'createdAt',
|
createdAt: 'createdAt',
|
||||||
updatedAt: 'updatedAt'
|
updatedAt: 'updatedAt'
|
||||||
|
|||||||
+145
-1
@@ -6714,6 +6714,8 @@ export namespace Prisma {
|
|||||||
medicalConditions: string | null
|
medicalConditions: string | null
|
||||||
preferredLanguage: string | null
|
preferredLanguage: string | null
|
||||||
status: $Enums.PatientStatus | null
|
status: $Enums.PatientStatus | null
|
||||||
|
remainingBenefit: string | null
|
||||||
|
thirdPartyInsurance: string | null
|
||||||
userId: number | null
|
userId: number | null
|
||||||
createdAt: Date | null
|
createdAt: Date | null
|
||||||
updatedAt: Date | null
|
updatedAt: Date | null
|
||||||
@@ -6738,6 +6740,8 @@ export namespace Prisma {
|
|||||||
medicalConditions: string | null
|
medicalConditions: string | null
|
||||||
preferredLanguage: string | null
|
preferredLanguage: string | null
|
||||||
status: $Enums.PatientStatus | null
|
status: $Enums.PatientStatus | null
|
||||||
|
remainingBenefit: string | null
|
||||||
|
thirdPartyInsurance: string | null
|
||||||
userId: number | null
|
userId: number | null
|
||||||
createdAt: Date | null
|
createdAt: Date | null
|
||||||
updatedAt: Date | null
|
updatedAt: Date | null
|
||||||
@@ -6762,6 +6766,8 @@ export namespace Prisma {
|
|||||||
medicalConditions: number
|
medicalConditions: number
|
||||||
preferredLanguage: number
|
preferredLanguage: number
|
||||||
status: number
|
status: number
|
||||||
|
remainingBenefit: number
|
||||||
|
thirdPartyInsurance: number
|
||||||
userId: number
|
userId: number
|
||||||
createdAt: number
|
createdAt: number
|
||||||
updatedAt: number
|
updatedAt: number
|
||||||
@@ -6798,6 +6804,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: true
|
medicalConditions?: true
|
||||||
preferredLanguage?: true
|
preferredLanguage?: true
|
||||||
status?: true
|
status?: true
|
||||||
|
remainingBenefit?: true
|
||||||
|
thirdPartyInsurance?: true
|
||||||
userId?: true
|
userId?: true
|
||||||
createdAt?: true
|
createdAt?: true
|
||||||
updatedAt?: true
|
updatedAt?: true
|
||||||
@@ -6822,6 +6830,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: true
|
medicalConditions?: true
|
||||||
preferredLanguage?: true
|
preferredLanguage?: true
|
||||||
status?: true
|
status?: true
|
||||||
|
remainingBenefit?: true
|
||||||
|
thirdPartyInsurance?: true
|
||||||
userId?: true
|
userId?: true
|
||||||
createdAt?: true
|
createdAt?: true
|
||||||
updatedAt?: true
|
updatedAt?: true
|
||||||
@@ -6846,6 +6856,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: true
|
medicalConditions?: true
|
||||||
preferredLanguage?: true
|
preferredLanguage?: true
|
||||||
status?: true
|
status?: true
|
||||||
|
remainingBenefit?: true
|
||||||
|
thirdPartyInsurance?: true
|
||||||
userId?: true
|
userId?: true
|
||||||
createdAt?: true
|
createdAt?: true
|
||||||
updatedAt?: true
|
updatedAt?: true
|
||||||
@@ -6957,6 +6969,8 @@ export namespace Prisma {
|
|||||||
medicalConditions: string | null
|
medicalConditions: string | null
|
||||||
preferredLanguage: string | null
|
preferredLanguage: string | null
|
||||||
status: $Enums.PatientStatus
|
status: $Enums.PatientStatus
|
||||||
|
remainingBenefit: string | null
|
||||||
|
thirdPartyInsurance: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
@@ -7000,6 +7014,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: boolean
|
medicalConditions?: boolean
|
||||||
preferredLanguage?: boolean
|
preferredLanguage?: boolean
|
||||||
status?: boolean
|
status?: boolean
|
||||||
|
remainingBenefit?: boolean
|
||||||
|
thirdPartyInsurance?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
createdAt?: boolean
|
createdAt?: boolean
|
||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
@@ -7035,6 +7051,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: boolean
|
medicalConditions?: boolean
|
||||||
preferredLanguage?: boolean
|
preferredLanguage?: boolean
|
||||||
status?: boolean
|
status?: boolean
|
||||||
|
remainingBenefit?: boolean
|
||||||
|
thirdPartyInsurance?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
createdAt?: boolean
|
createdAt?: boolean
|
||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
@@ -7060,6 +7078,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: boolean
|
medicalConditions?: boolean
|
||||||
preferredLanguage?: boolean
|
preferredLanguage?: boolean
|
||||||
status?: boolean
|
status?: boolean
|
||||||
|
remainingBenefit?: boolean
|
||||||
|
thirdPartyInsurance?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
createdAt?: boolean
|
createdAt?: boolean
|
||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
@@ -7085,12 +7105,14 @@ export namespace Prisma {
|
|||||||
medicalConditions?: boolean
|
medicalConditions?: boolean
|
||||||
preferredLanguage?: boolean
|
preferredLanguage?: boolean
|
||||||
status?: boolean
|
status?: boolean
|
||||||
|
remainingBenefit?: boolean
|
||||||
|
thirdPartyInsurance?: boolean
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
createdAt?: boolean
|
createdAt?: boolean
|
||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PatientOmit<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetOmit<"id" | "firstName" | "lastName" | "dateOfBirth" | "gender" | "phone" | "email" | "address" | "city" | "zipCode" | "insuranceProvider" | "insuranceId" | "groupNumber" | "policyHolder" | "allergies" | "medicalConditions" | "preferredLanguage" | "status" | "userId" | "createdAt" | "updatedAt", ExtArgs["result"]["patient"]>
|
export type PatientOmit<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetOmit<"id" | "firstName" | "lastName" | "dateOfBirth" | "gender" | "phone" | "email" | "address" | "city" | "zipCode" | "insuranceProvider" | "insuranceId" | "groupNumber" | "policyHolder" | "allergies" | "medicalConditions" | "preferredLanguage" | "status" | "remainingBenefit" | "thirdPartyInsurance" | "userId" | "createdAt" | "updatedAt", ExtArgs["result"]["patient"]>
|
||||||
export type PatientInclude<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = {
|
export type PatientInclude<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = {
|
||||||
user?: boolean | UserDefaultArgs<ExtArgs>
|
user?: boolean | UserDefaultArgs<ExtArgs>
|
||||||
appointments?: boolean | Patient$appointmentsArgs<ExtArgs>
|
appointments?: boolean | Patient$appointmentsArgs<ExtArgs>
|
||||||
@@ -7144,6 +7166,8 @@ export namespace Prisma {
|
|||||||
medicalConditions: string | null
|
medicalConditions: string | null
|
||||||
preferredLanguage: string | null
|
preferredLanguage: string | null
|
||||||
status: $Enums.PatientStatus
|
status: $Enums.PatientStatus
|
||||||
|
remainingBenefit: string | null
|
||||||
|
thirdPartyInsurance: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
@@ -7598,6 +7622,8 @@ export namespace Prisma {
|
|||||||
readonly medicalConditions: FieldRef<"Patient", 'String'>
|
readonly medicalConditions: FieldRef<"Patient", 'String'>
|
||||||
readonly preferredLanguage: FieldRef<"Patient", 'String'>
|
readonly preferredLanguage: FieldRef<"Patient", 'String'>
|
||||||
readonly status: FieldRef<"Patient", 'PatientStatus'>
|
readonly status: FieldRef<"Patient", 'PatientStatus'>
|
||||||
|
readonly remainingBenefit: FieldRef<"Patient", 'String'>
|
||||||
|
readonly thirdPartyInsurance: FieldRef<"Patient", 'String'>
|
||||||
readonly userId: FieldRef<"Patient", 'Int'>
|
readonly userId: FieldRef<"Patient", 'Int'>
|
||||||
readonly createdAt: FieldRef<"Patient", 'DateTime'>
|
readonly createdAt: FieldRef<"Patient", 'DateTime'>
|
||||||
readonly updatedAt: FieldRef<"Patient", 'DateTime'>
|
readonly updatedAt: FieldRef<"Patient", 'DateTime'>
|
||||||
@@ -48204,6 +48230,8 @@ export namespace Prisma {
|
|||||||
medicalConditions: 'medicalConditions',
|
medicalConditions: 'medicalConditions',
|
||||||
preferredLanguage: 'preferredLanguage',
|
preferredLanguage: 'preferredLanguage',
|
||||||
status: 'status',
|
status: 'status',
|
||||||
|
remainingBenefit: 'remainingBenefit',
|
||||||
|
thirdPartyInsurance: 'thirdPartyInsurance',
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
createdAt: 'createdAt',
|
createdAt: 'createdAt',
|
||||||
updatedAt: 'updatedAt'
|
updatedAt: 'updatedAt'
|
||||||
@@ -49210,6 +49238,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: StringNullableFilter<"Patient"> | string | null
|
medicalConditions?: StringNullableFilter<"Patient"> | string | null
|
||||||
preferredLanguage?: StringNullableFilter<"Patient"> | string | null
|
preferredLanguage?: StringNullableFilter<"Patient"> | string | null
|
||||||
status?: EnumPatientStatusFilter<"Patient"> | $Enums.PatientStatus
|
status?: EnumPatientStatusFilter<"Patient"> | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: StringNullableFilter<"Patient"> | string | null
|
||||||
|
thirdPartyInsurance?: StringNullableFilter<"Patient"> | string | null
|
||||||
userId?: IntFilter<"Patient"> | number
|
userId?: IntFilter<"Patient"> | number
|
||||||
createdAt?: DateTimeFilter<"Patient"> | Date | string
|
createdAt?: DateTimeFilter<"Patient"> | Date | string
|
||||||
updatedAt?: DateTimeFilter<"Patient"> | Date | string
|
updatedAt?: DateTimeFilter<"Patient"> | Date | string
|
||||||
@@ -49244,6 +49274,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: SortOrderInput | SortOrder
|
medicalConditions?: SortOrderInput | SortOrder
|
||||||
preferredLanguage?: SortOrderInput | SortOrder
|
preferredLanguage?: SortOrderInput | SortOrder
|
||||||
status?: SortOrder
|
status?: SortOrder
|
||||||
|
remainingBenefit?: SortOrderInput | SortOrder
|
||||||
|
thirdPartyInsurance?: SortOrderInput | SortOrder
|
||||||
userId?: SortOrder
|
userId?: SortOrder
|
||||||
createdAt?: SortOrder
|
createdAt?: SortOrder
|
||||||
updatedAt?: SortOrder
|
updatedAt?: SortOrder
|
||||||
@@ -49281,6 +49313,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: StringNullableFilter<"Patient"> | string | null
|
medicalConditions?: StringNullableFilter<"Patient"> | string | null
|
||||||
preferredLanguage?: StringNullableFilter<"Patient"> | string | null
|
preferredLanguage?: StringNullableFilter<"Patient"> | string | null
|
||||||
status?: EnumPatientStatusFilter<"Patient"> | $Enums.PatientStatus
|
status?: EnumPatientStatusFilter<"Patient"> | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: StringNullableFilter<"Patient"> | string | null
|
||||||
|
thirdPartyInsurance?: StringNullableFilter<"Patient"> | string | null
|
||||||
userId?: IntFilter<"Patient"> | number
|
userId?: IntFilter<"Patient"> | number
|
||||||
createdAt?: DateTimeFilter<"Patient"> | Date | string
|
createdAt?: DateTimeFilter<"Patient"> | Date | string
|
||||||
updatedAt?: DateTimeFilter<"Patient"> | Date | string
|
updatedAt?: DateTimeFilter<"Patient"> | Date | string
|
||||||
@@ -49315,6 +49349,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: SortOrderInput | SortOrder
|
medicalConditions?: SortOrderInput | SortOrder
|
||||||
preferredLanguage?: SortOrderInput | SortOrder
|
preferredLanguage?: SortOrderInput | SortOrder
|
||||||
status?: SortOrder
|
status?: SortOrder
|
||||||
|
remainingBenefit?: SortOrderInput | SortOrder
|
||||||
|
thirdPartyInsurance?: SortOrderInput | SortOrder
|
||||||
userId?: SortOrder
|
userId?: SortOrder
|
||||||
createdAt?: SortOrder
|
createdAt?: SortOrder
|
||||||
updatedAt?: SortOrder
|
updatedAt?: SortOrder
|
||||||
@@ -49347,6 +49383,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: StringNullableWithAggregatesFilter<"Patient"> | string | null
|
medicalConditions?: StringNullableWithAggregatesFilter<"Patient"> | string | null
|
||||||
preferredLanguage?: StringNullableWithAggregatesFilter<"Patient"> | string | null
|
preferredLanguage?: StringNullableWithAggregatesFilter<"Patient"> | string | null
|
||||||
status?: EnumPatientStatusWithAggregatesFilter<"Patient"> | $Enums.PatientStatus
|
status?: EnumPatientStatusWithAggregatesFilter<"Patient"> | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: StringNullableWithAggregatesFilter<"Patient"> | string | null
|
||||||
|
thirdPartyInsurance?: StringNullableWithAggregatesFilter<"Patient"> | string | null
|
||||||
userId?: IntWithAggregatesFilter<"Patient"> | number
|
userId?: IntWithAggregatesFilter<"Patient"> | number
|
||||||
createdAt?: DateTimeWithAggregatesFilter<"Patient"> | Date | string
|
createdAt?: DateTimeWithAggregatesFilter<"Patient"> | Date | string
|
||||||
updatedAt?: DateTimeWithAggregatesFilter<"Patient"> | Date | string
|
updatedAt?: DateTimeWithAggregatesFilter<"Patient"> | Date | string
|
||||||
@@ -52171,6 +52209,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
user: UserCreateNestedOneWithoutPatientsInput
|
user: UserCreateNestedOneWithoutPatientsInput
|
||||||
@@ -52204,6 +52244,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -52236,6 +52278,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
||||||
@@ -52269,6 +52313,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -52302,6 +52348,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -52325,6 +52373,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
}
|
}
|
||||||
@@ -52348,6 +52398,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -55403,6 +55455,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: SortOrder
|
medicalConditions?: SortOrder
|
||||||
preferredLanguage?: SortOrder
|
preferredLanguage?: SortOrder
|
||||||
status?: SortOrder
|
status?: SortOrder
|
||||||
|
remainingBenefit?: SortOrder
|
||||||
|
thirdPartyInsurance?: SortOrder
|
||||||
userId?: SortOrder
|
userId?: SortOrder
|
||||||
createdAt?: SortOrder
|
createdAt?: SortOrder
|
||||||
updatedAt?: SortOrder
|
updatedAt?: SortOrder
|
||||||
@@ -55432,6 +55486,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: SortOrder
|
medicalConditions?: SortOrder
|
||||||
preferredLanguage?: SortOrder
|
preferredLanguage?: SortOrder
|
||||||
status?: SortOrder
|
status?: SortOrder
|
||||||
|
remainingBenefit?: SortOrder
|
||||||
|
thirdPartyInsurance?: SortOrder
|
||||||
userId?: SortOrder
|
userId?: SortOrder
|
||||||
createdAt?: SortOrder
|
createdAt?: SortOrder
|
||||||
updatedAt?: SortOrder
|
updatedAt?: SortOrder
|
||||||
@@ -55456,6 +55512,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: SortOrder
|
medicalConditions?: SortOrder
|
||||||
preferredLanguage?: SortOrder
|
preferredLanguage?: SortOrder
|
||||||
status?: SortOrder
|
status?: SortOrder
|
||||||
|
remainingBenefit?: SortOrder
|
||||||
|
thirdPartyInsurance?: SortOrder
|
||||||
userId?: SortOrder
|
userId?: SortOrder
|
||||||
createdAt?: SortOrder
|
createdAt?: SortOrder
|
||||||
updatedAt?: SortOrder
|
updatedAt?: SortOrder
|
||||||
@@ -61353,6 +61411,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
appointments?: AppointmentCreateNestedManyWithoutPatientInput
|
appointments?: AppointmentCreateNestedManyWithoutPatientInput
|
||||||
@@ -61385,6 +61445,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
appointments?: AppointmentUncheckedCreateNestedManyWithoutPatientInput
|
appointments?: AppointmentUncheckedCreateNestedManyWithoutPatientInput
|
||||||
@@ -62116,6 +62178,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: StringNullableFilter<"Patient"> | string | null
|
medicalConditions?: StringNullableFilter<"Patient"> | string | null
|
||||||
preferredLanguage?: StringNullableFilter<"Patient"> | string | null
|
preferredLanguage?: StringNullableFilter<"Patient"> | string | null
|
||||||
status?: EnumPatientStatusFilter<"Patient"> | $Enums.PatientStatus
|
status?: EnumPatientStatusFilter<"Patient"> | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: StringNullableFilter<"Patient"> | string | null
|
||||||
|
thirdPartyInsurance?: StringNullableFilter<"Patient"> | string | null
|
||||||
userId?: IntFilter<"Patient"> | number
|
userId?: IntFilter<"Patient"> | number
|
||||||
createdAt?: DateTimeFilter<"Patient"> | Date | string
|
createdAt?: DateTimeFilter<"Patient"> | Date | string
|
||||||
updatedAt?: DateTimeFilter<"Patient"> | Date | string
|
updatedAt?: DateTimeFilter<"Patient"> | Date | string
|
||||||
@@ -63506,6 +63570,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
user: UserCreateNestedOneWithoutPatientsInput
|
user: UserCreateNestedOneWithoutPatientsInput
|
||||||
@@ -63538,6 +63604,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -63836,6 +63904,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
||||||
@@ -63868,6 +63938,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -65031,6 +65103,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
user: UserCreateNestedOneWithoutPatientsInput
|
user: UserCreateNestedOneWithoutPatientsInput
|
||||||
@@ -65063,6 +65137,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -65194,6 +65270,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
||||||
@@ -65226,6 +65304,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -65293,6 +65373,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
user: UserCreateNestedOneWithoutPatientsInput
|
user: UserCreateNestedOneWithoutPatientsInput
|
||||||
@@ -65325,6 +65407,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -65672,6 +65756,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
||||||
@@ -65704,6 +65790,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -66761,6 +66849,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
user: UserCreateNestedOneWithoutPatientsInput
|
user: UserCreateNestedOneWithoutPatientsInput
|
||||||
@@ -66793,6 +66883,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -66863,6 +66955,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
||||||
@@ -66895,6 +66989,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -67051,6 +67147,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
user: UserCreateNestedOneWithoutPatientsInput
|
user: UserCreateNestedOneWithoutPatientsInput
|
||||||
@@ -67083,6 +67181,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -67397,6 +67497,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
||||||
@@ -67429,6 +67531,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -68427,6 +68531,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
user: UserCreateNestedOneWithoutPatientsInput
|
user: UserCreateNestedOneWithoutPatientsInput
|
||||||
@@ -68459,6 +68565,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -68671,6 +68779,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
||||||
@@ -68703,6 +68813,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -69082,6 +69194,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
user: UserCreateNestedOneWithoutPatientsInput
|
user: UserCreateNestedOneWithoutPatientsInput
|
||||||
@@ -69114,6 +69228,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -69235,6 +69351,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
||||||
@@ -69267,6 +69385,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -69378,6 +69498,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
user: UserCreateNestedOneWithoutPatientsInput
|
user: UserCreateNestedOneWithoutPatientsInput
|
||||||
@@ -69410,6 +69532,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -69457,6 +69581,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
||||||
@@ -69489,6 +69615,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -70444,6 +70572,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
user: UserCreateNestedOneWithoutPatientsInput
|
user: UserCreateNestedOneWithoutPatientsInput
|
||||||
@@ -70476,6 +70606,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
userId: number
|
userId: number
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -70597,6 +70729,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
user?: UserUpdateOneRequiredWithoutPatientsNestedInput
|
||||||
@@ -70629,6 +70763,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
userId?: IntFieldUpdateOperationsInput | number
|
userId?: IntFieldUpdateOperationsInput | number
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -71308,6 +71444,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: string | null
|
medicalConditions?: string | null
|
||||||
preferredLanguage?: string | null
|
preferredLanguage?: string | null
|
||||||
status?: $Enums.PatientStatus
|
status?: $Enums.PatientStatus
|
||||||
|
remainingBenefit?: string | null
|
||||||
|
thirdPartyInsurance?: string | null
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
}
|
}
|
||||||
@@ -71507,6 +71645,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
appointments?: AppointmentUpdateManyWithoutPatientNestedInput
|
appointments?: AppointmentUpdateManyWithoutPatientNestedInput
|
||||||
@@ -71539,6 +71679,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
appointments?: AppointmentUncheckedUpdateManyWithoutPatientNestedInput
|
appointments?: AppointmentUncheckedUpdateManyWithoutPatientNestedInput
|
||||||
@@ -71571,6 +71713,8 @@ export namespace Prisma {
|
|||||||
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
medicalConditions?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
preferredLanguage?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
status?: EnumPatientStatusFieldUpdateOperationsInput | $Enums.PatientStatus
|
||||||
|
remainingBenefit?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
thirdPartyInsurance?: NullableStringFieldUpdateOperationsInput | string | null
|
||||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "prisma-client-06444e98f8328a30442a56cbded54a265df02594192db5e428350bbd93cc975b",
|
"name": "prisma-client-11a124b84a93d3e63379c8a6769ab83d8729151f8cca035589101564f7ede8ed",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"browser": "default.js",
|
"browser": "default.js",
|
||||||
|
|||||||
@@ -55,37 +55,39 @@ model User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Patient {
|
model Patient {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
firstName String
|
firstName String
|
||||||
lastName String
|
lastName String
|
||||||
dateOfBirth DateTime? @db.Date
|
dateOfBirth DateTime? @db.Date
|
||||||
gender String
|
gender String
|
||||||
phone String
|
phone String
|
||||||
email String?
|
email String?
|
||||||
address String?
|
address String?
|
||||||
city String?
|
city String?
|
||||||
zipCode String?
|
zipCode String?
|
||||||
insuranceProvider String?
|
insuranceProvider String?
|
||||||
insuranceId String?
|
insuranceId String?
|
||||||
groupNumber String?
|
groupNumber String?
|
||||||
policyHolder String?
|
policyHolder String?
|
||||||
allergies String?
|
allergies String?
|
||||||
medicalConditions String?
|
medicalConditions String?
|
||||||
preferredLanguage String? @default("English")
|
preferredLanguage String? @default("English")
|
||||||
status PatientStatus @default(UNKNOWN)
|
status PatientStatus @default(UNKNOWN)
|
||||||
userId Int
|
remainingBenefit String?
|
||||||
createdAt DateTime @default(now())
|
thirdPartyInsurance String?
|
||||||
updatedAt DateTime @updatedAt
|
userId Int
|
||||||
user User @relation(fields: [userId], references: [id])
|
createdAt DateTime @default(now())
|
||||||
appointments Appointment[]
|
updatedAt DateTime @updatedAt
|
||||||
procedures AppointmentProcedure[]
|
user User @relation(fields: [userId], references: [id])
|
||||||
claims Claim[]
|
appointments Appointment[]
|
||||||
groups PdfGroup[]
|
procedures AppointmentProcedure[]
|
||||||
payment Payment[]
|
claims Claim[]
|
||||||
communications Communication[]
|
groups PdfGroup[]
|
||||||
documents PatientDocument[]
|
payment Payment[]
|
||||||
conversation PatientConversation?
|
communications Communication[]
|
||||||
cloudFolders CloudFolder[] @relation("PatientCloudFolder")
|
documents PatientDocument[]
|
||||||
|
conversation PatientConversation?
|
||||||
|
cloudFolders CloudFolder[] @relation("PatientCloudFolder")
|
||||||
|
|
||||||
@@index([insuranceId])
|
@@index([insuranceId])
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
|
|||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Patient" ADD COLUMN "remainingBenefit" TEXT;
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Patient" ADD COLUMN "thirdPartyInsurance" TEXT;
|
||||||
@@ -73,6 +73,8 @@ model Patient {
|
|||||||
medicalConditions String?
|
medicalConditions String?
|
||||||
preferredLanguage String? @default("English")
|
preferredLanguage String? @default("English")
|
||||||
status PatientStatus @default(UNKNOWN)
|
status PatientStatus @default(UNKNOWN)
|
||||||
|
remainingBenefit String?
|
||||||
|
thirdPartyInsurance String?
|
||||||
userId Int
|
userId Int
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"generatorVersion": "1.0.0",
|
"generatorVersion": "1.0.0",
|
||||||
"generatedAt": "2026-07-21T03:13:42.342Z",
|
"generatedAt": "2026-09-03T01:38:13.906Z",
|
||||||
"outputPath": "/home/gg/Desktop/DentalManagementMH07/packages/db/shared",
|
"outputPath": "/home/gg/Desktop/DentalManagementMH07/packages/db/shared",
|
||||||
"files": [
|
"files": [
|
||||||
"schemas/enums/TransactionIsolationLevel.schema.ts",
|
"schemas/enums/TransactionIsolationLevel.schema.ts",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
|
|
||||||
export const PatientScalarFieldEnumSchema = z.enum(['id', 'firstName', 'lastName', 'dateOfBirth', 'gender', 'phone', 'email', 'address', 'city', 'zipCode', 'insuranceProvider', 'insuranceId', 'groupNumber', 'policyHolder', 'allergies', 'medicalConditions', 'preferredLanguage', 'status', 'userId', 'createdAt', 'updatedAt'])
|
export const PatientScalarFieldEnumSchema = z.enum(['id', 'firstName', 'lastName', 'dateOfBirth', 'gender', 'phone', 'email', 'address', 'city', 'zipCode', 'insuranceProvider', 'insuranceId', 'groupNumber', 'policyHolder', 'allergies', 'medicalConditions', 'preferredLanguage', 'status', 'remainingBenefit', 'thirdPartyInsurance', 'userId', 'createdAt', 'updatedAt'])
|
||||||
|
|
||||||
export type PatientScalarFieldEnum = z.infer<typeof PatientScalarFieldEnumSchema>;
|
export type PatientScalarFieldEnum = z.infer<typeof PatientScalarFieldEnumSchema>;
|
||||||
@@ -28,6 +28,8 @@ export const PatientFindFirstOrThrowSelectSchema: z.ZodType<Prisma.PatientSelect
|
|||||||
medicalConditions: z.boolean().optional(),
|
medicalConditions: z.boolean().optional(),
|
||||||
preferredLanguage: z.boolean().optional(),
|
preferredLanguage: z.boolean().optional(),
|
||||||
status: z.boolean().optional(),
|
status: z.boolean().optional(),
|
||||||
|
remainingBenefit: z.boolean().optional(),
|
||||||
|
thirdPartyInsurance: z.boolean().optional(),
|
||||||
userId: z.boolean().optional(),
|
userId: z.boolean().optional(),
|
||||||
createdAt: z.boolean().optional(),
|
createdAt: z.boolean().optional(),
|
||||||
updatedAt: z.boolean().optional(),
|
updatedAt: z.boolean().optional(),
|
||||||
@@ -63,6 +65,8 @@ export const PatientFindFirstOrThrowSelectZodSchema = z.object({
|
|||||||
medicalConditions: z.boolean().optional(),
|
medicalConditions: z.boolean().optional(),
|
||||||
preferredLanguage: z.boolean().optional(),
|
preferredLanguage: z.boolean().optional(),
|
||||||
status: z.boolean().optional(),
|
status: z.boolean().optional(),
|
||||||
|
remainingBenefit: z.boolean().optional(),
|
||||||
|
thirdPartyInsurance: z.boolean().optional(),
|
||||||
userId: z.boolean().optional(),
|
userId: z.boolean().optional(),
|
||||||
createdAt: z.boolean().optional(),
|
createdAt: z.boolean().optional(),
|
||||||
updatedAt: z.boolean().optional(),
|
updatedAt: z.boolean().optional(),
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ export const PatientFindFirstSelectSchema: z.ZodType<Prisma.PatientSelect> = z.o
|
|||||||
medicalConditions: z.boolean().optional(),
|
medicalConditions: z.boolean().optional(),
|
||||||
preferredLanguage: z.boolean().optional(),
|
preferredLanguage: z.boolean().optional(),
|
||||||
status: z.boolean().optional(),
|
status: z.boolean().optional(),
|
||||||
|
remainingBenefit: z.boolean().optional(),
|
||||||
|
thirdPartyInsurance: z.boolean().optional(),
|
||||||
userId: z.boolean().optional(),
|
userId: z.boolean().optional(),
|
||||||
createdAt: z.boolean().optional(),
|
createdAt: z.boolean().optional(),
|
||||||
updatedAt: z.boolean().optional(),
|
updatedAt: z.boolean().optional(),
|
||||||
@@ -63,6 +65,8 @@ export const PatientFindFirstSelectZodSchema = z.object({
|
|||||||
medicalConditions: z.boolean().optional(),
|
medicalConditions: z.boolean().optional(),
|
||||||
preferredLanguage: z.boolean().optional(),
|
preferredLanguage: z.boolean().optional(),
|
||||||
status: z.boolean().optional(),
|
status: z.boolean().optional(),
|
||||||
|
remainingBenefit: z.boolean().optional(),
|
||||||
|
thirdPartyInsurance: z.boolean().optional(),
|
||||||
userId: z.boolean().optional(),
|
userId: z.boolean().optional(),
|
||||||
createdAt: z.boolean().optional(),
|
createdAt: z.boolean().optional(),
|
||||||
updatedAt: z.boolean().optional(),
|
updatedAt: z.boolean().optional(),
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ export const PatientFindManySelectSchema: z.ZodType<Prisma.PatientSelect> = z.ob
|
|||||||
medicalConditions: z.boolean().optional(),
|
medicalConditions: z.boolean().optional(),
|
||||||
preferredLanguage: z.boolean().optional(),
|
preferredLanguage: z.boolean().optional(),
|
||||||
status: z.boolean().optional(),
|
status: z.boolean().optional(),
|
||||||
|
remainingBenefit: z.boolean().optional(),
|
||||||
|
thirdPartyInsurance: z.boolean().optional(),
|
||||||
userId: z.boolean().optional(),
|
userId: z.boolean().optional(),
|
||||||
createdAt: z.boolean().optional(),
|
createdAt: z.boolean().optional(),
|
||||||
updatedAt: z.boolean().optional(),
|
updatedAt: z.boolean().optional(),
|
||||||
@@ -63,6 +65,8 @@ export const PatientFindManySelectZodSchema = z.object({
|
|||||||
medicalConditions: z.boolean().optional(),
|
medicalConditions: z.boolean().optional(),
|
||||||
preferredLanguage: z.boolean().optional(),
|
preferredLanguage: z.boolean().optional(),
|
||||||
status: z.boolean().optional(),
|
status: z.boolean().optional(),
|
||||||
|
remainingBenefit: z.boolean().optional(),
|
||||||
|
thirdPartyInsurance: z.boolean().optional(),
|
||||||
userId: z.boolean().optional(),
|
userId: z.boolean().optional(),
|
||||||
createdAt: z.boolean().optional(),
|
createdAt: z.boolean().optional(),
|
||||||
updatedAt: z.boolean().optional(),
|
updatedAt: z.boolean().optional(),
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.literal(true).optional(),
|
medicalConditions: z.literal(true).optional(),
|
||||||
preferredLanguage: z.literal(true).optional(),
|
preferredLanguage: z.literal(true).optional(),
|
||||||
status: z.literal(true).optional(),
|
status: z.literal(true).optional(),
|
||||||
|
remainingBenefit: z.literal(true).optional(),
|
||||||
|
thirdPartyInsurance: z.literal(true).optional(),
|
||||||
userId: z.literal(true).optional(),
|
userId: z.literal(true).optional(),
|
||||||
createdAt: z.literal(true).optional(),
|
createdAt: z.literal(true).optional(),
|
||||||
updatedAt: z.literal(true).optional(),
|
updatedAt: z.literal(true).optional(),
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: SortOrderSchema.optional(),
|
medicalConditions: SortOrderSchema.optional(),
|
||||||
preferredLanguage: SortOrderSchema.optional(),
|
preferredLanguage: SortOrderSchema.optional(),
|
||||||
status: SortOrderSchema.optional(),
|
status: SortOrderSchema.optional(),
|
||||||
|
remainingBenefit: SortOrderSchema.optional(),
|
||||||
|
thirdPartyInsurance: SortOrderSchema.optional(),
|
||||||
userId: SortOrderSchema.optional(),
|
userId: SortOrderSchema.optional(),
|
||||||
createdAt: SortOrderSchema.optional(),
|
createdAt: SortOrderSchema.optional(),
|
||||||
updatedAt: SortOrderSchema.optional()
|
updatedAt: SortOrderSchema.optional()
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
||||||
appointments: z.lazy(() => AppointmentCreateNestedManyWithoutPatientInputObjectSchema).optional(),
|
appointments: z.lazy(() => AppointmentCreateNestedManyWithoutPatientInputObjectSchema).optional(),
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional()
|
updatedAt: z.coerce.date().optional()
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional()
|
updatedAt: z.coerce.date().optional()
|
||||||
}).strict();
|
}).strict();
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
user: z.lazy(() => UserCreateNestedOneWithoutPatientsInputObjectSchema),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
appointments: z.lazy(() => AppointmentCreateNestedManyWithoutPatientInputObjectSchema).optional(),
|
appointments: z.lazy(() => AppointmentCreateNestedManyWithoutPatientInputObjectSchema).optional(),
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.literal(true).optional(),
|
medicalConditions: z.literal(true).optional(),
|
||||||
preferredLanguage: z.literal(true).optional(),
|
preferredLanguage: z.literal(true).optional(),
|
||||||
status: z.literal(true).optional(),
|
status: z.literal(true).optional(),
|
||||||
|
remainingBenefit: z.literal(true).optional(),
|
||||||
|
thirdPartyInsurance: z.literal(true).optional(),
|
||||||
userId: z.literal(true).optional(),
|
userId: z.literal(true).optional(),
|
||||||
createdAt: z.literal(true).optional(),
|
createdAt: z.literal(true).optional(),
|
||||||
updatedAt: z.literal(true).optional()
|
updatedAt: z.literal(true).optional()
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: SortOrderSchema.optional(),
|
medicalConditions: SortOrderSchema.optional(),
|
||||||
preferredLanguage: SortOrderSchema.optional(),
|
preferredLanguage: SortOrderSchema.optional(),
|
||||||
status: SortOrderSchema.optional(),
|
status: SortOrderSchema.optional(),
|
||||||
|
remainingBenefit: SortOrderSchema.optional(),
|
||||||
|
thirdPartyInsurance: SortOrderSchema.optional(),
|
||||||
userId: SortOrderSchema.optional(),
|
userId: SortOrderSchema.optional(),
|
||||||
createdAt: SortOrderSchema.optional(),
|
createdAt: SortOrderSchema.optional(),
|
||||||
updatedAt: SortOrderSchema.optional()
|
updatedAt: SortOrderSchema.optional()
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.literal(true).optional(),
|
medicalConditions: z.literal(true).optional(),
|
||||||
preferredLanguage: z.literal(true).optional(),
|
preferredLanguage: z.literal(true).optional(),
|
||||||
status: z.literal(true).optional(),
|
status: z.literal(true).optional(),
|
||||||
|
remainingBenefit: z.literal(true).optional(),
|
||||||
|
thirdPartyInsurance: z.literal(true).optional(),
|
||||||
userId: z.literal(true).optional(),
|
userId: z.literal(true).optional(),
|
||||||
createdAt: z.literal(true).optional(),
|
createdAt: z.literal(true).optional(),
|
||||||
updatedAt: z.literal(true).optional()
|
updatedAt: z.literal(true).optional()
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: SortOrderSchema.optional(),
|
medicalConditions: SortOrderSchema.optional(),
|
||||||
preferredLanguage: SortOrderSchema.optional(),
|
preferredLanguage: SortOrderSchema.optional(),
|
||||||
status: SortOrderSchema.optional(),
|
status: SortOrderSchema.optional(),
|
||||||
|
remainingBenefit: SortOrderSchema.optional(),
|
||||||
|
thirdPartyInsurance: SortOrderSchema.optional(),
|
||||||
userId: SortOrderSchema.optional(),
|
userId: SortOrderSchema.optional(),
|
||||||
createdAt: SortOrderSchema.optional(),
|
createdAt: SortOrderSchema.optional(),
|
||||||
updatedAt: SortOrderSchema.optional()
|
updatedAt: SortOrderSchema.optional()
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
medicalConditions: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
||||||
preferredLanguage: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
preferredLanguage: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
||||||
status: SortOrderSchema.optional(),
|
status: SortOrderSchema.optional(),
|
||||||
|
remainingBenefit: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
||||||
|
thirdPartyInsurance: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
||||||
userId: SortOrderSchema.optional(),
|
userId: SortOrderSchema.optional(),
|
||||||
createdAt: SortOrderSchema.optional(),
|
createdAt: SortOrderSchema.optional(),
|
||||||
updatedAt: SortOrderSchema.optional(),
|
updatedAt: SortOrderSchema.optional(),
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
medicalConditions: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
||||||
preferredLanguage: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
preferredLanguage: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
||||||
status: SortOrderSchema.optional(),
|
status: SortOrderSchema.optional(),
|
||||||
|
remainingBenefit: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
||||||
|
thirdPartyInsurance: z.union([SortOrderSchema, z.lazy(() => SortOrderInputObjectSchema)]).optional(),
|
||||||
userId: SortOrderSchema.optional(),
|
userId: SortOrderSchema.optional(),
|
||||||
createdAt: SortOrderSchema.optional(),
|
createdAt: SortOrderSchema.optional(),
|
||||||
updatedAt: SortOrderSchema.optional(),
|
updatedAt: SortOrderSchema.optional(),
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const patientscalarwhereinputSchema = z.object({
|
|||||||
medicalConditions: z.union([z.lazy(() => StringNullableFilterObjectSchema), z.string()]).optional().nullable(),
|
medicalConditions: z.union([z.lazy(() => StringNullableFilterObjectSchema), z.string()]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.lazy(() => StringNullableFilterObjectSchema), z.string()]).optional().nullable(),
|
preferredLanguage: z.union([z.lazy(() => StringNullableFilterObjectSchema), z.string()]).optional().nullable(),
|
||||||
status: z.union([z.lazy(() => EnumPatientStatusFilterObjectSchema), PatientStatusSchema]).optional(),
|
status: z.union([z.lazy(() => EnumPatientStatusFilterObjectSchema), PatientStatusSchema]).optional(),
|
||||||
|
remainingBenefit: z.union([z.lazy(() => StringNullableFilterObjectSchema), z.string()]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.lazy(() => StringNullableFilterObjectSchema), z.string()]).optional().nullable(),
|
||||||
userId: z.union([z.lazy(() => IntFilterObjectSchema), z.number().int()]).optional(),
|
userId: z.union([z.lazy(() => IntFilterObjectSchema), z.number().int()]).optional(),
|
||||||
createdAt: z.union([z.lazy(() => DateTimeFilterObjectSchema), z.coerce.date()]).optional(),
|
createdAt: z.union([z.lazy(() => DateTimeFilterObjectSchema), z.coerce.date()]).optional(),
|
||||||
updatedAt: z.union([z.lazy(() => DateTimeFilterObjectSchema), z.coerce.date()]).optional()
|
updatedAt: z.union([z.lazy(() => DateTimeFilterObjectSchema), z.coerce.date()]).optional()
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const patientscalarwherewithaggregatesinputSchema = z.object({
|
|||||||
medicalConditions: z.union([z.lazy(() => StringNullableWithAggregatesFilterObjectSchema), z.string()]).optional().nullable(),
|
medicalConditions: z.union([z.lazy(() => StringNullableWithAggregatesFilterObjectSchema), z.string()]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.lazy(() => StringNullableWithAggregatesFilterObjectSchema), z.string()]).optional().nullable(),
|
preferredLanguage: z.union([z.lazy(() => StringNullableWithAggregatesFilterObjectSchema), z.string()]).optional().nullable(),
|
||||||
status: z.union([z.lazy(() => EnumPatientStatusWithAggregatesFilterObjectSchema), PatientStatusSchema]).optional(),
|
status: z.union([z.lazy(() => EnumPatientStatusWithAggregatesFilterObjectSchema), PatientStatusSchema]).optional(),
|
||||||
|
remainingBenefit: z.union([z.lazy(() => StringNullableWithAggregatesFilterObjectSchema), z.string()]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.lazy(() => StringNullableWithAggregatesFilterObjectSchema), z.string()]).optional().nullable(),
|
||||||
userId: z.union([z.lazy(() => IntWithAggregatesFilterObjectSchema), z.number().int()]).optional(),
|
userId: z.union([z.lazy(() => IntWithAggregatesFilterObjectSchema), z.number().int()]).optional(),
|
||||||
createdAt: z.union([z.lazy(() => DateTimeWithAggregatesFilterObjectSchema), z.coerce.date()]).optional(),
|
createdAt: z.union([z.lazy(() => DateTimeWithAggregatesFilterObjectSchema), z.coerce.date()]).optional(),
|
||||||
updatedAt: z.union([z.lazy(() => DateTimeWithAggregatesFilterObjectSchema), z.coerce.date()]).optional()
|
updatedAt: z.union([z.lazy(() => DateTimeWithAggregatesFilterObjectSchema), z.coerce.date()]).optional()
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.boolean().optional(),
|
medicalConditions: z.boolean().optional(),
|
||||||
preferredLanguage: z.boolean().optional(),
|
preferredLanguage: z.boolean().optional(),
|
||||||
status: z.boolean().optional(),
|
status: z.boolean().optional(),
|
||||||
|
remainingBenefit: z.boolean().optional(),
|
||||||
|
thirdPartyInsurance: z.boolean().optional(),
|
||||||
userId: z.boolean().optional(),
|
userId: z.boolean().optional(),
|
||||||
createdAt: z.boolean().optional(),
|
createdAt: z.boolean().optional(),
|
||||||
updatedAt: z.boolean().optional(),
|
updatedAt: z.boolean().optional(),
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
appointments: z.lazy(() => AppointmentUncheckedCreateNestedManyWithoutPatientInputObjectSchema).optional(),
|
appointments: z.lazy(() => AppointmentUncheckedCreateNestedManyWithoutPatientInputObjectSchema).optional(),
|
||||||
|
|||||||
+2
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
|
|||||||
+2
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
|
|||||||
+2
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
|
|||||||
+2
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
|
|||||||
+2
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
|
|||||||
+2
@@ -29,6 +29,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.string().optional().nullable(),
|
medicalConditions: z.string().optional().nullable(),
|
||||||
preferredLanguage: z.string().optional().nullable(),
|
preferredLanguage: z.string().optional().nullable(),
|
||||||
status: PatientStatusSchema.optional(),
|
status: PatientStatusSchema.optional(),
|
||||||
|
remainingBenefit: z.string().optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.string().optional().nullable(),
|
||||||
createdAt: z.coerce.date().optional(),
|
createdAt: z.coerce.date().optional(),
|
||||||
updatedAt: z.coerce.date().optional(),
|
updatedAt: z.coerce.date().optional(),
|
||||||
appointments: z.lazy(() => AppointmentUncheckedCreateNestedManyWithoutPatientInputObjectSchema).optional(),
|
appointments: z.lazy(() => AppointmentUncheckedCreateNestedManyWithoutPatientInputObjectSchema).optional(),
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional()
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional()
|
||||||
|
|||||||
+2
@@ -27,6 +27,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional()
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional()
|
||||||
}).strict();
|
}).strict();
|
||||||
|
|||||||
+2
@@ -35,6 +35,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
|||||||
+2
@@ -35,6 +35,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
|||||||
+2
@@ -35,6 +35,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
|||||||
+2
@@ -35,6 +35,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
|||||||
+2
@@ -35,6 +35,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
|||||||
+2
@@ -35,6 +35,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
userId: z.union([z.number().int(), z.lazy(() => IntFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
appointments: z.lazy(() => AppointmentUncheckedUpdateManyWithoutPatientNestedInputObjectSchema).optional(),
|
appointments: z.lazy(() => AppointmentUncheckedUpdateManyWithoutPatientNestedInputObjectSchema).optional(),
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
user: z.lazy(() => UserUpdateOneRequiredWithoutPatientsNestedInputObjectSchema).optional(),
|
user: z.lazy(() => UserUpdateOneRequiredWithoutPatientsNestedInputObjectSchema).optional(),
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional()
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional()
|
||||||
}).strict();
|
}).strict();
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
user: z.lazy(() => UserUpdateOneRequiredWithoutPatientsNestedInputObjectSchema).optional(),
|
user: z.lazy(() => UserUpdateOneRequiredWithoutPatientsNestedInputObjectSchema).optional(),
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
user: z.lazy(() => UserUpdateOneRequiredWithoutPatientsNestedInputObjectSchema).optional(),
|
user: z.lazy(() => UserUpdateOneRequiredWithoutPatientsNestedInputObjectSchema).optional(),
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ const makeSchema = () => z.object({
|
|||||||
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
medicalConditions: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
preferredLanguage: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
status: z.union([PatientStatusSchema, z.lazy(() => EnumPatientStatusFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
|
remainingBenefit: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
|
thirdPartyInsurance: z.union([z.string(), z.lazy(() => NullableStringFieldUpdateOperationsInputObjectSchema)]).optional().nullable(),
|
||||||
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
createdAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
updatedAt: z.union([z.coerce.date(), z.lazy(() => DateTimeFieldUpdateOperationsInputObjectSchema)]).optional(),
|
||||||
user: z.lazy(() => UserUpdateOneRequiredWithoutPatientsNestedInputObjectSchema).optional(),
|
user: z.lazy(() => UserUpdateOneRequiredWithoutPatientsNestedInputObjectSchema).optional(),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user