Every Monday, each active client gets a draft report. AICRO reviews and edits it, then publishes it. This page explains what each section is for, exactly where its data comes from, and the rules that stop the report from ever quietly lying to a client.
A weekly report is only worth sending if the client believes the numbers. Both rules below are enforced in code, not by convention, because the failure they prevent is silent.
Metrics are computed from cortex. The model is forbidden from authoring a figure, and a reviewer cannot hand-type one either. Captions and labels are editable; values are not.
A hand-typed number is indistinguishable from a real one. One invented figure puts every other number in the report in doubt, so the edit path strips values server-side and keeps the computed ones.
The leads section reads client.qualified_lead_board, the same canonical view
behind the portal's Qualified Leads report. One person, one row.
It used to read warm_lead_board, which is one row per message. A lead
who replied five times appeared five times, and the section claimed "8 qualified" when there
were four people. Reading the canonical view makes that impossible by construction.
Real output for Lucra Sports, week of Jul 6. The numbered pins map to the explanations underneath.
| Name | Company | Category | Next step |
|---|---|---|---|
| DeVone Claybrooks | Youbetme | Won | Closed, nurture for expansion |
| Jason Vanderburgh | In pipeline | Advance the Putting Edge deal | |
| Aram Fuchs | Buzztime.com | Meeting booked | Confirm the meeting held; log the outcome |
| Chris Klein | Meeting booked | Confirm the meeting held; log the outcome |
| Campaign | Status | Contacted | Positives | Rate |
|---|---|---|---|---|
| LI | Core | SE USA Expansion | Completed | 78 | 8 | 10% |
| EM | Q2 Rate Teaser | Banks | Completed | 1,500 | 2 | 0% |
| Expand the two LinkedIn sequences producing at 10% | AICRO |
| Confirm the ICP definition for the Banks segment | Client |
The report covers the last complete week (Mon to Sun), is generated the following Monday, and may be presented at a Tuesday or Wednesday client call. So it states both endpoints and when it was generated. It never says "this week" or "last 7 days".
week_start → report_title()One or two sentences, metric first. The thing a busy founder reads and nothing else.
Sent, positive replies, meetings booked, each against the prior 30-day average so direction reads before magnitude. Positive Reply Rate is positives over replies, never over sends.
outreach.v_client_daily_metrics summed per channel over the period
intelligence.meetings,
a different source from the leads board, so a duplicate lead cannot inflate the meeting count.
Two or three, each one backed by a number that appears above it. Add, edit, reorder or delete before publishing.
One row per person, with a real lifecycle (Won, In pipeline, Meeting booked, Qualified) and one concrete next action. The link goes to the full Qualified Leads report, which reads the same view, so the two can never disagree.
client.qualified_lead_board, deduped on coalesce(crm_contact_id, lower(reply_email))
What ran in the period, with real contacted and positive counts. Links out to the full campaign history.
outreach.campaign_daily_activity summed over the period
Actions split by owner, AICRO or the client. This is the one section that is not yet right, and it is the main open question in this handoff.
The report is an internal draft until someone publishes it. Editing is admin-only and draft-only; a published report is immutable.
A cron drafts a report for every active client, covering the last complete week.
Every section is editable in place, with an AI refine on the prose. Numbers are not.
Recomputes the metrics. A section you edited is pinned and survives; the rest recompute.
Only then does the client see it. Drafts are absent from the client's payload, not merely hidden.
A weekly report that is quietly absent is as bad as one that is wrong, and Slack alerts
do not fix it (the bot can be out of the channel, the message can go unread). So the failure
surfaces in the product: /admin/weekly-reports shows every client's state, with a
loud banner when the job itself is dead. A dead cron is detectable only by the absence of a
run, because a cron cannot report its own death. Each client's Notebook also carries a
"not generated yet" card with a one-click Generate, so a failure is an inconvenience, not a fire drill.
Ordered by what unblocks the most. Items 1 and 2 are small and high-leverage; 4, 5 and 6 need a decision from Josh before anyone writes code.
| Item | Why / evidence | Size | |
|---|---|---|---|
| 1 | Map Contract Status → client.clients.statusBlocks the cron |
Cortex says 15 clients are active; Airtable says 8. The other 7 are churned. Root cause: the n8n Clients sync never writes status (Airtable has no field called "Status", it has Contract Status), so status is the stale insert default while contract_status is current. One mapping makes cortex correct and fixes ~10 consumers at once. Until then the cron would report on 7 churned clients. |
S |
| 2 | Fireflies recording URL | crm_record_url is the de-facto "link to the recording" column and Granola and Fathom both set it. Fireflies never does, because _TRANSCRIPT_QUERY does not request transcript_url. Two-line fix, and it is what stands between us and attaching the week's call to the report. |
S |
| 3 | Fireflies binding gap | integrations.connections is keyed by anon_id (the onboarding browser session), with no anon-to-client binding. AICRO's connection reports 731 transcripts; only 21 landed for the client. The connection is orphaned. |
M |
| 4 | What’s Next: project, stop generating Needs Josh |
Violates the standing "never fabricate tasks" rule. It should render the same set the Weekly Strategy Doc does (On Us / On Them), sourced from call-extracted tasks union open Task Hub tasks, gated on the Lock In Strategy approval Josh already owns, so nothing reaches a client he has not locked. | M |
| 5 | Automated recap replacing the post-call flow Needs Josh |
Today the post-call flow costs roughly 30 minutes per call of cleanup; the target is 5 minutes of approving. The n8n extraction already exists, so the recap would replace the manual doc edit and the copy-paste into Slack. Depends on 2, 3, 4 and 6. Any automated Slack post must go through the n8n slack-post webhook (the AICRO bot), never the Slack MCP, which posts as Josh personally. |
L |
| 6 | Resolve the action-items doc conflict | Two committed docs disagree. The meeting-transcripts PRD says Airtable Client Calls is the source of truth and to "not migrate". The newer Airtable-to-cortex plan says Client Calls → intelligence.meetings. The recap design cannot start until one wins. |
S |
| 7 | Per-campaign deep links | Doug asked for a link from each campaign row into that campaign's background. Not buildable yet: the portal campaigns page is keyed on Airtable record ids while cortex keys on a uuid, only 25 of 1,189 cortex campaigns carry airtable_campaign_id, and there is no per-campaign route (the detail is a local-state modal). Needs an identifier decision plus a ?campaign= param. |
M |
| 8 | client.qualified_lead_funnel has no migration |
The RPC exists in the live database only. A fresh db reset will not have it, so CI and any new environment are one step from breaking. |
S |
| 9 | qualified_lead_board is not canonical enough |
It lives in the legacy migration tree, not the canonical bootstrap, and it inlines a predicate that disagrees with is_qualified_reply_v2 (missing the delegated_to_dm referral fix and the newer classifier vocabulary). The canonical definition is not actually canonical. |
M |
| 10 | Inbound campaign_id is NULL |
Per-campaign inbound is mapped by name, so a campaign can show positives against zero contacted, and a cross-client name collision mis-attributes positives. | M |
Should "What's Next" project the same set the Weekly Strategy Doc renders (On Us / On Them), gated on your existing Lock In Strategy approval? That would make the weekly report a surface on the flow you already own, rather than a second, LLM-invented list that can contradict it.