# Channel Insights · Overall (the master reading)

**Context:** Manage · **UI:** Lead → Intelligence → Insight → **Overall** (the first tab) · **Routes:** `manage.leads.overall-insights.*` · Built 2026-09-18 at the founder's request: *"analysis are to be analysed to produce a master analysis. we need to add one more tab 'Overall'."*

## What it does

Every other reading answers "what does this channel say about them". Overall answers **"what do we know about this person"** — by reading the readings.

Its input is the STORED per-channel readings in `lead_channel_insights` (WhatsApp, Zoom meetings, Zoom webinars, phone calls, AI calls, showroom, the Sales records, the member portal) plus the hard facts around them. **It never re-reads a transcript.** That is not a shortcut: the child readings already did that work, and a second pass over the words would let it write quotes nobody said. It may only carry a quote forward, with the channel it came from.

What it produces is the four things no single channel can see:

1. **The timeline** — what actually happened, in order, across channels.
2. **The contradictions** — where the channels, or the person's own statements over time, do not agree. On its first live run it found one immediately: Ryan said *"but i still under akpk"* on WhatsApp, then *"i already withdraw from akpk"* three weeks later, and told the Zoom consultation the same in Mandarin — with the reading that withdrawal is his current status but does not clear the restructuring marker.
3. **One deduped list of what is still owed** — a promise made on WhatsApp and repeated in a meeting is one thing to do. Its first run reconciled an "RM500 KLCC refund" on WhatsApp with an "outstanding 500 payment" in a meeting, and said so rather than listing both.
4. **One merged seven-area readiness view** — per field the strongest, most recent evidence, each item stamped with the channel it came from. This is the readiness a Work page should read: on the first run, 19 items across three channels, every carried quote still verified.

Plus: who they are (merged), where they are and whether they are warming, up to three actions **each naming the channel to do it on**, and one message to send — on the channel this person actually answers on, with the reason.

## How it works

- **Storage:** `lead_channel_insights` with `CHANNEL_OVERALL = 9` — the same table as every other reading, because it is the same thing: one stored AI reading per lead per subject. It inherits fingerprint reuse, the Prompt view, the JSON download, and the account-merge handling for free.
- **Input:** `LeadOverallInsightsController::part()` renders one block per channel that HAS a reading —
  `=== zoom-meetings · read 2026-09-18 01:23 · gpt-6-astra ===` followed by that reading's JSON. Sizes are small (3–25 KB each, so ~100 KB at the worst), which is why this is one request and no part prompt exists.
- **Facts (`meta`):** what the server COUNTED, never what a model thought — records and last activity per channel, which channels are unread, `lead.webinar.property_count`, the mined avatar persona, lead status and known-since. WhatsApp's count comes from `LeadConversationPresenter::countForLead()` (threads hang off the contact; there is no `lead_id` column), and it is 0 for a viewer without `view-whatsapp`.
- **Blind spots are part of the reading.** A channel with records and no reading is listed in `meta.blind_spots` and the prompt must say so in `summary` — *"three phone calls have not been read yet"*. The founder chose this over refusing to run: a salesperson can use a partial reading as long as it admits what it has not seen.
- **Fingerprint / staleness:** the fingerprint's source is the CHILDREN's fingerprints. Re-read any channel and the master goes stale the moment that lands — and no sooner, so an unchanged page never re-spends.
- **Citations:** every cited item carries `{channel, message_id}`. The panel maps the channel key to that channel's own endpoint (`zoom-meeting-insights`, `phone-call-insights`, …) and "View" opens the line where it was actually said. An unknown channel key is dropped by `MasterInsights::channel()` rather than rendered — a View that opens nothing is worse than no link.
- **Carried verification (`MasterInsights::carryVerification`)**: a child verified its quote character-for-character against the line it cites; the master never sees that line. So each carried item inherits the child's `quote_verified` when `(channel, message_id, quote)` match exactly, and gets `false` when the model reworded it on the way through. Showing everything as unverified would rate the merged view below the readings it is made of; showing everything as verified would be a lie.

### Access — why this one is different

A master reading quotes the channels it read, so the endpoint checks, **per reading it would quote**, that the viewer may hold that channel's words (`view-whatsapp` / `view-zoom` / `view-calls` / `view-f2f`; Sales and portal have no permission of their own). A viewer who cannot open Zoom sees that the reading exists and why it is withheld — and cannot generate one either. The tab itself is always offered: a permission gate at tab level would be coarser than the reading is.

## Related files

- [src/Conversation/MasterInsights.php](/src/Conversation/MasterInsights.php) — the `master-insights-v1` schema, the channel-key registry and `carryVerification()`
- [app/Http/Controllers/Manage/Leads/LeadOverallInsightsController.php](/app/Http/Controllers/Manage/Leads/LeadOverallInsightsController.php)
- [resources/prompts/lead_overall_insights.md](/resources/prompts/lead_overall_insights.md) — registered as `AiRequest::PROMPT_LEAD_OVERALL_INSIGHTS` in `config/ai_prompts.php`
- [resources/js/Pages/Manage/Leads/Partials/Tabs/Channel/OverallInsightsPanel.vue](/resources/js/Pages/Manage/Leads/Partials/Tabs/Channel/OverallInsightsPanel.vue) — coverage first, then the brief, contradictions, who they are, do-now, still owed, timeline, readiness
- [resources/js/composables/useLeadInsightTabs.js](/resources/js/composables/useLeadInsightTabs.js) — Overall is the first tab of Intelligence → Insight
- [OverallInsightsPanel.test.js](/resources/js/Pages/Manage/Leads/Partials/Tabs/Channel/OverallInsightsPanel.test.js) — coverage before the reading, the Analyse button present-but-disabled with its reason, the empty state pointing at a channel that has records, a citation opening the channel that holds it, a withheld reading
- [tests/Feature/Lead/LeadOverallInsightsTest.php](/tests/Feature/Lead/LeadOverallInsightsTest.php) — it reads readings and never a transcript; a channel with records but no reading is named as a blind spot; nothing read yet is refused rather than guessed; it goes stale when any channel under it is re-read (and reuses otherwise); it withholds a reading quoting a channel the viewer cannot open; a carried quote keeps the verification the child earned and a reworded one does not

**The Analyse button is never hidden.** With nothing read yet it stays, disabled, saying *"Analyse at least one channel first"*, and the empty state lists the channels that do hold records as buttons that switch the strip to them — the panel cannot read a channel itself. It was hidden on the first cut, and a panel offering Prompt and JSON but no Analyse read as broken (founder, same day).

**Naming:** inside a channel the whole-channel view is now labelled **All meetings / All calls / All visits**, not "Overall" — that word means the master reading, one level up, and one word cannot mean two things on one page.
