Session brief (scope, non-goals, pinned SQL type/number decisions, verification) + TDD plan + build_fixture.py (regenerates the sqlite golden; the committed db bytes need not reproduce — the golden compares the derived bundle).
76 lines
4.6 KiB
Markdown
76 lines
4.6 KiB
Markdown
# I4 — SQL-kilde på MAF-siden (brief)
|
||
|
||
> **Økt-type:** kode-økt, offline, IKKE gatet (lokalt/offline; driftsmodellens klarsignal
|
||
> dekker den). Andre kildetype i ingest-programmet, mot I1s polymorfe manifest-skjema UTEN
|
||
> spec-endring. Speiler I2 (`file`/CSV) for `sql`.
|
||
>
|
||
> **Kilder:** [ingest-spec](../../../shared/ingest-spec.md) (normativ, FROSSET) · [målbilde
|
||
> ingest](../../../docs/plan/2026-07-03-maalbilde-ingest-lag.md) · [sesjonsplan §I4](../../../docs/plan/2026-07-03-sesjonsplan-ingest.md).
|
||
|
||
## Mål (én lukket leveranse)
|
||
|
||
sqlite-konnektor på MAF-siden mot det polymorfe manifest-skjemaet: `manifest (source.type:
|
||
sql)` → read-only sqlite-uttrekk → materialisert OKF-bundle (samme §5–§7 provenance + §6
|
||
index som `file`), hele veien load-bearing- og golden-testet. `materialize()` går fra å
|
||
avvise alt ≠ `FileSource` til å dispatche `FileSource → read_csv`, `SqlSource → read_sql`
|
||
(`http` forblir I6-avvist).
|
||
|
||
## Suksesskriterier (hver = verifiserbar)
|
||
|
||
1. `read_sql(connection_ref, query, *, max_rows)` eksekverer et read-only SELECT mot en
|
||
sqlite-fil resolvet fra env-varen `connection_ref` navner, med §5-typet celle-rendering.
|
||
2. `materialize()` materialiserer et `sql`-manifest til en bundle bit-identisk med en golden
|
||
(`examples/ingest-golden-sql/`), idempotent ved re-run.
|
||
3. Nye load-bearing-tester (SQL-navigerbarhet + §5-type-sømmer) er RØDE ved detach (bevist).
|
||
4. Full suite grønn uten nøkkel/nettverk; `okf.py`/`shared/`/`run.py` urørt; ruff+format+mypy
|
||
grønne; null modellkall.
|
||
|
||
## Non-goals (bygg IKKE)
|
||
|
||
- Ingen spec-/commons-endring (spec FROSSET; oppdages drift → STOPP, egen GATET runde).
|
||
- Ingen `http`-konnektor (I6), ingen `python -m`-CLI, ingen live-DB, ingen ORDER-BY-håndhevelse
|
||
i kode (manifest-forfatters SHOULD, §4), ingen per-rad-filsplitting, ingen inkrementell
|
||
re-ingest, ingen index-typing-RFC (åpen, utsatt).
|
||
- Ingen søsken-repo-endring (I5, GATET).
|
||
|
||
## Låste beslutninger — SQL §5-type/tall-regler (delegert HIT av STATE; pinnes i kode+golden)
|
||
|
||
STATE: «CSV-celle-verbatim-regelen utsatte tall-/type-håndtering eksplisitt til sql/I4 →
|
||
SQL-kolonners type/tall-regler avgjøres HER.» Konsistente med §5, ikke en spec-endring:
|
||
|
||
| Python-type (sqlite default) | §5-tekst | Begrunnelse |
|
||
|---|---|---|
|
||
| `None` (SQL NULL) | `""` (tom) | §5 «SQL NULL as the empty string» |
|
||
| `int` (INTEGER) | `str(v)` plain decimal | §5 «integers in plain decimal» |
|
||
| `float` (REAL) | `repr(v)` korteste round-trip | §5 «non-integral numbers … shortest round-trip decimal form»; `repr` er Pythons korteste round-trip (ekstrem magnitude → E-notasjon, akseptert som korteste form) |
|
||
| `str` (TEXT) | verbatim (escapes i `render_table`) | §5 «text verbatim» |
|
||
| `bool` | `IngestError` | sqlite emitter den ikke; guard hindrer `str(True)`→`"True"` silent-coercion |
|
||
| `bytes` (BLOB) / annet | `IngestError` | §5 «any other value type MUST fail (never silent coercion)» |
|
||
|
||
**Konnektor-regler:**
|
||
- `connection_ref` = env-var-NAVN → verdi = **sti til sqlite-fil** (I4-scope: sqlite-fixture).
|
||
Env-var usatt → `IngestError` fail-fast; fil mangler → `IngestError` (speiler `read_csv`).
|
||
- Read-only: åpnes `file:{quote(path)}?mode=ro` med `uri=True` → et skrive-forsøk feiler på
|
||
DB-nivå (§4 «SHOULD enforce read-only», robust — ikke skjør streng-parsing).
|
||
- Single-statement: `Connection.execute` avviser flere statements naturlig (§4 «one statement»).
|
||
- `max_rows`: streaming-tak, `IngestError` ved overskridelse (§8, aldri silent truncation —
|
||
speiler `read_csv`).
|
||
- sqlite3-feil (syntaks, skrive-forsøk, korrupt db) → wrappes som `IngestError` (fail-fast,
|
||
ærlig — som `read_csv`s `FileNotFoundError`-wrapping).
|
||
|
||
## Antakelser (med test)
|
||
|
||
- **A1: sqlite3 default-typing gir int/float/str/bytes/None** (ingen registrerte konvertere)
|
||
→ testet i `test_ingest_sql.py` (typet fixture-db, én rad per type).
|
||
- **A2: `mode=ro` håndhever read-only** → test: skrive-query mot ro-connection raiser.
|
||
- **A3: golden-db-bytene påvirker ikke bundle-determinismen** (kun DB-INNHOLD via query;
|
||
env-sti lekker aldri inn i frontmatter) → golden bit-deterministisk uansett checkout-sti.
|
||
|
||
## Verifisering (kommandoer)
|
||
|
||
- `uv run pytest -q` — 211+ passerer, ingen ny skip, ingen nett/nøkkel.
|
||
- Detach-bevis (manuelt, revertert): bryt hver ny load-bearing-søm → bekreft RØD → revert.
|
||
- `uv run ruff check . && uv run ruff format --check . && uv run mypy src` — grønne.
|
||
- `git diff --stat` — `src/portfolio_optimiser/okf.py`, `shared/`, `src/portfolio_optimiser/run.py`
|
||
IKKE i diffen.
|
||
- MAF-fri: `test_ingest_module_is_maf_free_and_context_layer_pure` grønn (kun stdlib lagt til).
|