docs: record the FDI-first tooth rule in the voice spec
The spec's tooth section described the design that the first live test disproved — descriptive phrasing primary, bare numerals refused in en. Rewrites §6 around the rule the product actually wants, records the chip affordance in §7, and closes open item §11.5: a two-digit number is FDI in all three locales, with the Universal-numbering trade-off written down rather than left implied. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
# Voice treatment entry
|
# Voice treatment entry
|
||||||
|
|
||||||
**Status:** Implemented on `feat/voice-treatment-entry` — unreviewed, and blocked on the
|
**Status:** Implemented on `feat/voice-treatment-entry`. First live test on 2026-08-21
|
||||||
ASR spike (§11 item 1) before it is trustworthy in front of patients
|
sent the tooth path back for revision — a spoken number is now read as its FDI code (§6).
|
||||||
|
Still blocked on the ASR spike (§11 item 1) before it is trustworthy in front of patients
|
||||||
**Area:** Treatment workspace (CLINIC orgs)
|
**Area:** Treatment workspace (CLINIC orgs)
|
||||||
**Created:** 2026-08-20
|
**Created:** 2026-08-20
|
||||||
|
|
||||||
@@ -415,12 +416,20 @@ that justified this whole design.
|
|||||||
words, so the resolver needs no per-locale branches. The locale-specific part is the
|
words, so the resolver needs no per-locale branches. The locale-specific part is the
|
||||||
*prompt*: each enabled locale needs its own spoken tooth vocabulary (`شش بالا راست`,
|
*prompt*: each enabled locale needs its own spoken tooth vocabulary (`شش بالا راست`,
|
||||||
`upper right six`, `rechtsboven zes`).
|
`upper right six`, `rechtsboven zes`).
|
||||||
- **English carries a numbering hazard the other locales do not.** A clinician trained
|
- **A spoken tooth number is an FDI code, in every locale.** This is how clinicians
|
||||||
under Universal numbering says "tooth number 14" and means a different tooth than FDI
|
actually dictate — "بیست و شش" is tooth 26 — so the prompt *teaches* the notation
|
||||||
14. `nl` is safe — the Netherlands uses FDI — but `en` is not. The `en` prompt must
|
(first digit = quadrant from the patient's own point of view, second = position from
|
||||||
therefore not accept a bare two-digit number as `explicitFdi` without the speaker
|
the midline) rather than refusing it. `arch`/`side`/`position` is the reading of a
|
||||||
having made the notation explicit; ambiguous English numerals resolve to
|
tooth that was **described** instead of numbered, where a single digit is a position
|
||||||
**unresolved**. See §11.
|
and the quadrant comes from words. Revised after the first live test; the original
|
||||||
|
design had this backwards and made the descriptive form the only supported path.
|
||||||
|
- **A single digit alone is never resolved.** "دندون دو" names four teeth. It is reported
|
||||||
|
as `tooth_missing_quadrant` **with the candidate codes attached** — narrowed by whatever
|
||||||
|
*was* said, so "دو بالا" offers two — and the review sheet turns them into chips. The
|
||||||
|
clinician chooses; the resolver still never guesses.
|
||||||
|
- **Digits arrive in three scripts.** `normalizeFdiCode` (`common/fdi.ts`) folds Persian
|
||||||
|
and Arabic-Indic digits to ASCII and strips the spaces of a digit-by-digit dictation
|
||||||
|
before anything is matched, at both the wire branch choice and the final validation.
|
||||||
|
|
||||||
### `resolveDueDate()`
|
### `resolveDueDate()`
|
||||||
|
|
||||||
@@ -472,6 +481,10 @@ that justified this whole design.
|
|||||||
- any row carrying an unresolved item or an incomplete prosthesis map.
|
- any row carrying an unresolved item or an incomplete prosthesis map.
|
||||||
- Unresolved items are shown with what was heard ("دندان شیری — بازشناسی نشد"), so the
|
- Unresolved items are shown with what was heard ("دندان شیری — بازشناسی نشد"), so the
|
||||||
clinician can see what the system did not understand.
|
clinician can see what the system did not understand.
|
||||||
|
- An item that carries `candidates` renders them as **tappable chips** — the one place the
|
||||||
|
sheet is interactive. Picking one folds the tooth into the result (`withChosenTeeth`) and
|
||||||
|
ticks the teeth row, so an under-specified tooth is one tap from resolved instead of a
|
||||||
|
dead end. Everything the sheet renders comes from that folded result, not the raw one.
|
||||||
- RTL-safe: logical `text-start` / `text-end` only, never `text-left`/`text-right`.
|
- RTL-safe: logical `text-start` / `text-end` only, never `text-left`/`text-right`.
|
||||||
Dates via `lib/i18n/format.ts`.
|
Dates via `lib/i18n/format.ts`.
|
||||||
|
|
||||||
@@ -607,12 +620,11 @@ enabling this for real clinics.
|
|||||||
per-request one: re-verify it if the API key or the OpenRouter account changes, and
|
per-request one: re-verify it if the API key or the OpenRouter account changes, and
|
||||||
remember `whisper-1` is forwarded to OpenAI, so the effective policy is OpenRouter's
|
remember `whisper-1` is forwarded to OpenAI, so the effective policy is OpenRouter's
|
||||||
plus that provider's.
|
plus that provider's.
|
||||||
5. **English tooth numbering is unresolved as a product question.** Enabling `en` means
|
5. ~~**English tooth numbering**~~ — **resolved:** a bare two-digit number is read as
|
||||||
deciding what "tooth number 14" means when the speaker's notation is unknown —
|
**FDI in all three locales**. FDI is what the product is built on and what clinicians
|
||||||
Universal or FDI. The spec's current answer is to refuse ambiguous bare numerals in
|
dictate. Known trade-off, accepted: a clinician trained under Universal numbering says
|
||||||
`en`, which is safe but will feel broken to a US-trained clinician. Options are: refuse
|
"tooth 14" and means a different tooth, so an `en` clinic needs either training or a
|
||||||
(current), an org-level notation preference, or restricting `en` to quadrant-relative
|
later per-org notation setting. Revisit if a US clinic is onboarded.
|
||||||
phrasing. Decide before `en` ships to a real clinic; `fa` and `nl` are unaffected.
|
|
||||||
6. **`nl` and `en` have no spike data.** The Persian spike (item 1) should be repeated per
|
6. **`nl` and `en` have no spike data.** The Persian spike (item 1) should be repeated per
|
||||||
locale before that locale's mic is enabled for real users — same protocol, same
|
locale before that locale's mic is enabled for real users — same protocol, same
|
||||||
scoring, different speaker.
|
scoring, different speaker.
|
||||||
@@ -688,7 +700,7 @@ Settled in a grilling session on 2026-08-20.
|
|||||||
| 1 | Scope | Everything including lab dispatch |
|
| 1 | Scope | Everything including lab dispatch |
|
||||||
| 2 | AI supply chain | Domestic provider originally; OpenRouter for v1, registry keeps both open |
|
| 2 | AI supply chain | Domestic provider originally; OpenRouter for v1, registry keeps both open |
|
||||||
| 3 | Apply model | Review sheet, then apply |
|
| 3 | Apply model | Review sheet, then apply |
|
||||||
| 4 | Speech → FDI | LLM emits intent, code resolves |
|
| 4 | Speech → FDI | LLM emits intent, code resolves. A spoken number **is** the FDI code (revised 2026-08-21, §6) |
|
||||||
| 5 | Cardinality | One detail per recording |
|
| 5 | Cardinality | One detail per recording |
|
||||||
| 6 | Lab destination | Closed list of linked labs, explicit confirm, unticked when inexact |
|
| 6 | Lab destination | Closed list of linked labs, explicit confirm, unticked when inexact |
|
||||||
| 7 | Due date | Intent + deterministic resolver |
|
| 7 | Due date | Intent + deterministic resolver |
|
||||||
@@ -704,7 +716,7 @@ Settled in a grilling session on 2026-08-20.
|
|||||||
| 25 | Salvage target | Creates a new detail with only `comment` set — voice never writes into an existing detail |
|
| 25 | Salvage target | Creates a new detail with only `comment` set — voice never writes into an existing detail |
|
||||||
| 26 | Throttle | Configurable; v1 default 6 requests / 60s per user |
|
| 26 | Throttle | Configurable; v1 default 6 requests / 60s per user |
|
||||||
| 27 | Duration cap | **2 minutes**, configurable via `maxMs` |
|
| 27 | Duration cap | **2 minutes**, configurable via `maxMs` |
|
||||||
| 28 | Review sheet | Modal on desktop, full-screen overlay (not a route) on mobile |
|
| 28 | Review sheet | Modal on desktop, full-screen overlay (not a route) on mobile; candidate chips are its only interactive part |
|
||||||
| 29 | Cancel | Aborts the in-flight vendor call |
|
| 29 | Cancel | Aborts the in-flight vendor call |
|
||||||
| 30 | v1 gating | Open to everyone; `Plan.features` gate deferred, not dropped |
|
| 30 | v1 gating | Open to everyone; `Plan.features` gate deferred, not dropped |
|
||||||
| 15 | Gating | `Plan.features` flag — its first consumer |
|
| 15 | Gating | `Plan.features` flag — its first consumer |
|
||||||
|
|||||||
Reference in New Issue
Block a user