"""SQLite connection helper — stdlib sqlite3, parameterized queries only. PRAGMA foreign_keys=ON, journal_mode=WAL, busy_timeout=5000 on every connection. No ORM. Initializes schema on first boot and seeds number_sequences for current year. """ from __future__ import annotations import os import sqlite3 import uuid from datetime import datetime, timezone from pathlib import Path from typing import Any _SCHEMA_FILE = Path(__file__).resolve().parent / "schema.sql" def _utcnow_iso() -> str: return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") def get_db_path() -> str: return os.environ.get("DRE_DB_PATH", "/opt/dre-portal/data/dre.db") def get_upload_dir() -> str: return os.environ.get("DRE_UPLOAD_DIR", "/opt/dre-portal/data/uploads") def _apply_pragmas(conn: sqlite3.Connection) -> None: conn.execute("PRAGMA foreign_keys = ON;") conn.execute("PRAGMA journal_mode = WAL;") conn.execute("PRAGMA busy_timeout = 5000;") def get_conn() -> sqlite3.Connection: """Return a connection with pragmas applied and row factory.""" db_path = get_db_path() Path(db_path).parent.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(db_path, timeout=5.0) conn.row_factory = sqlite3.Row _apply_pragmas(conn) return conn def init_db() -> None: """Create schema if absent, migrate, and seed number_sequences for the current year.""" with get_conn() as conn: schema_sql = _SCHEMA_FILE.read_text() conn.executescript(schema_sql) _migrate(conn) # Seed sequences for current year (idempotent) year = datetime.now(timezone.utc).year for prefix in ("DRE", "CLT"): conn.execute( "INSERT OR IGNORE INTO number_sequences (prefix, year, last_value) VALUES (?, ?, 0)", (prefix, year), ) conn.commit() def _migrate(conn: sqlite3.Connection) -> None: """Add analysis/approval/letter columns to `claims` if they don't already exist. SQLite lacks `ADD COLUMN IF NOT EXISTS`, so we inspect PRAGMA table_info first. Idempotent — safe to run on every boot. """ existing = {row["name"] for row in conn.execute("PRAGMA table_info(claims)").fetchall()} additions = { "no_other_agency_at": "TEXT", "no_prior_action_at": "TEXT", "analysis_score": "INTEGER", "analysis_summary": "TEXT", "analysis_components": "TEXT", "analysis_at": "TEXT", "recommended_tier": "TEXT", "approval_status": "TEXT NOT NULL DEFAULT 'NONE'", "approval_decision_by": "TEXT", "approval_decision_at": "TEXT", "letter_subject": "TEXT", "letter_body": "TEXT", "letter_tier": "TEXT", "letter_updated_at": "TEXT", } for col, ddl in additions.items(): if col not in existing: conn.execute(f"ALTER TABLE claims ADD COLUMN {col} {ddl}") # onboarding_docs -> DocuSeal e-sign state (added later than the base schema) existing_od = {row["name"] for row in conn.execute("PRAGMA table_info(onboarding_docs)").fetchall()} od_additions = { "docuseal_submission_id": "TEXT", "docuseal_submitter_id": "TEXT", "docuseal_slug": "TEXT", "docuseal_embed_src": "TEXT", "docuseal_status": "TEXT", "docuseal_sent_at": "TEXT", } for col, ddl in od_additions.items(): if col not in existing_od: conn.execute(f"ALTER TABLE onboarding_docs ADD COLUMN {col} {ddl}") def new_uuid() -> str: return str(uuid.uuid4()) def utcnow_iso() -> str: return _utcnow_iso() def next_sequence_number(conn: sqlite3.Connection, prefix: str) -> str: """Atomically allocate the next DRE-YYYY-NNNN / CLT-YYYY-NNNN in a single transaction. Uses UPDATE ... RETURNING (SQLite 3.35+) — no races.""" year = datetime.now(timezone.utc).year # Ensure row exists conn.execute( "INSERT OR IGNORE INTO number_sequences (prefix, year, last_value) VALUES (?, ?, 0)", (prefix, year), ) cur = conn.execute( "UPDATE number_sequences SET last_value = last_value + 1 WHERE prefix = ? AND year = ? " "RETURNING last_value", (prefix, year), ) row = cur.fetchone() if row is None: # Should not happen, but handle defensively raise RuntimeError(f"failed to allocate sequence for {prefix}/{year}") n = row[0] if isinstance(row, tuple) else row["last_value"] val = n if isinstance(n, int) else int(n) return f"{prefix}-{year}-{val:04d}" def query_one(conn: sqlite3.Connection, sql: str, params: tuple[Any, ...] = ()) -> sqlite3.Row | None: cur = conn.execute(sql, params) return cur.fetchone() def query_all(conn: sqlite3.Connection, sql: str, params: tuple[Any, ...] = ()) -> list[sqlite3.Row]: cur = conn.execute(sql, params) return cur.fetchall()