Commit 049f04c
Address CI R13 P1: constant violation pattern is now a true level shift
R13 CI codex caught the next adjacent unit-mismatch — analogous to
R12's linear/level fix but for `violation_type='constant'`. REGISTRY
`## PreTrendsPower` documents the pattern as `δ_t = c` (per-period
level shift), but `_get_violation_weights('constant')` still
L2-normalized to `[1/√K, ..., 1/√K]`, so `δ_t = M/√K` not `δ_t = M`.
The Step 4 γ-unit fix for linear and the R12 holistic
`max_abs_pre_violation` fix uncovered this — once `mdv` is the
documented magnitude of a per-period shift on linear, the constant
pattern's silent `1/√K` re-scaling becomes visible and breaks the
documented contract.
**Holistic fix — constant/last_period skip L2 normalization**
- `_get_violation_weights('constant')`: early return `np.ones(n_pre)`,
no L2 norm. Now `δ_t = M` exactly per period — matching the
REGISTRY/API documented contract.
- `_get_violation_weights('last_period')`: also given an explicit
early return for symmetry. The `[0, ..., 0, 1]` vector already had
L2 norm 1, so this is a no-op numerically; the early return locks
the contract uniformly across level-pattern violation types.
- `power_at()` legacy reconstruction (fallback for old serialized
results without `violation_weights`): unchanged — still
L2-normalizes, preserving pre-PR-B numerical output for legacy
serialized fits per the same backwards-compat policy applied to
the linear-legacy and constant-legacy paths.
- Docstring on `_get_violation_weights` rewritten to enumerate the
per-type normalization convention explicitly: linear-with-times
(γ-unit), linear-legacy (L2-norm), constant (level), last_period
(level), custom (L2-norm).
**End-to-end regression** in `test_methodology_pretrends.py`:
- `test_constant_violation_pattern_is_level_shift`: real
`SunAbraham`-fit results, asserts `violation_weights == [1, ..., 1]`
(NOT L2-normalized → `||w||_2 = √K`), `max_abs_pre_violation == mdv`
(level-scale and γ-scale coincide for constant), and
`power_at(M) ≈ refit(M=M).power` at `atol=1e-4` so the
level-shift contract holds end-to-end through `fit()` and
`power_at()`. Codex specifically requested an end-to-end lock so
future audits cannot drift between "per-period shift" and
"normalized-direction magnitude".
- `test_constant_weights` in `tests/test_pretrends.py` flipped: was
pinning `np.linalg.norm(weights) == 1.0` (the OLD L2-normalized
contract); now asserts unnormalized `[1, 1, 1, 1]` with
`||w||_2 = 2` (√4). Docstring explains the contract change.
**P3 fix — BR rendered label**
`BusinessReport.full_report()` still labeled the
`mdv_share_of_att` row as `MDV / |ATT|`, but R12 redefined the
numerator as `max_abs_pre_violation`. Fix: rename the rendered
label to "Max pre-period level deviation / |ATT|" and add an
explicit row for `Max pre-period level deviation at MDV` above it
so users can see both the raw `mdv` and the level-scale scalar.
**Behavior change for users:** any caller passing
`violation_type='constant'` will see a √K-factor change in the
reported `mdv` and downstream `mdv_share_of_att`. The shift is
documented in REGISTRY `## PreTrendsPower` (the pattern was
`δ_t = c` all along — the IMPLEMENTATION is now what the docs
have always said).
445 tests pass across pretrends + DR + BR + SA. 4 skipped (R-parity
+ 1 fixture skip).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent b053faa commit 049f04c
4 files changed
Lines changed: 222 additions & 162 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2467 | 2467 | | |
2468 | 2468 | | |
2469 | 2469 | | |
| 2470 | + | |
2470 | 2471 | | |
2471 | 2472 | | |
2472 | 2473 | | |
| 2474 | + | |
| 2475 | + | |
2473 | 2476 | | |
2474 | | - | |
| 2477 | + | |
| 2478 | + | |
| 2479 | + | |
| 2480 | + | |
| 2481 | + | |
2475 | 2482 | | |
2476 | 2483 | | |
2477 | 2484 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
890 | 890 | | |
891 | 891 | | |
892 | 892 | | |
893 | | - | |
894 | | - | |
895 | | - | |
896 | | - | |
897 | | - | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
898 | 917 | | |
899 | 918 | | |
900 | 919 | | |
| |||
930 | 949 | | |
931 | 950 | | |
932 | 951 | | |
933 | | - | |
934 | | - | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
935 | 960 | | |
936 | | - | |
937 | | - | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
938 | 968 | | |
| 969 | + | |
939 | 970 | | |
940 | 971 | | |
941 | 972 | | |
942 | 973 | | |
943 | | - | |
944 | | - | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
945 | 979 | | |
946 | 980 | | |
947 | 981 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
523 | 523 | | |
524 | 524 | | |
525 | 525 | | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
526 | 564 | | |
527 | 565 | | |
528 | 566 | | |
| |||
0 commit comments