Repository navigation
Validator: the meaning must fit the sentence it is shown in - #900
Merged
Merged
Conversation
mircealungu
force-pushed
the
validator-card-fits-sentence
branch
from
October 25, 2026 00:57
94d56ee to
670ea2f
Compare
The validator passed "vil sige" = "mean" in "Jeg lærte, hvad det vil sige
at gå" 3/3 ("a correct, tense-neutral gloss"), and fixed "will say" to that
same "mean": it judged whether a translation holds in other sentences, never
whether it reads right in this one.
- The prompt now asks first whether the card fits THIS sentence (form
included, English free to add "does"/"not" around it), then whether it is
the word's own meaning, expanding to the smallest expression worth
learning; corrected words must appear in the sentence as written.
- A third verdict, UNRESOLVED, for broken sentences and cards nothing fits.
validate_and_fix already treats it as "no correction": unfit for study.
- The reply writes the sentence with the card in place and judges it, for
the card and for a correction. The model judges those sentences right but
cannot revise a field it already wrote ("what it mean to walk" marked
false, "mean" kept), so the code acts on it: one retry with its own
sentence in front of it, then UNRESOLVED rather than a card it said is
wrong.
- The audio lesson pair check treats UNRESOLVED as not confirmed: a broken
sentence is no reason to mark the meaning INVALID for every learner.
Regression set, 3 runs x 50 cases, runs failing by kind:
before after
wrong card shown 34 18
broken sentence let through 12 3
gave up when it should not 0 5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- validate_and_fix sent UNRESOLVED into _fix_bookmark's "no correction"
branch, which marks the meaning INVALID for every learner. A broken
sentence says nothing about the pair: now only this learner's word leaves
study, and the log reason starts with [unresolved].
- A failed misfit retry is no verdict (None), as for the first call, not a
give-up.
- UNRESOLVED returns before the options retry, which could re-ask a give-up
into an unchecked FIX.
- A correction counts as in its sentence when its words are there in order:
"put up for sale" is in "put their homes up for sale", "don't" matches
"don’t". The contiguous match gave sætter til salg up 3/3.
- The audio lesson pair check asks DeepSeek for the new fields, keeps the
booleans booleans, applies the same misfit check, and reports UNRESOLVED as
a verdict of its own rather than an error that every run retries; the
script generator treats it as not confirmed.
- "corrected_translation is in {target_lang}" back on the corrected pair.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The reply wrote "what it means to walk", judged it right, and passed the
card "mean" as VALID (1 of 3). The words-in-order check on corrections now
applies to the VALID card too.
- "homeowners who put up for sale" is wrong English (it needs an object),
so the reply rejected the right card "sætter til salg = put up for sale"
and gave up 3/3. The sentence may now put words between the card's words
("put the house up for sale"), which words_in_order already allows.
Regression set, 3 runs x 50 cases: 123/150; wrong card 18, broken sentence
let through 3, gave up when it should not 6. vil sige = mean: never VALID.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Options are reduced before the fit check: "by (doing something)" was
never word for word in any sentence, so it was retried and given up on
instead of becoming "by". The options retry can no longer replace a
checked reply with an unchecked one, nor revive a give-up.
- A corrected word must be in the sentence, checked in code ("særlig" in
"Det ganske særl", "umanned"); a word-only fix checks the old translation.
- A card missing word for word from its own sentence twice is kept, not
given up on: separable and inflected verbs do that ("anrufen" in "Ich
rufe dich an"). Give-ups are for what the reply calls wrong, or a word
not in the sentence.
- A failed misfit retry goes to Sentry, like a failed first call.
- A give-up clears the word's schedule (set_unfit_for_study).
- The retry prompt no longer quotes "None" when the reply wrote no sentence.
- Only the two flags stay booleans in the DeepSeek pair check; a boolean in
a text field would crash the parser.
- ValidationResult.verdict names the verdict in one place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mircealungu
force-pushed
the
validator-card-fits-sentence
branch
from
October 25, 2026 01:11
670ea2f to
0aa8720
Compare
UNRESOLVED covered two different failures. A broken sentence (cut off, ungrammatical, nonsense) says nothing about the word, which may fit any other sentence. A word no meaning fits, as given or after a fix, is the word's problem. ValidationResult.gave_up now names which; the code's own give-up (a meaning still misfitting after the retry) is WORD_NOT_FIT. Both still take this learner's word out of study, logged as [broken_sentence] or [word_not_fit]; the meaning is untouched either way. Regression set, 3 runs: 122/150 (125 before, within run-to-run noise); BROKEN_SENTENCE given for 7 of the broken-sentence runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rewrite of step 4 had lost "a translation that adds 'to' ... is a FIX, even though its meaning is right" (from #853). The example FIX øjnene = "the eyes", taken from "se virkeligheden i øjnene", now contradicts step 3, which expands to the idiom; the next example already shows that fix. Regression set, 3 runs: 122/150; vil sige right 6/6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They were logged as invalid with a [broken_sentence]/[word_not_fit] reason prefix, so anything counting action='invalid' mixed them with meanings that were marked INVALID. The prefix stays, to say which give-up. Migration: tools/migrations/26-10-25-a--validation_log_gave_up.sql, before deploy (appends an enum value; metadata only). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"It should still hold when the learner meets the word in an ordinary
sentence" read as if it tested the form too, against step 2. It now says
only the meaning is tested ("in that sentence's form"), with the case
behind it: noget = one, saved from "noget af det mest ekstreme", is
"something" in "Vil du have noget at spise?". That case leaves the
regression set, since the prompt now shows it.
Regression set, 3 runs: 126/147 (122/150 before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
judge_pair repeated validate_and_classify's steps (give-up, misfit, options) for a DeepSeek reply. The validator now takes `ask` (who answers) and `retry` (whether to ask again), so the pair check is the validator asked of DeepSeek without retries, and the decisions live in one place. Renamed for what they are: judge_pair -> second_opinion_on_meaning, and its caller script_generator._pair_verdict -> _meaning_confirmed_wrong (it returns the verdict only when the meaning is confirmed wrong). Fixes a flag read as true: DeepSeek writing "false" as a string kept it as the string "false". Only booleans count now; anything else is not given. Side effect: a DeepSeek reply without a verdict now also goes to Sentry, as the validator's own failed calls do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
validation_log's action says what happened to a meaning: valid, fixed, invalid. A give-up is the opposite (nothing happened to the meaning; the learner's word left study because of this sentence), so it does not belong in that column. Reverts the gave_up enum value and its migration; the give-up is in the log line, with its kind and the sentence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reply's judgement - A give-up only flipped fit_for_study, which update_fit_for_study recomputes on later events (starring, ...): the word came back, was validated again and given up again. It now also sets user_preference to DONT_USE_IN_EXERCISES, the only thing fit_for_study keeps (as #896 does with BAD_TRANSLATION). - A failed retry after an uncertain misfit returned no verdict, throwing away a first answer that would have been kept; it keeps it now. - Cutting a VALID "by (doing something)" to a FIX "by" lost the reply's "does not read right", so the cut card passed unchecked; the sentence and the judgement now go with it. - A give-up from the options retry was ignored; it wins, as on the first call (it moves nobody). - DeepSeek's "true"/"false" written as strings count as the booleans. Regression set, 3 runs: 126/147; vil sige 9/9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
user_preference is the learner's choice; the validator setting it made a system decision look like the learner's (the mix-up #895 fixes the other way). A give-up takes the word out of study only. What brings it back is the learner acting on the word again (a new look-up, starring), which recomputes fit_for_study and has it judged again, in the new sentence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Builds on #899 (the regression set, merged).
Problem. An exercise showed "mean" above "vil sige" in Jeg lærte, hvad det vil sige at gå ("what it mean to walk"). The validator passes that pair in 3 of 3 runs ("a correct, tense-neutral gloss"), and on 13 Aug it fixed "will say" to that same "mean". It checks whether a translation holds in other sentences, never whether it reads right in this one.
The PR in three lines
Changes
Prompt (
prompts/translation_validator.py). STEP 1 now runs: (1) is the sentence usable, (2) does the translation fit this sentence, form included (English may add "does"/"not"), (3) is it the word's own meaning, tested in another sentence (thenoget= "one" example), expanding to the smallest expression worth learning, (4) the "to" rule, (5) give up. A corrected word must appear in the sentence as written. Correct but less common translations are not "improved".Two ways to give up:
BROKEN_SENTENCE: the sentence is cut off, ungrammatical or nonsense.WORD_NOT_FIT: the sentence is fine, but no meaning of the word fits it.Both take only this learner's word out of study, with a log line giving the kind and the sentence. The meaning is not touched, nothing is written to
validation_log(whoseactionsays what happened to a meaning), anduser_preference(the learner's choice) is not written either. When the learner acts on the word again (a new look-up, starring), it's judged again.Self-check in code (
translation_validator.py). Four new reply fields hold the sentence with the meaning in place, and whether it reads right, for the meaning given and for any correction._misfitacts on them:WORD_NOT_FIT;"/" alternatives and "(glosses)" are reduced to one option before the fit check, keeping the model's own judgement. A failed retry is no verdict and goes to Sentry, unless the first answer would have been kept anyway.
One validator, two callers. The validator takes
ask(who answers) andretry. The audio-lesson check (second_opinion_on_meaning, formerlyjudge_pair) is now the same validator asked of DeepSeek without retries, so the decision logic lives in one place. A give-up there counts as not confirmed (script_generator._meaning_confirmed_wrong), so it never marks a meaning INVALID. "true"/"false" sent as strings by DeepSeek count as booleans.Regression set (3 runs per case)
"vil sige" = "mean" is no longer accepted in any run; from "will say" it comes out as "means". Still missed: Provencesanger (passes as "Provence singer"; it's a bird), the broken Kan jeg at få…, and
mere ved. Results vary by about ±4 runs between identical runs. Thenogetcase left the set when it became the prompt's example.Cost: about 60 more output tokens per call, plus one retry when a meaning doesn't fit (worst case 3 calls per word, normally 1).
Deploy: no migration. After deploy, old audio-lesson audit reports can't be acted on (
checker_idhashes the prompt); re-run the audit before any--deprecate.Not in this PR (see
docs/future-work/llm-tasks-roadmap.md):user_word.user_preferencewithstudy_choice+unfit_reason.🤖 Generated with Claude Code