Merge commit 'cfaf67010017eab368216aded483a64126dbcb2e' as 'bahn/wissensdatenbank'

This commit is contained in:
2026-06-30 21:19:25 +02:00
4724 changed files with 667022 additions and 0 deletions
+293
View File
@@ -0,0 +1,293 @@
# Wissensdatenbank ETL-Pipeline
## Worum geht es? (in einfach)
Dies ist die **zentrale Wissenssammlung** fuer den 1st-Level-Support und Chatbots rund um
DB InfraGO. Wir **sammeln hier das Wissen** aus vielen Quellen (Confluence, Webseiten,
PDFs), pruefen es und legen es **versioniert im Git-Repo** ab. Damit ist dieses Repo die
**Single Source of Truth**: Wenn etwas hier steht und freigegeben ist, gilt es - und genau
das lesen die Chatbots.
**Was ist „ETL"?** Drei einfache Schritte, die bei jeder Aufnahme passieren:
1. **Extract = Sammeln.** Wissen aus den Quellen holen (Confluence-Seiten, Webseiten, PDFs).
2. **Transform = Aufbereiten.** In einheitliches Markdown umwandeln, mit Schlagworten/Scope
versehen und vertrauliche Inhalte herausfiltern.
3. **Load = Ablegen.** Das geprueefte Wissen versioniert nach `output/processed/` schreiben -
die zentrale Wahrheit, aus der Chatbots/Vektor-DB lesen.
Kurz: **Quellen rein → aufbereiten/pruefen → als Single Source of Truth ablegen.**
## Ziel
Klar definieren, **welches Wissen in Chatbots darf und welches nicht.** Wissen wird bei
der Aufnahme speziell verarbeitet und bereitgestellt, sodass Chatbots es nutzen koennen.
Klassifikation nach Scope:
- **allgemein** darf intern **und** extern genutzt werden; nicht toolspezifisch
(z.B. Regulierung/INB, Kundeninformationen).
- **intern** nur DB InfraGO intern.
- **extern** public im Internet bzw. fuer EVUs/EIUs.
Technisch eine **ETL-Pipeline fuer LLM/RAG**: holt Wissen aus Confluence, Webseiten und
PDFs, klassifiziert es nach Scope, filtert vertrauliche Inhalte und gibt nur geprueftes
Wissen frei (`output/processed/<scope>/...` = Feed fuer die Vektor-DB).
> Architektur & vollstaendiger Wissensfluss inkl. Diagramme:
> [`.kiro/steering/architecture.md`](.kiro/steering/architecture.md)
> · Manuelle Einrichtungsschritte: [`docs/SETUP.md`](docs/SETUP.md)
## Denkmodell
```
Domaene -> Scope (intern|extern|allgemein) -> Tool -> Dokument(e)
```
- **Domaene**: fachlicher Bereich (jedes Tool ist eigene Domaene; `regulierung`, `kundeninfo` fuer allgemeines Wissen).
- **Scope**: `intern` | `extern` | `allgemein` (allgemein gilt fuer intern UND extern).
- **Tool**: eine Fachanwendung aus dem 1st-Level-Support (Katalog automatisch aus Seite 428909879).
- **Strategie pro Quelle** siehe Tabelle unten.
### Strategien (Verarbeitung pro Quelle)
| Strategie | Was sie tut | Wichtige Options |
|-----------|-------------|------------------|
| `confluence_page` | genau diese eine Seite | `scope` (auch `intern,extern`), `incremental`, `attachments` |
| `confluence_tree` | Seite inkl. Unterseiten | `max_depth` (-1=alle, 0=nur Seite), `incremental`, `attachments` |
| `confluence_faq` | FAQs aus Tabellen | `incremental` |
| `crawler` | Webseite: Index → Detailseiten | `detail_pattern`, `selector`, `max_pages` |
| `sitemap` | Detailseiten aus (gz-)Sitemap | `sitemap_url`, `url_pattern`, `limit`, `incremental` |
| `gitlab_md` | Markdown-Dateien aus einem GitLab-Repo | `ref`, `path`, `max_depth` (Token: `GITLAB_TOKEN`) |
| `file` | Dateien (md/pdf) aus dem Repo-Ordner `files/` | `max_depth` |
| `pdf` | PDFs einer Seite → Markdown (1→n) | `split: headings\|pages`, `heading_pattern`, `keep_raw`, `redact` |
### Confluence-Anhaenge (eingebundene PDFs)
Bei `confluence_page`/`confluence_tree` werden **eingebundene PDF-Anhaenge**
(`view-file`/`viewpdf`-Makros) automatisch geparst und als **eigene Dokumente** im
selben Feed abgelegt (Frontmatter `kind: attachment`, `parent_url` zeigt auf die Seite).
Scope/Domain/Owner werden von der Seite geerbt. Inkrementell ueber die Attachment-Version
(unveraenderter Anhang -> kein Re-Parse). Default an, pro Quelle ueber
`options: { attachments: false }` abschaltbar.
Bilder werden nicht binaer uebernommen; der **alt-Text/Dateiname** bleibt als
`[Bild: ...]` im Markdown - gibt dem RAG Kontext ohne Volumen. Datei-Embeds ohne
Parsing-Pfad (z.B. Office-Docs) tauchen als `[Anhang: ...]`-Marker im Seitentext auf.
### Mehrere Quellen pro Tool
Ein Tool kann beliebig viele, gemischte Quellen haben einfach weitere Eintraege
unter `sources:` anhaengen (z.B. mehrere Confluence-Quellen mit `max_depth`, eine
interne und eine externe Seite, oder zusaetzlich eine FAQ-Seite). Siehe die Tools
`pathos` (mehrere Quellen inkl. `confluence_faq` und `intern,extern`) und `nur`.
### intern vs. extern wie wird klassifiziert?
Es gibt **keine Inhaltstrennung innerhalb einer Seite** mehr. Klassifiziert wird pro
Tool und pro Quelle:
**Tool-`scope`** (in `config/tools.yaml`):
| Tool-`scope` | Bedeutung |
|--------------|-----------|
| `intern` | alles intern (Default, restriktiv z.B. Salesforce, NuR) |
| `extern` | alles extern |
| `allgemein` | gilt fuer intern UND extern |
| `mixed` | Tool hat Quellen mit **unterschiedlichem** Scope (z.B. eine interne und eine externe Seite) |
**Source-`scope`** (pro Quelle, gilt fuer die ganze Seite):
- `intern` | `extern` | `allgemein`
- `"intern,extern"` → die Seite wird fuer **beide** Scopes genutzt (dupliziert).
Quellen ohne eigenen `scope` erben den Tool-Scope (bei `mixed` => intern).
**Wichtig:** Hat ein Tool gemischtes Wissen, braucht es im Zweifel **zwei Seiten**
(eine interne, eine externe) als zwei Quellen statt einer gemischten Seite.
Allgemeines, tool-uebergreifendes Wissen steht separat in **`config/general.yaml`**.
Der Tool-Katalog wird **initial** mit `scripts/bootstrap_tools.py` erzeugt und danach
**manuell** in `config/tools.yaml` gepflegt. Uebersicht aller Quellen: GitLab-Pages-Seite
**„Wissensquellen"** (`config.html`, geparst aus tools.yaml/general.yaml/approvals.yaml).
## Neues Wissen hinzufuegen (Workflow)
Jedes Wissen hat **owners** (intern Verantwortliche, Accountability) und einen
**contact** (herausgebbare Kontaktadresse, Default `einfachbahn@deutschebahn.com`).
Beide stehen im Frontmatter jedes Dokuments und auf der Pages-Seite, damit klar ist,
wer verantwortlich ist und an wen man sich wenden darf.
1. **Issue anlegen** mit der Vorlage „Neues Wissen" (`.gitlab/issue_templates/`):
Tool/Domaene, **Quellen je Zeile** (`URL | Strategie | Scope`), **Verantwortliche**
(`owners`) und optional **Kontakt** (Default `einfachbahn@deutschebahn.com`).
2. **Reviewer uebernimmt** die Angaben nach `config/tools.yaml` bzw. `config/general.yaml`
(neuer Tool-/Quellen-Eintrag inkl. `owners:`) per GitLab Web-IDE (Variante A) oder
manuell (Variante B).
3. **Merge Request** öffnen. Die **MR-Freigabe ist das Quality Gate** (4-Augen, via
`CODEOWNERS` + Protected Branch).
4. **Vorschau** im MR: den `preview`-Job manuell starten und `PREVIEW_ONLY` auf
Tool-Id/Domaene/URL der neuen Quelle setzen. Er verarbeitet nur diese Quelle und
stellt die erzeugten Markdowns als Job-Artefakt bereit. Lokal alternativ
`python -m src.main --only "<tool|domaene|url>" --data preview`.
5. **Merge** → der ETL-Schedule verarbeitet die Quelle (stuendlich MoFr, inkrementell) und
committet das Wissen; die Vektor-DB liest direkt aus `output/processed/<scope>/...`.
## Freigabe-Prozess
```
MR mergen (Mensch) -> ETL: Extract -> Transform -> Auto-Filter -> output/processed/ -> Vektor-DB
```
- **Freigabe = Merge Request (Mensch, das eigentliche Gate):** Wer eine Quelle
(Link + Strategie + Scope) in `config/tools.yaml`/`general.yaml` eintraegt, stellt einen
MR. Beim **Merge** (4-Augen via `CODEOWNERS` + Protected Branch) wird entschieden,
welche Quelle mit welchem Scope aufgenommen wird. Danach laeuft der ETL automatisch.
- **Auto-Filter im ETL (Sicherheitsnetz, kein Freigabe-Knopf):** setzt pro Dokument
`review_status` standardmaessig **approved**:
- `approved` -> `output/processed/...` (**direkt live**, sobald gemergt + ETL gelaufen)
- `pending` -> `staging/pending/` (Blacklist-Treffer, Scope-Warnung, oder
Inhalt < 50 Zeichen Grund im Frontmatter `review_notes`)
- `trusted: true` (z.B. INB) bleibt trotz Treffer approved; `redact: false` schaltet
die Redaction pro Quelle ab.
- **`config/approvals.yaml` (nachtraegliche Korrektur):** hebt einzelne
`pending`-Dokumente per URL oder `hash:<content_hash>` doch auf `approved`.
Kein Schritt, den jedes Dokument durchlaeuft. Den Hash zeigt die Pages-Seite (Detail-Fenster).
- **Vektor-DB liest direkt aus `output/processed/<scope>/<domaene>/`** (implementiert):
Nur freigegebenes Wissen liegt dort, der Scope steckt im Ordnerpfad **und** im
Frontmatter (inkl. `owners`/`contact`). Kein separater Export-/Ingestion-Schritt
`processed/` IST der Feed. Der externe Index nimmt nur `extern/` + `allgemein/`.
- **`output/_meta.json`**: globale Statusdatei fuer nachgelagerte Systeme mit
`last_run` (letzter ETL-Lauf), `last_change` (wann sich der Bestand zuletzt
inhaltlich/metadatenseitig geaendert hat), `documents`, `by_scope` und einer
`content_signature`. Ein Index-/RAG-Consumer kann daran erkennen, ob ein erneutes
Einlesen ueberhaupt noetig ist.
- **`output/_index.json`**: dokument-genauer Katalog des gesamten Bestands.
Pro Voll-Dokument: `domain`/`tool`/`scope`, `url`, `path`, `content_hash`,
`last_updated` und - falls vorhanden - die zugehoerigen `chunks` (Anzahl + Pfad)
und `attachments` (Anhang-Dokumente: Name + Pfad). Anhang-Dokumente haben zudem
`kind: "attachment"` und `parent_url`. Aggregate: `documents_total`,
`chunks_total`, `attachments_total`, `by_domain` (mit `documents`/`chunks`/`attachments`),
`by_scope`. So sieht ein Anschliesser auf einen Blick, was pro Domaene/Tool wo liegt
und welche Dokumente zusaetzlich als Chunks oder Anhaenge vorliegen (z.B. INB =
Voll-Dokument **und** Chunks; pathOS-Seite = Voll-Dokument **und** Anhang-PDF).
Deterministisch (kein Zeitstempel) -> aendert sich nur bei echten Bestandsaenderungen.
- **`output/run_log.jsonl`**: append-only **Lauf-Historie** (1 JSON-Zeile je ETL-Lauf):
Zeitstempel, verarbeitete Dokumente, Status-Counts und **Fehler je Quelle**
(z.B. fehlgeschlagene Confluence-Abrufe). Auf die letzten 500 Laeufe gekappt. So sieht
man - auch historisch - ob ein Lauf sauber durchlief, ohne die fluechtigen CI-Job-Logs
zu durchsuchen. Die Uebersichtsseite zeigt zusaetzlich eine kompakte Health-Zeile
(„N Dok verarbeitet, M Quellen mit Fehler") in der Fusszeile.
### Wo liegt das final freigegebene Wissen?
Ausschliesslich unter **`output/processed/<scope>/<domaene>/[<tool>/]`** im Repo
(versioniert, = Single Source of Truth). `pending`-Dokumente liegen in
`staging/pending/` und werden zur **Transparenz ebenfalls committet** (auf der
Pages-Seite einsehbar). Der Audit-Trail steht in `staging/review_report.json` und in
der Git-Historie.
## Lokal testen
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
ruff check src tests # Lint
python -m pytest -q # Offline-Tests
python -m src.main --config config/tools.yaml --data output --staging staging # Live-Lauf
python -m src.chunk --data output # Chunks erzeugen (offline, optional)
```
## Confluence aktivieren
```bash
export CONFLUENCE_URL="https://arija-confluence.jaas.service.deutschebahn.com"
export CONFLUENCE_TOKEN="<PAT>" # Bearer-PAT (Server/DC)
pip install atlassian-python-api
```
Ohne Credentials werden Confluence-Quellen sauber uebersprungen.
## Betrieb (GitLab CI)
- `quality`-Stage: `ruff` (Lint) + `gitleaks` (Secret-Scan) bei jedem Push/MR.
- `etl`-Stage: **stuendlich MoFr 817 Uhr** (Cron `0 8-17 * * 1-5`), baut Wissen,
committet `data/` nach `main` (per `GIT_PUSH_TOKEN`); der Commit loest automatisch
einen `pages`-Refresh aus.
- `pages`-Stage: baut `public/` (Uebersicht, Hilfe, Chatbot-Anschluss, Wissensquellen)
fuer GitLab Pages auf `main`.
- `preview`-Job (MR, manuell): verarbeitet eine neue/geaenderte Quelle via
`--only` und stellt die erzeugten Markdowns als Job-Artefakt bereit (zum Pruefen
vor dem Merge). `PREVIEW_ONLY` beim Start auf Tool-Id/Domaene/URL setzen.
- **Kundeninfos inkrementell**: `incremental: true` crawlt nur NEUE Detailseiten und
ueberschreibt vorhandene nicht Re-Runs bleiben schnell.
- **Confluence inkrementell**: `options: { incremental: true }` prueft pro Seite die
Confluence-Versionsnummer (`source_version` im Frontmatter). Unveraenderte Seiten
werden uebersprungen, nur geaenderte/neue Seiten neu verarbeitet spart API-Last.
- **Re-Tagging bei Metadaten-Aenderung**: Aenderst du in `tools.yaml`/`general.yaml`
nur `tags`/`owners`/`contact` einer Quelle, weicht deren `meta_fingerprint`
(im Frontmatter) vom gespeicherten ab. Der naechste inkrementelle Lauf passt dann
die bestehenden Dateien dieser Quelle in-place an (nur Frontmatter, kein erneuter
Download/API-Call).
- **Scope-Wechsel**: Aenderst du den `scope` einer Quelle, wird die Seite neu
verarbeitet und an `output/processed/<neuer-scope>/...` geschrieben; die alte Ablage
wird nach erfolgreichem Schreiben **automatisch geloescht** (domaenen-intern, damit
eine URL, die legitim unter mehreren Domaenen liegt z.B. eine pathOS-Seite auch
unter `web` nicht versehentlich entfernt wird).
- **Domain-Umbenennung**: Die neue Ablage entsteht automatisch, die alte Domain-Ablage
bleibt aber stehen (domaenenuebergreifendes Loeschen waere wegen geteilter URLs
unsicher). Den alten `output/processed/<scope>/<alte-domain>/`-Ordner daher bei einer
Domain-Umbenennung manuell loeschen.
- CI/CD-Variablen: `GIT_PUSH_TOKEN`, `CONFLUENCE_URL`, `CONFLUENCE_TOKEN` (Masked).
- `scm-info.yaml`: Pflichtdatei fuer DB-GitLab-Compliance.
## Konfiguration
- `config/tools.yaml` Tools, Domaenen, Quellen, Strategien, Scopes, Tags
- `config/filter_rules.json` Blacklist-Keywords, Regex-Redaction
- `config/chunking.yaml` gemeinsame Chunk-Defaults (pro Quelle ueber `options.chunk` ueberschreibbar)
### Chunking (optional, Default aus)
Grosse, stark gegliederte Dokumente (v.a. **INB**) werden zusaetzlich zur Voll-Datei in
**Chunks entlang der Ueberschriften** zerlegt ein **abgeleitetes, jederzeit neu
erzeugbares** Artefakt unter `output/chunks/<scope>/<domaene>/<docslug>/`. Das Voll-Dokument
in `output/processed/` bleibt unangetastet (Parent-Document-Muster): ein Anschliesser waehlt
Voll-Dokument, Chunks oder beides. Es werden bewusst nur **deterministische, embedding-freie**
Strategien angeboten (`headings | faq | recursive`); semantisches Chunking gehoert in die
Vektor-DB des Anschliessers. Jeder Chunk bekommt eine kurze **Contextual-Retrieval**-Zeile
(`> Kontext: <Dokument> > <Abschnitt>`). Aktivierung pro Quelle via `options: { chunk: headings }`.
**Inkrementell (kein Voll-Rebuild):** Chunking laeuft am Ende jedes ETL-Laufs automatisch
mit (`src.main`) und ist Teil von `data/`. Pro Dokument wird nur dann neu gechunkt, wenn
sich der **Inhalt** (`parent_hash`) ODER die **wirksamen Optionen/Strategie**
(`chunk_fingerprint`) geaendert haben - sonst wird das Dokument uebersprungen (keine
Datei-Aenderung, kein Git-Churn). Aenderst du also bei einer Quelle z.B. `chunk_level`
oder von `headings` auf `recursive`, werden **nur die Dokumente dieser Ablage** neu
erzeugt. Wird `chunk` wieder auf `off` gestellt oder ein Voll-Dokument geloescht, werden
die zugehoerigen Chunks automatisch entfernt.
**Feinsteuerung (Default aus, pro Quelle ueberschreibbar):**
- `chunk_min_doc_chars`: Dokumente unter dieser Zeichenzahl bleiben **ganz** (kleine
FAQ-/How-to-Seiten muessen nicht zwingend gechunkt werden).
- `chunk_kind`: chunkt nur Dokumente mit passendem `kind` (`""` = alle; `attachment` =
nur Anhang-PDFs; `document` = nur Voll-Seiten).
Beispiel **pathOS** (an der Tool-Tree-Quelle in `config/tools.yaml`): grosse Anhang-PDFs
(z.B. EVU-Schnittstellen-Doku, 2,8 MB) werden gechunkt, Seiten und FAQs bleiben ganz.
```yaml
options:
incremental: true
chunk: headings
chunk_kind: attachment # nur Anhang-PDFs chunken
chunk_min_doc_chars: 3000 # < 3k Zeichen bleiben ganz (~750 Token)
```
## Version & Changelog
Aktuelle Version steht in **`VERSION`** (SemVer), die Historie in **`CHANGELOG.md`**
(Format „Keep a Changelog"). Beides wird auf der GitLab-Pages-Seite **„Changelog"**
angezeigt (Version auch in der Navigationsleiste). Pflege-Regeln:
`.kiro/steering/changelog-versioning.md`.