portfolio-optimiser/.claude/projects/2026-07-04-i4-ingest-sql-maf/brief.md
Kjell Tore Guttormsen 1b7612b22d docs(i4): brief + plan + golden fixture builder (I4)
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).
2026-07-04 06:56:08 +02:00

76 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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).