# K9a control – bernstein2015 (supplement rows, extractor opus-S1)

Controller: K9a · 30.10.2026 · Sources: `drive/supplements/bernstein2015_S1.txt` … `S5.txt`.
Scope: the 352 rows bernstein2015_164–515 (extractor opus-S1) plus dose_conversions bernstein2015_c2–c37. adjustments.csv got no new sets (m2 is reused).

## Method
- S1–S5 parsed by script (`scratchpad/k/bern.py`). This gave 354 printed estimates: S1 108, S2 108, S3 54, S4 54, S5 30.
- Each printed estimate was matched to a DB row on table × exposure × outcome × sex × timing × contrast × rank/unit.
- Compared: point, ci_low, ci_high, p_trend, n_cases, person_years and category label.
- Flags were checked by reading the tables and footnotes.

## Discrepancies

| estimate_id | field | in database | in paper | severity |
|---|---|---|---|---|
| bernstein2015_164–373 (S1/S2 category rows) | dose_low/dose_high | bounds printed per day are converted ×7 to servings/week (e.g. ">1 to ≤2 svg/d" → 7–14) | S1/S2 mix svg/wk and svg/d in the row labels | minor. The conversion is exact, the label is kept verbatim and the change is documented. Strictly this departs from the codebook rule "tall som publisert" (numbers as published). |
| bernstein2015_486–515 (S5) | p_trend | S5 "P value" stored in p_trend on both increment rows | S5 prints "P value", one per exposure × outcome | minor. It is the Wald p of the single linear term, so it is equivalent; p_value would be the more literal field. |
| bernstein2015_380–485 (S3/S4) | person_years | cohort total PY for the timing model (e.g. 2,439,732) | S3/S4 print no PY. The cohort PY are in the S1/S2 header. | minor. Documented in notes and consistent with the main-text convention. |
| bernstein2015_497 vs _496 | is_main_model | processed × colon: per 50 g = yes, per 30 g = no | S5 gives both increments of the same linear term | minor. It is a choice ("CUP increment") and is documented. NOTES_all.md wrongly calls this row "_498"; _498 is red_total × proximal per 100 g (= no). |
| bernstein2015_145, _146 (existing opus-F rows) | n_cases | empty | S3 distal colon NHS 514; S4 distal colon HPFS 303 | minor. The note says n_cases was added, but the field is still empty. The values (1.37 [1.02–1.85] P 0.04; 1.35 [0.98–1.85] P 0.07) match S3/S4 exactly. |

**No wrong numbers.** All 352 rows match S1–S5 on point, CI, n_cases and PY.
- S1/S2: p_trend is placed on the continuous row, not on the top category. The codebook allows either.
- Category labels match verbatim, including S1 "0.1 to 0.5 svg/wk" vs S2 "0.1 to ≤0.5 svg/wk".

## Flags checked
- **exposure:** Total red meat → `red_total`/yes; Unprocessed → `red_unprocessed`/partial (hamburger, R6); Processed red meat → `processed`/yes. This matches the main-text rows and studies.csv.
  - The S5 header says "processed meats", but its footnote says processed red meat. The coding is correct.
- **outcome:** S1/S2 colorectal. S3/S4 proximal_colon / distal_colon / rectum ("Rectal cancer"). S5 also has colon.
- **sex / cohort:** S1/S3 women/`nhs`; S2/S4 men/`hpfs`; S5 all/`nhs_hpfs` (pooling method not stated in S5 – noted).
- **subgroup:** timing codes are correct. Cumulative average = empty (the paper's primary analysis).
- **is_main_model:**
  - S1–S4: cumulative average = yes, the five lag/baseline timings = no. Correct per exposure × outcome × sex.
  - S5: the unprocessed per 100 g rows are main for all 5 outcomes. Fable has demoted the per-serving main-text rows _024, _060, _096 and _132 to no, so each combination now has exactly one main row.
  - Red_total/processed S5 rows are no where a per-serving main row exists. Colon: per 100 g / per 50 g = yes.
  - Consistent.
- **is_calibrated** = no; **is_mutually_adjusted_processed** = no. Both correct, since m2 does not include other meats.
- **adjustment_set_id** = m2. The S1–S5 footnotes match the m2 covariate list. S2/S4 lack the HRT term because they are men, so no new set is needed.
- **dose_conversions c2–c37:** 36 sets = 2 sexes × 3 exposures × 6 timings. Each has 5 categories, and category_set_ids are unique. g_day_assigned is empty because no gram per serving is printed. Correct.

## Missing estimates
None. Two printed values have no opus-S1 row: S3 and S4 processed red meat × distal colon × cumulative average. They are the existing rows _145/_146, with identical values, and were deliberately not duplicated.

## Rows confirmed
All 352 opus-S1 rows are confirmed on numbers. The flags are correct, apart from the minor points above.

## Internal inconsistencies in the paper
- None in S1–S5.
- S5 prints CIs with "-" in some cells and "," in others. This is formatting only.
