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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user