Document randomization options and display-order capture; clarify construct tags as provenance#69
Open
countercheck wants to merge 1 commit into
Open
Conversation
…struct tags as provenance Design doc + companion docs gain a randomization-and-display-order story alongside a sharper restatement of construct_block/_item as provenance (not a pooling key). No runtime/dbt code changes — this is the design/spec layer for FR-13 and the operational discipline that goes with it. What the design doc says now (§3.5 "Randomization and display-order capture"): - Three shipped mechanisms: Page.questionsOrder, Matrix.rowsOrder, choicesOrder. Block-aware randomization emerges from putting a scale in a matrix tagged with construct_block (no separate flag). - Per-respondent render order captured at submit time. shown_questions clarified as render-ordered; new shown_choice_orders jsonb on raw_responses (sparse, only for randomized-choice questions). Threads to fact_response_item.display_order. - Within-page / within-matrix / within-question randomization is order-invariant against visibleIf (values, not positions), so no new publish-gate lint there. - §4.11 rejects SQL reconstruction of display order (same logic as routing, §4.5). §4.12 rejects recording randomization config on dim_question_version (the snapshot already has it; duplication risks drift). - §5 deferrals: choice-order marts surface, static-panel container in the gate, survey-level page-order randomization. - §6 adds undetected-order-effects + future page-order-vs-cross-page-visibleIf risks. Construct tags get sharper language: - §3.5 "Construct membership" already covered provenance-not-pooling; this change extends the same explicitness into CLAUDE.md (invariant 5 + silent- defaults) and the researcher doc (§4a now spells out that a shared construct tag never licenses pooling — it surfaces candidates). CLAUDE.md gets the operational discipline: - Invariant 3 extended to cover display-order capture. - Silent-defaults list grows from three judgments to four (adds display-order capture: don't render randomized questions/choices without writing the realized order back; don't reconstruct display_order in SQL). - Publish-gate + "Don't without asking" updated for the shipped surface vs the deferrals. Researcher doc: - "Three commitments" → "four commitments" (new §4d on display-order capture). - §7 documents fact_response_item.display_order usage; §8 notes the three §5 deferrals. Pattern library: - New patterns/randomization.json — page-level questionsOrder, matrix-level rowsOrder tagged with construct_block: "phq9" (exercises the matrix-container inheritance rule from §3.5), and a choicesOrder radiogroup with a downstream visibleIf to demonstrate value-based gating is order-invariant. Passes the publish gate (validate_definition + the round-trip oracle) via test_patterns.py. Co-Authored-By: Claude Opus 4.7 (1M context) <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.
Summary
Page.questionsOrder,Matrix.rowsOrder,choicesOrder; per-respondent render order captured at submit, threaded tofact_response_item.display_order; static-panel + survey-level page-order shuffling deferred to §5, also tracked as api+dbt: support staticpanelas a structural grouping element in the publish gate #68).construct_blocknever licenses pooling —parent_question_idstays the explicit opt-in).display_orderin SQL).patterns/randomization.json— page-levelquestionsOrder, matrix-levelrowsOrdertagged withconstruct_block: "phq9"(exercises the matrix-container inheritance from §3.5), and achoicesOrderradiogroup with a downstreamvisibleIfshowing value-based gating is order-invariant.Pure docs + one new pattern fixture; no runtime/dbt/schema code touched. The capture model (
shown_questionsrender-ordered + newshown_choice_ordersjsonb onraw_responses+fact_response_item.display_order) is specified, not yet wired — wiring is a follow-up branch.Why these specific design choices
shown_questionsbecomes definitively render-ordered (no new column). Forward-only spec change: pre-FR-13 responses have definition order = render order by construction.shown_choice_orders(NEW jsonb onraw_responses), sparse, only for randomized choices. Backfill hook per A-2 — capture cheap now, surface in marts later.display_orderlands onfact_response_item, notfact_response(per @user choice). Same value across all option rows for the same(respondent, question_id, occurrence)— harmless denormalization across fan-out.rowsOrder: 'random'andconstruct_blocktag (inherited to row sub-questions per existing §3.5 rule) gives the "PHQ-9 items shuffle among themselves, stay contiguous" shape for free.dim_question_version— same reasoning as routing reconstruction (§4.5) and definition-snapshot single-source.What's intentionally deferred (design doc §5)
fact_response_item.choice_display_orderadded when an analysis first wants it.panelas a structural grouping element in the publish gate #68.Test plan
uv run pytest api/tests/test_patterns.py— 37/37 pass (includes the newrandomization.jsonthroughvalidate_definition+ the round-trip oracle).python3 scripts/check_invariants.py— all four invariants clean.uv run ruff check .— passes.shown_choice_ordersshape and thedisplay_orderdenormalization-onto-fan-out call — these are the load-bearing capture-model choices.🤖 Generated with Claude Code