feat(linkedin-studio): N17 — baseline-motor (median + variansbånd + minimum-N-refusal) [skip-docs]
Every reading now leads with "vs your own baseline", and no verdict is given when N is too small to carry one. - stats.ts: median + medianAbsoluteDeviation (robust pair; mean/stddev stay for the alert engine, which wants outlier sensitivity), rollingBaseline with a 10-post positional window, median ± 1·MAD band floored at 0, and a typed insufficient-data refusal below MIN_BASELINE_N=5. readAgainstBaseline returns above/within/below-band, or no-verdict when the baseline was refused. - baselineByGroup + buildBaselineBlock: per-format/per-pillar baselines, each judged on its own N; the reported period is excluded from its own baseline and compared on its median, not its mean. - queue-join.ts (new): read-only date join supplying format/pillar from the post queue. Every ambiguity resolves to unlabelled, an entry labels at most one post, and a missing/broken queue degrades to no labels. - weekly/monthly reports attach the block unconditionally (refusal included); optional in the types, so pre-N17 reports load unchanged. - CLI: report output leads with the baseline; new `baseline [--by format|pillar]` verb with coverage reporting. - report.md leads with baseline framing and prints the code's reading rather than judging the band by eye; WoW loses to the baseline on disagreement. analyze.md Step 2a tests whether the drop is real before diagnosing it. TDD: 58 analytics tests written red first (144 -> 202). test-runner Section 16x, 23 unconditional checks + self-test (247 -> 270; anti-erosion floor 228 -> 251). tsc clean. All suites green: trends 300, brain 134, editions 72, specifics-bank 45, contract-gate 33, hooks 191, tests 35, render 60. Also closes the OKF phase-4 scope follow-up in docs/okf-ingestion/plan.md §8 (coord round 2026-07-25): phase 4 tracks the contract, parse is in scope, and our claim on read_concept/navigate_bundle is withdrawn as unnecessary. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxvWAjte7vPcF79QeSRvRJ
This commit is contained in:
parent
63506f7d5c
commit
e2ad190dda
15 changed files with 1723 additions and 14 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -87,6 +87,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- **Boundary map brought current (E#9)** — the README boundaries now state all three honestly: dwell **explicitly unmeasurable** (internal to LinkedIn, no count to transcribe, no API), saves partner-gated (`POST_SAVE` on `/memberCreatorPostAnalytics` from v202604), and reach native-but-not-exported with export status **unverified**.
|
||||
- analytics suite 119 → 144 (25 new tests: percent contract, either-half resolution, split cross-check, weighted roll-up, backward-compat); test-runner 232 → 247 (Section 16w: 15 unconditional greps + self-test; anti-erosion floor 213 → 228); `tsc` clean.
|
||||
|
||||
### Added — baseline engine: your own normal, and a refusal when N is too small (N17 / E#5)
|
||||
|
||||
- **Every reading leads with "vs your own baseline" (E#5)** — at a normal publishing cadence a week holds two or three posts, so an absolute total answers nothing on its own and a week-over-week percentage answers worse than nothing: it swings on sample composition and reads as a trend. The weekly and monthly reports now carry a `baseline` block built from the operator's own recent history, and `/linkedin:report` presents it **first**, absolutes second.
|
||||
- **Median and MAD, never mean and standard deviation** — one viral post would otherwise define "normal" as a number no ordinary post can reach, and every ordinary week after it would read as a decline. `median()` + `medianAbsoluteDeviation()` are the robust pair; `mean`/`standardDeviation` stay untouched for the alert engine, which *wants* outlier sensitivity. An empty history yields `undefined`, never a fabricated 0.
|
||||
- **A normal-range band instead of a bare number** — `median ± 1·MAD` (k configurable), floored at 0 for naturally non-negative metrics. `readAgainstBaseline()` returns `above-band` / `within-band` / `below-band`, and `within-band` is the load-bearing answer: a value 7 % above the median looks like momentum in any week-over-week percentage and is ordinary variation. The reading is computed **in code**, so a renderer never judges the band by eye.
|
||||
- **Minimum-N refusal — no verdict on noise** — below `MIN_BASELINE_N` (5, documented and overridable) the engine returns a typed `insufficient-data` refusal carrying `n`, `required` and an operator-facing reason, and `readAgainstBaseline` degrades to `no-verdict`. With fewer than five values a median moves further when one post enters than any real change would move it. The report prints the refusal verbatim, keeps showing the absolute numbers **without** a verdict, and may not substitute a week-over-week percentage for the missing one. An operator who has just started publishing gets this refusal for several weeks — that is the correct output.
|
||||
- **The baseline excludes the period it judges** — history is taken strictly *before* the reported week/month (`historyBefore`), so the comparison is never partly with itself, and the period is compared on its own **median**, not its mean. The window is counted in **posts** (last 10), not days: a calendar window silently shrinks to three posts in a slow month without the operator seeing it happen.
|
||||
- **Per-format and per-pillar baselines, each judged on its own N** — format and pillar are not analytics data (they live in the post queue), so a new read-only date join supplies them (`queue-join.ts`, ±2 days for a reschedule, hook-preview tiebreak). Every ambiguity resolves to **unlabelled** and an entry labels at most one post — a guessed label would poison the baseline it is measured against. New `baseline [--by format|pillar]` CLI verb prints the overall baseline plus per-group ones, reports its own coverage, and refuses per group: a format used twice gets no verdict even when the overall history is long.
|
||||
- **`/linkedin:analyze` tests the problem before diagnosing it** — a new Step 2a reads the band first. `within-band` says plainly that this looks like ordinary variation rather than a drop (and asks whether to continue) instead of manufacturing a diagnosis; `no-verdict` continues on self-report only, labelled provisional. Step 5's severity assessment now measures against the band rather than a remembered peak.
|
||||
- Additive and backward-compatible: `baseline` is optional in the report types, so pre-N17 reports load unchanged (and a consumer says "no baseline in this report" rather than assuming one). analytics suite 144 → 202 (58 new tests: median/MAD, window, band, refusal, grouping, period reading, queue join, report wiring); test-runner 247 → 270 (Section 16x: 23 unconditional greps + self-test; anti-erosion floor 228 → 251); `tsc` clean.
|
||||
|
||||
### Built feedback (awaiting consumer-side proof)
|
||||
|
||||
- **MR-F9** (demand-sweep «innenfra og ut») — built; the (a)/(b)/(c) evidence gate is a runtime demonstration, proven consumer-side (plugin agents don't resolve in the dev repo).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue