Back Office · office.temerarii.xyz
REPORTING-SPEC.md

← all docs

Reporting System — Cross-Channel Performance Spec

Derived from the operator's reference report (~/Downloads/Example of Reporting* — a 6-section

visual dashboard; images decoded to .local_media/report_ref/image1..6.png). This is the design

the Office Reports tab grows toward. v1 (GA4 + GSC, the two live sources) ships first; this

spec is the full target the connectors + comparison engine fill in as channels light up.

Governing rule: the framework ships now with HONEST empty-states; numbers populate as (a) UTM

is stamped at publish, (b) channel keys/auth land, (c) launch traffic flows. **Never fabricate a

number.** A zero with "awaiting launch / connect <X>" is correct; a made-up number is a defect.


The reference design (what "track metrics across channels" means)

Six sections. The signature pattern is identical in all of them: **every metric is a comparison

(YoY / WoW / TY-vs-LY) shown as a big number + a colored delta arrow (↗ green up / ↘ red down),

never a bare number — and every channel rolls up to REVENUE.**

  1. Website KPIs — Conversion Rate · AOV · Traffic · Revenue/Session, each big-number + YoY delta.
  2. Traffic Channel Breakdown YoY — table: every channel (Paid · Email · SMS · Direct · Tapcart

[their e-comm app] · Organic Search · Social · Unclassified) × **Sessions TY/LY/YoY% AND

Revenue TY/LY/YoY%**, with a Total row.

  1. Site Speed — YoY + WoW big numbers + a daily grouped bar (this week / 4 weeks ago / 1 year ago).
  2. Email Performance — Recipients · Subscribers · Collected · Open Rate · Click Rate · Unsub Rate

· Last-Touch Revenue · Revenue-per-Send, each TY/LY/YoY% and LW/WoW%; + **Email Revenue by

Type** (Campaign vs Flow).

  1. SMS Performance — New Subs · Total Subs · Last-Touch Revenue · CTR · CVR · **Platform ROAS ·

Spend**, TW/LY/YoY%.

  1. Social (platform-native) — per-platform visits/profile-visits (Facebook, Instagram, …) as a

trend sparkline + delta. Pulled from the platforms, NOT GA4.

For a SERVICES business (not e-comm): "revenue/AOV/Rev-per-Session" map to **Stripe deal value +

Cal.com bookings**, not cart AOV. "Tapcart" has no analog → drop it; our channel list is the 9 social

+ Email + SMS + Direct + Organic + Referral + (later) Paid.


Architecture — the unified metrics layer

Mirror of how content-index.json is the content spine: ONE accumulating metrics file is the spine;

every surface bakes from it; connectors write slices; the dashboard never calls an API per render.

1. The snapshot store — content/_generated/metrics-snapshot.json (APPEND-ONLY by date)

Must accumulate history so WoW/YoY are computable (you can't compare to last week if you overwrote it).

Schema = a list of rows:

{ "date": "2026-06-08", "channel": "instagram", "campaign": "studio-launch",
  "cell": "W23-studio-launch-beat-1", "metric": "impressions", "value": 1234 }

Keys: date (day) · channel (ga4|gsc|email|sms|<9 social>|stripe|calcom|pagespeed) · campaign ·

cell (UTM manualAdContent) · metric · value. Connectors upsert by (date,channel,campaign,cell,metric).

Roll-ups (WoW/YoY, per-channel, per-funnel-stage) are computed at bake time from this one file.

2. Connectors — engine/integrations/metrics/<name>.py, each gated, each graceful-empty

ConnectorSourceMetricsStatus / gate
ga4.pyGA4 Data API (token live)sessions, users, channelGroup, hostName, pagePath, conversions+revenue (if events configured), CWV✅ token — wire conversions/revenue once GA4 events exist
gsc.pySearch Console (token live)clicks, impressions, CTR, position, top query/page✅ token
resend.pyResend API (key present)sent, delivered, open, click, unsub, bounce → open/click rate, rev-per-send (via UTM join)key present, pull unwired
twilio.pyTwilio APIsent, delivered, link-clicks → CTR/CVR/ROAS/spendneeds creds
stripe.pyStripe MCP/APIrevenue, AOV (deal value), new customers, MRRStripe MCP available — the money layer
calcom.pyCal.com APIbookings/consults (the services "conversion")needs key
social.pyMeta Graph (FB/IG), YouTube Analytics, LinkedIn, X, TikTok, Pinterest, Bluesky — OR Blotato analytics if exposedimpressions, views, engagement, follows, profile-visitsbiggest gap — none wired; check Blotato analytics read first
pagespeed.pyPageSpeed Insights / CrUX (free, no key)site speed / Core Web Vitals, dailyfree — wire early (the "Site Speed" section)

Each connector: pull(date_range) -> list[row]; missing key → returns [] (the dashboard renders the

honest empty-state). A nightly/build-time scripts/pull_metrics.py runs all available connectors,

upserts into the snapshot. Office bakes from the snapshot (no live API at render → fast, deterministic).

3. The comparison engine — engine/lib/metrics_report.py

Pure functions over the snapshot: kpi(metric, channel?, period) → `{value, prev, yoy_pct, wow_pct,

dir}; channel_table(metrics, period) → the §2 grid; delta_arrow(pct)` → the colored ↗/↘ render.

GA4/GSC can compute YoY/WoW NOW via dual date-range queries even before history accrues; other

channels compute from the accumulating snapshot.

4. The UTM prerequisite (BLOCKS the revenue half)

engine/lib/utm.py is wired but NOT invoked in distribute.py/publish_run.py. Until every

published link carries utm_source/medium/campaign/content(=cell), the channel→campaign→cell→revenue

join (the right half of the reference) can't populate. → stamp UTM at publish (a prerequisite task).

5. The funnel rollup — funnel.yaml (Reach → Capture → Convert)

Map each metric into a stage so the report answers "did the calendar move the needle," end-to-end:

  • Reach = social impressions/views + YouTube views + GSC impressions.
  • Capture = GA4 sessions + email signups + content downloads + follows.
  • Convert = Cal.com bookings + Stripe revenue + (CRM) pipeline.

Each stage rolls per-campaign + per-week, joined on UTM — the scorecard, not a traffic mirror.

6. Targets — gtm.yaml per-quarter kpi_emphasis

Render KPIs as actual-vs-target (the per-quarter operational goals already in gtm.yaml), so the

dashboard is a scorecard with green/red against plan, not just a mirror.


Office surfaces

  • /reports (hub) — Website KPI cards (delta-arrow) + the channel-breakdown table (sessions+revenue,

YoY) + the per-week PLAN-vs-RESULT calendar correlation + links to the per-site sub-pages.

  • /reports/xyz, /reports/com — full per-site dashboards (KPIs · acquisition · hostName subdomain

transparency · top pages · GSC queries/pages · site speed).

  • (as connectors land) per-channel deep sections: /reports/email, /reports/sms, /reports/social

— each mirroring the reference's dedicated section.

Build order

  1. v1 (in flight): GA4 + GSC dashboards, hub + 2 sub-pages, calendar correlation. ← background agent.
  2. Design pass (integration): adopt the reference visual language — delta-arrow KPI cards w/ YoY+WoW

(GA4/GSC dual-range), the channel table w/ Sessions+Revenue comparison columns (revenue cols render

"awaiting conversion tracking" until UTM+Stripe land), the per-channel section scaffold w/ honest

empty-states.

  1. Snapshot store + comparison engine (metrics-snapshot.json + metrics_report.py).
  2. Free/keyed connectors now: pagespeed.py (free), stripe.py (MCP), resend.py (key) →

populate Site-Speed + revenue + Email sections.

  1. UTM-at-publish (prerequisite) → unlocks the revenue/channel join.
  2. Social + Twilio + Cal.com connectors (keys/auth) → the remaining reference sections.
  3. Funnel rollup + targets → the scorecard.

Verification

v1 renders (real .com numbers + honest .xyz empty-state). Each connector: graceful-empty without its

key; with its key, writes dated rows to the snapshot; the comparison engine renders the delta arrow.

No secret/token ever written into apps/office/public/. Every empty cell carries a "connect <X> /

awaiting launch" reason — zero fabricated numbers.