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

19 KiB
Raw Blame History

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, match_filenames.py och test_duration_matching.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.

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:

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 AUTOINCREMENTmovie_history.history_id,
  • primärnyckel med AUTOINCREMENTmovie_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:

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.
match_filenames.py Läser id, name, duration_seconds. Fungerar mot både produktions- och Git-schemat.
test_duration_matching.py Läser id, name, duration_seconds. Fungerar mot både produktions- och Git-schemat.

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.
match_filenames.py Läser movie_id, duration_seconds och archived_at.
test_duration_matching.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
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:

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:

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:

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.