## Summary
Adds a Due Diligence dashboard powered by an LOI-anchored cohort sourced from REBL3's Postgres backend (via the internal REBL3_SUPABASE_MCP server, refreshed every 15 min into a Convex table). Ships:
- DD list view with status-tinted workflow columns and a cut-site toggle.
- DD per-site detail page redesigned around two tabs (Workflow Status, Agents) and a wiki-style right rail (Maps / Site Details / Property Details).
- Portfolio Kanban Prospecting column enriched with REBL3-only sites (those not yet in Rhodes) so Ops can see early-stage candidates.
## Cohort definition
LOI-anchored: every REBL3 site that has entered the LOI workflow at any status (cut / submitted / claimed / done), excluding globally-excluded sites. ~179 rows steady-state.
FROM rebl3_status loiJOIN rebl3_sites s USING (site_id)
LEFT JOIN rebl3_status st ON st.site_id = s.site_id AND st.system = 'strategy'
LEFT JOIN rebl3_status leasing ON leasing.site_id = s.site_id AND leasing.system = 'leasing'
LEFT JOIN rebl3_status dd ON dd.site_id = s.site_id AND dd.system = 'due-diligence'
WHERE loi.system = 'loi'
AND COALESCE(s.excluded, FALSE) = FALSE
ORDER BY s.region, loi.updated_at DESC;
Strategy / leasing / DD are LEFT JOINs — they enrich each row but don't filter inclusion. Cut LOIs are intentionally cached so the DD page can opt into showing them via the Show cut sites checkbox; the Portfolio merge filters cut sites at render time on cut_by.
### How to broaden / narrow
Edit COHORT_SQL in [chat/convex/activeSitesCohort.ts](chat/convex/activeSitesCohort.ts):
| Alternative | ~Count | SQL change |
|---|---|---|
| Current (LOI any-status) | ~179 | WHERE loi.system = 'loi' |
| LOI completed only | ~61 | append AND loi.status = 'done' |
| LOI in flight or done | ~93 | append AND loi.status IN ('submitted','claimed','done') |
| Strategy-active | ~48 | flip anchor to rebl3_status st, WHERE st.system = 'strategy' AND st.status IN ('start','sign') |
| DD-anchored | ~82 | flip anchor to rebl3_status dd, WHERE dd.system = 'due-diligence' AND dd.status IN ('complete','data-gathering','follow-up') |
If new columns are needed, add them to the SELECT + the schema in [chat/convex/schema.ts](chat/convex/schema.ts) in lock-step.
## Refresh cadence + failure mode
- Every 15 minutes — Convex cron ([chat/convex/crons.ts](chat/convex/crons.ts)) runs the SQL via the MCP and upserts into activeSitesCohort.
- Worst-case staleness — 15 minutes. Dashboard reads from the table; no live REBL3 calls in the request path.
- MCP unreachable — cron logs and exits cleanly; last-good cohort persists; dashboard renders normally with stale data.
- Cold-start on deploy — sync-worker boot-time HTTP trigger ([sync/src/analytics-worker/cohort-trigger.ts](sync/src/analytics-worker/cohort-trigger.ts)) warms the cache before the first cron tick.
## DD list view
- Workflow column split into 4 sortable columns: Strategy, LOI, Lease, DD. Values render as moderate tone-tinted pills:
- done / complete → green
- cut / kill / killed / rejected → red
- claimed / submitted / start / sign / negotiating / collecting-feedback / data-gathering / follow-up → accent
- unknown → neutral
- "Show cut sites" checkbox in the filter bar; default unchecked; URL-backed via ?showCut=1 for shareability. Cut sites are cached but hidden by default at render time.
- Search input relocated from the cohort header into the filter bar, left of the State dropdown.
- Removed columns + filters: Score, Type, Listing.
## DD per-site detail page
Redesigned into a two-column wiki layout (max-w-7xl; ~340px right rail; collapses to single column on mobile).
Sticky header — site name + classification chip + region/state + In-Rhodes link.
Left/main column — URL-backed tabs (?tab=workflow|agents):
- Workflow Status tab (default) — 5 per-system cards in workflow order: Strategy → LOI → Leasing → Due Diligence → Parents. Each card surfaces Ops-actionable fields with structured bodies (no JSON dumps). Highlights:
- Color-coded status pills (success / in-progress / danger / muted) per known REBL3 status enum.
- LOI card: prominent Screening grade (GREEN/YELLOW/RED) + score; deal terms grid (landlord, broker, lease length, $/SF, annual cost, escalation, free rent, security deposit, break option); LOI document link.
- DD card: prominent GO / NO-GO badge; report link; side-by-side Fast Open + Max Cap scenarios (capacity / capex / projected open).
- Leasing: pipeline stage, claimed-at, "Needs Zeke confirmation" warning chip when set.
- Parents: deadline, vote tally (interested / not_here / unique), detail page link.
- Audit/housekeeping fields (slug_dup_*, imported_from, backfilled_at, mirrored_at, changes, synced_at, writeback_flagged, normalized_from) intentionally dropped.
- Agents tab — 11 cards in Operations priority order. Real estate has already approved the acquisition; Ops inherits the site, so the order favors physical/operational realities and RE-flagged concerns over acquisition economics:
1. Acquisition Summary — RE team's verdict + dimension scores. Prominent "RE flagged concerns" banner when cutBy non-empty.
2. Building — physical reality (condition, entrance, parking, size, positives + tradeoffs).
3. Outdoor Play — daily Ops concern: on-site space, closest/best park, top 6 nearby parks with walk times.
4. Vision Inspection — photo-analysis red flags + positive signals, outdoor play details.
5. Co-Tenancy — exclusive use, building type, dangerous-tenants count.
6. Zoning — operational permissions: K-12 by-right vs CUP, zoning code/city.
7. Neighborhood — name, character, crime/safety, family life.
8. Web Diligence — color-coded risk-level badge; facility type; primary occupant; signals.
9. Proximal Environment — businesses count, character, notable nearby.
10. Demographics & Schools — qualifying schools (10m), available children (10/20/30m).
11. Nearby Schools — existing component with Mapbox map widget.
12. Cost — collapsed by default; lease economics for budget reference only.
Right rail (340px):
- Collapsible Maps card — aerial + street images stacked vertically.
- Site Details card (existing pros/cons synthesis bullets).
- Property Details card (existing 6-stat grid).
## Portfolio Kanban — Prospecting REBL3 merge
Extends the Portfolio dashboard's Kanban to inject REBL3-only sites into the Prospecting column so Ops can see early-stage candidates that aren't yet in Rhodes.
- Parallel Convex query against activeSitesCohort on the Portfolio page; client-side merge with the Rhodes payload.
- Dedupe by slug — Rhodes wins; only REBL3 sites with no matching Rhodes slug appear.
- Cut sites filtered out of the synthetic cards (cut_by IS NOT NULL).
- Synthetic PortfolioSite rows: slug/name=address, phase: "prospecting", source: "rebl3" discriminator. Card UI shows a small REBL3 pill in place of the DRI bubble; clicking links to the DD detail page (Rhodes detail would 404).
- Kanban only — list view stays Rhodes-only. Other Diligence sub-buckets (Conducting Diligence / Acquiring Property) unchanged.
- Editing REBL3 cards is out of scope; deferred to a future PR (REBL3 → Rhodes ingest).
## What this PR is NOT
- Not a sync of REBL3's tables. We cache the result of one targeted SQL query (~179 rows). Aligns with the team-lead directive that REBL3 is an entry point being deprecated long-term.
- Not a runtime cohort-config knob. SQL is hardcoded; the table above is the documented broadening path.
When REBL3 is deprecated, this dashboard goes away by deleting one cron entry, the activeSitesCohort table, the action file, the MCP-client file, the boot-trigger helper, and the route file.
## Backward compatibility
- Bookmarked URLs with ?mode=green|yellow|cut still parse — mode is accepted but no longer filters.
- The portfolio board's Diligence stage header navigates to this dashboard like Buildout/Operating already do (drops the "not available yet" tooltip).
## Notable file additions / deletions
- New: [chat/convex/lib/mcpSse.ts](chat/convex/lib/mcpSse.ts) — single-shot MCP-over-SSE client.
- New: [chat/convex/activeSitesCohort.ts](chat/convex/activeSitesCohort.ts) — refresh action, upsert mutation, dashboard query, boot-trigger HTTP action.
- New: [chat/convex/schema.ts](chat/convex/schema.ts) activeSitesCohort table.
- New: [chat/components/dashboards/due-diligence/workflow-status-panel.tsx](chat/components/dashboards/due-diligence/workflow-status-panel.tsx) — 5 per-system Workflow Status cards.
- New: [chat/components/dashboards/due-diligence/agents-panel.tsx](chat/components/dashboards/due-diligence/agents-panel.tsx) — 11 Ops-priority Agents cards.
- New: [sync/src/analytics-worker/cohort-trigger.ts](sync/src/analytics-worker/cohort-trigger.ts) — boot-time warm-up.
- Deleted: grouped-sub-agent-sections.tsx + test, rebl3-due-diligence-cache.ts, due-diligence-filter-spec.ts, due-diligence-mode-tabs.tsx (replaced or obsolete).
## Required env var
REBL3_SUPABASE_MCP must be set in the Convex environment (in .env.local and via npx convex env set --prod REBL3_SUPABASE_MCP "..." for prod). Without it the cron logs and exits cleanly; the dashboard renders an empty-state message.
Also: MAPBOX_ACCESS_TOKEN for the Maps card + Nearby Schools map.
## Verification
- [x] pnpm typecheck workspace-wide clean (chat + sync + infra + contracts).
- [x] pnpm test 3,825 / 3,825 pass.
- [x] pnpm exec biome check chat/ clean.
- [ ] Visit /dashboards?tab=due-diligence while signed in — confirm <1s load, ~93 rows visible (cut hidden by default), tone-tinted workflow pills per row.
- [ ] Toggle "Show cut sites" — confirm cut rows appear, URL gains ?showCut=1, link is shareable.
- [ ] Click a row — detail page loads with the Workflow Status tab default; switching to Agents preserves via ?tab=agents.
- [ ] Visit a site with rich agent data (e.g. 620-5th-ave-s-kirkland-wa) — confirm Acquisition Summary's dimension grid + Building / Vision / Outdoor Play render structured bodies, no JSON dumps.
- [ ] Visit Portfolio Kanban — confirm Prospecting column shows REBL3-only cards with a "REBL3" pill; clicking links to the DD detail page.
- [ ] Wait 16 minutes — confirm cron tick happened (fetchedAt advances on cohort rows).