fixed combos, and pdf format updated

This commit is contained in:
2025-08-29 01:30:53 +05:30
parent 4c818d511b
commit 1d13f1a73a
4 changed files with 84 additions and 29 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useRef } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
@@ -39,7 +39,6 @@ import {
} from "@repo/db/types";
import { Decimal } from "decimal.js";
import {
COMBO_BUTTONS,
mapPricesForForm,
applyComboToForm,
getDescriptionForCode,
@@ -257,6 +256,16 @@ export function ClaimForm({
setForm({ ...form, serviceLines: updatedLines });
};
// for serviceLine rows, to auto scroll when it got updated by combo buttons and all.
const rowRefs = useRef<(HTMLDivElement | null)[]>([]);
const scrollToLine = (index: number) => {
const el = rowRefs.current[index];
if (el) {
el.scrollIntoView({ behavior: "smooth", block: "start" });
}
};
// Map Price function
const onMapPrice = () => {
setForm((prev) =>
@@ -573,7 +582,11 @@ export function ClaimForm({
return (
<div
key={i}
className="grid grid-cols-[1.5fr,0.5fr,1fr,1fr,1fr,1fr,1fr] gap-1 mb-2 items-center"
ref={(el) => {
rowRefs.current[i] = el;
if (!el) rowRefs.current.splice(i, 1);
}}
className="scroll-mt-28 grid grid-cols-[1.5fr,0.5fr,1fr,1fr,1fr,1fr,1fr] gap-1 mb-2 items-center"
>
<div className="grid grid-cols-[auto,1fr] items-center gap-2">
<button
@@ -708,23 +721,25 @@ export function ClaimForm({
+ Add Service Line
</Button>
<div className="space-y-8 mt-10 mb-10">
<div className="space-y-4 mt-8">
{Object.entries(COMBO_CATEGORIES).map(([section, ids]) => (
<div key={section}>
<div className="mb-3 text-sm font-semibold opacity-70">
{section}
</div>
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap gap-1">
{ids.map((id) => {
const b = PROCEDURE_COMBOS[id];
if(!b){return}
if (!b) {
return;
}
return (
<Button
key={b.id}
variant="secondary"
onClick={() =>
setForm((prev) =>
applyComboToForm(
setForm((prev) => {
const next = applyComboToForm(
prev,
b.id as any,
patient?.dateOfBirth ?? "",
@@ -732,8 +747,12 @@ export function ClaimForm({
replaceAll: true,
lineDate: prev.serviceDate,
}
)
)
);
setTimeout(() => scrollToLine(0), 0);
return next;
})
}
>
{b.label}
@@ -744,13 +763,12 @@ export function ClaimForm({
</div>
))}
<div className="pt-4">
<div className="pt-2">
<Button variant="success" onClick={onMapPrice}>
Map Price
</Button>
</div>
</div>
</div>
{/* File Upload Section */}

View File

@@ -1,6 +1,6 @@
export const PROCEDURE_COMBOS: Record<
string,
{ id: string; label: string; codes: string[] }
{ id: string; label: string; codes: string[]; toothNumbers?: (string | null)[] }
> = {
childRecall: {
id: "childRecall",
@@ -16,6 +16,7 @@ export const PROCEDURE_COMBOS: Record<
id: "adultRecall",
label: "Adult Recall",
codes: ["D0120", "D0220", "D0230", "D0274", "D1110"],
toothNumbers: [null, "9", "24", null, null], // only these two need values
},
newChildPatient: {
id: "newChildPatient",

View File

@@ -251,7 +251,10 @@ export function applyComboToForm<T extends ClaimFormLike>(
procedureCode: code,
procedureDate: lineDate,
oralCavityArea: original?.oralCavityArea ?? "",
toothNumber: original?.toothNumber ?? "",
toothNumber:
preset.toothNumbers?.[j] ??
original?.toothNumber ??
"",
toothSurface: original?.toothSurface ?? "",
totalBilled: price,
totalAdjusted: new Decimal(0),

View File

@@ -4,35 +4,68 @@ import re
app = Flask(__name__)
DOB_RE = re.compile(r'(?<!\d)(\d{1,2})/(\d{1,2})/(\d{4})(?!\d)')
ID_RE = re.compile(r'^\d{8,14}$') # 814 digits, whole line
# lines that tell us we've moved past the name/DOB area
STOP_WORDS = {
'eligibility', 'coverage', 'age band', 'date of', 'service',
'tooth', 'number', 'surface', 'procedure', 'code', 'description',
'provider', 'printed on', 'member id', 'name', 'date of birth'
}
@app.route("/extract", methods=["POST"])
def extract():
file = request.files['pdf']
doc = fitz.open(stream=file.read(), filetype="pdf")
text = "\n".join(page.get_text() for page in doc)
text = "\n".join(page.get_text("text") for page in doc)
lines = [line.strip() for line in text.splitlines() if line.strip()]
member_id = ""
name = ""
dob = ""
for i, line in enumerate(lines):
if line.isdigit() and (len(line) <= 14 or len(line) >= 8):
member_id = line
name_lines = []
j = i + 1
while j < len(lines) and not re.match(r"\d{1,2}/\d{1,2}/\d{4}", lines[j]):
name_lines.append(lines[j])
j += 1
name = " ".join(name_lines).strip()
if j < len(lines):
dob = lines[j].strip()
# 1) Find the first plausible member ID (814 digits)
id_idx = -1
for i, line in enumerate(lines):
if ID_RE.match(line):
member_id = line
id_idx = i
break
return {
if id_idx == -1:
return jsonify({"memberId": "", "name": "", "dob": ""})
# 2) Scan forward to collect name + DOB; handle both same-line and next-line cases
collected = []
j = id_idx + 1
while j < len(lines):
low = lines[j].lower()
if any(sw in low for sw in STOP_WORDS):
break
collected.append(lines[j])
# If we already found a DOB, we can stop early
if DOB_RE.search(lines[j]):
break
j += 1
# Flatten the collected chunk to search for a date (works if DOB is on same line or next)
blob = " ".join(collected).strip()
m = DOB_RE.search(blob)
if m:
dob = m.group(0)
# name is everything before the date within the same blob
name = blob[:m.start()].strip()
else:
# fallback: if we didn't find a date, assume first collected line(s) are name
name = blob
return jsonify({
"memberId": member_id,
"name": name,
"dob": dob
}
})
if __name__ == "__main__":
app.run(port=5001)