Loop FIN-1 · Finance · reports to Shahbaz + collections

Every unpaid invoice, aged daily — with the chase text pre-written.

Buckets every open invoice by how overdue it is, flags what newly crossed 30/60/90 days, and drafts the polite chase email — which a person always sends.

Built & tested — pilotWave 3🕐 Daily 08:00⚙️ Hermes bizops (planned)
Part 1 — the plain-english view

Money owed to us should never age quietly.

What it watches
  • Every open customer invoice, bucketed 0–30 / 31–60 / 61–90 / 90+ days
  • Newly overdue since yesterday — the state diff that makes it a watchdog, not a report
  • Top 10 by amount with days overdue; customers whose invoices must route via specific banking arrangements get flagged for the compliance calendar
When & how it speaks

Daily digest to the collections team: buckets, new crossings, top 10 — plus ready-drafted chase text per customer, saved into the digest for the team to copy, edit and send themselves.

Why it exists

Invoice data didn't exist in our reporting warehouse at all — the finance team had no aging view outside the ERP. One data extension (built for this loop) now syncs invoices nightly, and the first live run found AED 2.08M overdue across 96 invoices.

AED 2.08M
overdue across 96 invoices on the first live run
AED 731K
of it sitting in the 90+ day bucket — 40 invoices
Daily
the cadence that catches an invoice the day it crosses a bucket, not the month after

Daily 08:00

After the overnight invoice sync

👀

Reads open invoices

Amounts unpaid, due dates, customers

📋

Buckets & diffs

Aging buckets + what newly crossed since yesterday

✍️

Drafts chase text

Polite, per-customer — saved into the digest

People send

The team copies what they approve; the loop never emails a customer

What it will never doNever emails a customer. Drafts only — always sent by a person.
Where it stands today · July 2026Built and live-tested on the new nightly invoice sync. Every number is a door: each invoice links to its ERP record, each bucket to a live drill-down list. Thresholds and recipient list are open questions with Shahbaz before promotion.
Part 2 — under the hood

How it's wired: systems, models, and the path a number takes.

For implementers and the technically curious. The full build sheet — verified queries, thresholds, and build notes — lives on the specs page.

ReadsNetSuite warehouse
Invoices + amounts unpaid, synced nightly (the extension built for this loop)
Stepfin1_ar.py
Aging buckets, day-over-day diff, top-10 ranking
StateYesterday's snapshot
So “newly overdue” is precise, not estimated
StepChase-text drafting
Tone-templated per customer
DeliversDaily digest
To the collections team, with drafts inline
Human gatePerson sends
Chase emails go out from a human mailbox, or not at all
System we read Automated step State / memory Human decision
SystemRole in this loop
NetSuite warehousePostgres on our serverOur nightly read-only copy of NetSuite (ERP) data — items, stock, transactions — refreshed by the ETL sync every morning. The loop queries this copy, never NetSuite itself.
ETL extensionraw.invoicesThe data connection built for this loop: 24 months of customer invoices with open balances, syncing nightly with per-step fault isolation — it also unlocked FIN-2's calculations.
Metabasebi.huxapps.comThe BI layer over the warehouse. The loop runs its SQL through Metabase's API, and every number in the digest links to a live, clickable Metabase list so you can drill into the exact records.
Postgres “loops” schemareporting databaseA small dedicated schema where loops keep snapshots and history that need SQL (week-over-week diffs, mirrors for drill-down links). Scoped role; Metabase can read it for the clickable aggregate links.
Hermesagent runtime on our serverThe scheduler that wakes the loop up. Each loop is a cron job under a Hermes profile; the planned bizops profile will host the business digests (IT loops run under vpsops).
Healthcheckshealthchecks.huxapps.comThe dead-man's switch. The loop pings it only after a clean run — if the loop dies or errors, the ping stops and Healthchecks raises the alarm independently. This is how 'never silent' is enforced by machinery, not promises.
Email renderer + gwsrender_email.pyAll digests pass through one shared renderer: Huxberry-branded HTML, tables for repeated rows, a coral 'needs your response' box when the loop has questions, and an arrow link on every record. Sent from the loops mailbox via the Google Workspace CLI.
Loop chassisloop_common.pyShared plumbing every loop reuses instead of reinventing: state files, run-over-run diffing, Metabase drill-down link building, quiet-on-green notify logic.
Model / brainWhat it does here
None at run timedeterministic scriptA normal cycle is a plain Python script — no AI tokens are spent unless a diagnosis or judgment step is actually needed. AI wrote the script; the script does the rounds.
GPT-5.5 via Codexthe bulk-work modelWrote and maintains the mechanical parts — SQL, diffing, digest assembly. Effectively free on our existing subscription, so routine cycles cost almost nothing.
Claude Opusthe judgment modelReviews alert wording, thresholds, and anything a human will read and act on. Post-Fable, Opus owns everything that ships.
State & memory

Yesterday's invoice snapshot for the newly-overdue diff; a running ledger of what's been flagged so nothing is nagged twice in one bucket.

Delivery

Daily email digest; invoice links to the ERP, bucket totals to live BI lists. A finance feed card (headline: total AR overdue; tiles per bucket) follows.

Safety rails

Read-only. Outward-facing text (chase drafts) is the one place the judgment model always reviews wording — it ships to customers, via human hands.

Before it can run for real
  • Shahbaz's thresholds + recipient list (open questions from the Wave-3 results email)
Full build sheet →