# Headline algorithm, judged on privacy and the business use cases

Page: `PYTHONPATH=src python scripts/decision_brief_business.py --html out/m1/decision-business.html`
(after `scripts/m1_sweep.py`, `scripts/usecase_benchmark.py`, and `out/candidates/run.py` for the
two candidate rungs). Seed 42, verified Parquet, 14 ladder configurations + 2 measured candidates.

| configuration | singled out | app / heavy inferable | fingerprint | 3-obs linkage | rows removed | max core distortion | business visibility | large prizes answerable |
|---|---:|---:|---:|---:|---:|---:|---:|---:|
| id_removal_coarsen | 509 | 491 / 17 | 99.3 % | — | 0 | 0.00 | 0.68 | 4/5 |
| c3_time_coarsen | 66 | 98 / 5 | 98.9 % | — | 0 | 0.00 | 0.46 | 3/5 |
| c6_k5_suppress | 0 | 1,198 / 1 | 99.1 % | — | 2,205 | 1.64 | 0.67 | 4/5 |
| c8_time_k5 | 0 | 449 / 2 | 98.9 % | — | 332 | 1.64 | 0.45 | 3/5 |
| c11_time_noise2 | 66 | 98 / 5 | 1.5 % | — | 0 | 1.06 | 0.46 | 3/5 |
| c14_time_k5_noise10 | 0 | 449 / 2 | 0.1 % | — | 332 | 4.67 | 0.45 | 3/5 |
| **x15_window_k5_noise2** (candidate) | 0 | 1,198 / 1 | 1.7 % | — | 2,205 | 1.64 | 0.67 | 4/5 |
| **x16_window_k5_noise2_key** (candidate) | 0 | 1,198 / 1 | 1.7 % | 78.8 % | 2,205 | 1.64 | 0.84 | 4/5 (+ 2/3 very large) |

## What the two evaluations say together

- **Time resolution is the biggest business lever and the ladder never tested it against suppression and noise.** Every hour-grain rung (incl. the previous recommendation c11) scores 0.45–0.46 on business visibility: forecasting (#6), the insights product (#11), fault detection (#15), change regression (#16) and the festival analysis (#17) become unanswerable. Every window-grain rung scores 0.67–0.68. Hour coarsening's privacy gain (509 → 66 singled out) is small next to suppression's (→ 0).
- **Suppression at window grain buys isolation 0 for 2,205 rows and an inference cost**: 1,198 subscribers (0.6 %) in app-homogeneous classes vs 98 on c3 — chance on this file, higher on real data.
- **±2 % multiplicative noise closes the numeric fingerprint (≈99 % → 1.5–1.7 %) at either grain** and costs the business headline figures nothing measurable.
- **Fair use (#3) and churn (#2), the very-large subscriber prizes, need a released pseudonym; churn also needs windows.** Only x16 answers both — at 78.8 % three-observation linkability (16.6 % for the hour-grain key).
- **Capex (#1) and vendor benchmarking (#8) need the station id**; no rung serves them — a station-retaining product with a minimum-cell rule, not a row release.

## Top three

1. **x15_window_k5_noise2** — windows kept, suppress < 5, ±2 % noise. 0 singled out, fingerprint 1.7 %, business 0.67 with the time-dependent questions kept; costs 2,205 rows, HTTP/tethering families unsupported, and 1,198 app-inferable — the one open number. The only rung clean on isolation and fingerprint that keeps the time-dependent use cases.
2. **c11_time_noise2** — hour, 25 categories, ±2 % noise (previous recommendation). Lowest exposure per person of any rung keeping 25 categories (66 / 98 / 1.5 %, nothing removed, distortion 1.06); right when the recipient's questions are the aggregate mixes and quality distributions and nothing time-dependent. Loses first place only because the business benchmark shows what time detail is worth.
3. **x16_window_k5_noise2_key** — x15 plus a pseudonym; the subscriber-question tier, not a default release. Business 0.84, the only rung answering fair use and churn; price is 78.8 % linkability from three known observations, defensible only inside the safe-box controls (one package per period, named recipient, logged use case).

Not in the top three: c8/c14 (dominated on business by x15, on distortion by c11), c6 (x15 without the noise: fingerprint 99 %), c3 (zero-distortion control, fingerprint 99 %), c12/c13 (noise curve flat after 2 %), c4/c5/c7 (app grouping loses category detail for little gain), id_removal_coarsen (business 0.68 at zero distortion, but 509 singled out and 99 % fingerprint).

Decisions for the humans: whether 0.6 % app-homogeneous classes is acceptable or aggregate questions suffice (then c11); whether the key tier exists and under what controls; whether to add x15/x16 to the ladder as c15/c16.

## The recommended four (added to the ladder as c15–c18)

Chosen with two more measured rungs (hour + groups + k5 + noise; hour + station + k5 + noise; also
hour + k5 + noise and hour + key + k5 + noise, which were not selected). Rendered as the
"Recommended four" tab of the comparison page (`--recommendation docs/recommendation/recommended-four.json`).

| pick | configuration | singled out | app / heavy inferable | fingerprint | 3-obs linkage | rows removed | business visibility | what it adds |
|---|---|---:|---:|---:|---:|---:|---:|---|
| 1 · Balanced default | `c15_window_k5_noise5` | 0 | 1,198 / 1 | 1.7 % | — | 2,205 | 0.67 | time-dependent questions kept with isolation 0 and fingerprint closed |
| 2 · Companion: subscriber tier | `c16_window_k5_noise5_key` | 0 | 1,198 / 1 | 1.7 % | 78.8 % | 2,205 | 0.84 | fair use (#3) and churn (#2) |
| 3 · Maximum privacy | `c17_hour_groups_k5_noise10` | 0 | 286 / 0 | 1.4 % | — | 68 | 0.45 | lowest exposure on every benchmark; aggregate questions at group level |
| 4 · Companion: station tier | `c18_hour_station_k5_noise5` | 0 | 1,908 / 169 | 1.4 % | — | 57,059 | 0.56 | capacity prioritisation (#1) and vendor benchmarking (#8) |

Not selected as companions: `x20` hour + k5 + noise + key (fair use at 16.6 % linkability, but no
churn — needs windows) and `x19` window + station + k5 + noise (capex 0.89 but 302,346 rows
removed and 14,070 app-inferable: the station cell at window grain is too small).

## Re-run with row-level fidelity (the noise dial, measured)

The use-case scores are blind to per-row jitter, so `dsh.business.fidelity.row_fidelity` was
added: median and p90 relative change per released value, share of rows removed, and
fidelity = (1 − removed) × (1 − median change). Measured on the four recipes at ±2 / ±5 / ±10 %
(seed 42; isolation, inference and business visibility identical at every level):

| noise | fingerprint | row fidelity | median change | p90 change | max core distortion (window k5 / hour groups k5) |
|---|---:|---:|---:|---:|---:|
| ±2 % | 1.4–1.7 % | 0.984 | 1.4 % | 4.6 % | 1.64 / 2.42 |
| ±5 % | 0.1–0.2 % | 0.963 | 3.5 % | 11.5 % | 2.52 / 5.90 |
| ±10 % | 0.0–0.1 % | 0.929 | 6.9 % | 23.0 % | 4.67 / 11.33 |

Decision: the releases analysts explore row by row (default, subscriber tier, station tier)
take ±5 % — fingerprint 0.2 % for a 3.5 % median value change; the aggregate-only
maximum-privacy release takes ±10 % — fingerprint 0.0 %, where row fidelity is not the point.
The 0.1–0.2 % residual is the chance that the noise lands inside the attacker's ±1 % tolerance on
every measurement they know; it is a dial, not a floor, and the table shows the dial.

### Final four (on the ladder as c15–c18)

| pick | configuration | singled out | app / heavy inferable | fingerprint | 3-obs linkage | rows removed | business vis. | row fidelity |
|---|---|---:|---:|---:|---:|---:|---:|---:|
| 1 · Balanced default | `c15_window_k5_noise5` | 0 | 1,198 / 1 | 0.2 % | — | 2,205 | 0.67 | 0.96 |
| 2 · Companion: subscriber tier | `c16_window_k5_noise5_key` | 0 | 1,198 / 1 | 0.2 % | 78.8 % | 2,205 | 0.84 | 0.96 |
| 3 · Maximum privacy | `c17_hour_groups_k5_noise10` | 0 | 286 / 0 | 0.0 % | — | 68 | 0.45 | 0.93 |
| 4 · Companion: station tier | `c18_hour_station_k5_noise5` | 0 | 1,908 / 173 | 0.2 % | — | 57,059 | 0.55 | 0.92 |

Why c18 is a companion and not the default despite two zeros: it is worse on two of the four
benchmarks (app / heavy inference 1,908 / 173 vs 1,198 / 1), releases the strongest
quasi-identifier (station id, location data), removes 5 % of rows, has 3.6× the distortion and
a lower business visibility (0.55 vs 0.67) — its one very large prize (capex) is what earns it the
companion slot.

## Re-run with the four product-exploration questions (29 use cases)

Mikael's commercial scenarios (`docs/analysis/telecom-commercial-scenarios.md`) became four
measured use cases — #13 Work/Game/Stream priorities, #16 aggregate quality-improvement
priorities, #19 premium video, #20 gaming product — using the app × RAT quality contract of
`experiments/mikael_app_quality` (cells with ≥ 30 valid observations; fixed raw-reference
tails: throughput ≤ raw p10, RTT ≥ raw p90; a lost cell is unavailable, never zero error).
Same seed, same 18 rungs, `scripts/usecase_benchmark.py` re-run:

| configuration | #13 experience | #16 quality priorities | #19 premium video | #20 gaming | quality cells kept | business vis. (29) | (was, 25) |
|---|---:|---:|---:|---:|---:|---:|---:|
| `c15_window_k5_noise5` | 0.94 | 0.95 | 0.98 | 0.99 | 138 / 144 | **0.70** | 0.67 |
| `c16_window_k5_noise5_key` | 0.94 | 0.95 | 0.98 | 0.99 | 138 / 144 | **0.85** | 0.84 |
| `c17_hour_groups_k5_noise10` | 0.98 (group level) | 0.98 (group level) | unsup | unsup | 40 (groups) | **0.48** | 0.45 |
| `c18_hour_station_k5_noise5` | 0.81 | 0.63 | 0.92 | 0.90 | 116 / 144 | **0.58** | 0.55 |
| `c3_time_coarsen` (control) | 1.00 | 1.00 | 1.00 | 1.00 | 144 / 144 | 0.52 | 0.46 |
| `c8_time_k5` (control) | 0.97 | 0.98 | 1.00 | 1.00 | 140 / 144 | 0.51 | 0.45 |

What changed: nothing in the order, and the four picks stand. The new questions reward keeping
the detailed application category and punish suppression on a fine cell — which is exactly the
c15/c16 vs c17/c18 split already in the table. Two findings are new evidence rather than
confirmation:

- **The subscriber code buys nothing for product questions.** c15 and c16 are identical on all
  four (Mikael's independent run agrees). c16's higher business number is fair use and churn
  only; it stays a named-recipient tier.
- **Averages hide sparse-cell loss.** c15 keeps 138 of 144 quality cells, but the six it loses
  are whole questions for small categories (all three Chat/5G cells, VoIP/5G RTT, File
  transfer/5G throughput), and the worst surviving cell moves 7.9 pp (IoT/5G radio RTT tail).
  c18's station-cell suppression loses 28 cells. The per-cell coverage count now travels with
  every measurement note, and a lost cell scores as a full loss inside the use case rather than
  disappearing from the average.

The prize sizes of the four are hypotheses (no demand, pricing or satisfaction exists in the
data): two medium (#13, #16), two small (#19, #20). They share measurements with #5, #9 and
#12 and are not additive revenue; the weighted summary treats them as four more questions, not
four more markets.

## Audit fixes (read-only benchmark audit, 2026-09-19) and what moved

Seven findings, all addressed in the scorer; the 18-rung re-run afterwards moved **one
figure**: c16's weighted visibility 0.8519 → 0.8518 (churn's low-quality line is now the raw
p10 held fixed instead of re-drawn per table). Every other cell is unchanged, so the fixes are
edge-case protection for future releases, not corrections of the current ranking.

1. Empty evidence scored perfect — an empty share vector or quantile grid on both sides compared
   as identical. Empty components are now dropped before scoring; a use case with no evidence
   is `unsupported`, and a release that empties a component the raw table has loses it.
2. Row fidelity rewarded absent values — with no comparable value it returned 1 − removed.
   It is now unavailable (None) with zero comparable values; values finite on raw but missing
   in the release are counted (`values_missing`, `share_values_missing`) and lower the
   fidelity like removed rows; dropped measurement fields are listed.
3. App-group projection changed the question — kept for share-based questions (marked partial,
   coarse answer), but the detailed Gaming/Streaming questions no longer inherit it: they are
   `unsupported` (0) under grouping, and a use case the projection makes unmeasurable is scored
   against the unprojected raw table, not reported as n/a.
4. Coverage now travels with every comparison (`coverage`: cells in the raw components, cells
   lost, worst single-cell distance and its label, observations behind the quantile grids on
   both sides) and is shown in the page hover; a 1.00 is labelled "distributions unchanged",
   not "evidence unchanged".
5. Proxy wording — `recommended-four.json` now names the measured proxy and the missing
   prerequisite (forecasting = per-window volume profile, no history; churn = sustained-low-
   quality share, no outcomes; insights = province × window counts, randomised location; vendor
   benchmarking = per-station quality, no vendor labels).
6. Weighting sensitivity (`scripts/weight_sensitivity.py`, `docs/analysis/weight-sensitivity.md`):
   equal, prize and family-once weights over a fixed raw-measurable denominator. Only two
   configurations change rank between the three — `id_removal` and `c16` swap first and second —
   and neither swap touches a decision: `id_removal` is the no-anonymisation baseline and c16
   is the named-recipient tier. c15 is 5th, c18 9th and c17 18th under all three weightings.
7. Provenance — `visibility.json` now records the seed, the executing git sha and the full
   transformation parameters of every configuration. One seed remains one run; robustness
   across seeds is not claimed.
