Files
WCX/docs/database-schema-gap-analysis.md

464 lines
19 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.

# Gap-analys för reproducerbart facitdatabasschema
## 1. Omfattning och metod
Analysen är avgränsad till om facitdatabasens struktur kan återskapas från
versionshanterade filer. Följande har inspekterats:
- `docs/project-split-analysis.md` och
`docs/project-split-analysis-summary.md`,
- `scripts/schema.sql`,
- databasberoendena i `import_site.py`, `migrate_csv.py`, `check_ocr.py`,
`process_pending_ocr.py` samt de externa read-only-konsumenterna
`/storage/disk1/WCX-collection/scripts/match_filenames.py` och
`/storage/disk1/WCX-collection/scripts/diagnose_duration_match.py`,
- `sqlite_schema`, `PRAGMA table_xinfo`, `PRAGMA foreign_key_list`,
`PRAGMA index_list` och `PRAGMA index_xinfo` i
`/storage/disk1/WCX/database/wcx.db`, öppnad med `sqlite3 -readonly`, och
- resultatet av att läsa `scripts/schema.sql` i en separat SQLite-databas i
minnet.
**Verifierat faktum:** Produktionsdatabasen öppnades endast med
`sqlite3 -readonly`. Ingen kontroll eller verifiering skrev till den.
**Verifierat faktum:** `PRAGMA integrity_check` gav `ok` och
`PRAGMA foreign_key_check` gav inga rader. Både `PRAGMA user_version` och
`PRAGMA application_id` är `0`.
**Antagande:** Schemat som finns i den inspekterade `wcx.db` är den avsedda
produktionsmodellen. Analysen kan verifiera faktisk struktur och kodberoenden,
men inte om varje historiskt schemaingrepp var avsiktligt.
## 2. Fullständigt faktiskt produktionsschema
### 2.1 Tabeller
**Verifierat faktum:** Följande användardefinierade tabeller finns i
produktionen. SQL-definitionerna nedan är hämtade från `sqlite_schema`. Den
kompakta placeringen av OCR-kolumnerna på samma rad som `modified_at` är bara
hur SQLite lagrade den stegvis ändrade tabellens SQL-text; den ändrar inte
kolumnernas semantik.
```sql
CREATE TABLE movie (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
aka TEXT,
nationality TEXT,
age INTEGER,
shoot_location TEXT,
shoot_date TEXT,
duration_seconds INTEGER,
description TEXT,
rating REAL,
web_url TEXT,
thumbnail TEXT,
published TEXT,
updated TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
modified_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
ocr_status TEXT NOT NULL DEFAULT 'pending',
ocr_raw_text TEXT,
ocr_error TEXT,
ocr_processed_at TEXT,
CHECK (age IS NULL OR age >= 0),
CHECK (rating IS NULL OR rating BETWEEN 0 AND 10),
CHECK (duration_seconds IS NULL OR duration_seconds >= 0)
);
CREATE TABLE movie_history (
history_id INTEGER PRIMARY KEY AUTOINCREMENT,
movie_id TEXT NOT NULL,
name TEXT NOT NULL,
aka TEXT,
nationality TEXT,
age INTEGER,
shoot_location TEXT,
shoot_date TEXT,
duration_seconds INTEGER,
description TEXT,
rating REAL,
web_url TEXT,
thumbnail TEXT,
published TEXT,
updated TEXT,
created_at TEXT,
modified_at TEXT,
ocr_status TEXT,
ocr_raw_text TEXT,
ocr_error TEXT,
ocr_processed_at TEXT,
archived_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
change_source TEXT NOT NULL DEFAULT 'site_import',
change_summary TEXT NOT NULL,
FOREIGN KEY (movie_id) REFERENCES movie(id)
);
CREATE TABLE movie_name_alias (
alias_id INTEGER PRIMARY KEY AUTOINCREMENT,
movie_id TEXT NOT NULL,
alias TEXT NOT NULL,
normalized_alias TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'manual',
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (movie_id) REFERENCES movie(id),
UNIQUE (movie_id, normalized_alias)
);
```
**Verifierat faktum:** SQLite har dessutom skapat den interna tabellen
`sqlite_sequence(name, seq)` eftersom `movie_history.history_id` och
`movie_name_alias.alias_id` använder `AUTOINCREMENT`. Den ska inte deklareras
manuellt i projektets schema.
### 2.2 Index
**Verifierat faktum:** Produktionen innehåller följande explicita index:
```sql
CREATE INDEX idx_movie_history_movie_id
ON movie_history(movie_id);
CREATE INDEX idx_movie_history_archived_at
ON movie_history(archived_at);
CREATE INDEX idx_movie_name_alias_movie_id
ON movie_name_alias(movie_id);
CREATE INDEX idx_movie_name_alias_normalized
ON movie_name_alias(normalized_alias);
```
**Verifierat faktum:** SQLite har också skapat två unika automatiska index:
| Index | Ursprung | Nyckelkolumner | Partiellt |
| --- | --- | --- | --- |
| `sqlite_autoindex_movie_1` | `PRIMARY KEY` | `movie.id` | nej |
| `sqlite_autoindex_movie_name_alias_1` | `UNIQUE` | `movie_id`, `normalized_alias` | nej |
Alla sex index använder stigande ordning och `BINARY`-kollation. De fyra
explicita indexen är icke-unika och icke-partiella.
### 2.3 Constraints
**Verifierat faktum:** Produktionsschemat deklarerar:
- primärnyckel på `movie.id`,
- primärnyckel med `AUTOINCREMENT``movie_history.history_id`,
- primärnyckel med `AUTOINCREMENT``movie_name_alias.alias_id`,
- `NOT NULL` enligt SQL-definitionerna ovan,
- standardvärdena `CURRENT_TIMESTAMP`, `'pending'`, `'site_import'` och
`'manual'` enligt SQL-definitionerna ovan,
- `CHECK` för icke-negativ `age` och `duration_seconds` samt `rating` i
intervallet 010,
- främmande nyckel från `movie_history.movie_id` till `movie.id`, med
`NO ACTION` för både update och delete,
- främmande nyckel från `movie_name_alias.movie_id` till `movie.id`, med
`NO ACTION` för både update och delete, och
- unikhet för kombinationen
`movie_name_alias(movie_id, normalized_alias)`.
**Verifierat faktum:** Det finns ingen `CHECK`-constraint som begränsar
`ocr_status` till de statusvärden som koden använder (`pending`, `completed`,
`manual_review` och `failed`).
**Verifierat faktum:** Främmande nycklar är del av schemat, men SQLite kräver
att `PRAGMA foreign_keys = ON` aktiveras per anslutning för enforcement.
`import_site.py` aktiverar detta. De övriga inspekterade Python-skripten gör
det inte. Detta är ett runtime-beteende, inte en strukturell skillnad mellan
produktionsschemat och Git-schemat.
### 2.4 Triggers och views
**Verifierat faktum:** Produktionsdatabasen innehåller inga triggers och inga
views. Historisering görs uttryckligen av `import_site.py`, inte av en trigger.
## 3. Schema som kan återskapas från Git i dag
**Verifierat faktum:** När `scripts/schema.sql` läses i en tom SQLite-databas
skapas `movie`, `movie_history`, `movie_name_alias`, den interna
`sqlite_sequence`, samma fyra explicita index och samma två automatiska index
som i produktionen. Inga triggers eller views skapas.
Det återskapade Git-schemats `movie` är:
```sql
CREATE TABLE IF NOT EXISTS movie (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
aka TEXT,
nationality TEXT,
age INTEGER,
shoot_location TEXT,
shoot_date TEXT,
duration_seconds INTEGER,
description TEXT,
rating REAL,
web_url TEXT,
thumbnail TEXT,
published TEXT,
updated TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
modified_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (age IS NULL OR age >= 0),
CHECK (rating IS NULL OR rating BETWEEN 0 AND 10),
CHECK (duration_seconds IS NULL OR duration_seconds >= 0)
);
```
Git-schemats `movie_history` och `movie_name_alias` är semantiskt identiska
med definitionerna i avsnitt 2.1. De omfattar redan samtliga fyra
OCR-kolumner i `movie_history`. Git skapar även exakt de fyra explicita index
som visas i avsnitt 2.2.
**Verifierat faktum:** `IF NOT EXISTS` i Git-filen men inte i den lagrade
produktionsdefinitionen är ingen strukturell skillnad i den skapade databasen.
**Verifierat faktum:** Git innehåller ingen versionsmarkör eller
migrationshistorik. En nyskapad databas får `user_version = 0`, precis som den
inspekterade produktionsdatabasen.
## 4. Exakt jämförelse
### 4.1 Skillnader
**Verifierat faktum:** Den fullständiga strukturella skillnaden är fyra
kolumner som finns i produktionens `movie` men saknas i Git-schemats `movie`:
| Position i produktion | Kolumn | Typ | `NOT NULL` | Standardvärde | Saknas från Git |
| ---: | --- | --- | --- | --- | --- |
| 16 | `ocr_status` | `TEXT` | ja | `'pending'` | ja |
| 17 | `ocr_raw_text` | `TEXT` | nej | inget | ja |
| 18 | `ocr_error` | `TEXT` | nej | inget | ja |
| 19 | `ocr_processed_at` | `TEXT` | nej | inget | ja |
Konsekvenserna är konkreta:
- nya `movie`-rader från `import_site.py` förlitar sig på standardvärdet
`'pending'`,
- OCR-kön kan inte läsa `ocr_status` eller `ocr_error`,
- OCR-resultat kan inte lagras, och
- `import_site.py` kan inte läsa en komplett rad för arkivering till
`movie_history`.
### 4.2 Element som inte skiljer sig
**Verifierat faktum:** Följande matchar exakt semantiskt mellan produktion och
en tom databas skapad från Git:
- tabellerna `movie_history` och `movie_name_alias`, inklusive samtliga
kolumner, ordning, typer, nullbarhet och standardvärden,
- alla `movie`-kolumner som föregår OCR-kolumnerna,
- samtliga tre `CHECK`-constraints på `movie`,
- båda främmande nycklarna och deras `NO ACTION`-beteende,
- unikhetskravet på `(movie_id, normalized_alias)`,
- samtliga fyra explicita index,
- båda automatiska unika indexen,
- avsaknaden av triggers, och
- avsaknaden av views.
**Verifierat faktum:** Inga tabeller, index, constraints, triggers eller views
finns endast i Git-schemat. Den interna tabellen `sqlite_sequence` uppstår i
båda fallen och är inte ett gap.
## 5. Skriptberoenden per schemaelement
### 5.1 `movie`
| Skript | Lästa eller skrivna element | Förutsättning |
| --- | --- | --- |
| `import_site.py` | Alla 20 produktionskolumner läses för befintliga poster; webbägda fält och `modified_at` uppdateras; nya poster infogas. | Kräver de fyra OCR-kolumnerna för `SELECT` och historisering. Nya poster förlitar sig på `ocr_status DEFAULT 'pending'`. Förlitar sig även på `movie_history`, dess OCR-kolumner och obligatoriska `change_summary`. Aktiverar främmande nycklar. |
| `migrate_csv.py` | Läser `id`; infogar eller uppdaterar alla metadatafält samt `ocr_status`; uppdaterar `modified_at`. | Kräver `ocr_status`. Anger status explicit och förlitar sig därför inte på dess default i sina egna inserts. Skriptet aktiverar inte främmande nycklar. |
| `check_ocr.py` | Läser `name`, `thumbnail`; skriver `nationality`, `shoot_location`, `shoot_date`, samtliga fyra OCR-kolumner och `modified_at`. | Kräver alla fyra OCR-kolumnerna. Använder statusvärdena `completed`, `manual_review` och `failed`, men schemat validerar inte statusdomänen. |
| `process_pending_ocr.py` | Läser `id`, `name`, `published`, `ocr_status`, `ocr_error`. | Kräver `ocr_status` och `ocr_error`; förutsätter att nya obehandlade poster får status `pending`. |
| `/storage/disk1/WCX-collection/scripts/match_filenames.py` | Läser `id`, `name`, `duration_seconds`. | Extern read-only-konsument; fungerar mot både produktions- och Git-schemat. |
| `/storage/disk1/WCX-collection/scripts/diagnose_duration_match.py` | Läser `id`, `name`, `duration_seconds`. | Externt manuellt diagnosverktyg som öppnar facitdatabasen read-only. |
### 5.2 `movie_history`
| Skript | Beroende |
| --- | --- |
| `import_site.py` | Infogar den föregående kompletta `movie`-raden, inklusive samtliga OCR-fält, samt `change_source` och `change_summary`. `archived_at` lämnas till standardvärdet. |
| `/storage/disk1/WCX-collection/scripts/match_filenames.py` | Läser `movie_id`, `duration_seconds` och `archived_at`. |
| `/storage/disk1/WCX-collection/scripts/diagnose_duration_match.py` | Läser `movie_id` och `duration_seconds`. |
**Verifierat faktum:** Inget skript förutsätter en historiseringstrigger;
`import_site.py` gör historik-insert och film-update i samma anslutningskontext.
### 5.3 `movie_name_alias`
| Skript | Beroende |
| --- | --- |
| `/storage/disk1/WCX-collection/scripts/match_filenames.py` | Läser `movie_id`, `alias`, `normalized_alias` och `source`. |
**Verifierat faktum:** Inget inspekterat skript skriver alias. Constraints och
index på aliastabellen finns ändå likadant i produktion och Git.
## 6. Minsta säkra åtgärd
**Rekommendation:** Gör en enda avgränsad schemaändring: lägg till de fyra
verifierade produktionskolumnerna i `movie` i `scripts/schema.sql`, direkt
efter `modified_at`, med exakt följande definitioner:
```sql
ocr_status TEXT NOT NULL DEFAULT 'pending',
ocr_raw_text TEXT,
ocr_error TEXT,
ocr_processed_at TEXT,
```
Detta är den minsta åtgärden eftersom alla andra tabeller, index och
constraints redan reproduceras. Lägg inte samtidigt till triggers, views,
nya index, status-`CHECK`, ändrad främmande-nyckelpolicy eller andra
modellförbättringar; sådana ändringar skulle gå utöver att återskapa dagens
produktion.
**Rekommendation:** Behandla den korrigerade `schema.sql` som ett komplett
grundschema för helt nya databaser. Verifiera det först mot en ny temporär
databas. Kör inte den kompletta filen som en påstådd reparationsmigration mot
produktion: `CREATE TABLE IF NOT EXISTS` kompletterar inte kolumner i en redan
existerande tabell.
**Antagande:** Produktionsdatabasen behöver ingen strukturändring för detta
gap, eftersom den redan har exakt de fyra föreslagna kolumnerna. Den behöver
endast adopteras av en framtida migrationsmodell efter separat kontroll.
## 7. Enkel migrationsmodell för SQLite
**Rekommendation:** Inför en liten, versionshanterad modell med två delar:
1. `schema.sql` är aktuell baseline för nya, tomma databaser.
2. En katalog med ordnade migrationer, exempelvis
`migrations/0001_add_movie_ocr_columns.sql`, beskriver övergångar för äldre
databaser.
Använd en liten Python-runner baserad på standardbibliotekets `sqlite3` och en
tabell som exempelvis:
```sql
CREATE TABLE schema_migration (
version INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
Runnern bör:
- öppna en explicit vald databas och aldrig ha produktionssökvägen som dold
teststandard,
- aktivera `PRAGMA foreign_keys = ON`,
- kontrollera befintliga kolumner med `PRAGMA table_xinfo` före migration,
- köra varje ej registrerad migration atomärt i en transaktion,
- registrera versionen först när hela migrationen lyckats,
- avbryta om databasen har en okänd eller partiell struktur, och
- köra `PRAGMA foreign_key_check` efteråt.
**Rekommendation:** Eftersom SQLite saknar portabelt
`ALTER TABLE ... ADD COLUMN IF NOT EXISTS` ska migration `0001` inte bara
exekveras blint. Runnern ska skilja mellan tre tillstånd:
- inga OCR-kolumner finns: lägg till alla fyra och registrera migrationen,
- alla fyra finns med exakt rätt typ, nullbarhet och default: registrera
migrationen som adopterad utan att ändra tabellen, och
- endast några finns eller definitionerna avviker: avbryt och kräv manuell
analys.
Exempel på de faktiska övergångssatserna för ett äldre Git-skapat schema är:
```sql
ALTER TABLE movie
ADD COLUMN ocr_status TEXT NOT NULL DEFAULT 'pending';
ALTER TABLE movie
ADD COLUMN ocr_raw_text TEXT;
ALTER TABLE movie
ADD COLUMN ocr_error TEXT;
ALTER TABLE movie
ADD COLUMN ocr_processed_at TEXT;
```
**Rekommendation:** Använd antingen `schema_migration` eller konsekvent
`PRAGMA user_version` som versionskälla, inte två oberoende sanningar. Tabellen
är enklare att granska eftersom den även lagrar migrationsnamn och tidpunkt.
Nuvarande `user_version = 0` visar att ingen versionsmodell används i dag.
## 8. Verifieringsplan utan produktionsskrivningar
Alla steg nedan ska använda en ny katalog skapad med `mktemp -d` och en ny
databasfil där. Ingen testparameter får peka på
`/storage/disk1/WCX/database/wcx.db`.
1. **Skydda referensen.** Registrera produktionsdatabasens filstorlek, mtime
och kryptografiska hash före testet. Använd därefter endast
`sqlite3 -readonly` om referensmetadata behöver läsas igen.
2. **Skapa baseline.** Skapa `${TEMP_DIR}/wcx.db` från den korrigerade
`schema.sql`.
3. **Jämför struktur maskinellt.** Jämför tabeller, kolumner, ordning, typer,
nullbarhet, defaults, primärnyckelflaggor, främmande nycklar, explicita och
automatiska index, indexkolumner, triggers och views mot den dokumenterade
produktionsmodellen. Ignorera endast harmlösa skillnader i SQL-format och
`IF NOT EXISTS`.
4. **Kontrollera constraints.** Verifiera att ogiltig negativ `age` och
`duration_seconds`, rating utanför 010, dubblett av
`(movie_id, normalized_alias)` samt främmande nyckelbrott med
`PRAGMA foreign_keys = ON` nekas i temporärdatabasen.
5. **Kontrollera defaults.** Infoga en minimal `movie(id, name)` och verifiera
att `created_at` och `modified_at` sätts samt att `ocr_status = 'pending'`
och övriga OCR-fält är `NULL`.
6. **Testa import.** Kör `import_site.py` med explicit `--input` till en liten
temporär JSON-fil och explicit `--database` till temporärdatabasen. Kör en
andra import med nyare uppdateringsdatum och verifiera att
`movie_history` får hela den föregående raden inklusive OCR-fälten.
7. **Testa CSV-operationer.** Kör först `migrate_csv.py --dry-run`. Om apply
behöver provas, använd endast explicit `--database` till temporärdatabasen
och en isolerad test-CSV.
8. **Testa OCR:s databasberoenden utan API-anrop.** Anropa de rena
databasfunktionerna i `check_ocr.py` mot temporärdatabasen för statusfallen
failed, manual review och completed. Verifiera alla skrivna metadata- och
OCR-kolumner. Testa `process_pending_ocr.py`-frågorna separat så att
`pending` kan väljas och `ocr_error` läsas. Gör inget Vision API-anrop.
9. **Testa konsumentfrågorna.** Kör matchningskodens databasladdning och
längddiagnostik mot temporärdatabasen efter att film, alias och historik har
lagts in.
10. **Testa migrationsvägen separat.** Skapa ytterligare en ny temporär
databas från dagens ofullständiga Git-schema, kör migration `0001`, och
upprepa strukturjämförelsen. Testa även adoption mot en ny databas som
redan har alla fyra korrekta kolumner samt avbrott mot ett medvetet
partiellt schema.
11. **Kör integritetskontroller.** Kräv `PRAGMA integrity_check = 'ok'` och
tomt resultat från `PRAGMA foreign_key_check` i varje temporärdatabas.
12. **Bekräfta produktionsskyddet.** Kontrollera att produktionens storlek,
mtime och hash är identiska med värdena från steg 1. Radera därefter endast
den explicit skapade temporärkatalogen.
**Rekommendation:** Godkänn reproducerbarheten först när både baseline-vägen
och migrationsvägen ger samma semantiska schema som avsnitt 2 och samtliga
databasberoende skript klarar sina isolerade operationer mot temporärdata.
## 9. Slutsats
**Verifierat faktum:** Facitdatabasen är nästan, men inte helt,
reproducerbar från Git. Gapet består exakt av fyra OCR-kolumner i `movie`.
Inga tabeller, index, constraints, triggers eller views saknas utöver dessa
kolumndefinitioner, och inga sådana objekt skiljer sig i övrigt.
**Rekommendation:** Komplettera baselineschemat med de fyra exakta
produktionsdefinitionerna och inför en liten, transaktionell migrationsrunner
som kan migrera äldre Git-skapade databaser och adoptera en redan korrekt
produktion utan att ändra den. All utveckling och verifiering ska ske mot nya
temporära databaser.