Grid/contacts unification step 1: real contact_id link + grid as front door (v0.1.0:52)

Structural fix for the duplicate-people class of bug: instead of matching a grid
contact "pill" to a contacts row heuristically by name/email (which drifted and
caused the 1406 double-count), link them by id.

Backend:
- Migration 0004: fundraising_contacts.contact_id (additive, nullable, logical FK
  to contacts(id)) + index. Paired down migration.
- sync_fundraising_relational now stores the id that _upsert_contact_from_fundraising
  already returns, so every grid contact carries its contacts-table id.
- _backfill_grid_contact_ids: one-time, idempotent backfill on startup (re-runs the
  grid sync once if any row lacks contact_id), so existing data links immediately.
- entity_resolution: grid pass prefers the explicit contact_id link (match_kind
  'grid_link') over heuristic email / name+investor, guarded by a PRAGMA check so
  older DBs without the column still work.

Frontend:
- Fundraising grid "+ Row" -> "+ Investor" (clear, single investor entry point).
- Contacts page: the "+ Add Contact" trigger is replaced by a pointer to the grid;
  the page is now a read/search/edit view (ContactDetailPanel still edits all
  fields). New people are added from the grid. No contact data is removed.

Tests: backend/ingest/test_entity_resolution.py extended (explicit-link case, 11/11)
and a new backend/test_grid_contact_link.py integration test (init_db applies 0004,
sync populates contact_id to the right contact, re-sync is idempotent). py_compile +
frontend html.parser clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Keysat
2026-06-05 15:10:26 -05:00
parent d16264f401
commit 2afed210cb
10 changed files with 203 additions and 41 deletions
+21
View File
@@ -0,0 +1,21 @@
import { VersionInfo } from '@start9labs/start-sdk'
// Grid/contacts unification (step 1): the fundraising grid is the single front
// door. Adds a real link (fundraising_contacts.contact_id, migration 0004) from
// each grid contact "pill" to its contacts-table row — populated by the grid sync
// and backfilled once on upgrade — so entity resolution links by id instead of
// heuristic name/email matching (ending the duplicate-people class of bug). UI:
// the grid's "+ Row" is now "+ Investor"; the Contacts page is a read/search/edit
// view (new people are added from the grid). Additive migration; no data loss.
export const v_0_1_0_52 = VersionInfo.of({
version: '0.1.0:52',
releaseNotes: {
en_US: [
'Grid/contacts unification: the Fundraising Grid is now the single place to',
'add investors and contacts, each grid contact is linked to its contact record',
'by a real id (no more name-matching), and the Contacts page becomes a',
'view/search/edit list. Existing links are backfilled automatically on upgrade.',
].join(' '),
},
migrations: { up: async () => {}, down: async () => {} },
})