/tmp/restic-stage/reports-reference/content-extraction-readonly-import-20260702.md
# Content Extraction — Read-only Import aus YouTube Research — 2026-07-02
**Status: Importfunktion implementiert, lokal getestet (inkl. Idempotenz), Prozess sauber beendet. Kein systemd, keine Nginx-Bindung.**
## Vorher-Pruefung (Schritt 1)
- Grundgeruest /opt/struktur/content-extraction/ existierte (app.py, db.py, config.py, templates/, data/, venv/)
- content_extraction.db existierte
- ce_sources existierte, war leer (0 Zeilen)
- knowledge.db lesbar, 877 Videos, mtime 2026-07-02 12:39:57.497487969 UTC
- YouTube Research lief unveraendert (MainPID 1952952, aktiv seit 12:33:39 UTC)
## Backup-Pfade
- /opt/struktur/content-extraction/backups/db.py.20260702-145540.bak
- /opt/struktur/content-extraction/backups/app.py.20260702-145540.bak
- /opt/struktur/content-extraction/backups/content_extraction.db.20260702-145540.bak
## Geaenderte Content-Extraction-Dateien
- db.py — Migration fuer 4 neue Spalten in ce_sources (summary, skill_file, is_high_value, claude_score; per ALTER TABLE ADD COLUMN falls nicht vorhanden) + neue Funktionen get_last_import_stats() und import_sources_from_youtube_research()
- app.py — neue Routen POST /admin/import-sources und GET /admin/import-status, index() liefert zusaetzlich last_import an das Template
- templates/index.html — neuer Bereich "Quellenimport" mit Button, Statusanzeige, JS-Fetch-Aufruf (keine automatische Ausfuehrung beim Laden)
Keine Aenderung an: /opt/struktur/content-extraction/config.py, /opt/struktur/youtube-research/ (gesamter Ordner unangetastet).
## Warum eine Schema-Migration noetig war
Die urspruengliche ce_sources-Tabelle (aus dem Grundgeruest-Schritt) enthielt die Spalten summary, skill_file, is_high_value, claude_score noch nicht -- diese wurden aber in diesem Auftrag explizit als Pflichtfelder gefordert. Statt die Tabelle neu anzulegen (Datenverlust-Risiko), wurde eine additive Migration in init_db() ergaenzt (_migrate_ce_sources_columns via PRAGMA table_info + ALTER TABLE ADD COLUMN, nur fuer fehlende Spalten). Beim App-Start automatisch und idempotent ausgefuehrt, verifiziert durch fehlerfreien Start und korrekt befuellte Felder im Testlauf.
## Importlogik (Kurzbeschreibung)
- get_youtube_research_ro() oeffnet knowledge.db ausschliesslich per SQLite-URI mode=ro
- Join aus videos + source_processing_registry (LEFT JOIN ueber source_system='youtube-research' AND source_id=CAST(v.id AS TEXT)), identisch zur bereits in der MVP-Spezifikation dokumentierten Referenzabfrage
- Fuer jede Zeile: Lookup in ce_sources ueber UNIQUE(source_system, source_video_id) -- existiert sie nicht, INSERT; existiert sie und Felder haben sich geaendert, UPDATE; sonst unveraendert gelassen
- UNIQUE-Constraint auf DB-Ebene verhindert zusaetzlich strukturell jede Duplikatbildung, auch falls die Anwendungslogik einen Fehler haette
- Keine Fundus-Tabellen (fundus_collections, fundus_items) werden gelesen oder migriert
## Ergaenzte Routen
- GET /sources — zeigt aktuelle ce_sources (unveraendert von vorherigem Schritt, jetzt befuellt)
- POST /admin/import-sources — stoesst den Import manuell an, liefert Statistik zurueck
- GET /admin/import-status — zeigt die zuletzt gelaufene Importstatistik (In-Memory, ueber _last_import_stats)
## UI-Ergaenzung
Auf der Startseite (/) neuer Bereich "Quellenimport": Beschreibungstext, Button "Quellen aus YouTube Research importieren", Live-Statusanzeige nach Klick (Fetch gegen /admin/import-sources, danach /sources zur Aktualisierung der Quellenanzahl). Keine automatische Ausfuehrung beim Laden der Seite -- Import startet ausschliesslich per Klick.
## Lokaler Test ohne systemd (Schritt 6)
App manuell gestartet: venv/bin/uvicorn app:app --host 127.0.0.1 --port 8100 (nohup, kein systemd-Unit).
| Schritt | Ergebnis |
|---|---|
| /health | 200 — {"status":"ok","app":"content-extraction"} |
| /sources vor Import | 200 — {"sources":[]} (leer) |
| POST /admin/import-sources (1. Lauf) | 200 — read_count 877, inserted 877, updated 0, unchanged 0, error null |
| /sources nach Import | 200 — 877 Eintraege, alle Pflichtfelder befuellt (Beispiel geprueft: title, channel, language, transcript_status, summary, skill_file, is_high_value, claude_score, obsidian_artifact_path, graphiti_status, graphiti_import_key, youtube_url, imported_at) |
| /admin/import-status | 200 — zeigt identische Statistik wie letzter Import |
| POST /admin/import-sources (2. Lauf, Idempotenz-Test) | 200 — read_count 877, inserted 0, updated 0, unchanged 877, error null |
| ce_sources nach 2. Lauf | weiterhin 877 Zeilen, keine Duplikate (GROUP BY source_video_id HAVING COUNT(*) > 1 liefert 0 Treffer) |
Prozess danach sauber beendet (kill), Port 127.0.0.1:8100 anschliessend frei, kein content-extraction-Prozess mehr aktiv.
## Verifikation (Schritt 7)
| Groesse | Vorher | Nachher |
|---|---|---|
| knowledge.db mtime | 2026-07-02 12:39:57.497487969 UTC | 2026-07-02 12:39:57.497487969 UTC — **identisch, keine Aenderung** |
| knowledge.db Zeilenzahl videos | 877 | 877 — unveraendert |
| content_extraction.db ce_sources Zeilenzahl | 0 | 877 |
knowledge.db wurde nachweislich nicht veraendert (mtime exakt gleich vor und nach zwei Importlaeufen).
## Bestaetigung: YouTube Research unveraendert
MainPID und ActiveEnterTimestamp von youtube-research.service vor und nach diesem Auftrag identisch (1952952, seit 12:33:39 UTC). Keine Datei unter /opt/struktur/youtube-research/ angefasst, kein systemctl-Befehl gegen den Service ausgefuehrt.
## Bestaetigung: kein systemd / kein Nginx / kein oeffentlicher Zugriff
- Kein neuer systemd-Unit angelegt oder gestartet.
- Keine Nginx-Config angelegt.
- Keine Subdomain aktiviert.
- App lief nur temporaer manuell auf 127.0.0.1, wurde nach dem Test beendet.