feat: DDMA claim submission with OTP, PDF, claim number extraction

- Add full DDMA claim Selenium flow (steps 1-8): search patient, open
  member page, create claim, fill form, attach files, next, submit,
  extract claim number and save confirmation PDF
- Add fee schedule price-mismatch dialog for all claim buttons (MH,
  CCA, DDMA, United, Tufts, Save) with optional price update to JSON
- Add OTP modal for DDMA claim when session expires, mirroring
  eligibility OTP flow
- Close Chrome after claim submission via quit_driver() (session
  preserved in profile)
- Move Map Price button between Direct Submission and procedure table,
  right-aligned above Billed Amount column
- Add fee-schedule update-price backend route
- Add DDMA claim processor with claimNumber/pdf_url result handling

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-24 13:35:04 -04:00
parent 5ceecbeb7f
commit cd1381e9c6
13 changed files with 2139 additions and 22 deletions

View File

@@ -2,6 +2,7 @@ import { InputServiceLine } from "@repo/db/types";
import Decimal from "decimal.js";
import rawCodeTable from "@/assets/data/procedureCodesMH.json";
import rawCCACodeTable from "@/assets/data/procedureCodesCCA.json";
import rawDDMACodeTable from "@/assets/data/procedureCodesDDMA.json";
import { PROCEDURE_COMBOS } from "./procedureCombos";
/* ----------------------------- Types ----------------------------- */
@@ -15,6 +16,7 @@ export type CodeRow = {
};
const CODE_TABLE = rawCodeTable as CodeRow[];
const CCA_CODE_TABLE = rawCCACodeTable as CodeRow[];
const DDMA_CODE_TABLE = rawDDMACodeTable as CodeRow[];
export type ClaimFormLike = {
serviceDate: string; // form-level service date
@@ -56,9 +58,19 @@ const CCA_CODE_MAP: Map<string, CodeRow> = (() => {
return m;
})();
const DDMA_CODE_MAP: Map<string, CodeRow> = (() => {
const m = new Map<string, CodeRow>();
for (const r of DDMA_CODE_TABLE) {
const k = normalizeCode(String(r["Procedure Code"] || ""));
if (k && !m.has(k)) m.set(k, r);
}
return m;
})();
/** Return the correct fee-schedule map for the given insurance type. */
function getCodeMap(insuranceSiteKey?: string): Map<string, CodeRow> {
if (insuranceSiteKey === "CCA") return CCA_CODE_MAP;
if (insuranceSiteKey === "DDMA") return DDMA_CODE_MAP;
return CODE_MAP; // default: MassHealth
}
@@ -333,4 +345,43 @@ export function applyComboToForm<T extends ClaimFormLike>(
}
export { CODE_MAP, CCA_CODE_MAP, getCodeMap, getPriceForCodeWithAgeFromMap };
export { CODE_MAP, CCA_CODE_MAP, DDMA_CODE_MAP, getCodeMap, getPriceForCodeWithAgeFromMap };
export type PriceMismatch = {
procedureCode: string;
enteredPrice: number;
schedulePrice: number;
};
/** Compare each service line's totalBilled against the fee schedule.
* Returns lines where the entered price differs from the schedule price.
* Returns empty array if the siteKey has no schedule (United, Tufts, etc.). */
export function findPriceMismatches(
serviceLines: InputServiceLine[],
insuranceSiteKey: string | undefined,
patientDOB: string,
serviceDate: string,
): PriceMismatch[] {
const supported = ["MH", "MASSHEALTH", "CCA", "DDMA"];
if (!insuranceSiteKey || !supported.includes(insuranceSiteKey.toUpperCase())) return [];
const map = getCodeMap(insuranceSiteKey);
const mismatches: PriceMismatch[] = [];
for (const line of serviceLines) {
const code = normalizeCode(line.procedureCode || "");
if (!code) continue;
const enteredPrice = new Decimal(Number(line.totalBilled) || 0);
if (enteredPrice.isZero()) continue;
const age = ageOnDate(patientDOB, serviceDate);
const schedulePrice = getPriceForCodeWithAgeFromMap(map, code, age);
if (!schedulePrice.isZero() && !enteredPrice.equals(schedulePrice)) {
mismatches.push({
procedureCode: code,
enteredPrice: enteredPrice.toNumber(),
schedulePrice: schedulePrice.toNumber(),
});
}
}
return mismatches;
}