feat(backend): extract shared FDI tooth geometry
Voice extraction needs quadrant mapping and adjacency server-side, and treatment.utils.ts already held a private copy of the tooth set. Lift it into common/fdi.ts rather than create a second source of truth; treatment.utils now imports it, behaviour unchanged (existing suites still pass). toFdi() is the single place the patient-right convention lives: quadrant 1 is the patient's upper right, so upper+patient_right -> 1x, upper+patient_left -> 2x, lower+patient_left -> 3x, lower+patient_right -> 4x. Getting this backwards mirrors every quadrant and yields a valid-looking code for the wrong tooth, which no schema check can catch — so all four quadrants are pinned by tests, along with out-of-range positions never being clamped and deciduous teeth being rejected outright (the chart is permanent dentition only). Adjacency mirrors the frontend's arch-order rule, so the midline pairs 11-21 and 41-31 count as neighbours exactly as the chart treats them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,4 @@
|
||||
const FDI_TOOTH_IDS = new Set([
|
||||
'11', '12', '13', '14', '15', '16', '17', '18',
|
||||
'21', '22', '23', '24', '25', '26', '27', '28',
|
||||
'31', '32', '33', '34', '35', '36', '37', '38',
|
||||
'41', '42', '43', '44', '45', '46', '47', '48',
|
||||
]);
|
||||
import { FDI_TOOTH_IDS } from '../../common/fdi';
|
||||
|
||||
export function normalizeTeeth(teeth: unknown): string[] {
|
||||
if (!Array.isArray(teeth)) {
|
||||
@@ -35,7 +30,10 @@ export function normalizeToothSelectionGroups(
|
||||
for (const row of value) {
|
||||
if (!row || typeof row !== 'object') continue;
|
||||
const rec = row as Record<string, unknown>;
|
||||
const groupId = typeof rec.groupId === 'string' && rec.groupId.trim() ? rec.groupId.trim() : '';
|
||||
const groupId =
|
||||
typeof rec.groupId === 'string' && rec.groupId.trim()
|
||||
? rec.groupId.trim()
|
||||
: '';
|
||||
if (!groupId) continue;
|
||||
const kind = rec.kind === 'connected' ? 'connected' : 'single';
|
||||
const teeth = normalizeTeeth(rec.teeth);
|
||||
@@ -64,7 +62,8 @@ export function generateTreatmentTitle(
|
||||
}
|
||||
|
||||
const parts = cases.map((c) => {
|
||||
const label = c.treatmentType.charAt(0).toUpperCase() + c.treatmentType.slice(1);
|
||||
const label =
|
||||
c.treatmentType.charAt(0).toUpperCase() + c.treatmentType.slice(1);
|
||||
if (c.teeth.length > 0) {
|
||||
return `${label} ${c.teeth.join(', ')}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user