fix(linkedin-studio): S15 harden report — drop baselines/metadata phantom, fix engagement_rate flag + JSON schema fields

S15 report through the v2 interactive quality gate (analytics class). 3 findings,
6 surgical edits in commands/report.md, all grep-confirmed + grounded against a
throwaway-root CLI run:

- FUNN 1 (S14 twin): Step 5c baseline/std-dev mechanism + Reference Files referenced
  baselines.json/metadata.json that no code writes (storage.ts writes only
  posts/weekly-reports/monthly-reports). Rewrote Step 5c to read the report's own
  alerts[] (detectAlerts + detectWeeklyAlerts); fixed posts filename to
  YYYY-MM-DD-<shortid>.json; added real monthly-reports file.
- FUNN 2: trends --metric engagement_rate (snake_case) → exit 1 'Invalid metric';
  CLI accepts only engagementRate (cli.ts:47,202-209). Fixed report.md:172.
- FUNN 3: Step 4/Step 6 claimed JSON fields dateRange/postCount/aggregateMetrics that
  don't exist; real schema is summary/topPerformers/underperformers/trends/alerts.
  Rewrote to real fields; fixed Deep-Dive post-search path.

Gate: test-runner 81/0/0 exit 0; counts 29/19 unchanged (.md-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
This commit is contained in:
Kjell Tore Guttormsen 2026-06-19 06:18:44 +02:00
commit 060ce8a371
2 changed files with 82 additions and 21 deletions

View file

@ -1180,3 +1180,60 @@ functionally safe but mis-labelled "(Step 3b)". Root cause: conflating "the phas
post-fix.
---
### /linkedin:report — imported analytics → weekly/monthly/heatmap report, alerts surfaced, interpretation delegated to analytics-interpreter
**WHAT VERIFIED CLEAN (no change).**
- Path-seam (axis-a): `getAnalyticsRoot()` (`storage.ts:6772`) → `getDataRoot("analytics")` (`:5459`)
= the exact `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics` paths the prose reads;
`ANALYTICS_ROOT` override confirmed (the grounded sim ran isolated in `/private/tmp`).
- CLI surface (axis-a): `report --week`/`--month`, `trends --period --metric`, `heatmap` all dispatch
(`cli.ts:447459`); grounded sim produced verbatim weekly report + trends + heatmap on a 3-row fixture.
- Wiring (axis-d): `subagent_type: linkedin-studio:analytics-interpreter` (`report.md:305`) resolves —
`agents/analytics-interpreter.md` present with report-mode (`:10,40,295`); one namespaced `Task`.
- Degradation (axis-d): `trends`/`heatmap` "No posts found. Import some data first." exit 1
(`cli.ts:245248,397400`); weekly report try/catch exit 1 (`:191194`); Step 1 import-nudge;
npm-install Step 1b for fresh-clone `ERR_MODULE_NOT_FOUND`.
- saves/dwell honesty (axis-c): `totalSaves` surfaced only when present, labelled "(manual entry — top
engagement signal)" (`cli.ts:144146,342344`; `weekly.ts:172173`); grounded run — FIXTURE B's blank
Saves cell produced **no** saves suffix (unknown≠0); never folded into engagement. **dwell** absent,
explicit "do not fabricate a dwell field" (`types.ts:2628`).
**THE FIX (3 findings, 6 surgical edits, all in `commands/report.md`).**
1. [REWORK · axis-a/d] **Over-promise: `baselines.json` + `metadata.json` infrastructure the code never
had** (the S14 import twin). Step 5c told the model to `cat baselines.json` and flag ">2 standard
deviations above/below mean"; Reference Files listed both files. Grep over `scripts/analytics/src` =
**NONE writes either**; `storage.ts` writes only `posts/<date>-<shortid>.json`,
`weekly-reports/<week>.json`, `monthly-reports/<month>.json`; `ensureDirectories` (`:7790`) makes no
baselines dir; a full import+report cycle in the fixture created **NEITHER**. The real alerts are
`detectAlerts` (intra-week) + `detectWeeklyAlerts` (WoW) surfaced in the report's own `alerts[]`
(`weekly.ts:222238`). Fix: Step 5c rewritten to read `alerts[]` straight from the generated
weekly-report JSON; Reference Files dropped both phantoms, fixed the posts filename to
`YYYY-MM-DD-<shortid>.json`, added the real `monthly-reports/` file.
2. [REWORK · axis-a] **Broken metric flag `engagement_rate` (snake_case, `:172`).** Grounded:
`--metric engagement_rate` → exit 1 "Invalid metric"; the CLI accepts only camelCase `engagementRate`
(`cli.ts:47,202209`). Fix: `:172` snake→camel (the other call, old `:363`, was already correct).
3. [REWORK · axis-a] **Step 4 / Step 6 schema field names that don't exist.** Step 4 claimed JSON fields
`dateRange`, `postCount`, `aggregateMetrics`; the real `WeeklyReport` (`weekly.ts:124149`) top keys
are `week, generatedAt, summary, topPerformers, underperformers, trends, alerts` (no dateRange, no
aggregateMetrics, no top-level postCount — it is `summary.totalPosts`). Fix: Step 4 rewritten to the
real schema; Step 6 template `({dateRange})` dropped, `{postCount}``{summary.totalPosts}`,
eng-rate→`{summary.avgEngagementRate}`; Deep-Dive post-search fixed from the non-existent
`posts/<YYYY-WXX>.json` to iterating the real per-batch `posts/*.json`.
**VERIFY.**
- Re-grepped the final file: `engagement_rate` / `baselines.json` / `metadata.json` / `aggregateMetrics`
/ `({dateRange})` / `{postCount}` / `posts/<YYYY-WXX>.json` all GONE; corrected strings present
(`metric engagementRate` `:171,366`; `get('alerts', [])` `:227`; `posts/YYYY-MM-DD-<shortid>.json`
`:414`; `monthly-reports/YYYY-MM.json` `:416`; Step 4 `summary` schema `:143`; Step 6
`{summary.totalPosts}`/`{summary.avgEngagementRate}` `:271272`); no residual `dateRange`/`postCount`.
- `bash scripts/test-runner.sh``Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit).
- Grounded simulation (throwaway `ANALYTICS_ROOT=/private/tmp/claude-report-fixture`, discarded): import
seeded 3 fake W25 posts → `report --week 2026-W25` printed "Total saves: 10 (manual entry…)", FIXTURE B
(blank saves) showed no suffix, empty alerts (no baseline consulted); `trends --metric engagementRate`
ok, `--metric engagement_rate` exit 1; heatmap rendered.
- Disposition: **FIXED** (6 edits, all in `commands/report.md`) · 0 deferrals · axes a/c/d all PASS
post-fix (b N/A — not feed-emitting).
---