fix: recognize CDT codes with primary tooth letter notation (# H)

extractToothSurface now handles A-T primary tooth letters in addition to
numeric #1-32. strippedCleaned strips both formats so "D7111 # H" reduces
to "D7111" for the CDT pass-through. Pass-through no longer returns null
for codes not in the fee schedule JSON.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-08 00:28:56 -04:00
parent 967a53fc6c
commit 09cd0328cb

View File

@@ -224,15 +224,22 @@ const DIRECT_CODE_MAP: Record<string, string> = {
* e.g. "extraction #14" → { toothNumber: "14" }
*/
function extractToothSurface(input: string): { toothNumber?: string; toothSurface?: string } {
// Numeric tooth #1-32 (e.g. "#29 OB")
const m = input.match(/#(\d{1,2})(?:\s+([A-Za-z]{1,5}))?/);
if (!m) return {};
const toothNum = parseInt(m[1]!, 10);
if (toothNum < 1 || toothNum > 32) return {};
const surfaces = (m[2] ?? "").toUpperCase();
return {
toothNumber: String(toothNum),
...(surfaces && /^[OMDBLFIV]+$/.test(surfaces) ? { toothSurface: surfaces } : {}),
};
if (m) {
const toothNum = parseInt(m[1]!, 10);
if (toothNum >= 1 && toothNum <= 32) {
const surfaces = (m[2] ?? "").toUpperCase();
return {
toothNumber: String(toothNum),
...(surfaces && /^[OMDBLFIV]+$/.test(surfaces) ? { toothSurface: surfaces } : {}),
};
}
}
// Primary/baby tooth letter A-T (e.g. "# H", "#H")
const pm = input.match(/#\s*([A-Ta-t])\b/);
if (pm) return { toothNumber: pm[1]!.toUpperCase() };
return {};
}
// Composite filling tooth classification
@@ -336,14 +343,13 @@ function score(queryTokens: string[], entry: { tokens: Set<string>; description:
function matchOne(input: string): CdtMatch | null {
const cleaned = input.trim().toLowerCase();
// Direct CDT code pass-through (e.g. "D0120")
// Direct CDT code pass-through (e.g. "D0120", "D7111")
if (/^d\d{4}$/i.test(cleaned)) {
const row = ALL_CODES.find(
(r) => r["Procedure Code"].toLowerCase() === cleaned.toLowerCase()
);
return row
? { code: row["Procedure Code"], description: row.Description, input }
: null;
const code = row?.["Procedure Code"] ?? cleaned.toUpperCase();
return { code, description: row?.Description ?? code, input };
}
// Apply alias before tokenizing
@@ -390,9 +396,13 @@ export function lookupCdtCodes(
const cleaned = name.trim().toLowerCase();
// Extract tooth# from "#NN" notation — applies to any procedure
const toothInfo = extractToothSurface(name);
// Version of cleaned with tooth notation stripped, for alias matching
// e.g. "1 pa, #3" → "1 pa" so it hits the "1 pa" alias
const strippedCleaned = cleaned.replace(/,?\s*#\d{1,2}\b/g, "").replace(/\s+/g, " ").trim();
// Strip tooth notation for alias matching:
// numeric (#29), numeric with space (# 3), primary letter (#H, # H)
const strippedCleaned = cleaned
.replace(/,?\s*#\s*\d{1,2}\b/g, "")
.replace(/,?\s*#\s*[a-t]\b/gi, "")
.replace(/\s+/g, " ")
.trim();
// 1. Custom alias exact match (highest priority)
const customKey = customMap[cleaned] ? cleaned : customMap[strippedCleaned] ? strippedCleaned : null;