`marketing/metrics/weekly.md` has been a placeholder since the marketing tree landed — it promised a funnel and the one thing that moved, and nothing produced it. This adds the producer: one command reads the live databases and rewrites the file. `scripts/weekly-metrics.ts` writes `marketing/metrics/weekly.md` from `VisitorAnalytics` (visits, downloads, campaigns) and the-nook's `trials`, `licenses`, `activations` (the paid funnel). It needs four env vars — TURSO_DB_URL/TOKEN and NOOK_DB_URL/TOKEN — and imports no server env, so the job runs from a checkout that has no Stripe or Apple secrets. `src/server/weekly-metrics.ts` holds the queries and the renderer, taking both connections as arguments; the tests run them against an in-memory SQLite shaped like production, the same way the backfill suite does. The campaign breakdown groups exactly as `getAnalyticsSummary.topCampaigns` does (top 10, untagged excluded), so the weekly read and /analytics cannot drift into different label sets. The first real report says more about the instrumentation than about demand, and the renderer computes that rather than asserting it: - every addressed row the table has ever held (12,035 of 12,048) is loopback, so "unique visitors" is one host and per-channel reach cannot be read; - the landing door and the file route both write `/api/downloads/thenook`, so "download click" and "DMG served" are one label — one served click writes two rows, a failed click one, a Sparkle update one; - `POST /api/the-nook/checkout` records nothing, so checkout start is only a page-view proxy; - `VisitorAnalytics` has no host dimension, so the Nook's `/` is freno.me's `/`. Those three gaps are reported beside the numbers, not papered over. Week 0 (2026-10-22 → 2026-10-29): visit 61, download 156, trial 2, checkout 0, purchase 0, activation 0 — and the one thing that moved is the download stage, 0 → 156, because the label was introduced that week. Tests: `bun test src/server/weekly-metrics.test.ts` — 12 pass, 0 fail. The full suite's 15 failures are pre-existing: this checkout's .env lacks NOOK_LICENSE_PRIVATE_KEY, NOOK_STRIPE_SK/WEBHOOK/PRICE and LINEAGE_JWT_SECRET, and every failure is that env validation. Scripts/ is gitignored, so `scripts/weekly-metrics.ts` was added with `-f`, the way the existing tracked scripts are. FRE-202
34 lines
1.5 KiB
Markdown
34 lines
1.5 KiB
Markdown
# Marketing tree
|
|
|
|
One voice, one place. Every later artifact draws on `positioning.md` — do not
|
|
invent framing per-asset.
|
|
|
|
```
|
|
positioning.md # source of truth: wedge, voice, banned phrasings, approved claims
|
|
calendar.json # scheduled pushes: date, channel, status
|
|
briefs/<channel>.md # per-channel rules, format, limits, what "good" looks like
|
|
drafts/<id>/ # agent output: copy.md, assets/, rationale.md
|
|
pending/ approved/ posted/ # human gate by directory move
|
|
metrics/weekly.md # funnel snapshot, generated
|
|
listening.md # buying-intent threads + drafted replies
|
|
```
|
|
|
|
## Hard rule
|
|
|
|
**`approved/` is the only thing a human ever publishes from. Nothing posts itself.**
|
|
|
|
Workflow: agent drafts into `drafts/<id>/` → moves to `pending/` when complete →
|
|
a human reviews and moves it to `approved/` → the human publishes from `approved/`
|
|
under their own account → the artifact (or a pointer) lands in `posted/`.
|
|
|
|
Nothing in `drafts/` or `pending/` is ever publishable, by anyone, by any means.
|
|
|
|
## The weekly read
|
|
|
|
`bun --env-file=.env scripts/weekly-metrics.ts` (or `bun run metrics:weekly`)
|
|
rewrites `metrics/weekly.md` from the live databases: the staged funnel, the
|
|
campaign breakdown (`getAnalyticsSummary.topCampaigns`' grouping), and the one
|
|
stage that moved. It needs `TURSO_DB_URL`, `TURSO_DB_TOKEN`, `NOOK_DB_URL` and
|
|
`NOOK_DB_TOKEN` — no other production secret. The file is generated; never
|
|
hand-edit it. The numbers are engineering's; the interpretation is the CMO's.
|