Skip to content

refactor(growth): typed run plan + recorded estimand/population metadata (#394 pillar 4) - #451

Open
ethanbuckley wants to merge 1 commit into
mainfrom
feat/lrp394-growth-runplan
Open

refactor(growth): typed run plan + recorded estimand/population metadata (#394 pillar 4)#451
ethanbuckley wants to merge 1 commit into
mainfrom
feat/lrp394-growth-runplan

Conversation

@ethanbuckley

Copy link
Copy Markdown
Contributor

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.py gives the family the same typed boundary:

  • GrowthModelSettings — fail-fast on a misspelled spec.extra key; validates the 4 legacy keys (outcomes, baseline_covariate, use_shared_factor, age_ability_interaction). This is a multi-outcome family, so there's no single outcome_symbol.
  • GrowthRunPlan — resolved and validated before the context resets an output directory or the loader reads data; drives load_wave_panel + build_growth_model and records the design / estimand / causal status / analysis population / missing-data assumption. The estimand prose names gamma (baseline non-verbal ability → growth rate, the headline Q5 association) and delta (baseline level), both associational and latent-GA-confounded, never causal.

fit_growth consumes plan.prepare_kwargs() / plan.factory_kwargs(), and reads the outcomes/baseline_covariate/shared-factor/age-ability locals off the plan for the downstream diagnostics and readouts. reporting._resolved_run_plan gains a growth branch so the recipe + config metadata are written.

Byte-identical

Verified programmatically: for all 3 registered growth models (gc-069/070/085), the resolved prepare_kwargs and factory_kwargs reproduce the former inline fit_growth logic exactly. gc-069 dev-fits clean end-to-end and now emits model_recipe.md + a populated resolved_run_plan.

12 new run-plan tests (test_growth_run_plan.py); the drift guards pass; ruff / format:check / spellcheck clean.

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.

…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 + GrowthRunPlan and a resolve_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 drive load_wave_panel() and build_growth_model() via plan.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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants