Status: proposal, not started. Audience: IT engineering. Estimated total effort: 4–5 working days incl. validation and the auth piece (§4 step 4).
- Store: plain Excel file
data/master_database.xlsx— no DB server, no connection string, no network. - Access pattern: the Flask webapp opens the file directly on the local filesystem via
openpyxl. All reads/writes funnel through one module,IST_CD/db.py(load_contacts,save_contact,save_contacts,overwrite_all,get_stats, …) with a cross-process file lock and auto-backups todata/backups/. - Schema: one worksheet; columns =
_DB_COLUMNSinIST_CD/db.py(name, company, title, email, phone, address, country, category, tags, assistant fields, notes, verification status, …) plus a hidden immutable_Record ID(UUID) column and a sequentialcustomer_id. - Sidecar state (file-based, also to migrate):
data/metadata.json— per-contact diagnostic/enrichment metadata (keyed by record identity)data/dedup_state.json— Step 2 runs, pair assessments, merge plans, human decisionsdata/agent_feedback_log.jsonl— human review feedback events
- Implication: single-host, single-user; concurrency and backups are hand-rolled. This is why a real DB server is the goal.
Browser (LAN)
│ HTTP :8000
▼
Flask webapp (IST_CD) ── unchanged app code ──
│
▼
IST_CD/db.py ← single seam; implementation swapped only here
│ SQLAlchemy + psycopg, DATABASE_URL env var
▼
PostgreSQL 15+ (LAN host, :5432, TLS if available)
├── contacts (core table, see §3)
├── contact_metadata (JSONB diagnostic/enrichment data)
├── dedup_runs / dedup_plans / dedup_decisions
└── feedback_events
Excel remains: import/export format only (legacy import path already exists, unchanged).
Key point: the app never touches Excel directly today — it calls db.py functions. Migration reimplements those ~15 functions; no webapp/UI changes.
CREATE TABLE contacts (
record_id UUID PRIMARY KEY, -- from hidden _Record ID column
customer_id INTEGER UNIQUE NOT NULL, -- sequential, preserved
name TEXT NOT NULL,
company TEXT NOT NULL,
title TEXT,
email TEXT,
phone TEXT,
address TEXT,
country TEXT,
category TEXT,
industry TEXT,
tags TEXT,
assistant_name TEXT,
assistant_email TEXT,
assistant_phone TEXT,
notes TEXT,
verification_status TEXT DEFAULT 'unverified',
extra JSONB DEFAULT '{}', -- corrected_fields, evidence, flags
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Sidecar tables mirror the JSON files 1:1 (metadata, dedup state, feedback events).- Step 1 — Provision PostgreSQL 15+ (0.5 d, IT). LAN host; create database + least-privilege app user. Output: running instance.
- Step 2 — Create schema (0.5 d).
contacts+ sidecar tables per §3. Output: DDL applied. - Step 3 — Backend swap (1–1.5 d, dev). Reimplement
IST_CD/db.pyagainst SQLAlchemy with identical function signatures;DATABASE_URLenv var; delete hand-rolled file-lock/backup logic (Postgres handles concurrency and backups). Output: app running on Postgres with zero webapp/UI changes. - Step 4 — Minimal auth + reviewer identity (0.5–1 d, dev). Today there is no login and every review is recorded as "local-reviewer". Add: (a) simple login — a small user table (username + password hash) with Flask session, LAN-only; no SSO needed for pilot scale; (b) reviewer identity stamped on every approve/reject/merge decision (feeds
feedback_eventsand the dedup decision audit trail); (c) two roles at most — operator (import, run agents, review) and viewer (read-only). Output: multi-user access with accountability. - Step 5 — One-time ETL (0.5 d). Read Excel via existing
load_contacts()→ insert; importmetadata.json,dedup_state.json, feedback log. Output: migrated data. - Step 6 — Validation (0.5 d). Row counts, per-field diff on the 50-sample test set, full pytest suite, smoke test 6/6. Output: sign-off.
- Step 7 — Cutover (0.5 d). Set
DATABASE_URL, restart app; keep the Excel file read-only as a fallback snapshot. Output: go-live.
Decision needed before step 3: Step 2's review workflow currently writes color marks into the Excel workbook. Either (a) keep color-marking in an exported review workbook, or (b) store review state as Postgres columns and render colors in the UI. Recommend (b); adds ~0.5 d if chosen.
Set DATABASE_URL back to the Excel backend (keep old db.py as db_excel.py behind a backend switch), restart. Data loss risk ≈ 0: ETL is copy-only, Excel file untouched until cutover is accepted.
- Do this after pilot sign-off on the Excel store, not mid-pilot.
- Multi-user readiness = PostgreSQL (concurrent writes) + the auth piece (step 4). Neither alone is sufficient for several staff working with accountability.
- The existing 50-sample test set (
IST_test_sample_50.xlsx) is the validation fixture. - Bulk-import and dedup logic is backend-agnostic (operates on
Contactobjects), so no Sprint 1–3 pipeline changes are expected beyonddb.py.