feat(backend): read a spoken tooth number as its FDI code
The prompt had this backwards. "Never output an FDI tooth code unless the speaker used FDI notation. Prefer arch + side + position" pushed the model to decompose speech into "upper / patient_right / six", so the clinician effectively had to *describe* every tooth. Saying "دندون بیست و شش" — the way a dentist actually dictates — was the unsupported path. FDI is what clinicians speak, so the prompt now teaches the notation instead of forbidding it: first digit = quadrant from the patient's own point of view, second digit = position from the midline. arch/side/position stays as the reading of a *described* tooth, where a single digit is a position and the quadrant comes from words. Two guards come with it, because bare numbers are now teeth: a single digit alone still refuses to guess a quadrant, and dates, counts and quantities are explicitly not teeth. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,21 +3,19 @@ import type { ExtractionCatalog } from './voice.providers';
|
||||
/** Locale-specific guidance. Only the tooth vocabulary and numbering habits differ. */
|
||||
const LOCALE_NOTES: Record<string, string> = {
|
||||
fa: [
|
||||
'The clinician is speaking Persian. Tooth references are usually quadrant-relative:',
|
||||
'The clinician is speaking Persian. A tooth number can be said as a whole number',
|
||||
'("بیست و شش" = 26), digit by digit ("دو شش" = 26), or with a lead-in',
|
||||
'("دندون شماره ۲۶"). Digits may arrive in Persian or Latin script — either way, copy',
|
||||
'the number into "fdi" as two Latin digits. The descriptive form is quadrant-relative:',
|
||||
'"شش بالا راست" = upper right six -> arch "upper", side "patient_right", position 6.',
|
||||
'Digits may appear in Persian or Latin script. Two-digit FDI notation ("یک چهار") does',
|
||||
'occur — use the "fdi" field only for that.',
|
||||
'A bare "دندون دو" carries no quadrant: report position 2 with arch and side null.',
|
||||
].join(' '),
|
||||
nl: [
|
||||
'The clinician is speaking Dutch and uses FDI notation, which is standard in the',
|
||||
'Netherlands. "rechtsboven zes" = upper right six. A bare two-digit number is FDI.',
|
||||
'The clinician is speaking Dutch, where FDI is standard. "zesentwintig" and "26" are',
|
||||
'tooth 26. The descriptive form is "rechtsboven zes" = upper right six.',
|
||||
].join(' '),
|
||||
en: [
|
||||
'The clinician is speaking English. IMPORTANT: a bare two-digit number is ambiguous,',
|
||||
'because Universal numbering and FDI disagree ("tooth 14" is a different tooth in each).',
|
||||
'Set "fdi" ONLY when the speaker made the notation explicit (e.g. "FDI one four").',
|
||||
'Otherwise describe the tooth with arch/side/position, or leave it unresolved.',
|
||||
'The clinician is speaking English and uses FDI. "twenty-six", "two six" and "26" are',
|
||||
'all tooth 26. The descriptive form is "upper right six".',
|
||||
].join(' '),
|
||||
};
|
||||
|
||||
@@ -41,19 +39,32 @@ export function buildExtractionPrompt(
|
||||
'1. Never invent a code. treatmentType, prosthesisDefaultType and prosthesisOverrides[].type',
|
||||
' must be codes from the lists below. labId must be an id from the lab list. If what you',
|
||||
' heard is not in a list, use null.',
|
||||
'2. Never output an FDI tooth code unless the speaker used FDI notation. Prefer',
|
||||
' arch + side + position.',
|
||||
'3. "side" is always the PATIENT\'s side. The patient\'s upper right is quadrant 1. Never',
|
||||
'2. "side" is always the PATIENT\'s side. The patient\'s upper right is quadrant 1. Never',
|
||||
" flip to the viewer's point of view.",
|
||||
'4. Never do calendar arithmetic. Report the deadline as it was said, using due.kind.',
|
||||
'3. Never do calendar arithmetic. Report the deadline as it was said, using due.kind.',
|
||||
' If no deadline was mentioned, use due.kind = "none".',
|
||||
'5. Copy the exact spoken words for each tooth into "spoken", so the clinician can see',
|
||||
'4. Copy the exact spoken words for each tooth into "spoken", so the clinician can see',
|
||||
' what was heard.',
|
||||
'6. If you are unsure about a value, use null. A missing field is recoverable; a wrong',
|
||||
'5. If you are unsure about a value, use null. A missing field is recoverable; a wrong',
|
||||
' one is not.',
|
||||
'7. A tooth number spoken WITHOUT a quadrant ("دندون دو", "tooth two") does not identify',
|
||||
' a tooth — four teeth carry that position. Still report it: set "position" and leave',
|
||||
' "arch" and "side" null. Never pick a quadrant that was not said.',
|
||||
'',
|
||||
'TOOTH NUMBERS',
|
||||
'A number the clinician says for a tooth IS that tooth\'s FDI code. Put it in "fdi" as',
|
||||
'two digits. FDI is built from the two digits:',
|
||||
" first digit = quadrant, from the PATIENT's own point of view —",
|
||||
' 1 upper right, 2 upper left, 3 lower left, 4 lower right.',
|
||||
' (5-8 are those same four quadrants in primary/deciduous teeth.)',
|
||||
' second digit = position from the midline — 1 central incisor ... 8 third molar.',
|
||||
'So 26 is the upper left first molar, and 47 is the lower right second molar.',
|
||||
'',
|
||||
'- Use "arch" + "side" + "position" only when the tooth is DESCRIBED rather than',
|
||||
' numbered ("upper right six" -> arch "upper", side "patient_right", position 6).',
|
||||
'- A single digit is a position, never an FDI code. If a single digit is said with no',
|
||||
' quadrant words at all, set "position" and leave "arch" and "side" null. Never pick a',
|
||||
' quadrant that was not said.',
|
||||
'- If a number is given AND the quadrant is spelled out as well, still use "fdi".',
|
||||
'- Not every number is a tooth. Dates, counts and quantities ("two teeth", "the 26th")',
|
||||
' are not teeth, and must never appear in the teeth list.',
|
||||
'',
|
||||
localeNote,
|
||||
'',
|
||||
|
||||
@@ -21,7 +21,7 @@ import { WEEKDAYS } from './voice.types';
|
||||
|
||||
export type WireToothIntent = {
|
||||
spoken: string;
|
||||
/** Two-digit FDI code, only when the speaker genuinely used FDI notation. */
|
||||
/** The two-digit FDI code the clinician spoke; null when the tooth was described. */
|
||||
fdi: string | null;
|
||||
arch: 'upper' | 'lower' | null;
|
||||
side: 'patient_right' | 'patient_left' | null;
|
||||
@@ -66,7 +66,8 @@ const TOOTH_SCHEMA = {
|
||||
fdi: {
|
||||
type: ['string', 'null'],
|
||||
description:
|
||||
'Two-digit FDI code ONLY if the speaker used FDI notation. Otherwise null.',
|
||||
'The two-digit FDI code the clinician said for this tooth, e.g. "26". Null only ' +
|
||||
'when the tooth was described in words instead of numbered.',
|
||||
},
|
||||
arch: { type: ['string', 'null'], enum: ['upper', 'lower', null] },
|
||||
side: {
|
||||
@@ -76,7 +77,8 @@ const TOOTH_SCHEMA = {
|
||||
},
|
||||
position: {
|
||||
type: ['integer', 'null'],
|
||||
description: '1 = central incisor … 8 = third molar.',
|
||||
description:
|
||||
'Position from the midline: 1 = central incisor … 8 = third molar. Never an FDI code.',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user