Loading…
Reference in a new issue
No description provided.
Delete branch "fix/issue-5-shuffle-choices"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #5 and fixes #6.
#5 — answer options need to be shuffled
Models put the correct choice first most of the time, leaking the answer
key through the generated order. All three question generators (tutor
checks, assignment questions, diagnostic questions) now shuffle their
substantive choices once at normalisation time — before anything is
persisted — via a new
shuffle_choiceshelper inagents/_common.py.Grading is by
choice_id, so the key is unaffected.The permutation is deterministic (seeded from the question's own
content), so replays, tests, and re-served questions are stable.
#6 — "None of the above" standard on every option set
Every generated multiple-choice question now carries a pinned
__none_of_the_above__/ "None of the above" choice, giving learners anhonest answer when a generated set contains no correct answer.
Design decisions:
accidentally generated without a correct answer stays gradeable. The
__dont_know__sentinel still never is.validators.validate_choice_setrequires exactly one NOTA choice withthe fixed text for every model-generated question (the generate-validate
loop retries until it complies), while
schemas.question_key_errorsonly validates NOTA if present so persisted courses/attempts from
before this rule still load.
content choices → "None of the above" → "I don't know".
Changes
plugin/schemas.py:NONE_OF_THE_ABOVEconstants + cross-field checks(unique, fixed text, allowed as
correct_choice_id)plugin/validators.py:validate_choice_sethelper;validate_questionenforces NOTA presence; scoring already grades by
choice_idplugin/agents/: prompt rules for assignments + diagnostics, tutorfallback protocol and
turn_defects(checks now validated too), thepedagogy-tutoring skill §2, and
shuffle_choicespinningdesktop/: pinned-tail rendering in attempts, checks, and diagnostics;ensureDontKnowbackfills both pinned choices for older persisted datadocs/: design.md question-shape rule updatedTesting
mainremain:test_install.py, onetest_events.py)tests/test_agents_shuffle.py: permutation, sentinel pinning,determinism, key integrity, position scrambling, NOTA pinning,
key-on-NOTA grading
All three findings fixed in
3246dfb:Duplicate
shuffle_choicesstub - deleted; the real definition (with the pinned-tail docstring) is now the only one.Substantive floor -
question_key_errorsnow counts non-pinned choices and rejects< 2withneeds at least 2 substantive choices, found N. Verified: legacy[a, b, __dont_know__]still passes,[a, __none_of_the_above__, __dont_know__]is rejected.one_substantivemoved back intoschema_rejectsin tests/test_validators.py, andtest_question_with_one_substantive_choice_rejectednow asserts the substantive floor (not minItems) does the rejecting.ensureDontKnow->ensurePinnedChoices- now rebuilds the tail the wayorderedChoices/choicesWithSentineldo (filter both pinned ids from content, re-append NOTA then sentinel last), so legacy[substantive, __dont_know__]checks render with the sentinel last. TranscriptView updated. All three renderers now share the same ordering contract.Minors: duplicate factory constants deduped; the tautological shuffle assertion replaced with an unsorted input so the permutation check is meaningful; a11y fixtures (transcript check, both attempt questions, diagnostic question) now supply the NOTA choice from the "backend", so the updated assertions exercise the normal no-duplication path.
Python: 448 passed (same 5 pre-existing env failures). Desktop: typecheck clean, 6/6 tests, dist rebuilt.