feat: fix DDMA eligibility — patient list, name extraction, PDF page, OTP session
- Filter patient list by userId so each user sees only their own patients - Sort patients by updatedAt DESC so recently checked patients appear first - Add updatedAt field to Patient model (DB migration via raw SQL + db:generate) - Fix DDMA name extraction: read from detail page "Name:" label, not search results row text which included appended dates - Fix PDF capture: use driver.get() instead of click() to avoid race condition that was saving the search results page instead of the patient detail page - Strip trailing bare dates from extracted names (e.g. "Rodriguez 04/27/2026") - Handle "Last, First" comma format and single-word last names in splitName - Normalize insuranceId consistently in createOrUpdatePatientByInsuranceId - Fix OTP persistent session: stop clearing LocalStorage/IndexedDB on startup (these hold the DDMA device trust token that skips OTP on subsequent logins) - Increase post-navigation wait time for full page render before PDF generation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -94,10 +94,16 @@ export async function createOrUpdatePatientByInsuranceId(options: {
|
||||
const { insuranceId, firstName, lastName, dob, userId } = options;
|
||||
if (!insuranceId) throw new Error("Missing insuranceId");
|
||||
|
||||
// Normalize insuranceId the same way insertPatientSchema does (strip spaces)
|
||||
const normalizedId = insuranceId.replace(/\s+/g, "");
|
||||
|
||||
const incomingFirst = (firstName || "").trim();
|
||||
const incomingLast = (lastName || "").trim();
|
||||
|
||||
let patient = await storage.getPatientByInsuranceId(insuranceId);
|
||||
console.log(`[createOrUpdatePatient] insuranceId="${normalizedId}" firstName="${incomingFirst}" lastName="${incomingLast}" userId=${userId}`);
|
||||
|
||||
let patient = await storage.getPatientByInsuranceId(normalizedId);
|
||||
console.log(`[createOrUpdatePatient] existing patient lookup: ${patient ? `found id=${patient.id}` : "not found"}`);
|
||||
|
||||
if (patient && patient.id) {
|
||||
const updates: any = {};
|
||||
@@ -110,8 +116,9 @@ export async function createOrUpdatePatientByInsuranceId(options: {
|
||||
if (!isNaN(parsed.getTime())) updates.dateOfBirth = parsed;
|
||||
}
|
||||
if (Object.keys(updates).length > 0) {
|
||||
console.log(`[createOrUpdatePatient] updating patient id=${patient.id} with`, updates);
|
||||
await storage.updatePatient(patient.id, updates);
|
||||
patient = await storage.getPatientByInsuranceId(insuranceId);
|
||||
patient = await storage.getPatientByInsuranceId(normalizedId);
|
||||
}
|
||||
return patient;
|
||||
}
|
||||
@@ -123,24 +130,31 @@ export async function createOrUpdatePatientByInsuranceId(options: {
|
||||
gender: "",
|
||||
phone: "",
|
||||
userId,
|
||||
insuranceId,
|
||||
insuranceId: normalizedId,
|
||||
};
|
||||
|
||||
let patientData: InsertPatient;
|
||||
try {
|
||||
patientData = insertPatientSchema.parse(createPayload);
|
||||
} catch {
|
||||
// Remove fields that may fail validation (invalid date or alphanumeric insuranceId)
|
||||
} catch (e1) {
|
||||
console.warn(`[createOrUpdatePatient] schema parse failed (attempt 1):`, e1);
|
||||
const safePayload = { ...createPayload };
|
||||
delete safePayload.dateOfBirth;
|
||||
try {
|
||||
patientData = insertPatientSchema.parse(safePayload);
|
||||
} catch {
|
||||
// Last resort: skip schema validation and cast directly
|
||||
} catch (e2) {
|
||||
console.warn(`[createOrUpdatePatient] schema parse failed (attempt 2):`, e2);
|
||||
patientData = safePayload as InsertPatient;
|
||||
}
|
||||
}
|
||||
|
||||
await storage.createPatient(patientData);
|
||||
return storage.getPatientByInsuranceId(insuranceId);
|
||||
try {
|
||||
await storage.createPatient(patientData);
|
||||
console.log(`[createOrUpdatePatient] patient created successfully for insuranceId="${normalizedId}"`);
|
||||
} catch (dbErr: any) {
|
||||
console.error(`[createOrUpdatePatient] DB create failed:`, dbErr?.message ?? dbErr);
|
||||
throw dbErr;
|
||||
}
|
||||
|
||||
return storage.getPatientByInsuranceId(normalizedId);
|
||||
}
|
||||
|
||||
@@ -90,9 +90,33 @@ async function processDdmaResult(
|
||||
? seleniumResult.patientName.trim()
|
||||
: null;
|
||||
|
||||
const { firstName, lastName } = rawName
|
||||
? splitName(rawName)
|
||||
: { firstName: formFirstName ?? "", lastName: formLastName ?? "" };
|
||||
let firstName: string;
|
||||
let lastName: string;
|
||||
|
||||
if (rawName) {
|
||||
// Strip trailing bare dates DDMA appends to names e.g. "Christian Rodriguez 04/27/2026"
|
||||
const cleanName = rawName.replace(/\s+\d{1,2}\/\d{1,2}\/\d{2,4}$/, "").trim();
|
||||
|
||||
if (cleanName.includes(",")) {
|
||||
// "LAST, FIRST" format common on insurance portals
|
||||
const [last, ...firstParts] = cleanName.split(",").map((s: string) => s.trim());
|
||||
lastName = last || formLastName || "";
|
||||
firstName = firstParts.join(" ").trim() || formFirstName || "";
|
||||
} else {
|
||||
const parsed = splitName(cleanName);
|
||||
if (!parsed.lastName) {
|
||||
// Single word — treat as last name, pull first name from form
|
||||
lastName = parsed.firstName || formLastName || "";
|
||||
firstName = formFirstName || "";
|
||||
} else {
|
||||
firstName = parsed.firstName || formFirstName || "";
|
||||
lastName = parsed.lastName || formLastName || "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
firstName = formFirstName ?? "";
|
||||
lastName = formLastName ?? "";
|
||||
}
|
||||
|
||||
// 2) Create / update patient
|
||||
await createOrUpdatePatientByInsuranceId({
|
||||
@@ -104,7 +128,9 @@ async function processDdmaResult(
|
||||
});
|
||||
|
||||
// 3) Fetch patient (needed for ID)
|
||||
const patient = await storage.getPatientByInsuranceId(insuranceId);
|
||||
const normalizedInsuranceId = insuranceId.replace(/\s+/g, "");
|
||||
const patient = await storage.getPatientByInsuranceId(normalizedInsuranceId);
|
||||
log("ddma-processor", `patient lookup after create: ${patient ? `id=${patient.id}` : "NOT FOUND"} for insuranceId="${normalizedInsuranceId}"`);
|
||||
if (!patient?.id) {
|
||||
output.patientUpdateStatus = "Patient not found; no update performed";
|
||||
return output;
|
||||
@@ -177,6 +203,7 @@ async function processDdmaResult(
|
||||
output.pdfFileId = createdPdfFileId;
|
||||
return output;
|
||||
} catch (err: any) {
|
||||
log("ddma-processor", `processDdmaResult ERROR: ${err?.message ?? String(err)}`, err);
|
||||
return {
|
||||
...output,
|
||||
pdfUploadStatus:
|
||||
|
||||
Reference in New Issue
Block a user