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