# 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 `data/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 (`data/processed//...` = 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 "" --data preview`. 5. **Merge** → der ETL-Schedule verarbeitet die Quelle (stuendlich Mo–Fr, inkrementell) und committet das Wissen; die Vektor-DB liest direkt aus `data/processed//...`. ## Freigabe-Prozess ``` MR mergen (Mensch) -> ETL: Extract -> Transform -> Auto-Filter -> data/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` -> `data/processed/...` (**direkt live**, sobald gemergt + ETL gelaufen) - `pending` -> `data/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:` doch auf `approved`. Kein Schritt, den jedes Dokument durchlaeuft. Den Hash zeigt die Pages-Seite (Detail-Fenster). - **Vektor-DB liest direkt aus `data/processed///`** (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/`. - **`data/_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. - **`data/_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. - **`data/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 **`data/processed///[/]`** im Repo (versioniert, = Single Source of Truth). `pending`-Dokumente liegen in `data/staging/pending/` und werden zur **Transparenz ebenfalls committet** (auf der Pages-Seite einsehbar). Der Audit-Trail steht in `data/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 data # Live-Lauf python -m src.chunk --data data # Chunks erzeugen (offline, optional) ``` ## Confluence aktivieren ```bash export CONFLUENCE_URL="https://arija-confluence.jaas.service.deutschebahn.com" export CONFLUENCE_TOKEN="" # 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 Mo–Fr 8–17 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 `data/processed//...` 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 `data/processed///`-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 `data/chunks////`. Das Voll-Dokument in `data/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: > `). 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`.