Skip to content

Protocol v0.4.1 and Phase 1 tasks 1.0 and 1.3 - #12

Merged
DougManuel merged 34 commits into
mainfrom
protocol
Aug 27, 2026
Merged

Protocol v0.4.1 and Phase 1 tasks 1.0 and 1.3#12
DougManuel merged 34 commits into
mainfrom
protocol

Conversation

@DougManuel

Copy link
Copy Markdown
Collaborator

Protocol v0.4.1 and Phase 1 remediation, tasks 1.0 and 1.3.

Protocol (docs/protocol, v0.4.0 → v0.4.1). Two previously open study decisions ratified (issue #5): the PUMF initiation-age floor stays at 13 (the 5–11 category is too coarse to date initiation events below that age; cessation follow-up still starts from the category midpoint of 8; Master files use exact ages with a floor of 8), and the first CCHS cycle is assigned survey year 2001 for cohort assignment. Editorial revisions from PI review of the Word render; Word rendering restored as a docstyle subproject; ethics statement and study team updated (supersedes #11, whose commits are included here).

Pipeline.

  • Task 1.0: analysis dataset uses the unified cchsflow v3 smoking variables (age_first_cigarette, smoked_100_lifetime, time_quit_smoking_complete); feeder rows added so the derivation chain closes; check_feeder_closure() is database-specific.
  • Task 1.3: cessation risk set rebuilt. Established smokers (100+ cigarettes, any status) are at risk from their own age at first whole cigarette; a quit counts as an event only after apc.cessation_durability_years (2); same-age start and stop is one trial with the event; missing status, missing 100-cigarette answer, missing entry age and missing quit timing (2001 and 2022 PUMF, NA(c)) are excluded and counted in cessation_diagnostics pending imputation. Fixes Cessation model: exclusion of former occasional smokers #1.
  • Value codes and analytic thresholds live in config.yml; variable ranges are derived from the variable-details worksheet (survey_range() / details_range()), and range checks fail closed when a non-missing value has no worksheet range.
  • Diagnostics attributes on the APC datasets; fit guards; assert_correction_applied().

Version bump per document is enforced by .github/workflows/protocol-version.yml.

The 'peto' option returned weights unchanged while being described
as a correction, and was the default. It is replaced by three
explicit methods: 'none' (no correction; the data carry a
mortality_correction attribute and an estimand note saying the
results describe respondents who survived to be surveyed), 'mport'
(primary, stops as not implemented, task 1.7b), and 'peto'
(sensitivity, stops as not implemented). A configured correction
that leaves every weight unchanged now stops the pipeline
(assert_correction_applied), so a stub can no longer pass as a
correction. README, workflow and reference docs, the rate-table
schema, and config comments now state that no correction is
applied yet and that outputs are estimates among survivors
(protocol section 3.4.5). Tests cover all four paths and the guard.
Operational specification implementing protocol v0.4.0 section 3.4.1
and adjudication A1: state definitions with their CCHS variables,
the two modelled transitions, event-time conventions (one-year
steps, one spell per person, cessation risk from first cigarette,
two-year durability, recent quitters current and censored), target
population and immigration entry, and the output contract with the
smoking-history generator. Records one open decision for the PI:
the same-age initiation/cessation rule (proposed: one-year spell as
primary, exclusion as sensitivity). Names the code consequence that
the cessation model must move from the ever-daily universe and
time_quit_smoking_daily to all ever-smokers and
time_quit_smoking_complete.
…-op guard; fix stale docs

Review findings on PR #4. fit_apc_model() now copies the
mortality_correction and estimand_note attributes from the APC
dataset onto the fitted model, with a test, so Stage 8 outputs
cannot pass as corrected. assert_correction_applied() checks that a
correction changed only the weights: same row count, identical
age/period/cohort/event in the same order, finite positive weights,
and at least one weight changed; tests cover dropped rows,
reordering, and non-finite weights. apc-plan.md is marked
superseded in part and its two stale 'peto' passages updated. The
Stage 7 workflow page now lists the real columns (event, weight),
notes that spline columns are built in Stage 8, and documents the
two attributes.
…interval conventions, recent-quitter semantics

Revisions from external review of PR #6. The 100-cigarette gate
(Manuel et al. 2020) now defines membership; experimental smokers
are Never; age at first cigarette dates entry only for those who
pass the gate (smoked_100_lifetime; SMK_01A for 2022). Delayed
entry for immigrants is stated per transition (arriving Never,
Current, or Former). The annual interval is defined (age row = year
from the a-th birthday; event row in the risk set; initiation before
cessation within a year; survey year included). Recent quitters are
separated into observed status (Former), modelled state (Current up
to survey), and risk-set contribution (censored at quit age), so
each person has one modelled state at each age. The same-age
sensitivity removes the person from both transition models. The
dependency on PR #4 for the 'none' correction value is declared.
The PI's 2026-08-27 decision on 2001 (complete-cessation timing is
NA(c), imputed) is recorded.
Wording and plain-language edits throughout; no methodological change.
Ethics statement rewritten (secondary analysis of de-identified data;
Statistics Canada Open Licence). Study team updated. NHIS typo fixed.

Edits were made in Word and merged back by hand: the docstyle harvest
remaps citekeys when field-codes.json entries lack a Zotero URI or share
one, so the harvested file was used only as a source of text changes.
The edited .docx is kept under docs/protocol/source/ for provenance.
The protocol's Word configuration was lost when docs/_quarto.yml was
removed in the documentation restructure (4c96354); _docstyle/page-config.json
was a fossil of it. Restore it as docs/protocol/_quarto.yml, mirroring the
manuscript subproject, with the original header, footer, TOC and
version-history settings and the popcorn-base/pop-draft-manuscript CSS.

The docstyle tooling resolves _extensions/ and _docstyle/ relative to the
project directory only, so the subproject has an _extensions symlink and
its own _docstyle/ sidecar. A nested _quarto.yml does not remove the
protocol pages from the root website build (checked with quarto inspect
and a root render). Output goes to docs/protocol/output/ (gitignored).
Phase 1, task 1.7a: mortality method 'none' by default; no-op corrections fail
Phase 1, task 1.0: smoking-state and transition analysis specification
Text edits from the second review of the Word render merged into the
QMD (same 0.4.1 entry; history description updated). The edited .docx is
kept under docs/protocol/source/.

The "STROBE: Potential bias" placeholder sat between two items of a
nested list, so Pandoc turned the remaining items into an indented code
block (literal ** and hard-wrapped text in Word). The placeholder now
follows the list, combined with the STRESS tag.

New docs/protocol/protocol.css layers spacing over the base theme: more
space above level-1 and level-2 headings, space before and after the
reporting-guideline blockquotes, and a Dateline paragraph style
(custom-style div) for the date and version line under the title.
PUMF initiation-age floor stays at 13 (the 5-11 category, midpoint 8,
is too coarse to date entry; Master uses exact ages with a floor of 8);
the first CCHS cycle is assigned survey year 2001 for cohort
assignment. Recorded in config comments and protocol sections 3.2 and
3.3 with a version-history entry. No change to methods or code.
The body section already starts with a nextPage section break; the nested
::: page-break div added a second break in an otherwise empty paragraph,
producing a blank page 2 in the Word render.
…ields

Enable the docstyle author plate (authors and affiliations from
_quarto.yml; list and order TBA, more authors to come) with the
::: author-plate ::: placeholder under the date line.

Holford_SM_2006, Rao_SM_1992 and StatCan_CCHS_UserGuide_2022 were absent
from field-codes.json, so Pandoc citeproc rendered them as plain text and
appended its own bibliography after the version history. Added their CSL
data (converted from references.bib) so all 26 citations are Zotero fields
and the stray reference list disappears.
Author list and order are still to be decided; the authors block stays in
_quarto.yml (not rendered) for when the plate is re-enabled.
…ts below 13; cessation entry still uses the midpoint; ratifies open decisions
Record study decisions from public issue #5 (protocol 0.4.1)
The cessation model now follows the established-smoking estimand.
Universe: established smokers (100 or more cigarettes,
smoked_100_lifetime, via a config-driven yes_code) across all
ever-smoker statuses, replacing the ever-daily subset; the same gate
applies to the initiation numerator, so experimental smokers are
Never in both models. Event: stopping smoking completely, dated by
time_quit_smoking_complete (new config key years_since_quit_complete;
years_since_quit keeps the daily variable for intensity and
sensitivity work). Clock: each person's risk starts at their own age
at first whole cigarette (expand_denominator gains a per-person
age_denom_min), replacing the floor of zero that started the clock at
birth. Durability: quits under cfg$apc$cessation_durability_years (2)
are censored at the quit age with no event. A quit at the entry age is
one trial with the event. Missing entry age, entry after survey, quit
before entry, and missing quit timing (all of 2001, where the
stopped-completely questions were not asked) are excluded and counted
per cycle, unweighted and weighted, in a cessation_diagnostics
attribute pending imputation (task 1.8c). Worksheet roles updated.
Tests cover the universe, the gate in both models, no person-year
before entry, durable and recent quitters, the same-age spell, and
each exclusion class. Docs updated.
…odes)

SMKDSTY_original groupings (ever, current, former, never) and the sex
codes now live under config.yml survey.* as *_codes / *_code entries,
read through survey_code(); R/apc-model.R no longer contains literal
status or sex codes (prepare_apc_data, build_initiation_data,
build_cessation_data, get_period_constraint). A test changes the
former-smoker codes in config and checks the universe classification
follows. First step of remediation task 3.1 (value-code semantics
layer), scoped to the APC stage.
Public issue #3, items 4 and 7. fit_binomial_apc() now stops when there
are no person-year rows, when the numerator has no events or zero
weighted events (a model that would encode 'nobody ever made this
transition'), or when a cell has non-positive person-years or more
events than person-years; after glm() it stops on non-convergence and
warns on a boundary fit. Tests cover the empty and zero-event cases and
a converging fit.
PI style review. 'Established-smoker criterion' replaces 'gate';
'the model includes' replaces 'universe'; 'time at risk begins'
replaces 'clock starts'; 'a person who started and stopped at the
same age' replaces 'one-year spell'; the diagnostics group is
same_age_quits. Error and guard comments say what happens instead of
'fail loudly' or 'plausible-looking'. Applied to the workflow page,
the estimand specification, CLAUDE.md, config comments, the
worksheet purpose text, and R comments and messages.
…osure at Stage 1

The first CI pipeline run failed after 56 minutes at the imputation
stage: time_quit_smoking_complete was never derived because two of its
cchsflow inputs, SMK_10_gate and SMK_10A_cont, were not in the study
variables sheet, and rec_with_table() skips a derivation with missing
inputs without an error. Both are added as intermediate rows. A new
check_feeder_closure() reads every DerivedVar rule for a study variable
from the variable-details sheet and stops in the coverage_check target
if any input is absent from the variables sheet, so this class of gap
fails in seconds at Stage 1. Tests: the project worksheets pass the
check; a synthetic sheet with a missing feeder fails with a named
message. Verified locally: harmonizing the 2013-14 and 2001 fixtures
yields time_quit_smoking_complete (present for 2013-14, absent for 2001
as expected).
…ounds

External review of PR #7 (three blocking, four further findings).

Cessation risk now begins at each person's own age at first cigarette;
the reporting floor is no longer applied to the start age. Quit
timing is validated: it must be finite, within the configured bounds
for the data source (survey.years_since_quit_complete min/max), and
place the quit no later than the survey; otherwise the person is
excluded and counted (excluded_quit_timing_invalid). The durability
setting must be present in config; the hard-coded fallback is gone.

The initiation builder is rewritten with the same classification
discipline: never smokers and experimental smokers are at risk from
the floor age to the survey with no event; established smokers have
one event at their entry age; established smokers who started below
the floor were already smoking when observation begins and contribute
nothing here (they stay in the cessation model); missing status,
missing 100-cigarette answer, missing entry age, and entry after the
survey or outside the configured bounds are excluded and counted,
never reclassified as Never. Per-cycle counts are the
initiation_diagnostics attribute (shared summarise_groups helper).

expand_denominator() coerces cohort and ages to integers again. The
worksheet row for time_quit_smoking_complete gains apc-denominator
and documents that the variable is absent from the 2022 PUMF as well
as 2001 (both NA(c), imputed); the specification, config comment, and
workflow page say the same. Stage 7 docs use event and weight. The
test that hard-coded status and criterion codes reads them from
config. New respondent-level invariant tests: risk from an entry age
below the floor; negative and out-of-bounds quit durations excluded;
no person-year after the survey age or before entry; missing or
invalid initiation records excluded rather than treated as Never;
never smokers at risk from the floor to the survey; early initiators
contribute no initiation rows.
PI direction: the variable-details worksheet is the reference for
minimum and maximum values; config.yml refers to it instead of holding
copies. New details_range() reads a variable's valid range for one
database from the worksheet rules -- [lo, hi] copy rules, the set of
category-to-value midpoints, and, for derived variables, the union of
their feeders' ranges -- so ranges follow each cycle's grouping and
top-codes. config.yml survey entries for continuous variables now say
`range: variable_details`; the 28 literal min/max lines are removed.
survey_range() resolves the pointer; the APC builders receive the
variable-details sheet from the pipeline and validate entry ages and
quit durations per respondent against the range for that respondent's
cycle. The initiation floor (PUMF 13, Master 8; public issue #5) is an
analysis decision, not a range, and moves to apc.initiation_floor_age.

Finding made by the change: the PUMF groups quit duration as under 1,
1-2, 2-3, and 3 or more years from 2003 onward, so the largest
midpoint is 5, not the 15 that config comments, worksheet notes, and
the specification claimed; only the 2001 cycle reaches 15. All those
claims are corrected, and the coarsening is flagged for task 1.10.
Tests: details_range on synthetic rules; survey_range on the real
worksheets (0.5-5 for 2013-14, 15 for 2001 daily); out-of-range quit
durations excluded; synthetic test histories now use in-range values.
- The entry age in the cessation model is checked against the worksheet range for
  age_first_cigarette in the respondent's database, as the initiation model does; an
  entry age outside it is excluded and counted (excluded_entry_invalid).
- Missing smoking status and a missing 100-cigarette answer are counted before the
  established-smoker filter (excluded_status_missing, excluded_criterion_missing), with
  experimental smokers reported as not_established_ever_smokers.
- Range resolution fails closed: a cycle code not listed in cchs_cycles stops, and a
  non-missing value from a database with no worksheet range stops rather than passing.
  Test fixtures now carry NA(c) where the real PUMF does (2001/2022 quit timing, 2022
  age at first cigarette).
- config/statscan.yml.example uses the worksheet database names (cchs2001_m, ...).
- check_feeder_closure() is database-specific: a feeder rule in another cycle does not
  close the chain. A feeder absent from cshm-variables.csv stops; a feeder with no rule
  in a study database warns (the 2019-20 PUMF has no SMKG040, which the cchsflow rules
  for SMKG203_cont, SMKG207_cont and age_start_smoking assume).
- Manuscript, variable reference, estimand specification and Stage 7 workflow no longer
  describe a fixed cessation floor or survey_bound(); they describe the respondent-specific
  entry age and the worksheet range check.
Phase 1, task 1.3: cessation risk set per the estimand specification
Copilot AI lite review requested due to automatic review settings August 27, 2026 23:42
@DougManuel
DougManuel merged commit 31d75a5 into main Aug 27, 2026
10 checks passed

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 pull request updates the prespecified study protocol to v0.4.1 and implements Phase 1 pipeline remediation tasks (1.0 and 1.3) to align APC initiation/cessation datasets with the ratified estimand specification, using unified cchsflow v3 smoking variables and worksheet-derived validation ranges.

Changes:

  • Protocol v0.4.1: ratifies key study decisions (PUMF initiation floor; cycle-1 survey year), incorporates PI editorial revisions, and restores Word rendering via a docstyle subproject under docs/protocol/.
  • Pipeline: rebuilds cessation risk set around established smokers (100+ cigarettes) with durable-quit event logic, adds diagnostics attributes and fit guards, and standardizes value-code access via config helpers.
  • Validation & tests: adds feeder-chain closure checking at Stage 0, introduces worksheet-driven range utilities, and expands test coverage for APC dataset construction and survival-correction guards.

Reviewed changes

Copilot reviewed 31 out of 36 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/testthat/test-validate-coverage.R Adds tests for check_feeder_closure() behavior (warnings vs stops; cycle specificity).
tests/testthat/test-apc-data.R Expands APC tests for new cessation estimand, range checks, and mortality-correction guards.
tests/testthat/setup.R Loads combined variable-details sheet once for all tests (TEST_DETAILS).
tests/testthat/helper-apc.R Updates APC test data generator to include established-smoker and complete-cessation timing fields.
schemas/cshm-rate-tables.yaml Updates schema documentation for mortality_correction including "none" semantics.
README.md Clarifies that mortality corrections are not implemented; documents "none" behavior and guardrails.
R/variable-details-sheet-utils.R Adds details_range() to derive per-database min/max from variable-details recoding rules.
R/validate-coverage.R Adds check_feeder_closure() to fail early when derivation feeders are missing from worksheets.
R/config-utils.R Introduces survey_code(), cycle_database(), survey_range(), and initiation_floor() helpers.
R/apc-model.R Refactors APC data construction to use established-smoker criterion, durable cessation logic, per-cycle range validation, diagnostics, and mortality-correction labeling/guards.
manuscript/manuscript.qmd Updates manuscript text to reflect initiation floor logic and revised cessation risk-set definition.
docs/workflow/8-apc-model.qmd Updates workflow docs to reflect mortality_method: "none" and new guard behavior.
docs/workflow/7-apc-data-preparation.qmd Updates Stage 7 documentation for new variables, event column naming, and worksheet-derived ranges.
docs/reference/variables.qmd Updates reference docs for weight/mortality-correction semantics and initiation/cessation definitions.
docs/protocol/protocol.css Adds protocol-specific spacing styles for docstyle Word output.
docs/protocol/full-protocol.qmd Bumps protocol to v0.4.1; records ratified decisions and incorporates PI editorial revisions and docstyle layout updates.
docs/protocol/.gitignore Ignores protocol-local Quarto artifacts under the protocol subproject.
docs/protocol/_quarto.yml Adds nested docstyle subproject configuration for Word rendering of protocol documents.
docs/protocol/_docstyle/section-map.json Adds docstyle-generated section mapping for Word round-tripping.
docs/protocol/_docstyle/reference.docx.hash Tracks docstyle reference-doc hash for reproducible Word rendering.
docs/protocol/_docstyle/page-config.json Adds docstyle-generated header/footer and table style configuration.
docs/protocol/_docstyle/field-codes.json Adds Zotero field-code store for Word rendering/round-tripping of citations.
docs/development/protocol-todo.md Updates mortality-method status notes to reflect "none" default and no-op guard.
docs/development/pipeline-progress.md Updates pipeline progress notes to reflect "none" survival correction default and guards.
docs/development/estimand-specification.md Adds/records the Phase 1 estimand specification and conventions (states, transitions, timing rules).
docs/development/apc-plan.md Marks parts of the prior APC plan as superseded and updates mortality-method defaults/notes.
config/statscan.yml.example Renames Master cycle IDs to _m database naming convention.
config.yml Moves ranges to worksheet-derived variable_details, adds value-code groupings and thresholds, sets initiation floor and cessation durability, and defaults mortality method to "none".
CLAUDE.md Updates developer guidance for protocol rendering and new unified-variable/code/range conventions.
.gitignore Ignores protocol Word-render output directory (docs/protocol/output/).
_targets.R Runs check_feeder_closure() in Stage 0 and passes variable_details_sheet into prepare_apc_data().
Suppressed comments (1)

docs/protocol/full-protocol.qmd:302

  • Spelling/style: “Statistics Canada Open Licence” uses Canadian spelling (“Licence”), consistent with the rest of the repo and earlier references in this document.
This study involves the secondary analysis of de-identified survey data. Analysis of CCHS Master Files will be conducted within the secure environment of Statistics Canada’s Regional Data Centres, following all protocols for data privacy and disclosure control. No individual-level data that could identify respondents will be released. The use of PUMF data for model development is subject to the Statistics Canada Open License agreement.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread config.yml
var: age_first_cigarette # Age smoked first whole cigarette (midpoint-estimated)
min: 13 # Study floor — NOTE: SMKG01C_cont has a 5-11 category (midpoint 8);
range: variable_details
# 13 excludes it. Lowering to 8 is an open study decision.

1. **Estimate historical and current smoking parameters:** Reconstruct rates of smoking initiation, cessation, and intensity (cigarettes per day) by birth cohort (1890--2030) for each province and territory.
2. **Examine regional and temporal variations:** Analyse how smoking patterns have evolved across Canadian jurisdictions in response to differing policy environments and social trends.
2. **Examine regional and temporal variations:** Analyze how smoking patterns have evolved across Canadian jurisdictions in response to differing policy environments and social trends.
4. **Develop an open-access model:** Create a publicly-accessible, reproducible R-based model that can be used and updated by researchers, policy analysts, and health system planners.

The model will provide demographic and geographic estimates (by single year of age and birth cohort) to interface with existing population health models. Historical reconstruction will cover the period 1965--2023, with projections extending from 2024 to 2050. Analysis will be conducted at the provincial level, with national estimates derived from aggregated provincial data.
The model will provide demographic and geographic estimates (by single year of age and birth cohort) to interface with existing population health models and support smoking projections and the evaluation of smoking preventive interventions, amongst other uses Historical reconstruction will cover the period 1965--2023, with projections extending from 2024 to 2050. Analysis will be conducted at the provincial level, with national estimates derived from aggregated provincial data.
## Resource and access considerations

Model development uses publicly available PUMF data in an open R environment hosted on GitHub. The codebase is designed to run on both PUMF and Master File data without modification, controlled by a configuration profile. Production estimates using CCHS Master Files will be produced within a Statistics Canada RDC. RDC analysis requires institutional affiliation, an approved project proposal, and compliance with Statistics Canada disclosure control protocols. The lead analyst will apply for RDC access upon protocol approval.
This study involves the secondary analysis of de-identified survey data. Analysis of CCHS Master Files will be conducted within the secure environment of Statistics Canada's Regional Data Centres, following all protocols for data privacy and disclosure control. No individual-level data that could identify respondents will be released. The use of PUMF data for model development is subject to the Statistics Canada Open License agreement.
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.

Cessation model: exclusion of former occasional smokers

2 participants