Share:

Studio 1 — the report estate

Everything known about all 166 reports: how they run, what each one answers, the definitions that must be settled before counting, the traps that have produced wrong answers, and what is still open. Written 15 September 2026.

The inventory tab is generated from reports_manifest.json at build time, so it cannot drift from what actually runs. Everything else is authored and dated.

Reports in the manifest
166
the only registration point
Run every night
119
the rest are on-demand
Built by Render
49
the laptop SKIPS these
Built by the laptop
117
reach live ONLY via git push or Supabase
On the dashboard
38
a card block in the manifest entry
Paths in serve_reports.js
441
reports, APIs, Iconic, Kelly, war room

Eight things that have each caused a wrong answer or a dead site. Read them before touching anything.

1

The manifest is the estate.

reports_manifest.json is the ONLY registration point. A report that is not in it is a bug — if you find one, add it. Never add a script to run_all.bat directly. A new report gets its manifest entry, and its dashboard card block, in the SAME commit that creates it.

2

owner is not bookkeeping — it stops double-running.

owner:"render" means Render's nightly cron already builds it and the laptop runner SKIPS it. On 17 Jul 2026 the laptop ran the outreach push alongside Render's copy and pushed every Boston and Chicago sale twice. If you move a script into Render's DAILY_SCRIPTS, flip its manifest owner in the same commit, and vice versa.

3

Render's disk is ephemeral. Every push is a deploy, and every deploy wipes it.

A generated HTML built on Render survives until the next deploy and no longer. Two escapes: park the output in Supabase (most laptop-built pages do this), or commit a file that is genuinely static. NEVER commit a nightly-generated report — the next deploy reverts the live copy to what is in git.

4

owner:"laptop" reaches live ONLY via git push or a Supabase park.

On 3 Aug 2026 the live site was serving HTML from as far back as 10 March because nothing had been committed. A file mtime on this machine is not evidence of anything being live.

5

Nothing rebuilds on open. You warm views yourself.

Cached reports serve the LAST-BUILT copy instantly — Neil's rule, never a "Building…" spinner. So after ANY change to a generator you must rebuild every visible view yourself: the default landing, every cached month, and every date range opened this session. refresh=1 re-pulls the DATA; rebuild=1 only re-renders what is already cached and will silently keep stale numbers.

6

Never build a heavy report from a web request.

A CRM pull or a big lead load spawned from an HTTP request OOM-crashes the whole shared Render instance. That is what 502'd /lead-groups. And never require a report script from serve_reports.js: page.write() calls toolbar.inject(), which fetches its own live URL, so the box fetches itself in a loop. That took the site down via /debt-plan on 20 Aug 2026.

7

A 200 is the gate. The grepped data row is the proof.

Never call a report fresh off a build stamp, a "Last checked" line, an HTTP 200, a green push or a file mtime. Grep the newest real data row and paste the output. Two traps: build time ≠ latest data day, and some reports end at YESTERDAY by design (/rejected-leads is a rolling 7 COMPLETE days and never shows today).

8

Another session is often live in this repo.

Studio1-Automation frequently has a second Claude session running. It commits serve_reports.js wholesale and will sweep uncommitted work into its commit. Check git status and git log origin/master..master before you push. The local serve_reports.js has diverged from origin before — rebase, never force.

Two runners, two owners, one manifest. Neil's rule: everything runs at 7am and if he needs an update he will ask for one — never run the daily reports at session start.

WhenWhatDetail
07:00 UKRender cron — DAILY_SCRIPTS in serve_reports.jsEvery owner:"render" report, in a deliberate order: zombie-accounts FIRST (writes the id file the collections summary reads), lead-dupes EARLY (writes temp/dupes_cache that four other reports read), square-declines BEFORE the GHL push and the chase list, reductions BEFORE reduction-recovery BEFORE reduction-groups, outreach-pipeline BEFORE outreach-reconcile.
07:00 localWindows Task Scheduler → run_all.batscripts/run_daily_reports.jsEvery daily:true + owner:"laptop" manifest entry, in manifest order. One failure never stops the rest. Ordering inside the manifest IS the dependency graph — finance-cohort-ledger before finance-cohort, issues before complaints and complaint-log, square-attempts-history before every decline report, contracts-pull before contracts, crm-leads-by-code before cpl-history, dashboard LAST.
07:30 / 07:40 / 09:00 / every 30 minFurther crons in serve_reports.jsIconic module rebuilds, contract-signed polling and the failsafe re-run.
08:00 UKrunElenaDailyReportsElena's collections and deposit reports — a separate path from the manifest runner.
LogsReports/logs/daily_reports_YYYY-MM-DD.logPer-report OK/FAIL, duration and total runtime. The _output.log twin holds full stdout/stderr. READ THIS FIRST when Neil says a report is stale — diagnose before re-running anything, then re-run only what failed with --only=<slug>.
Known faultThe 08:00 laptop run fails ~15 of 20 reports on DNSStudio1_WakeUp only allows 5 minutes for Wi-Fi to come up. The 09:00 failsafe is what actually produces the reports.

Useful while working

node scripts/run_daily_reports.js --dry-run lists what would run · --only=slug runs one · --all ignores daily:false. A manifest entry whose script no longer exists is reported as FAIL up front rather than failing silently.

Every entry in the manifest, grouped by what it is for. Route, cadence, owner and the gate come straight out of reports_manifest.json. The full operational note for any report — its dependencies, its traps, why its owner is what it is — lives in that entry's note field, which is the single most useful thing to read before editing a report.

SlugRouteCadenceBuilt byWhat it answersScript
Money in 16
daily-reports /daily-reports nightly render Per-city Square daily reports → HTML + Google Drive upload. run_daily.js
collections /collections nightly render The all-city collections summary — the 7am flagship. scripts/reports/report_summary.js
sales /sales nightly render Daily sales totals per city, off Square. scripts/reports/report_sales_totals.js
big-sales /big-sales nightly render Every sale over a $ threshold, windowed. Key-gated. scripts/reports/report_big_sales.js
sales-contacts /sales-contacts nightly laptop Trailing-12-month contact list of sales over $600 — name/email/phone/date/platform, no money column. scripts/reports/report_sales_contacts.js
hv-buyers /hv-buyers on demand render High-value buyer contact list (email+phone) for Google Customer Match. On-demand, 3-feed 18-month join. scripts/reports/report_hv_buyers.js
square-daily-merge /square-daily-merge nightly render Merges the per-city Square daily files into one view. scripts/reports/square_daily_merge.js
chargebacks-refunds /chargebacks-refunds nightly render All-cities chargebacks and refunds, group-level monthly trend. scripts/reports/report_chargebacks_refunds.js
elena-collections /elena-collections nightly render Elena's daily collections report — built by the separate 8:00 AM UK cron. scripts/reports/elena_collections_report.js
elena-deposit /elena-deposit nightly render Elena's daily deposit report — same 8:00 AM UK cron. scripts/reports/elena_deposit_report.js
deposit-analysis /deposit-analysis nightly laptop What customers put down at the point of sale. scripts/reports/report_deposit_analysis.js
sales-history /sales-history nightly laptop Monthly sales history. scripts/reports/report_sales_monthly_history.js
collections-city /collections-city on demand laptop 7-day collections per city. scripts/reports/report_collections.js
cash-summary /cash-summary key nightly laptop Cash banked each month by city — a CASH PERIOD, not a cohort. Plus average sale and average up-front. scripts/reports/report_cash_summary.js
total-revenue /total-revenue key nightly laptop Every sale, financed or paid in full, against total revenue booked: banked day 1, finance collected, written off, still owed. scripts/reports/report_total_revenue.js
collections-monthly /collections-monthly on demand laptop Every month since Jan 2025: gross collected, success rate, by city AND by collector. The collector is the Square team-member NAME; automation is decided by BEHAVIOUR (20+ payments a minute on 10+ days), not by a name list. scripts/reports/report_collections_monthly.js
Finance book 15
zombie-accounts /zombie-accounts nightly render Recurring accounts still being billed that stopped paying. Runs FIRST — writes zombie_customer_ids.json for the collections summary. scripts/reports/report_zombie_accounts.js
zombie-reasons /zombie-reasons?key=studio1-sales-2026 key nightly laptop The 4+ consecutive-miss pile: WHY each is failing (issuer code dated to that run) and what switching them off would take. Square CANNOT end a recurring series via API — a shut-off is a nightly sweep. scripts/reports/report_zombie_reasons.js
first-payments /first-payments?key=studio1-sales-2026 key nightly laptop What we take when a Square customer account opens, across three matched calendar windows a year apart with an EQUAL 12-month lookback on all three. scripts/reports/report_first_payments.js
opening-payment /opening-payment?key=studio1-sales-2026 key nightly laptop The three outcomes of an agreement's FIRST instalment — missed and never recovered / missed but banked in 10 days / paid first time — valued at 30→365 days, age-matched. scripts/reports/report_opening_payment.js
instalment-recovery /instalment-recovery?key=studio1-sales-2026 key nightly laptop What a missed FIRST or SECOND instalment costs over the life of an agreement, and how much of a missed first comes back inside ten days. Everything age-matched. scripts/reports/report_instalment_recovery.js
reductions /reductions key nightly render Every credit posted against a finance agreement (discount / package reduction / write-off / Monterey settlement). The CRM has NO reductions report — this builds one. scripts/reports/report_reductions.js
reduction-recovery /reduction-recovery key nightly render After an agreement is reduced, how much do we actually collect? Answer: $0.12 back per $1. scripts/reports/report_reduction_recovery.js
reduction-groups /reduction-groups key nightly render Every reduced account grouped by WHY it was not paying — the issuer decline reason before the reduction. scripts/reports/report_reduction_groups.js
payment-uploads /payment-uploads nightly render Pre-builds CAET's CRM Diary upload CSV — Recurring only, completed payments that local day, split at 100 rows. scripts/reports/report_payment_uploads.js
first3 /first3 nightly laptop The first three instalments of a new recurring account. The cache FREEZES statuses at pull time — audit P1+P2+P3, not just P1. scripts/reports/report_first3_payments.js
missed-payments /missed-payments nightly laptop Missed instalments. "Miss #" is the invoice SEQUENCE number, not the miss count — wrong 29 times in 41. scripts/reports/report_missed_payments.js
model-portal /model-portal key nightly laptop usa-models.com portal export joined to the sales ledger: who never opens their site vs their payment cycle. 37% never log in; they pay 45% of instalments vs 66%. scripts/reports/report_model_portal.js
finance-cohort /finance-cohort key nightly laptop Per MONTH OF SALES: finance written, collected, written off, outstanding. A COHORT, never a cash period. scripts/reports/report_finance_cohort.js
sales-payments /sales-payments. nightly laptop Every finance agreement and how many payments actually landed against it. Never sum plannedPayments. scripts/reports/report_sales_payments.js
note-finance /note-finance. nightly laptop Affirm/Afterpay/Klarna as typed into the CRM booking notes — the only place third-party finance is recorded. Square settles Affirm as a plain Visa. scripts/reports/report_note_finance.js
Declines & cards 14
declines /declines nightly render Declines from INVOICES + the GHL trigger CSV. Cannot carry an issuer reason — that is /square-declines. scripts/reports/report_declines.js
square-declines /square-declines nightly render Every declined Square charge WITH the issuer's reason code, from /v2/payments. Feeds the GHL push and the per-day chase files. scripts/reports/report_square_declines.js
payment-test /payment-test key nightly laptop 33 of the last two complete days' failed charges in three behaviour groups (first decline ever / regular decliner / paid six months then stopped) for testing recovery messages. scripts/reports/report_payment_test_groups.js
card-types /card-types key nightly laptop Debit vs credit vs unknown across all 6 Square accounts, crossed with brand, entry method, city and month — plus the decline rate of every cut. scripts/reports/report_card_types.js
decline-supply /decline-supply?key=studio1-sales-2026 key nightly laptop Every failed charge per day against what actually reached the hand-over list — so a genuine month-end lull is never mistaken for a broken pull. scripts/reports/report_decline_supply.js
decline-trends /decline-trends?key=studio1-sales-2026 key nightly laptop Every recurring decline since Jan 2026 bucketed six ways month by month, plus every issuer we charge 300+ times, worst first. scripts/reports/report_decline_trends.js
due-next-7 /due-next-7?key=studio1-sales-2026 key nightly laptop THE FORWARD LIST: every instalment Square is scheduled to take in the next 7 days, with BIN, issuing bank, arrears and full paid/failed history. ~20,000 per-customer invoice searches, ~20 min. scripts/reports/report_due_next7.js
retry-list /retry-list?key=studio1-sales-2026 key nightly laptop Six lists to re-charge, SAME AMOUNT, no step-down. Soft declines only. THE FIRST AUTOMATED RE-CHARGE THE BUSINESS HAS EVER RUN — carries no expected rate and holds a 10% control slice. scripts/reports/report_retry_list.js
signup-buckets /signup-buckets?key=studio1-sales-2026 key nightly laptop 12 buckets: card issuer class × deposit put down. Carries the fitted logistic weights — issuer class, deposit %, term and deposit cash survive; day of week, day of month, instalment size and city do NOT. scripts/reports/report_signup_buckets.js
payer-types /payer-types?key=studio1-sales-2026 key nightly laptop Everyone with an instalment due this week bucketed by their COMPLETE charge history — never paid / under 80% / 80%+ — crossed against who issued the card. scripts/reports/report_payer_types.js
missed-chase /missed-chase?key=studio1-sales-2026 key nightly laptop THE RECOVERY LIST: every finance customer who missed an instalment and was never handed over, because the hand-over ledger has no time limit. scripts/reports/report_missed_chase.js
decline-buckets /decline-buckets?key=studio1-sales-2026 key nightly laptop Every failed charge of the last 3 complete months as structural / hard / soft, with what we attempted afterwards and whether it worked. scripts/reports/report_decline_buckets.js
card-issuers /card-issuers?key=studio1-sales-2026 key nightly laptop Which banks the recurring book sits on, resolved from the card BIN, with the neobank/early-access sponsors split out. Uses an OFFLINE BIN file — nothing about the book leaves the machine. scripts/reports/report_card_issuers.js
chase-daily /chase-daily key nightly render One CSV per day for the GHL hand-over, holding only people never handed over before. The Supabase ledger behind it is the ONE file that cannot be rebuilt from anything else. scripts/reports/report_chase_daily.js
Debt team (GHL) 6
debt-trend /debt-trend. open nightly laptop Are misses rising and does the chase still convert? Miss RATE against the size of the book, cure rates at 7 and 30 days (maturity-gated), and the text reply rate by week. scripts/reports/report_debt_trend.js
debt-plan /debt-plan. nightly laptop The collections plan written 20 Aug 2026 and the evidence under each line — including the four ideas that were ruled out and why. STATIC by design. scripts/reports/report_debt_plan.js
debt-workload /debt-workload. open nightly laptop Who on the debt team is actually getting the work. Built after the collectors said they had less work while the account looked flat — both were true. scripts/reports/report_debt_workload.js
debt-unassigned /debt-unassigned?key=studio1-sales-2026 SALES_KEY nightly laptop The ownerless cards, crossed against payments: who can be crossed off and who can be redistributed. Joins on GHL companyName, which holds the ORDER REFERENCE. scripts/reports/report_debt_unassigned.js
debt-handout /debt-handout?key=studio1-sales-2026 SALES_KEY nightly laptop One board (Unassigned) stripped of PIF / unjoinable / already-collected / already-owned, split evenly. That board is where NON-RESPONDERS collect, so it is a CALLING list. scripts/reports/report_debt_handout.js
debt-status /debt-status?key=studio1-sales-2026 SALES_KEY nightly laptop The WIDE view of the whole US Debt Collection sub-account. Every open card collapsed to one row per ACCOUNT with three independent states: money, contact, owner. scripts/reports/report_debt_status.js
Leads — volume 18
live-leads-blue-rooms /live-leads-blue-rooms on demand render Blue Rooms-only Live Leads funnel. scripts/reports/report_live_leads.js
lead-funnel /lead-funnel on demand laptop Neil's order, once only: leads in − OOA − dupes ≤15d − removed − rejected = usable. scripts/reports/report_lead_funnel.js
lead-funnel-blue-rooms /lead-funnel/blue-rooms on demand laptop Blue Rooms-only Lead Funnel. scripts/reports/report_lead_funnel.js
lead-groups /lead-groups nightly render Every marketing code rolled into its group per calendar month, with a before-vs-after PMax table. Both default views built nightly on Render. scripts/reports/report_lead_groups.js
crm-leads /crm-leads nightly laptop Live CRM pull of leads and sales. scripts/reports/report_crm_leads.js
lead-performance /lead-performance nightly laptop Per-code lead performance (merged into the Master Lead Sheet engine). scripts/reports/report_lead_performance.js
lead-vintage /lead-vintage nightly laptop How old the leads being worked actually are. scripts/reports/report_lead_vintage.js
lead-volume /lead-volume nightly render Leads got → delivered → bookers → left, fresh vs recycled. ONE CRM pull produces this AND /master-lead-sheet. scripts/reports/report_lead_flow.js
fresh-leads /fresh-leads nightly laptop Non-duplicate leads per week (Fri–Thu) plus daily. scripts/reports/report_fresh_leads.js
lead-intake /lead-intake nightly laptop One live CRM pull over the last 10 complete days — no cache. Kids = KIDSX+KX+KPX+KDX, never includes("KIDS"). scripts/reports/report_lead_intake.js
leads-vs-bookings /leads-vs-bookings nightly laptop Bookings against leads. The finding: bookings are not down, booking DAYS are — use per working day. scripts/reports/report_lead_booking_compare.js
live-leads-daily /live-leads-daily nightly render The daily-safe Live Leads build: settled days from frozen history + the current month live. scripts/reports/report_live_leads_daily.js
snapshot /snapshot nightly render Lead Command Center — leads in, clean-up funnel, CPL, conversion, leads left/due, all from the on-disk dupes cache. scripts/reports/report_snapshot.js
live-leads-rebuild /live-leads-rebuild on demand laptop The whole-history rebuild from Jan 2025 — OOM risk, writes the SAME file as live-leads-daily. Manual only. scripts/reports/report_live_leads.js
code-share /code-share on demand laptop Fresh ordered leads by city, Fri–Thu weekly, each code as % of its city. Blue Rooms / Alan / Lead Pronto / Boost only. scripts/reports/report_code_share.js
lead-engine /lead-engine on demand laptop Static 25 Aug 2026 snapshot of the Studio Lead Engine audit. scripts/reports/report_lead_engine_audit.js
kids-volume /kids-volume. open nightly laptop Kids leads from META alone (#…KIDSX) per area per week against the 400-per-area target. Shortfall is summed over DEFICIT areas only — surplus is never netted off. scripts/reports/report_kids_volume.js
sle-codes /sle-codes on demand laptop The 20 Studio Lead Engine GHL sub-accounts: what the first message asks for (a YES → 6AB, a PHOTO → N1N) against the code the lead became. YES wins 2:1 at equal lead age. scripts/reports/report_sle_codes.js
Leads — quality 19
lead-dupes /lead-dupes nightly render How many of a month's leads are people already in the CRM. Runs EARLY — writes temp/dupes_cache, which snapshot, dupe-insights, fresh-leads and live-leads all read. scripts/reports/report_lead_dupes.js
repeat15 /repeat15?key=studio1-dnc-2026 key on demand laptop The nightly repeatleads_15.csv email attachments matched person-by-person against booked / unsubscribed / rejected / removed, carrying the three dates Neil asked for. scripts/reports/report_repeat15.js
rejected-leads /rejected-leads nightly render Lead quality / rejections. A rolling 7 COMPLETE days — it never shows today. scripts/reports/report_rejected_leads_html.js
out-of-area /out-of-area nightly render Out-of-area leads, multi-tab, periods + supplier tabs. scripts/reports/report_ooa_copy.js
supplier-ooa /supplier-ooa nightly render The give-able per-supplier OOA pages — one generator emits the combined page and the three supplier copies from a config list. scripts/reports/report_alan_ooa.js
removed-leads /removed-leads nightly render Every CRM lead removal — who, which supplier, why. A COHORT report: % = share of LEADS BROUGHT IN, keyed on lead-in date. scripts/reports/report_removed_leads.js
do-not-call /do-not-call nightly render The DNC list. scripts/reports/report_do_not_call.js
unsubscribed /unsubscribed?key=studio1-feed-2026 nightly render The CRM's Unsubscribed List as a readable report — THE suppression list, 251k people. scripts/reports/report_unsubscribed.js
no-contact /no-contact?key=studio1-feed-2026 nightly render Every no-contact group in one list — booked, rejected, removed, DND, unsubscribed — joined on phone/email. scripts/reports/report_no_contact.js
repeat-block-check /repeat-block-check on demand laptop Cross-checks an UPLOADED supplier repeat file against the Blocked/DND registry. scripts/reports/report_repeat_block_check.js
removed-leads-blue-rooms /removed-leads-blue-rooms on demand laptop Blue Rooms-only Removed Leads. scripts/reports/report_removed_leads.js
pmax /pmax nightly render GX/KX (Google PMax) per-city leads got / removed / estimated cost over the last 5 worked days. scripts/reports/report_pmax.js
pmax-removed /pmax-removed key on demand render Every PMax lead removed as Inappropriate since PMax started. ~78% of bad leads are PMax. scripts/reports/report_pmax_removed.js
scrub /scrub nightly render The daily scrub/suppression list handed to lead suppliers. scripts/reports/report_scrub.js
suppression-list /suppression-list nightly render Rolling 15-day de-duped list of recent enquirers — give-able so suppliers stop sending dupes. scripts/reports/report_suppression_list.js
dupe-insights /dupe-insights nightly render Which duplicate / re-submitted / rejected leads actually convert, plus a permanent-fail suppression list. Exposed the 28% rejected/removed re-appearance leak. scripts/reports/report_dupe_insights.js
lead-outcomes /lead-outcomes?key=studio1-outcomes-2026 nightly laptop Daily unsubscribed / booked / rejected counts. The CRM unsubscribed export carries NO marketing code — city/supplier comes from a join on email. scripts/reports/report_lead_outcomes.js
ooa-ziptarget /ooa-ziptarget. nightly render Old rule (200 miles) vs the canonical ZIP-target rule, side by side. scripts/reports/report_ooa_ziptarget.js
ooa-compare /ooa-compare on demand laptop 200-mile vs 4-hour drive-time rule comparison. Geocodes at volume. scripts/reports/report_ooa_drivetime_compare.js
Leads — cost & ROI 19
roi /roi nightly render Daily ROI. scripts/reports/report_roi_daily.js
roi-trend /roi-trend nightly render ROI trend. scripts/reports/report_roi_trend.js
roi-weekly /roi-weekly nightly render Weekly ROI. scripts/reports/report_roi_weekly.js
roi-suppliers /roi-suppliers nightly render Per-supplier give-able conversion reports. scripts/reports/report_roi_supplier.js
google-outcomes /google-outcomes own key on demand render Per-lead outcome export for the Blue Rooms codes so results can be fed back to the ad accounts. HANDED TO BLUE ROOMS — has its own give-able key, never SALES_KEY. scripts/reports/report_google_outcomes.js
lead-ordering /lead-ordering nightly laptop Best leads to order — per-city tabs, every marketing code, 8-week economics. scripts/reports/report_lead_ordering.js
cpl-history /cpl-history nightly laptop 24 months of cost per lead — $8.19 blended, but $10.53 → $6.42 → $10.63 across them. Per FRESH lead it is $12.09. Quote the period, never the average. scripts/reports/report_cpl_history.js
kidsx-roi /kidsx-roi nightly laptop KIDSX ROI. scripts/reports/report_kidsx_roi.js
outreach /outreach nightly laptop Outreach tracker — full payment + invoice history for 3 cities. Isolated in its own child process so an OOM kills only this report. scripts/reports/report_outreach_tracker.js
outreach-impact /outreach-impact nightly laptop Does outreach lift payments? After ~4 months: NO lift on 1st/2nd payment vs a tenure-matched control — but roughly HALF the chargebacks. scripts/reports/report_outreach_impact.js
meta-spend /meta-spend on demand laptop Reconciles Alan's Meta invoices against his lead sheet. Hardcoded May-26 invoice set. scripts/reports/report_meta_spend.js
meta-ads /meta-ads on demand laptop Meta Marketing API pull, date-range parameterised. scripts/reports/report_meta_ads.js
spend-vs-cash /spend-vs-cash nightly laptop Ad spend vs booked revenue vs cash taken up front, month on month, with a per-day spend-coverage model. scripts/reports/report_spend_revenue_cash.js
spend-income /spend-income on demand laptop Ad spend vs income by three lead groups (Kids / Recycled / everything else). scripts/reports/report_spend_income.js
blue-rooms-cpl /blue-rooms-cpl on demand laptop Blue Rooms CPL. Depends on a JSON Neil pulls by hand (the USA Models session is IP-bound). scripts/reports/report_bluerooms_cpl.js
cpl-trend /cpl-trend nightly laptop Weekly per-code CPL trend, Alan vs Blue Rooms. Re-renders whatever is committed — both feeds are manual pulls. scripts/reports/report_supplier_cpl_trend.js
roi-combined /roi-combined on demand laptop Also imported as a LIBRARY (detectSource/detectCity) by 8 reports — running it standalone is not the point of the file. scripts/reports/report_roi_combined.js
bid-caps /bid-caps. open on demand laptop What we can afford to pay per LEAD, BOOKING and SHOW for KIDSX and 1FX, per city, in GBP and USD. Built off a deliberately SETTLED cohort — never rebuild it nightly. scripts/reports/report_bid_caps.js
hash-lead-costs /hash-lead-costs. open on demand laptop Mike Hannah's supplied leads/removals sheet priced at his emailed rates: cost per DISTRIBUTABLE lead and per booking. Snapchat has no rate, so it is excluded from every total. scripts/reports/report_hash_lead_costs.js
Recycled 4
recycled-codes /recycled-codes on demand render The recycled families per DAY with a date picker. ?bookings=1 adds the booking-rate tab. scripts/reports/report_recycled_codes.js
recycled-cmn /recycled-cmn nightly laptop Answers ONE question: 6AB/5AB/N1N/R are STAGES of one journey (code → R → N1N → CMN) and reaching a stage should CONSUME the one before. 51.6% of CMN still carry a row that should have gone. scripts/reports/report_recycled_cmn.js
recycled-income /recycled-income on demand render The monthly invoice figure — income written on the recycled codes, by month, broken by family/city/code. scripts/reports/report_recycled_income.js
recycled-history /recycled-history nightly render Recycled leads RECEIVED per month since Apr 2023, with a send-sizing tab. Pre-2025 volume is 100% 4AB, so every table carries an excl-4AB line. scripts/reports/report_recycled_history.js
Diary, shows & cities 10
viewers /viewers nightly laptop Viewer performance. scripts/reports/report_viewers.js
zip-conversion /zip-conversion. nightly laptop ZIP-code booking conversion rate per city, prefix and full zip. scripts/reports/report_zip_conversion.js
distance-age /distance-age. nightly laptop Booking % and show rate by distance (straight-line AND drive-time) and by age. Turns into a lead-value calculation: cash per lead indexed per band. scripts/reports/report_distance_age.js
popup-markets /popup-markets. nightly laptop Pop-up studio one-pager for Austin + San Antonio, rolling last 30 complete days. A new market is a config entry, not a new file. scripts/reports/report_popup_markets.js
city-scorecard /city-scorecard. nightly laptop Every city side by side over a trailing 6 months. THREE bases that do not reconcile — publishes BOTH a 14-day-block and a calendar-month grain because months cannot be summed out of fortnights. scripts/reports/report_city_scorecard.js
show-rate /show-rate. nightly laptop Show rate on an appointment-date basis — differs from the ROI lead-cohort rate by design. scripts/reports/report_show_rate.js
weekend-effect /weekend-effect. nightly laptop Sat/Sun vs Mon–Fri shoots, KIDS vs ADULTS: show rate and what the people who turned up spent. PMax codes excluded from both segments on purpose. scripts/reports/report_weekend_effect.js
diary-fill /diary-fill key nightly laptop How full the next 14 days are, per city — the morning manager read. Capacity is seats ON OFFER (A+N+I+L), never the raw 24h grid. scripts/reports/report_diary_fill.js
no-shows /no-shows nightly laptop No-show rate by how far the client lives from the studio and what time the seat was sold for. Denominator is SETTLED appointments; cancellations excluded entirely. scripts/reports/report_no_shows.js
month-gap /month-gap nightly laptop Why the current month is off the $3.5M target against the four before it. Every headline is days 1..N and every window is scored against a day-of-week baseline. scripts/reports/report_month_gap.js
People, contracts & complaints 7
bookers /bookers nightly laptop Booker performance — bookings, sales, deposits, close rates per booker. scripts/reports/report_bookers.js
booker-levels /booker-levels. on demand laptop Every booker's turnover month by month against the Junior → Associate → Senior ladder and the 6-month bonus. Source is a Google Sheet with no service account — pulled through the browser. scripts/reports/report_booker_levels.js
goto-call-activity /goto-call-activity nightly laptop GoTo (Jive) call activity per agent per day. No transcripts exist — the org is still on recording v1. scripts/reports/report_goto_call_activity.js
issues /issues. key nightly laptop Every account with a problem written into its CRM note thread — wants out, asked for a reduction, refund, went to the bank, threatened legal — with the quote that proves it. ~41k requests, ~60 min cold. scripts/reports/report_note_issues.js
complaints /complaints. key nightly laptop Every client with a GRIEVANCE, read out of the same note threads. Narrower than /issues on purpose: a hardship story is a money conversation, not a complaint. scripts/reports/report_complaints.js
complaint-log /complaint-log?key=studio1-complaints-2026 key nightly laptop The WORKING SURFACE for complaints — a board rendered per request. Nothing auto-detected counts until a person confirms it. State is an append-only EVENT LOG in Supabase. scripts/reports/seed_complaint_log.js
contracts /contracts key nightly laptop Every contract the business can issue — 57 templates, three sale types, several generations — with a BLANK copy of all 48 that render. Built for the legal review; deliberately NOT key-gated so it can be handed to a lawyer. scripts/reports/report_contracts.js
Iconic & other 3
iconic-payments-detail /iconic-payments-detail nightly render ICONIC payments per individual — payers, chargebacks, refunds. Distinct from the 7:40 AM module cron. scripts/reports/report_iconic_payments_detail.js
photo-scores /photo-scores on demand laptop Photo-testing tooling, not a business report. scripts/reports/report_photo_scores.js
photo-tests /photo-tests on demand laptop Photo-testing tooling, not a business report. scripts/reports/report_photo_tests.js
Feeds and pushes (not pages) 18
square-attempts-history /square-attempts-history nightly laptop Incremental per-location pull → square_attempts.jsonl, the ONLY per-customer card history we have. Most decline reports depend on it. scripts/reports/pull_square_declines_history.js
card-types-pull /card-types-pull nightly laptop Every Square card attempt of the last 3 complete months, looping EVERY location id. The only feed keeping card_type, prepaid_type and BIN. scripts/reports/pull_square_card_types.js
square-declines-ghl-push /square-declines-ghl-push nightly render DRY RUN. Pushes yesterday's declines to GHL. Needs --live only once the GHL SMS step reading the reason message exists. scripts/leads/push_square_declines_ghl.js
square-customer-import /square-customer-import nightly laptop DRY RUN. Replaces Natalia's manual CRM→Square upload of each day's diary, rolling today+3. scripts/leads/square_customer_import.js
outreach-pipeline /outreach-pipeline nightly render CLOUD ONLY — the daily GHL push. The laptop must NEVER run this. scripts/leads/outreach_daily.js
outreach-reconcile /outreach-reconcile nightly render CLOUD ONLY. Asks CRM which qualifying sales are absent from GHL and pushes exactly those, so the gap self-heals nightly. scripts/leads/outreach_reconcile.js
suppression-feed /suppression-feed nightly render The CRM's Unsubscribed List as a keyed machine-readable feed for an outside system. Full deterministic rebuild each run, ~392k rows → ~250k people. scripts/leads/build_suppression_feed.js
push-suppression-sheet /push-suppression-sheet nightly laptop Pushes booked customers + rolling 15-day enquirers to the 'Neil Supression List' Google Sheet. Needs the service-account key — laptop only. scripts/leads/push_suppression_sheet.js
crm-leads-by-code /crm-leads-by-code nightly laptop Fills leads-per-code-per-month for any complete month it lacks. Must run BEFORE cpl-history. scripts/reports/pull_crm_monthly_leads_by_code.js
dashboard /dashboard nightly laptop Runs LAST — regenerates Reports/index.html so the card page reflects everything built above. scripts/reports/generate_dashboard.js
supplier-baseline /supplier-baseline on demand laptop Heavy ~8-week supplier baseline pull — weekly, not daily. scripts/reports/pull_supplier_baseline.js
finance-cohort-ledger /finance-cohort-ledger nightly laptop Pulls the FULL CRM Debtor Ledger (~112 posts, ~5 min) to ledger.json. Feeds finance-cohort, cash-summary, total-revenue, issues, debt-*. scripts/reports/pull_debtor_ledger.js
finance-cohort-schedules /finance-cohort-schedules on demand laptop Dated finance schedules — the ONLY source of payment DATES. Cold backfill ~59k booking fetches (>1h). scripts/reports/pull_finance_schedules.js
daily-sales /daily-sales nightly laptop Pulls EVERY sale from the CRM daily sales report — the ONLY source for sales PAID IN FULL (the Debtor Ledger holds financed only). scripts/reports/pull_daily_sales.js
debt-trend-sms /debt-trend-sms nightly laptop Pulls the debt GHL SMS metadata to weekly/monthly counts. READ-ONLY — counts only, no bodies, names or numbers. scripts/reports/pull_ghl_debt_sms.js
debt-workload-pull /debt-workload-pull nightly laptop Pulls the debt GHL BOARDS to per-person card counts. READ-ONLY. scripts/reports/pull_ghl_debt_workload.js
debt-unassigned-pull /debt-unassigned-pull nightly laptop Pulls the debt GHL keeping the CONTACT on each card — companyName is the only key that reaches the Debtor Ledger. Refuses to overwrite the cache with a truncated pull. scripts/reports/pull_ghl_debt_unassigned.js
contracts-pull /contracts-pull nightly laptop Probes the CRM three ways for the contract estate, then renders each template. EVERY PDF is blanked before it is written — a raw render carries the client's name, DOB, address, card number and signature. scripts/reports/pull_contract_templates.js
Dormant / superseded 16
square-import-plan /square-import-plan on demand laptop Dated write-up of the import job. The LIVE list is /square-import. scripts/reports/report_square_import_plan.js
noshow-april-2026 /noshow-april-2026 on demand laptop One-off analysis pinned to April 2026. scripts/leads/report_noshow_april2026.js
outreach-coverage-audit /outreach-coverage-audit on demand laptop One-time coverage audit. Re-running rewrites the baseline. scripts/reports/report_outreach_coverage_audit.js
outreach-dup-cleanup /outreach-dup-cleanup on demand laptop One-off manual-cleanup list Neil is actioning by hand in GHL. scripts/reports/report_outreach_dup_cleanup.js
leads-roi-monthly /leads-roi-monthly on demand laptop Month-parameterised, dormant. scripts/reports/report_leads_roi.js
roi-all /roi-all on demand laptop Rebuilds every historical week+month archive on each run — overlaps roi-combined. scripts/reports/report_roi_all.js
master-collections /master-collections on demand laptop Dormant — no artifact on disk. scripts/reports/report_master.js
trends /trends on demand laptop Dormant — no artifact on disk. scripts/reports/report_trends.js
collections-deep-dive /collections-deep-dive on demand laptop Dormant — no artifact on disk. scripts/reports/report_collections_deep_dive.js
three-month-comparison /three-month-comparison on demand laptop Heavy dated one-off; also touches the Google OAuth token. scripts/reports/report_3month_comparison.js
refunds /refunds on demand laptop Superseded by chargebacks-refunds. scripts/reports/report_refunds.js
failed-payments /failed-payments on demand laptop Superseded by missed-payments. scripts/reports/report_failed_payments.js
boost-daily /boost-daily on demand laptop Boost was removed as a supplier. scripts/reports/report_boost_daily.js
remote-viewing /remote-viewing on demand laptop Built from a Google Sheet rota transcribed by hand into a June 2026 template. scripts/reports/report_remote_viewing.js
qb-summary /qb-summary on demand laptop QuickBooks is DISABLED pending verification. scripts/reports/report_qb_summary.js
rejected-leads-csv /rejected-leads-csv on demand laptop CSV-only precursor to rejected-leads. scripts/reports/report_rejected_leads.js
This document 1
reports-handoff /Studio1_Reports_Handoff.html on demand laptop This page. Re-run it and COMMIT the HTML whenever the manifest gains, loses or renames a report — the commit is the only way it reaches Render. scripts/reports/report_reports_handoff.js

Settle these before counting anything. Most wrong numbers in this estate have been a definition problem, not an arithmetic one.

TermWhat it means here
Conversion %ALWAYS leads → bookings. Never anything sales-related. Do not answer a conversion question with a close rate.
Out of areaIn-area = the ZIP prefix is on that studio's TARGET LIST. Suppliers hold the list, so an off-list lead is a BREACH and the target is zero. The old 200-mile radius is superseded — .claude/docs/business-data.md still documents the 200-mile rule and is WRONG on this point.
DuplicateA re-hit within 15 days ONLY. Not "this person has ever been in the CRM before".
The lead funnelNeil's order, once only: leads in − OOA − dupes ≤15d − removed − rejected = usable. Do not re-order it and do not subtract a category twice.
CMN supersedesA recycled lead that later becomes CMN must be REMOVED from its original code (6AB/5AB/N1N/NIN/4AB). One person never counts under two families. The recycled codes are STAGES of one journey, not parallel families.
Supplier attributionsuppliers.classify() bills recycled Blue Rooms codes (#HOU1FXR, #DALGXR, #BOSKXR) to ALAN. Always classify(stripR(code)). The canonical map is in reference_marketing_codes and ORDER MATTERS — everything unmatched falls to Alan.
KidsKids = KIDSX + KX + KPX + KDX explicitly. Never code.includes("KIDS"). And /kids-volume counts META kids only (#…KIDSX) — Google kids is a different product at roughly a quarter of the conversion.
Show rateThe CRM's Show Rate report is appointment-date basis and counts cancels. The ROI feed's lead→show is a lead cohort. They differ by design — say which one you used.
Recurring locationMatch on the location NAME with /^Recurring/i, never === "Recurring". New York's is called Recurring NYC, and an exact match silently drops the whole city.
Square payments/v2/payments returns the DEFAULT LOCATION ONLY unless you pass location_id. That undercounted 47k against a real 151,318. Loop every location id, always.
Studio → cityStudio 1 = FLORIDA, Icon + Soul = ATLANTA. Use lib/finance_cities.js — the two other maps in the repo are both wrong.
A cohort vs a cash period/finance-cohort is a COHORT (per month of SALES). /cash-summary is a CASH PERIOD (per month BANKED). They will never tie, and neither is wrong.
Paid upA ledger balance under $10. Thousands of rows carry paidInFull=Yes with a residual — a cent-exact test calls people who owe nothing.
Per account, not per cardIn the debt GHL a debtor can hold several cards. A per-card sum of balance overstated the book by $327k on the first build. Collapse to one row per order reference.

Each of these has produced a wrong answer that reached Neil. The first three are the most important thing on this page.

1

NOTHING retries a failed payment. A collector chases it.

There is no automated retry anywhere in this business — not Square, not the CRM, not GHL. Every second attempt is a person: a collector on the phone taking a card, or a customer paying a reminder link. The words re-presented, retry, retried, retry rate are banned about our own book, in replies, on pages, in CSV columns, in commit messages and in variable names. Write "chased by a collector" instead.

2

Therefore every conversion rate on a second attempt is SELECTED.

A collector only puts a card through once the customer has agreed to pay, so of course it converts. The day-2–3 cell reading 90.3% measures "a conversation went well", not "day 2 is a good day to charge a card". Only the things that did NOT happen are unselected and safe to quote: "58.5% of soft declines were never chased within 30 days — $12.4M never asked for again." Lead with numbers of that shape. Anything automated is a NEW capability with NO baseline — write UNKNOWN, and give it a no-retry control arm.

3

Before calling any metric a lever, ask who performed the action.

If a human chose to act, the metric is selected. Verifying the arithmetic is not verifying the claim — a subagent reproduced a table exactly and it was still wrong, because the interpretation was wrong.

4

A part-month needs TWO guards.

The same day window (days 1..N of each month) AND a day-of-week baseline — revenue swings 1.8× across weekdays. Decompose multiplicatively, not additively.

5

State the TENSE of a blended finding.

A finding off a multi-month blend must be checked month by month before it is called live. The blend hides patterns that already ended. No mechanism = wrong driver.

6

Never quote a lifetime flag as engagement.

"Has replied" on the debt account reads 58% engaged, but 4,551 of those 6,706 last spoke over 90 days ago and only 968 have answered inside 30 days. /debt-status leads with the 30-day number for exactly this reason.

7

Check whether the denominator moved before you call a rate a decline.

The debt GHL reply rate did not fall — the denominator grew. A first-month conversation answers at 51–57% and has not moved in a year; the re-chased pile answers at 12–13%. Never quote the blended rate.

8

Maturity-gate every recovery window, and fix every lookback.

A miss only enters a denominator once its full window has elapsed, or the newest month always looks like a collapse. And "repeat misser" must be a FIXED 90-day lookback — an all-history one grows month by month and manufactures an ageing trend out of pure observation time.

9

Changing a SOURCE changes a metric's BASIS.

Sanity-check any headline number against the figure Neil already knows before shipping it. The "ad spend doubled" claim came from an Alan-ONLY baseline that omitted Boost and Lead Pronto — the real move was +14%, and spend has stayed ~20% of revenue throughout.

10

Scope to the live population before aggregating.

Dead rows silently dominate. Ask which population an aggregate covers — all-time or currently-active — before you report it.

11

An append-only .jsonl MUST be deduped to the last line per key.

Reading it line by line double-counts every re-fetched record. ghl_messages.jsonl holds 23,013 lines for 12,285 conversations — a naive read roughly doubles every reply number.

12

A mid-pull cache file is a VALID file with zero useful rows.

card_attempts.jsonl went 44MB/78,830 rows → 1.9MB/0 mid-pull on 5 Sep 2026. /signup-buckets now scores the live file and every .bak sibling and uses the best. Any report reading a re-pulled feed needs the same guard.

13

A month cached MID-month stays short.

It needs refresh=1, not rebuild=1. /dupes now self-heals, but every other cached-month report still relies on you knowing the difference.

14

Stale crm_form_ids.json silently filters leads out.

When leads look missing from a report, suspect the form-ids cache FIRST — it drops any campaign not in it. Same class of fault as the CRM stateid[] mapping, which dropped every Chicago and Orlando removal until 8 Jul 2026.

15

Guard main() in anything that mutates.

An unguarded IIFE runs on a bare require. Never require outreach_daily.js from the main repo — it PUSHES TO GHL.

16

Cloudflare edge-caches CSVs for 4 hours and rewrites email addresses.

A freshly rebuilt daily CSV serves stale until the cache expires — verify with a cache-buster. And emails on the page become "[email protected]" unless you split the @ with a span.

17

Supabase upserts need ?on_conflict=key — and you must CHECK the status.

A POST with merge-duplicates alone 409s on every re-save, so only the FIRST write to a key ever lands. /dupes binned its output nightly for ten days because the save logged success without reading the status code.

The standing conclusions the estate has produced — what Neil already knows. Quoted from dated analyses; re-measuring one is a deliberate act carrying a new date.

Leads and supply

Diary, shows and the month

The finance book and declines

The debt team

Outreach and portals

Pay

The house rules for a new report. Missing any of them means it is not done.

1

Build it as a VIEW, never a copy of another report

Require the libs — lib/page (shell, theme, toolbar, sorting), lib/crm, lib/square, lib/suppliers, lib/dates, lib/csv. The view file holds the QUERY and the LAYOUT and nothing else. If you are writing a <style> block, a cookie jar, a cursor loop or a detectSource(), it belongs in the lib and almost certainly already exists there. A per-supplier or per-city variant is a CONFIG ENTRY, not a new file.

2

Register it in the same commit

Manifest entry (slug, script, daily, owner) plus a card block so it lands on the dashboard. / is served from a COMMITTED Reports/index.html, so after adding a card you must regenerate and commit it or the live dashboard will not change.

3

Sorting, the yellow days and the date picker are automatic

They come from toolbar.inject(), which page.write() calls for you. Requirements: semantic <thead>/<tbody>, NO colspan on the sortable header row, class="no-sort" on summary rows, and a machine-readable date column (put an ISO date in data-sort — a cell printing only "Jul 17" will never highlight).

4

Tabs break three separate ways

The pane class, the tab- id prefix and the switchTab key must ALL line up: <button data-tab="x" onclick="switchTab('x',this)"> against <div id="tab-x" class="tab-content">.

5

Count and % are NEVER the same colour

The % is explicitly #fff AND the count is colour-coded. A white % next to a default-ink number is the SAME BUG as a red % next to a red number — most dark themes set --ink to near-white. Check what the NUMBER renders as, not just that .mut is defined.

6

Never put the full row-level list on the main view

The landing view is aggregated numbers — totals, rates, and breakdowns by supplier / city / month / bucket. Raw rows go in a CSV and/or a separate detail tab. Prefer rate tables (segment | population | matched | %) over raw counts.

7

Answer the metric he asked for, at the TOP

The exact metric, in the form he named it, as the first thing on the page. An equivalent he has to convert or hunt for does not count as answering. And ship ONE file — a supporting audit file alongside it reads as not doing what was asked.

8

Key-gate anything carrying names, phones or per-customer money

SALES_KEY (studio1-sales-2026) covers most of them. Give-able pages get their OWN key — /google-outcomes uses bluerooms-outcomes-2026, because SALES_KEY also opens /big-sales, /sales-contacts, /hv-buyers, /issues and 14 more. Aggregate-only pages stay open; /contracts is deliberately open so it can be handed to a lawyer.

9

Post the full live URL, as text, every time

Never start, never open a file on his machine, never open a report URL in the Browser pane (it hijacks his own link clicks). Show the full path — he reads it to know which report he is about to open.

What is blocked, dry-run, faulty or contradictory right now.

ThingStatusDetail
Ad-cost logins have lapsedBLOCKED — only Neil can fixSupplier cost dashboards stopped feeding ~23 Jul 2026, so Jul and Aug 2026 have no usable spend. /bid-caps, /cpl-trend and /blue-rooms-cpl all read a cost side that stops there. Run login_costs_profile.js, then log in to USA Models and Alan's leadcodes.
No July 2026 commission run in PaycomASK PAYCOMTen pay runs in 52 weeks; July earned $209,466 on the booker sheet and has no matching run.
Square declines → GHL pushDRY RUN by designIt computes and logs the full send list but pushes nothing. Add --live only once the GHL SMS step reading the reason-specific message exists — until then a live run enrols people into the OLD miss-count copy.
Square customer importDRY RUN pending sign-offReplaces Natalia's manual CRM→Square upload. Add --live only on Neil's go-ahead — it writes to the live Square accounts.
QuickBooksDISABLEDSandbox works; production is blocked on bookkeeper verification. qb_sync.js and qb_daily_pipeline.js stay out of the daily run.
/collections-health understates collectionsKNOWN FAULTIt misfiles ~48% of hand-keyed collector money as "auto book", and its invoice-based rate cannot see phone payments at all.
/missed-payments "Miss #"KNOWN FAULTIt is the invoice SEQUENCE number, not the miss count — wrong 29 times in 41. The report also page-caps at 6k of 32k Boston invoices.
4AB attribution is unreconciledKNOWN CONFLICTreport_roi_combined.js bills 4AB to recycled; lib/suppliers.js still bills it to Alan. Pre-Jan-2025 recycled volume is 100% 4AB, so every recycled-history table carries an excl-4AB line to work around it.
.claude/docs/business-data.md still documents the 200-mile OOA ruleSTALE DOCThe canonical rule since 17 Jul 2026 is the ZIP target list. The doc has not been updated; the reports have.
/goto-call-activity sourceVERIFYThe manifest note flags that the code path reads a manual .xlsx export while the project notes say it moved to the live GoTo API. If it is still manual, it should be daily:false.
GoTo calls cannot be joined to customers at allDATA GAPgoto_calls_daily.json is per agent per day with no phone and no customer, and GHL TYPE_CALL is 97.7% inbound. The missing field is the other party's phone on a per-call pull, which would join to the debt status file.
Phase 4 archive auditQUEUEDRoughly a dozen dormant or superseded manifest entries — master-collections, trends, collections-deep-dive, collections-city, three-month-comparison, refunds, failed-payments, boost-daily, roi-all — are kept registered but have no artifact on disk. They are candidates for retirement, not for running.
Dupe arrival-time analysisSHELVED — do not rebuildThe CRM lead timestamp is a per-minute BATCH-PROCESSING time, not the real enquiry time, so it cannot measure sub-day simultaneity. Do not build an arrival-gap section on it.
Collector credit attributionDEAD ENDSquare credits whoever processes the payment and the invoice creator IS the processor, so "credit the sender" cannot be recovered from the data.