You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-ups from the 2026-08-24 implementation pass. Workstreams A–C of the release-prep plan are merged (8276e46..d886341); this tracks what was deliberately left, what was discovered along the way, and the owner-only release runbook.
Cross-references, so nothing here is duplicated: #77 (release prep phase 3 — most of its checklist is now done in-tree), #81 (API contract hardening — several items closed below), #82 (maintenance polish — analysis.py, mypy, coverage), #84 (decompose analysis_result.py), #100 (ODS ↔ SDS terminology — still open, see note), #86 (CI matrix restoration — superseded, the full matrix is live).
Closed by this pass — worth verifying before ticking elsewhere
get_statistics shape — the four-key contract {N, center, lpl, upl} now actually holds on X/mR, pinned by TestStatisticsKeyContract over chart × design state × stratification.
result.dataset mutability leak — now copy-on-read, matching Study.dataset.
Raw ValueError on user-facing paths — 8 converted; ~25 internal-invariant sites remain deliberately.
ChartNotAvailableError reparented under ValidationError, which is what the exception-convention docs already claimed.
Full CI matrix, wheel smoke-test, trusted publishing, SHA-pinned actions, Dependabot — all present. Added since: a tag↔__version__ guard, workflow_dispatch removed from publish.yml, docs build on PRs, pinned mystmd, pytest timeout, pre-commit hygiene hooks, issue/PR templates, CITATION.cff.
1. Deliberately deferred (structural)
These were listed as non-goals in the plan. None is blocked; all are invasive.
R6 materialises onto the study-level frame.Study._compute_r6 writes a column onto the shared dataset from inside execute(), making execute non-pure. Contained today by one metadata gate in AnalysisResult._locate_residual. Computing it onto the per-request frame instead (Analysis already copies per calculation) would delete the wart and the gate defending it. Touches CLAUDE.md-protected code — do not "simplify" the gate without doing this properly.
Chart identity lives in three parallel authorities — VALID_BASE_CHARTS (spc_constants), VALID_CHART_TYPES (plotting/contracts), and an inline set in analysis.py. Cheap first step: make the latter two import the first. The full per-chart registry stays deferred until a second chart family is real. (Overlaps Tier 3: Maintenance polish — analysis.py refactor, vocabulary, mypy, coverage #82.)
analysis.py is 2,897 lines with structurally duplicated stratified/unstratified pairs. This pass extracted the ungrouped replication raises into a shared helper, but the mR family (~950 lines across three near-parallel calculators) is untouched. The changelog's own "three fixes from one cause" entry is the evidence for why. (Overlaps Tier 3: Maintenance polish — analysis.py refactor, vocabulary, mypy, coverage #82.)
Effects dict namespace. Keys are the user's own factor names sharing a namespace with library vocabulary — a factor literally named time or main_effect collides silently. Docstrings now describe the real keys and name the hazard; the structural fix (namespacing, plus a tidy long-format effects_table()) is a breaking change best made before 1.0.
Pre-1.0 surface pruning.AnalysisResult carries a dict-emulation shim "for backward compatibility" with no release to be compatible with; plotting.__all__ exports Plotter while labelling it internal. Free to delete now, a deprecation cycle later.
16 mypy errors remain in analysis.py, all TypedDict mismatches in the chart-payload contract. Flip continue-on-error: false in ci.yml once cleared.
Extend the per-module strict list. Currently 7 modules re-enable arg-type/return-value/assignment. Verified not yet clean: data_preparation (5), focused_result (4), result_tabular (1), exporters.excel_exporter (1).
signals/config.py has 2 annotation-unchecked notes (untyped function bodies).
Note on the mypy config:python_version = "3.12" is the stub-parsing version, not the support floor (requires-python = ">=3.10"). numpy's bundled stubs use 3.12 type statements and mypy aborts on numpy/__init__.pyi below that — which is why type-checking had been silently dead. Revisit if numpy's stubs change.
3. Docs
Six tutorials are pulled from the TOC but still on disk, awaiting a rewrite against the current API: basic-imr, signal-detection, stratified-analysis, xbar-s-analysis, sds-validation, sds1-complete-analysis. They rejoin docs/myst.yml and the CI nbmake gate together — not one without the other.
Two tracked docs files are unreachable from the TOC: docs/sds-detection.md (505 lines) and docs/reference/sds_definitions.md (133 lines). The first is not a duplicate of docs/user-guide/sds-detection.md — it is a deeper "how classification works" reference. Neither has stale-API markers. Decision needed: publish (after verifying their examples) or delete. Left untouched rather than guessed at.
The API reference is hand-written and drifts.docs/reference/api.md is 702 lines of hand-maintained signatures; it was missing statistics(), stratify_by, and supports_calibration. mystmd has no mature Python autodoc, so this needs an explicit mitigation: a generation script, or minimally a CI check that every __init__.py export appears in the reference.
Comparison page + SEO landing pages (planned as C4, not written). A "vs. other Python SPC options" page and short worked-example pages for "XmR chart in Python" / "control chart in Python" / "process behavior chart". Deliberately left to the maintainer: they make factual claims about other projects and are positioning copy.
Docs versioning strategy before 1.0 — mystmd has none built in.
⚠️ This pass initially relabelled the Design Report's observed row SDS (Sampling) → ODS (Observed), treating it as an inconsistency. That was reverted (d886341) once #100 surfaced: the label is deliberate, and #100 records that reconciling it needs a naming decision to settle with Tom, since "SDS" already carries the umbrella meaning. A comment now sits at the site so the next reviewer finds the open question instead of an apparent typo.
Nothing else in this pass touched ODS/SDS naming. #100 is unaffected and still the right place for that decision.
5. Validation
ODS 4–6 end-to-end against Bishop's Minitab output. Smaller than it looks: detection and the deterministic ODS→ADS collapse (4→1, 5→2, 6→3) are covered by ~118 tests, and the collapse rule is definitional within the lineage model, so the ADS 1–3 numbers are the ones that apply. What remains is analysing the tidied PM SDS 4/5/6 subsets in Minitab and adding a spot-check assertion block — roughly three runs, not a validation campaign. Until then docs/reference/validation.md says so plainly.
6. Owner-only — release runbook
Sequential, one sitting, not to be automated. Full detail is in the local plan file.
Freeze — no open PRs (a history rewrite orphans them), fresh-clone backup.
Pre-rewrite audit — git ls-files sweep for internal planning files beyond the three known ones.
History rewrite — git filter-repo --invert-paths over those files; force-push; verify the paths are gone from all history and the tip tree is unchanged.
Retro-tagv0.1.0 / v0.1.1 at their (rewritten) commits — the CHANGELOG's compare links point at tags that have never existed. Push these before configuring the PyPI trusted publisher so the runs they trigger fail harmlessly at OIDC.
Version cut — __version__ → 0.2.0, move the ~582-line [Unreleased] into a dated section, add its compare link.
Enable GitHub Pages (the docs deploy job is gated on it).
Enable private vulnerability reporting (Settings → Security) — SECURITY.md now points reporters there; the feature is public-repo-only, so it can only be switched on at go-live. Also fold the historical author-email scrub (personal gmail on 797 commits, and 20 commits with a *.gha.chartermi.net machine email) into the history-rewrite step.
Publish — push v0.2.0; verify pip install processbehavior in a clean venv runs the README quickstart end to end.
Post-publish — CODECOV_TOKEN secret, then flip fail_ci_if_error: true and add the coverage badge; reconsider pip-audit --strict (it can only work once the project is on PyPI); submit the conda-forge feedstock.
7. Compatibility posture
Test against the pandas 3 RC and plan lifting pandas<3; library upper caps export resolver conflicts downstream, so the cap needs an expiry rather than riding indefinitely.
Verify and publicly declare numpy 2.x support — the numpy>=1.23 floor spans the 1.x/2.x break.
Follow-ups from the 2026-08-24 implementation pass. Workstreams A–C of the release-prep plan are merged (
8276e46..d886341); this tracks what was deliberately left, what was discovered along the way, and the owner-only release runbook.Cross-references, so nothing here is duplicated: #77 (release prep phase 3 — most of its checklist is now done in-tree), #81 (API contract hardening — several items closed below), #82 (maintenance polish — analysis.py, mypy, coverage), #84 (decompose analysis_result.py), #100 (ODS ↔ SDS terminology — still open, see note), #86 (CI matrix restoration — superseded, the full matrix is live).
Closed by this pass — worth verifying before ticking elsewhere
From #81's API-contract list:
get_statisticsshape — the four-key contract{N, center, lpl, upl}now actually holds on X/mR, pinned byTestStatisticsKeyContractover chart × design state × stratification.result.datasetmutability leak — now copy-on-read, matchingStudy.dataset.ValueErroron user-facing paths — 8 converted; ~25 internal-invariant sites remain deliberately.ChartNotAvailableErrorreparented underValidationError, which is what the exception-convention docs already claimed.From #77:
__version__guard,workflow_dispatchremoved frompublish.yml, docs build on PRs, pinnedmystmd, pytest timeout, pre-commit hygiene hooks, issue/PR templates,CITATION.cff.1. Deliberately deferred (structural)
These were listed as non-goals in the plan. None is blocked; all are invasive.
Study._compute_r6writes a column onto the shared dataset from insideexecute(), making execute non-pure. Contained today by one metadata gate inAnalysisResult._locate_residual. Computing it onto the per-request frame instead (Analysisalready copies per calculation) would delete the wart and the gate defending it. Touches CLAUDE.md-protected code — do not "simplify" the gate without doing this properly.VALID_BASE_CHARTS(spc_constants),VALID_CHART_TYPES(plotting/contracts), and an inline set inanalysis.py. Cheap first step: make the latter two import the first. The full per-chart registry stays deferred until a second chart family is real. (Overlaps Tier 3: Maintenance polish — analysis.py refactor, vocabulary, mypy, coverage #82.)analysis.pyis 2,897 lines with structurally duplicated stratified/unstratified pairs. This pass extracted the ungrouped replication raises into a shared helper, but the mR family (~950 lines across three near-parallel calculators) is untouched. The changelog's own "three fixes from one cause" entry is the evidence for why. (Overlaps Tier 3: Maintenance polish — analysis.py refactor, vocabulary, mypy, coverage #82.)timeormain_effectcollides silently. Docstrings now describe the real keys and name the hazard; the structural fix (namespacing, plus a tidy long-formateffects_table()) is a breaking change best made before 1.0.AnalysisResultcarries a dict-emulation shim "for backward compatibility" with no release to be compatible with;plotting.__all__exportsPlotterwhile labelling it internal. Free to delete now, a deprecation cycle later.2. Typing (#47 / #82)
analysis.py, all TypedDict mismatches in the chart-payload contract. Flipcontinue-on-error: falseinci.ymlonce cleared.arg-type/return-value/assignment. Verified not yet clean:data_preparation(5),focused_result(4),result_tabular(1),exporters.excel_exporter(1).signals/config.pyhas 2annotation-uncheckednotes (untyped function bodies).Note on the mypy config:
python_version = "3.12"is the stub-parsing version, not the support floor (requires-python = ">=3.10"). numpy's bundled stubs use 3.12typestatements and mypy aborts onnumpy/__init__.pyibelow that — which is why type-checking had been silently dead. Revisit if numpy's stubs change.3. Docs
basic-imr,signal-detection,stratified-analysis,xbar-s-analysis,sds-validation,sds1-complete-analysis. They rejoindocs/myst.ymland the CI nbmake gate together — not one without the other.docs/sds-detection.md(505 lines) anddocs/reference/sds_definitions.md(133 lines). The first is not a duplicate ofdocs/user-guide/sds-detection.md— it is a deeper "how classification works" reference. Neither has stale-API markers. Decision needed: publish (after verifying their examples) or delete. Left untouched rather than guessed at.docs/reference/api.mdis 702 lines of hand-maintained signatures; it was missingstatistics(),stratify_by, andsupports_calibration. mystmd has no mature Python autodoc, so this needs an explicit mitigation: a generation script, or minimally a CI check that every__init__.pyexport appears in the reference.4. Terminology — #100 stays open
SDS (Sampling)→ODS (Observed), treating it as an inconsistency. That was reverted (d886341) once #100 surfaced: the label is deliberate, and #100 records that reconciling it needs a naming decision to settle with Tom, since "SDS" already carries the umbrella meaning. A comment now sits at the site so the next reviewer finds the open question instead of an apparent typo.Nothing else in this pass touched ODS/SDS naming. #100 is unaffected and still the right place for that decision.
5. Validation
PM SDS 4/5/6subsets in Minitab and adding a spot-check assertion block — roughly three runs, not a validation campaign. Until thendocs/reference/validation.mdsays so plainly.6. Owner-only — release runbook
Sequential, one sitting, not to be automated. Full detail is in the local plan file.
git ls-filessweep for internal planning files beyond the three known ones.git filter-repo --invert-pathsover those files; force-push; verify the paths are gone from all history and the tip tree is unchanged.v0.1.0/v0.1.1at their (rewritten) commits — the CHANGELOG's compare links point at tags that have never existed. Push these before configuring the PyPI trusted publisher so the runs they trigger fail harmlessly at OIDC.__version__→0.2.0, move the ~582-line[Unreleased]into a dated section, add its compare link.*.gha.chartermi.netmachine email) into the history-rewrite step.cnicholas/processbehavior, workflowpublish.yml, environmentpypi.v0.2.0; verifypip install processbehaviorin a clean venv runs the README quickstart end to end.CODECOV_TOKENsecret, then flipfail_ci_if_error: trueand add the coverage badge; reconsiderpip-audit --strict(it can only work once the project is on PyPI); submit the conda-forge feedstock.7. Compatibility posture
pandas<3; library upper caps export resolver conflicts downstream, so the cap needs an expiry rather than riding indefinitely.numpy>=1.23floor spans the 1.x/2.x break.