The Movement Athlete · audience data & safety
Built + hardened 7 Aug 2026 · every number measured live that day · canonical sources:
AUDIENCE_INTEGRITY_MASTER_2026-08-07.md (machine) + AUDIENCE_DATA_DICTIONARY_2026-08-07.md (data) — this page is their browsable twin; on conflict the .md wins
The problem: thousands of people download the app, get partway, and stall. The app records where — but that knowledge lived only in Intercom, so ActiveCampaign (where we email from) couldn't use it. Result: ~11,000 warm people we sent nothing to.
The build: every night at 02:30, a job reads Intercom, works out one label per person — where they stopped — and writes it onto their existing ActiveCampaign contact. Now we can email each group the one message that fits, at £0 ad cost.
The hardening (same day): Aga caught a member celebrated as "141 workouts without paying" who was actually a $497 lifetime member since 2022. A payer check on live subscriptions cannot see lifetime (a one-time charge) or PayPal/app-store buyers (no Stripe record). Measured: the 25 heaviest "free" users were 23/25 payers. So this system now carries a four-layer money check and a blocking gate no send can bypass.
/email-fleet and Aga's approval.
Two families, from two journeys. TAG = a yes/no fact (for picking who). FIELD = a value (for writing what). If a tag's name would differ between two people, it must be a field — that law exists because 5,623 junk tags were once minted by values leaking into tag names.
| What | Names | Carries |
|---|---|---|
| 15 tags — how far they got | NEW quiz-email-captured · NEW quiz-questions-done · NEW quiz-results-seen · NEW quiz-reached-checkout · NEW quiz-card-started · NEW quiz-all-fundamentals-mastered · NEW quiz-gave-email · 8 × NEW quiz-path-* | State — segmentable, fixed set. Everything real carries the NEW quiz- prefix; tags without it are legacy junk. |
| 10 fields — who they are | %TMA_QUIZ_PERSONA% (the goal) · %TMA_QUIZ_OBSTACLE% · %TMA_QUIZ_FUTURE_STAKES% (their own words) · age · gender · all-fundamentals-mastered · life-context · background · training-recency · weakest-fundamental | Data — usable in copy as merge tags. |
| Field value | Who they are (clean census) | The natural message |
|---|---|---|
survey_unfinished | signed up in the app, never finished setup (3,276 stamped) | "Finish setup — 2 minutes" |
no_workout | setup done, never finished a workout (5,872 stamped) | "Your 3 free workouts are waiting" |
no_trial | trains on free, genuinely never paid (1,909 stamped post-purge — the raw 2,809 was ~36% contaminated) | "Here's what week 2 looks like" |
| (blank) | payer · checkout-abandoner (abandon-fleet's) · >90d inactive | BY DESIGN — leave them alone |
no_workout (or the other values). Quiz cohorts: Contacts → Tags → search
NEW quiz-.✅ GATE PASS — N contacts, 0 payers, stamps fresh line. No PASS line → don't approve.
That's the whole rule from your side.com.tma.funnelstage%TMA_FUNNEL_STAGE% on each existing contactsegment_guard.py: pipeline health · live segment re-pull · every email vs live Stripe · ever-paid set · freshness · 5 eyeballed members · PASS expires in 24hcom.tma.jobguardian (every 30 min) kickstarts it and alerts (macOS notification + email to
hello@). The cockpit's 06:00 watchdog also runs a funnelstage health check — a sick pipeline shows up
as DRIFT in the daily cockpit email without anyone asking.No send, no bulk tag/field write (>10 contacts), no automation entry-trigger
— from any fleet — without the officer's PASS. Wired as a non-skippable step into
/email-fleet (Step 1.4c) and /abandon-fleet, carried by 9 send-arming seats, and
bound in the project rules every session reads.
The five checks: ① pipeline healthy ② the audience pulled live from AC (never a doc count) ③ every email against live Stripe and the ever-paid set (lifetime is a charge; the AC lifetime tags catch PayPal/store buyers with no Stripe record) ④ labels fresh (≥95% under 48h) ⑤ five real members eyeballed against the copy's premise.
last_seen_at only.# Is the pipeline healthy? (HEALTHY / SICK with reasons) python3 tools/retention/funnel_stage_export.py --health # Full read-only census — the real counts, freshness, samples python3 tools/retention/funnel_stage_export.py --dry-run # Gate an audience before ANY send (exit 0 = the only PASS) python3 tools/retention/segment_guard.py --stage no_workout python3 tools/retention/segment_guard.py --tag "NEW quiz-results-seen" # Rebuild the ever-paid exclusion set (required <48h before any apply) python3 tools/retention/ever_paid_build.py # Quiz signals census + tag-leak tripwire python3 tools/retention/quiz_lead_signal_check.py --sample 2