BranchScout: net-lease intelligence tool for bank branch acquisitions
BranchScout helps institutional investors find banks with owned branches they're likely to sell (sale-leaseback). It ingests public data from FDIC + NCUA, scores 8,648 institutions on a 100-point scale, and surfaces "Hot" targets in a deal-management UI. The app is live on Vercel; the data is in Supabase Seoul. Several features are partially working — knowing which ones helps you prioritize what to fix first.
6-layer thinking chainChuỗi tư duy 6 tầng
"coi lại trong explainer về dự án đã làm 1 cách trực quan dễ hiểu từng lớp layer của dữ liệu, use case cụ thể để người đọc tìm ra vấn đề nhanh nhất"
5-Layer System Architecture
Data flows top-to-bottom: public government sources to Python ingestion pipelines to Supabase PostgreSQL to Next.js API routes to React UI. Each layer has a specific responsibility. Problems in layer N ripple down to all layers below it — knowing which layer broke tells you exactly what to fix.
| Technical termThuật ngữ kỹ thuật | Plain nameTên dễ hiểu | RoleVai trò |
|---|---|---|
institutions | Bank or credit union master record | One row per financial institution. Contains financials (assets, deposits, loans), regulatory flags (CRA rating, enforcement actions), BBS score 0-100, and tier label (hot/warm/monitor). |
branches | Physical branch location record | One row per branch. Linked to institution. Has address, lat/lng (approximate via ZIP centroid for NCUA), deposit estimate, and is_owned field — currently NULL for all 101k rows because no pipeline populates it. |
BBS score | Branch Buy Score (0-100) | Hot is 70 or above, Warm is 45-69, Monitor is below 45. Factors: financial stress, ownership concentration, regulatory pressure, branch size, market competition. Owned % factor is currently skipped (renormalized) since data is missing. |
pipelines/scoring/score.py | Score calculator script | Reads institutions + branches from DB, computes per-institution BBS using weighted factors, writes back bbs_score + score_tier + score_factors JSON. Must be re-run after any data ingest. |
createAdminSupabaseClient() | Server-side DB connection with full access | All server-side API routes use the service role key, bypassing Row Level Security. Safe for an internal tool; would need RLS policies before multi-tenant or public use. |
owned_pct | Percentage of branches the bank owns vs leases | The marquee differentiator — investors want banks with many owned branches (sale-leaseback opportunity). Currently NULL for all institutions because branches.is_owned is never set by any pipeline. This is the core Phase 2 gap. |
migration 005 | Database patch for owned_pct = NULL | Sets owned_pct = NULL for all institutions instead of 0. COMMITTED to repo but NOT YET APPLIED to the live Supabase DB. If FDIC/NCUA ingest re-runs without applying this, the trigger recomputes owned_pct back to 0.0. |
Read the diagram in 30 seconds: green = live and working, yellow = working but with a caveat, red = broken or fix not deployed, dashed gray = feature stub with no real data. The entire owned-vs-leased story (the product's core pitch to investors) lives in the branches.is_owned field — which is currently empty across all 101,000 rows.
Data ingestion run order (must follow this exact sequence)
Layered decision cardsCác quyết định theo tầng
Artifact mapBản đồ tệp tạo ra
| PathĐường dẫn | WhatLà gì | Who reads itAi dùng |
|---|---|---|
supabase/migrations/001_core_schema.sql | Full DB schema — all tables, ENUMs, indexes, triggers | Apply once to Supabase SQL editor; foundation of everything |
supabase/migrations/002_app_compat.sql | Adds regulatory alert_type enum value, nullable institution_id on deals, new columns on alerts/documents/branches | Required before regulatory alerts can be inserted into the DB |
supabase/migrations/005_owned_pct_unknown.sql | Sets owned_pct=NULL for all institutions — removes false 0% display | WARNING: COMMITTED BUT NOT APPLIED to live DB — apply via Supabase SQL editor (project avubqzlpfdizxwsxhiwg) |
pipelines/fdic/ | FDIC bank ingestion pipeline: download ZIP to parse to ingest institutions + branches to load financials | Run quarterly. Produces 4,312 banks + ~60k branches. Amounts in $thousands. |
pipelines/ncua/ | NCUA credit union ingestion: mirrors FDIC pipeline; includes geocode step (ZIP to lat/lng via pgeocode) | Run quarterly. 4,336 CUs + 22k branches. Amounts divided by 1000. Branch deposits are estimates. |
pipelines/scoring/score.py | BBS score computation for all institutions | Run after every ingest. Writes bbs_score + score_tier + score_factors back to institutions table. |
pipelines/alerts/generate.py | Generates all 4 alert types: hot_prospect, credit_downgrade, regulatory, competitor_move | Run after scoring + competitor ingest. competitor_move type requires ingest to have run first. |
pipelines/competitors/ingest.py | Pulls rival acquirer news from NewsAPI into competitor_activities table | Needs NEWS_API_KEY. Free tier is non-commercial only. institution_id always NULL — no entity linking yet. |
apps/web/app/(dashboard)/institutions/[id]/page.tsx | Institution detail page — financials, score breakdown, branches list | WARNING: NPL/Tier-1 ratio x100 bug fixed locally (commit ad1a666) but NOT YET DEPLOYED to Vercel. Deployed app shows wrong values. |
apps/web/app/(dashboard)/map/ | Branch map using Leaflet. NCUA branches show as hollow dashed rings (approximate coordinates) | Dev-only crash with react-leaflet + StrictMode. Works in production build. |
landing/index.html | BranchScout marketing landing page — rebranded, live at branchscout.vercel.app | Static HTML, no build step. Screenshots are real app captures with CAPTURE_BYPASS=1 middleware. |
- CRITICAL: Apply migration 005_owned_pct_unknown.sql via Supabase SQL editor (project avubqzlpfdizxwsxhiwg) BEFORE the next ingest run — otherwise the DB trigger recomputes owned_pct back to 0.0 for all institutions
- Deploy uncommitted local changes: cd /Users/trang/Projects/branchiq && git add apps/web && git commit -m 'fix: npl tier1 ratio x100 bug, rebrand BranchIQ to BranchScout' && git push origin main. This fixes the ratio bug that shows 44% as 4400% on the institution detail page.
- Add NEWS_API_KEY to Vercel env vars and local .env to enable competitor news ingest (free key at newsapi.org — note the non-commercial restriction on the free tier)
- Re-tune Hot/Warm tier thresholds (currently 70/45) — Hot tier jumped from 576 to 2,343 institutions after rescoring; may want to raise the threshold to maintain a useful Hot tier size
- To connect branchscout.com domain: Vercel project branchscout → Add Domain → copy A/CNAME records to Porkbun DNS → then remove X-Robots-Tag noindex from vercel.json
- Phase 2 (the core differentiator): populate branches.is_owned via parcel data (Regrid API) or FDIC premises proxy to enable real owned-vs-leased scoring — this unlocks the full BBS scoring weight and the product's main pitch