# MSC-P-035 — saturation and adstock reproducibility protocol

## Frozen design

- Synthetic data only; no real media spend or revenue data.
- 156 weekly observations generated with seed `20260914`: one media channel whose spend arrives in bursts, a geometric carryover of rate **0.60**, a Hill saturation with shape **1.80** and half-saturation **55**, an amplitude of 400, plus a baseline with level, linear trend, annual seasonality carried by two Fourier harmonics, and Gaussian noise.
- Declared response model, fixed before the file is read: `A(t) = x(t) + λ·A(t−1)` with `A(0) = 0`, then `S(t) = A(t)^β / (γ^β + A(t)^β)`, entered linearly beside a baseline of intercept, linear trend and two annual harmonics. Seven linear parameters are solved in closed form at every grid point.
- Declared grid: `λ ∈ {0.0, 0.1, …, 0.9}`, `β ∈ {0.6, 1.0, 1.4, 1.8, 2.2, 2.6, 3.0}`, `γ ∈ {25, 35, 45, 55, 65, 75, 85, 95}` — 560 combinations. **The true generating values are grid points**, so the dossier can report whether the truth lies inside the region the data cannot distinguish. A real analyst never has that check; it exists here only because the case is synthetic.
- Declared region of indistinguishable fits: every grid point whose residual sum of squares is within **1 %** of the smallest. That is the set of parameter combinations the data cannot separate.
- Declared marginal return: at each grid point, the derivative of the fitted response with respect to the adstock level, evaluated at that grid point's own mean adstock. This is the quantity a reallocation decision actually uses — the return of one more unit of spend at the level currently spent.
- Declared thresholds, fixed before any result is read: the carryover range inside the region must not exceed **0.20**, and the ratio between the largest and smallest marginal return inside the region must not exceed **1.50**.
- The verdict is fail-closed: it authorizes a reallocation only when both checks pass.
- Three quantities are reported without thresholds because they exist only in a synthetic case: whether the true parameters fall inside the region, by how much their fit is worse than the best, and the marginal return they imply. These three lines were added to the printed output **after** the first run, once the true parameters turned out to fall outside the region; no threshold and no verdict rule was changed.

## Expected output

`weeks=156`, `grid_points=560`,
`best_carryover=0.600000`, `best_shape=1.000000`, `best_half_saturation=45.000000`, `best_amplitude=629.019162`,
`r_squared=0.938098`, `residual_sd=25.033965`,
`region_tolerance=0.01`, `region_points=8`,
`carryover_min=0.600000`, `carryover_max=0.600000`, `carryover_range=0.000000`, `carryover_flag=PASS`,
`shape_min=0.600000`, `shape_max=1.400000`, `half_saturation_min=25.000000`, `half_saturation_max=65.000000`,
`marginal_min=1.478765`, `marginal_max=1.586695`, `marginal_ratio=1.072987`, `marginal_flag=PASS`,
`best_marginal_return=1.535957`,
`true_parameters_inside_region=NO`, `true_parameters_fit_excess_percent=2.556023`, `true_marginal_return=1.539121`,
`verdict=RESPONSE_CURVE_IDENTIFIED_FOR_REALLOCATION`.

## Run

```text
python msc-p035-reference.py msc-p035-media-series.csv
Rscript msc-p035-reference.R msc-p035-media-series.csv
```

The SPSS syntax carries its computation in a `BEGIN PROGRAM Python3` block; that block was executed outside SPSS on the shipped CSV and printed the same values as the Python reference. The SAS program uses the same declared grid but delegates the 560 fits to PROC REG over BY groups, whose solver and residual conventions are its own; it checks the two flags and the verdict rather than every digit. The R reference was executed on the shipped CSV with R 4.6.1 and printed the same twenty-seven lines as the Python reference, digit for digit. The SPSS and SAS runtimes are not available in the local verification environment, so those two files are reviewed statically and must be run independently before relying on their printed output.

## Variable dictionary

| Column | Type | Unit | Definition |
|---|---|---|---|
| `week` | integer, 1…156 | week | Week number, ordered without gaps. |
| `media_spend_keur` | number > 0 | thousands of currency units | Media spend that week. |
| `revenue_keur` | number > 0 | thousands of currency units | Revenue observed that week. |

Derived quantities printed by the reference scripts:

| Output | Definition |
|---|---|
| `best_carryover`, `best_shape`, `best_half_saturation`, `best_amplitude` | The grid point with the smallest residual sum of squares, and the coefficient the fit gives to its saturated regressor. |
| `r_squared`, `residual_sd` | How closely that single best combination tracks the outcome. These say nothing about identification. |
| `region_points` | How many of the 560 combinations fit within 1 % of the best. A large number means the data cannot separate them. |
| `carryover_min`, `carryover_max`, `carryover_range`, `carryover_flag` | The spread of the carryover rate inside that region, and whether it stays within 0.20. |
| `shape_min`, `shape_max`, `half_saturation_min`, `half_saturation_max` | The spread of the two curve-shape parameters inside the region, reported without a threshold because the decision does not read them directly. |
| `marginal_min`, `marginal_max`, `marginal_ratio`, `marginal_flag` | The range of the decision-relevant marginal return across the region, and whether the largest is at most 1.50 times the smallest. |
| `best_marginal_return` | The marginal return implied by the best-fitting combination: the number a practitioner would actually take to a budget meeting. |
| `true_parameters_inside_region`, `true_parameters_fit_excess_percent`, `true_marginal_return` | Synthetic-only diagnostics: whether the true combination survives the 1 % region, how much worse its fit is, and the marginal return it implies. |
| `verdict` | `RESPONSE_CURVE_IDENTIFIED_FOR_REALLOCATION` only when both flags pass; otherwise `DIAGNOSTIC_BLOCKS_RESPONSE_CURVE`. |

## Numbered analysis steps

1. Load the CSV; refuse any file whose header, cell count, week numbering or positivity differ from the frozen schema, or that is shorter than the declared grid search requires.
2. Build the declared baseline columns: intercept, linear trend and two annual harmonics.
3. For each declared carryover rate, build the adstock series once, and record its mean level.
4. For each of the 560 combinations, build the saturated regressor and solve the seven-parameter least squares in closed form by Gaussian elimination with partial pivoting; refuse a singular design.
5. Take the combination with the smallest residual sum of squares, and collect every combination within 1 % of it.
6. Inside that region, compute the spread of each parameter and the range of the marginal return at each combination's own mean adstock; refuse a region containing a non-positive marginal return.
7. Apply the two declared thresholds to obtain both flags, apply the fail-closed verdict rule, print every quantity of the expected output including the three synthetic-only diagnostics; a third party compares the printed lines with the expected output above, digit for digit.

## Scientific boundary

This dossier separates fit from identification, and its sealed result shows why that separation matters. The best combination tracks the outcome closely, with an R² of 0.938098. It also recovers the carryover rate exactly: every combination inside the 1 % region has `λ = 0.6`, which is the true value. But it does **not** recover the curve: the best shape is 1.000000 against a true 1.80, the best half-saturation 45 against a true 55, and the true combination sits outside the region altogether, its fit only 2.556023 % worse than the best. The shape parameters are simply not pinned down by 156 weeks of one channel.

What survives is narrower and more useful than a curve: the marginal return at the spend level actually observed. Across the whole region it varies between 1.478765 and 1.586695, a ratio of 1.072987, and the true value of 1.539121 lies inside that band. In other words, the data determine the local slope where money has been spent, not the global shape of the response. That is why the verdict authorizes a marginal reallocation and nothing more.

Four limits follow directly. The fitted `β` and `γ` must not be read as quantities with meaning, and must not be quoted as "the saturation point". The curve must not be extrapolated outside the observed spend range, where the region's combinations diverge sharply even though they agree near the mean. The carryover rate is identified here because the spend arrives in sharp bursts; a smoother spend pattern would not separate `λ` from a trend. And nothing in this exercise is causal: spend was generated independently of revenue here, whereas real budgets are planned around seasons and recent performance, so an observational fit on real data carries a selection problem this dossier does not address and cannot solve.

Dataset license: CC0-1.0. Code license: MIT.
