refactor(growth): typed run plan + recorded estimand/population metadata (#394 pillar 4) - #451
Open
ethanbuckley wants to merge 1 commit into
Open
refactor(growth): typed run plan + recorded estimand/population metadata (#394 pillar 4)#451ethanbuckley wants to merge 1 commit into
ethanbuckley wants to merge 1 commit into
Conversation
…ata (#394 pillar 4) Extends the ITT / gain-factor / level-factor / DiD / concurrent / aligned run-plan pattern to the joint multivariate latent growth-curve family (a multi-outcome family, so no single outcome_symbol). A new growth.py provides GrowthModelSettings (fail-fast on a misspelled spec.extra key; validates the 4 legacy keys) and a resolved GrowthRunPlan that drives data preparation, factory construction and the config.json / model_recipe.md audit trail, recording the design, estimand (gamma = baseline non-verbal ability -> growth rate, the headline Q5 association; delta = baseline level), causal status (associational, latent-GA-confounded) and analysis population. fit_growth now resolves and validates the plan before the context resets an output directory or the loader reads data, then loads via plan.prepare_kwargs() (load_wave_panel) and builds via plan.factory_kwargs(); the downstream diagnostics/readouts read the outcomes / baseline_covariate / shared-factor / age-ability locals off the plan. reporting._resolved_run_plan gains a growth branch so write_model_recipe and config.json's resolved_run_plan pick it up. Byte-identical: the resolved prepare + factory kwargs reproduce the former inline fit_growth logic exactly for all 3 registered growth models (verified programmatically). gc-069 dev fit is clean end-to-end and now emits model_recipe.md + a populated resolved_run_plan. 12 new run-plan tests; drift suites pass; ruff / format:check / spellcheck clean. Part of #394 (pillar 4: typed settings + resolved plans). No estimand, likelihood, prior, population, fitted equation, sampling preset or artefact schema changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends the “typed settings + resolved run plan” pattern (#394 pillar 4) to the latent growth-curve statistical-model family, so growth models resolve/validate their configuration before any data are loaded or output directories are reset, and persist design/estimand/population metadata as part of the fit audit trail.
Changes:
- Introduces
GrowthModelSettings+GrowthRunPlanand aresolve_growth_run_plan()resolver for the growth family, including recorded design/estimand/causal-status/population/missing-data metadata. - Refactors
fit_growth()to resolve the run plan up front and driveload_wave_panel()andbuild_growth_model()viaplan.prepare_kwargs()/plan.factory_kwargs(). - Adds run-plan test coverage for settings validation, resolution behaviour, and “every registered growth model resolves with metadata”, and wires run-plan reporting/recipe writing for
kind="growth".
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/language_reading_predictors/statistical_models/growth.py |
Adds typed settings + resolved run plan for growth models, including audit metadata and recipe generation. |
src/language_reading_predictors/statistical_models/pipeline.py |
Updates fit_growth() to resolve/record the run plan before preparing data / building the model. |
src/language_reading_predictors/statistical_models/reporting.py |
Allows reporting to reconstruct or reuse the resolved growth run plan for metadata/recipe persistence. |
tests/statistical_models/test_growth_run_plan.py |
Adds unit tests covering validation, resolution defaults/overrides, and registered-spec coverage. |
|
|
||
|
|
||
| def _tuple_of_strings(value: Any, *, name: str) -> tuple[str, ...]: | ||
| if isinstance(value, str) or not hasattr(value, "__iter__"): |
| "Declare GrowthModelSettings so misspellings fail fast." | ||
| ) | ||
| # Pass raw values through so __post_init__ is the single validation/coercion | ||
| # point; pre-coercing here would silently reshape misshaped legacy settings. |
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.
Note
Drafted by an LLM-based AI tool (Claude Code/Opus 4.8).
#394 pillar 4 (typed settings + resolved plans), extending the pattern from the merged ITT/gain-factor (#418) and the level-factor (#445) + DiD (#448) + concurrent (#449) + aligned (#450) run-plans to the joint multivariate latent growth-curve family (LRP69/70/85). Independent, off
main.What this does
A new
growth.pygives the family the same typed boundary:GrowthModelSettings— fail-fast on a misspelledspec.extrakey; validates the 4 legacy keys (outcomes,baseline_covariate,use_shared_factor,age_ability_interaction). This is a multi-outcome family, so there's no singleoutcome_symbol.GrowthRunPlan— resolved and validated before the context resets an output directory or the loader reads data; drivesload_wave_panel+build_growth_modeland records the design / estimand / causal status / analysis population / missing-data assumption. The estimand prose namesgamma(baseline non-verbal ability → growth rate, the headline Q5 association) anddelta(baseline level), both associational and latent-GA-confounded, never causal.fit_growthconsumesplan.prepare_kwargs()/plan.factory_kwargs(), and reads theoutcomes/baseline_covariate/shared-factor/age-ability locals off the plan for the downstream diagnostics and readouts.reporting._resolved_run_plangains agrowthbranch so the recipe + config metadata are written.Byte-identical
Verified programmatically: for all 3 registered growth models (gc-069/070/085), the resolved
prepare_kwargsandfactory_kwargsreproduce the former inlinefit_growthlogic exactly.gc-069dev-fits clean end-to-end and now emitsmodel_recipe.md+ a populatedresolved_run_plan.12 new run-plan tests (
test_growth_run_plan.py); the drift guards pass;ruff/format:check/spellcheckclean.Scope
Mechanical, behaviour-preserving relocation only — no estimand, likelihood, prior, analysis population, fitted equation, sampling preset, diagnostic threshold or artefact schema change (per #394's contract). Pillar-4 families now converted: gain (merged), level, DiD, concurrent, aligned, growth. Remaining: adjusted, survival, historical/measurement (clean single-fit), and mediation (the 3-fit-function outlier, a dedicated multi-PR effort).
Assigning to a human for review and merge.