feat(backend): assemble resolved extraction from voice intents
Composes the tooth, span, prosthesis, catalog and date resolvers into the payload the review sheet renders. Connected spans expand: "a bridge from 14 to 16" selects 15, which was never spoken. Overlapping spans merge into one bridge, group teeth sort along the arch (16-15-14, and 11 beside 21 across the midline), and a span collapsing to a single tooth degrades to a single group without losing that tooth — there is no such thing as a one-tooth bridge. A cross-arch span is impossible and is reported rather than guessed at. Prosthesis expands a default across the selection then applies per-tooth overrides, because "همه زیرکونیا، ۲۶ پیافام" is how clinicians actually speak. Completeness is computed here so an unshippable map surfaces at review rather than failing later at dispatch. Everything the model names is checked against the catalog we supplied it, and anything rejected is reported rather than dropped — a hallucinated lab id must not look identical to "no lab was spoken", since silence and a wrong lab lead to very different corrective actions. Also fixed, from review of this commit: - an empty prosthesis object no longer fabricates an "incomplete, cannot ship" warning on a plain restoration - an override naming a tooth outside the selection now reports tooth_not_selected rather than malformed; the clinician was understood, the tooth just is not on this detail - a due object with no `kind` is treated as no deadline rather than a blank "heard but lost" row; an unrecognised kind is still flagged, and named Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -87,8 +87,12 @@ function describe(intent: DueIntent): string {
|
||||
return `${intent.jy}/${intent.jm}/${intent.jd}`;
|
||||
case 'gregorian':
|
||||
return `${intent.y}-${intent.m}-${intent.d}`;
|
||||
default:
|
||||
return '';
|
||||
default: {
|
||||
// Reaching here means an unrecognised `kind`, which resolveDueDate has already
|
||||
// established is a string — echo it so the review row names what was heard.
|
||||
const kind = (intent as { kind?: unknown })?.kind;
|
||||
return typeof kind === 'string' ? kind : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +166,12 @@ export function resolveDueDate(
|
||||
if (typeof intent !== 'object') {
|
||||
return unresolved(String(intent).slice(0, 120));
|
||||
}
|
||||
// An object carrying no `kind` at all says nothing about a deadline; flagging it would
|
||||
// put a blank "heard but lost" row in front of a clinician who never mentioned one. An
|
||||
// object with an *unrecognised* kind did try to say something, and is flagged below.
|
||||
if (typeof (intent as { kind?: unknown }).kind !== 'string') {
|
||||
return { dueDate: null, unresolved: null };
|
||||
}
|
||||
if (!isRealCivilDate(todayIso)) {
|
||||
return unresolved(describe(intent));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user