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).
This commit is contained in:
parent
4f45fe6037
commit
1b7612b22d
3 changed files with 201 additions and 0 deletions
76
.claude/projects/2026-07-04-i4-ingest-sql-maf/brief.md
Normal file
76
.claude/projects/2026-07-04-i4-ingest-sql-maf/brief.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# 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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue