Files
swp-01-ingest/NOTES.md
T
irrlichtandClaude Sonnet 5 c43d8496bf Branding auf Wurzelwerk (SWP) umstellen, Deployment systemagnostisch machen
Ersetzt "Lagebild" durch den Projektnamen Wurzelwerk (Stinkwurzpresse) in
allen Units, Configs und Docs. Entfernt konkrete Hostnamen (Bebop, Pi 5,
Debby) aus README/NOTES zugunsten generischer Rollenbezeichnungen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013vHZCwcCJnz4LKNgvT4bxv
2026-09-04 20:28:57 +02:00

135 lines
4.7 KiB
Markdown

# Wurzelwerk — Ingest
Stufe 0 und 1 der Pipeline: Quellenregister und Roh-Erfassung. Keine
Kodierung — was hier landet, soll sich beliebig oft neu kodieren lassen.
## Dateien
| Datei | Zweck |
|---|---|
| `sources.toml` | Quellenregister, einzige Wahrheit ueber die Quellen |
| `schema.sql` | Postgres-Schema (`quellen`, `raw_items`, `codings`, `poll_laeufe`) |
| `poller.py` | Poller, zwei Senken: Postgres und gzip-JSONL |
| `Containerfile` | Image fuer den Produktivbetrieb |
| `wurzelwerk-ingest.container`, `wurzelwerk.network` | Quadlet-Units, rootless |
| `ingest.env.example` | Vorlage fuer die Zugangsdaten |
## Deploy mit Postgres + Podman/Quadlet (rootless)
```sh
# 1. Schema einspielen
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f schema.sql
# 2. Image bauen
podman build -t localhost/wurzelwerk-ingest:latest .
# 3. Konfiguration ablegen
mkdir -p ~/.config/wurzelwerk
cp ingest.env.example ~/.config/wurzelwerk/ingest.env
cp sources.toml ~/.config/wurzelwerk/sources.toml
chmod 600 ~/.config/wurzelwerk/ingest.env
$EDITOR ~/.config/wurzelwerk/ingest.env
# 4. Quadlet installieren
mkdir -p ~/.config/containers/systemd
cp wurzelwerk-ingest.container wurzelwerk.network ~/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start wurzelwerk-ingest
journalctl --user -u wurzelwerk-ingest -f
# Damit der Dienst ohne offene Sitzung weiterlaeuft:
loginctl enable-linger "$USER"
```
`sources.toml` ist eingehaengt, nicht ins Image gebacken: eine neue Quelle ist
ein `systemctl --user restart wurzelwerk-ingest`, kein Neubau.
## Schatten-Ingest auf einem Zweitgeraet
Ohne Postgres, ohne Container:
```sh
python3 poller.py --sink jsonl:/pfad/zu/deinem/ablageordner
```
Legt `<dir>/YYYYMMDD/YYYYMMDDHHMMSS.jsonl.gz` an, dedupliziert ueber eine
SQLite daneben. Als `systemd --user`-Unit mit `Restart=always` einrichten.
Der Sinn ist nicht Redundanz um ihrer selbst willen: RSS-Feeds haben kein
Archiv. Steht der Produktiv-Poller einen Tag still, ist dieser Tag
unwiederbringlich weg. Kodierungen lassen sich wiederholen, Rohdaten nie.
## Betrieb
```sh
# Lizenzstatus im Register gegen die aktuelle robots.txt pruefen
python3 poller.py --check-robots # Exit != 0 bei Abweichung
# ein einzelner Durchlauf
python3 poller.py --sink "$DATABASE_URL" --once
```
`--check-robots` gehoert in einen woechentlichen Cron. Ein Verlag, der seine
robots.txt aendert, aendert damit seinen Nutzungsvorbehalt — und das ist der
Punkt, an dem eine Quelle stillgelegt werden muss.
Aendert sich etwas, wird **`sources.toml` angepasst, nie der Code.** Eine
gesperrte Quelle bleibt im Register stehen (`aktiv = false`), damit
nachvollziehbar bleibt, warum sie fehlt. Der Poller verweigert den Start,
wenn eine als `gesperrt` gefuehrte Quelle aktiv geschaltet ist.
### Waechter
```sql
-- aktive Quellen, die seit ueber sechs Stunden nichts Neues liefern
select * from quellen_stillstand;
-- Fehler der letzten Stunde
select quelle, http_status, fehler, begonnen_am from poll_laeufe
where fehler is not null and begonnen_am > now() - interval '1 hour';
```
### Arbeitsvorrat fuer Yantra
```sql
select * from unkodiert('qwen3-14b-q4/prompt-1') limit 500;
```
Liefert alle Items, die diese Kodierer-Version noch nicht gesehen hat. Eine
neue Prompt-Version ist damit automatisch ein vollstaendiger Recode-Auftrag
ueber die gesamte Historie, ohne dass ein Feed erneut abgerufen wird.
## Verhalten
- **15-Minuten-Takt**, an den Slotgrenzen ausgerichtet (+30 s Versatz),
identisch zu GDELTs Paketgrenzen — damit bleiben die Slots vergleichbar.
- **Bedingter GET** ueber ETag/Last-Modified, im Register gespeichert. Im
Test antworteten 4 von 12 Quellen im zweiten Durchlauf mit 304.
- **Dedup** ueber `unique (quelle, item_key)`; `item_key` ist guid, ersatzweise
Link, ersatzweise ein Titel-Hash.
- **Fehlertoleranz**: eine kaputte Quelle beendet den Durchlauf nicht, der
Fehler landet in `poll_laeufe`.
- **Kein Volltext-Abruf.** Nur der Feed selbst wird geholt. Titel und Teaser
reichen als Kodierungsinput, und damit stellt sich die TDM-Frage nach
§ 44b UrhG gar nicht erst.
## Gemessen (2026-09-04)
Erster Lauf ueber 12 aktive Quellen: 820 Items gesehen, 810 neu, gesamter
Durchlauf unter 3 s. Zweiter Lauf unmittelbar danach: 1 neues Item.
Das Trigramm-Clustering findet quellenuebergreifende Dubletten sofort:
```
zeit | handelsblatt | 1.00 | Bekennerschreiben: Erneut Sabotage am Stromnetz
spiegel | derstandard | 0.92 | Niederlage fuer Donald Trump - Gericht in Missouri
```
## Offen
- Telegram-Fetcher (Telethon/MTProto). Register ist vorbereitet, Eintraege
stehen auf `aktiv = false`. Die Bot-API kann fremde Kanaele nicht lesen.
- Ersatz-URL fuer den BR24-Feed (aktuell 404).
- Quellenpool von 12 auf ~40 erweitern; Regionalzeitungen ueber SearXNG
suchen.