refactor composer to use sqlalchemy, avoid string concatenations and follow conventions

This commit is contained in:
2026-06-03 12:10:30 -03:00
parent cbc7df8c60
commit be09fcde2c
16 changed files with 546 additions and 273 deletions

42
api/composer/encodings.py Normal file
View File

@@ -0,0 +1,42 @@
"""Storage-encoding library — how a column's raw storage maps to a value the
composer can compare against.
Some warehouses store logical types in surprising physical encodings (BIRD
ships YYMMDD-encoded *integer* "dates", for instance). The composer needs a way
to coerce such a column to a comparable value without baking any one dataset's
quirk into the generic SQL builder.
The model is **defaults with override**:
- `DEFAULT_ENCODINGS` ships the generic, reusable patterns here — keyed by the
`Column.semantic_type` a dataset declares in `schema_docs.yaml`.
- A dataset may add a novel encoding or override a default via the optional
`encodings:` section of its `schema_docs.yaml` (carried onto
`Recon.encodings`). The dataset always wins.
Each encoding is plain data: a map of role → SQL template with a `{col}`
placeholder for the (already-qualified) column reference. Today the only role
is `as_date` — the expression that yields a DATE for `date_range` filters; new
roles are additive.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from api.recon.types import Recon
DEFAULT_ENCODINGS: dict[str, dict[str, str]] = {
# BIRD-style YYMMDD stored as a 6-digit integer → real DATE.
"date_yymmdd": {"as_date": "TO_DATE(LPAD({col}::text, 6, '0'), 'YYMMDD')"},
# Add further reusable patterns here (e.g. epoch_seconds, julian_day) as
# they recur across datasets — never a single dataset's one-off quirk.
}
def effective_encodings(recon: "Recon") -> dict[str, dict[str, str]]:
"""The encoding map the composer should use for `recon`: built-in defaults
overlaid with the dataset's own `encodings` (dataset entries win)."""
return {**DEFAULT_ENCODINGS, **(recon.encodings or {})}