fix(voice): hear "یادداشت" as a note request, and stop the note at the next instruction

Two reports from live dictation in fa.

The locale trigger vocabulary listed only verbs that ask for a note ("بنویس",
"یادداشت کن"). Naming the field itself — "توی یادداشت‌ها بنویس", "در توضیحات" —
reported no commentTrigger, and the resolver discards a comment without one, so
the note was silently dropped. Added the field-name shape to fa, en and nl.

Where the dictated note ends was never stated, so the model answered it
inconsistently: "توی توضیحات بنویس سلام چطوری و بفرست برای لابراتوآر سانشاین"
sometimes put the lab dispatch inside the comment. Added a no-overlap rule — a
clause reported as a lab, tooth, prosthesis or deadline is an instruction and
must not appear in comment as well — with that sentence as a worked example.

Both rules go in the prompt and in the JSON-schema field descriptions, because
the schema description is what the model reads while filling the field. No code
enforcement for the second: stripping an extracted clause out of free-speech
note text by string matching is guesswork, and the review sheet shows the note
before it is saved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-11 19:33:49 +08:00
parent b71b4e9ca9
commit 78fd3eb092
4 changed files with 61 additions and 8 deletions

View File

@@ -524,6 +524,32 @@ step — only build and deploy. Combined with `nest build` excluding `**/*spec.t
running transpile-only under `isolatedModules`, nothing in this repo would ever catch a type error
in a spec file. That is how two of them reached this branch unnoticed.
## 2026-09-11 — note triggers and where a note ends (dyolink)
Two reports from live dictation in `fa`, both prompt-side, no resolver change.
1. «یادداشت» / «یادداشت‌ها» produced no trigger, so the note was discarded. The locale notes
listed only verbs that ask («بنویس», «یادداشت کن»); naming the field itself was not in the
vocabulary. Added the field-name shape for all three locales — fa «یادداشت/یادداشت‌ها/توضیحات/
کامنت», en "note/notes/comments/remarks", nl "notitie/notities/opmerkingen".
2. The note swallowed the next instruction. For «توی توضیحات بنویس سلام چطوری و بفرست برای
لابراتوآر سانشاین» the model sometimes put the whole tail in `comment`, sometimes only
«سلام چطوری». Added a no-overlap invariant: a clause reported as a lab, tooth, prosthesis or
deadline is an instruction and must not also appear in `comment`, plus that exact sentence as
a worked example.
Both rules are stated twice on purpose — in `extraction.prompt.ts` and in the `comment` /
`commentTrigger` descriptions in `extraction.wire.ts`, since the schema description is what the
model reads while filling the field.
No code enforcement for (2). Stripping an extracted clause out of free-speech note text by string
matching is guesswork, and the review sheet shows the note before it is saved.
Files: `backend/src/modules/voice/extraction.prompt.ts`,
`backend/src/modules/voice/extraction.wire.ts`, `docs/specs/voice-treatment-entry/spec.md`.
Verified: `npx eslint src/modules/voice/**/*.ts` clean, `npm test -- extraction` 74/74.
No unit test — both are prompt text, which the extraction specs deliberately do not assert on.
## Remaining before this is shippable
- The **manual pass in §12** — none of it has been run. Safari and iPad especially, since that is

View File

@@ -535,7 +535,21 @@ what was captured, so nothing is hidden by leaving it out.
Per-locale trigger vocabulary lives in the prompt's locale notes, exactly like the tooth
vocabulary. The resolver only checks that a trigger was reported, so it needs no per-locale
knowledge and stays locale-neutral by construction (§6).
knowledge and stays locale-neutral by construction (§6). The vocabulary covers two shapes: a
verb that asks («بنویس که», "noteer") and the *name of the field* the words go into («یادداشت»,
«یادداشت‌ها», «توضیحات», "notes", "comments"). Naming the field is how a clinician usually asks,
and the first version listed only the verbs, so «توی یادداشت‌ها بنویس» produced no trigger and
the note was discarded.
Where the dictated note ends is a second question, and the model answered it inconsistently: for
«توی توضیحات بنویس سلام چطوری و بفرست برای لابراتوآر سانشاین» the note sometimes ran to the end
of the sentence and swallowed the lab dispatch. The rule is that the note ends where the next
instruction begins, stated as a no-overlap invariant: a clause reported as a lab, a tooth, a
prosthesis or a deadline is that instruction and must not also appear in `comment`. It lives in
both the prompt and the JSON-schema field description, because the schema description is what the
model reads while it fills the field. This is a prompt-side rule with no code enforcement —
stripping an already-extracted clause out of the note text by string matching would be guesswork
on free speech, and the review sheet shows the note before it is saved.
### Prosthesis work implies the treatment type