Files
dre/backend/db.py
T
root 7a62b0b340 Backend Aug 22-25: AI analysis, welcome-packet templating, LetterStream, DocuSeal, staff RBAC + tier gate
- analysis.py: deterministic claim scorer + /analyze /approve /letter /advance-tier endpoints (auto-runs on intake)
- packet.py + packet_fields.json: welcome-packet templating engine (6 onboarding docs, field catalog)
- letterstream.py + letters.py: certified-mail send pipeline + letter lifecycle (webhook verified)
- docuseal.py: DocuSeal signing integration
- staff.py/models.py/schema.sql/auth.py: approval actor from staff key, tier gate (APPROVED+ACTIVE+onboarding docs), onboarding_docs table
- frontend/: dependency-free static portal (intake, magic-link login/verify, dashboard)
- landing-mockups/: 4 design-stance mockups + favicons
- legal/: aup/privacy/sms-terms/terms HTML
- docs/: letter-queue scope, letterstream API contract, 6 welcome-packet templates
- review-dre-landing-2026-08-21.md: 3-variant landing feedback sprint
- compliance/DRE_Compliance_Manual.md: updated

Source synced from deployed /opt/dre-portal/app/ (was 4 days ahead of git).
2026-08-26 02:26:33 -04:00

143 lines
4.8 KiB
Python

"""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()