Priority: medium Complexity: high Status: planned
The codebase uses raw sqlite3 with hand-written SQL strings throughout:
history_compare_store.py — 10+ queries with repeated WHERE patternsusage_backends.py — dynamic IN-clause constructionhistory_ingest.py, history_normalize.py, history_derive.py — pipeline stage queriescost_audit.py — usage resolution queriesAs query complexity grows (joins, subqueries, optional filters, aggregations), raw SQL becomes harder to maintain, test, and refactor safely. Dynamic SQL construction requires careful nosec annotation to satisfy bandit, and optional WHERE clauses use the (? IS NULL OR col = ?) workaround pattern.
Migrate to SQLAlchemy Core (SQL Expression Language) as the query-building layer:
select(), where(), join() expressionsTable / Column definitions as the schema source of truthSQLAlchemy ORM is not needed — the data model is read-heavy analytics, not entity lifecycle management. Core alone provides:
.where() chaining instead of SQL string patternssqlalchemy as a dependencyTable objects for existing warehouse tables (derived_goals, derived_session_usage, derived_projects, etc.)history_compare_store.py (most repetitive queries)sqlite3 in modules that don’t benefit from the migration (simple single-query lookups)