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.
Eight things that have each caused a wrong answer or a dead site. Read them before touching anything.
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.
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.
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.
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.
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.
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.
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).
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.
| When | What | Detail |
|---|---|---|
| 07:00 UK | Render cron — DAILY_SCRIPTS in serve_reports.js | Every 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 local | Windows Task Scheduler → run_all.bat → scripts/run_daily_reports.js | Every 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 min | Further crons in serve_reports.js | Iconic module rebuilds, contract-signed polling and the failsafe re-run. |
| 08:00 UK | runElenaDailyReports | Elena's collections and deposit reports — a separate path from the manifest runner. |
| Logs | Reports/logs/daily_reports_YYYY-MM-DD.log | Per-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 fault | The 08:00 laptop run fails ~15 of 20 reports on DNS | Studio1_WakeUp only allows 5 minutes for Wi-Fi to come up. The 09:00 failsafe is what actually produces the reports. |
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.
| Slug | Route | Cadence | Built by | What it answers | Script |
|---|---|---|---|---|---|
| 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.
| Term | What it means here |
|---|---|
| Conversion % | ALWAYS leads → bookings. Never anything sales-related. Do not answer a conversion question with a close rate. |
| Out of area | In-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. |
| Duplicate | A re-hit within 15 days ONLY. Not "this person has ever been in the CRM before". |
| The lead funnel | Neil'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 supersedes | A 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 attribution | suppliers.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. |
| Kids | Kids = 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 rate | The 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 location | Match 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 → city | Studio 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 up | A 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 card | In 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.
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.
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.
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.
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.
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.
"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.
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.
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.
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.
Dead rows silently dominate. Ask which population an aggregate covers — all-time or currently-active — before you report it.
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.
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.
It needs refresh=1, not rebuild=1. /dupes now self-heals, but every other cached-month report still relies on you knowing the difference.
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.
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.
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.
?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.
The house rules for a new report. Missing any of them means it is not done.
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.
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.
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).
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">.
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.
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.
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.
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.
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.
| Thing | Status | Detail |
|---|---|---|
| Ad-cost logins have lapsed | BLOCKED — only Neil can fix | Supplier 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 Paycom | ASK PAYCOM | Ten pay runs in 52 weeks; July earned $209,466 on the booker sheet and has no matching run. |
| Square declines → GHL push | DRY RUN by design | It 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 import | DRY RUN pending sign-off | Replaces Natalia's manual CRM→Square upload. Add --live only on Neil's go-ahead — it writes to the live Square accounts. |
| QuickBooks | DISABLED | Sandbox works; production is blocked on bookkeeper verification. qb_sync.js and qb_daily_pipeline.js stay out of the daily run. |
| /collections-health understates collections | KNOWN FAULT | It 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 FAULT | It 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 unreconciled | KNOWN CONFLICT | report_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 rule | STALE DOC | The canonical rule since 17 Jul 2026 is the ZIP target list. The doc has not been updated; the reports have. |
| /goto-call-activity source | VERIFY | The 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 all | DATA GAP | goto_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 audit | QUEUED | Roughly 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 analysis | SHELVED — do not rebuild | The 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 attribution | DEAD END | Square credits whoever processes the payment and the invoice creator IS the processor, so "credit the sender" cannot be recovered from the data. |