feat: Chemikalien-Recherche über lokale RiSU-Stoffliste (kein KI-Aufruf mehr)

Ersetzt den ursprünglichen KI-Websuche-Ansatz (GESTIS scheiterte sowohl über
Websuche als auch über direkten API-Zugriff an fehlenden schulspezifischen
Tätigkeitsbeschränkungen) durch einen reinen lokalen Datenbank-Lookup gegen die
offizielle RiSU-Stoffliste (1764 Stoffe inkl. Tätigkeitsbeschränkungs-Codes,
Legende zur Abfragezeit übersetzt statt in die Datenbank gebacken). Keine
KI-Kosten, keine Drittanbieter-Abhängigkeit mehr für diese Funktion.

HazardSubstance.AgeRestriction zu ActivityRestriction umbenannt, da die
Tätigkeitsbeschränkung mehr abdeckt als reine Altersgrenzen. Details und
verworfene Zwischenstände in TODO.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 23:43:56 +02:00
co-authored by Claude Sonnet 5
parent 038337997f
commit 7b883555bf
14 changed files with 647 additions and 37 deletions
+70
View File
@@ -84,6 +84,61 @@ in `db.php` verschoben, damit `explain.php` sie mitverwenden kann, ohne sie zu d
Kein neues DB-Schema nötig (nutzt dieselben `users`/`tokens`/`transactions`-Tabellen wie
`plan.php`/`explain.php`). Einfach die neue Datei `gbu.php` hochladen.
## Update für bereits deployte Installationen (Chemikalien-Recherche, `substance.php`)
Neues Feature — reiner lokaler Datenbank-Lookup, **kein KI-Aufruf, keine Kosten, keine
Drittanbieter-Abhängigkeit** (Nutzerwunsch: "wir brauchen die KI gar nicht mehr im Backend").
Vorgeschichte in Kurzform (Details siehe TODO.md, Nachtrag zu 4.2): zunächst wurde versucht, die
Daten per KI-Websuche bei GESTIS zu recherchieren — das scheiterte, weil `gestis.dguv.de` eine
reine JavaScript-Anwendung ohne durchsuchbaren/abrufbaren Inhalt ist. Ein direkter Abruf über
GESTIS' eigene (inoffizielle) API funktionierte zwar technisch, hatte aber keine strukturierte,
schulspezifische Tätigkeitsbeschränkung (Freigabe/Verbot/Altersgrenzen) — genau die für den
Unterricht wichtigste Angabe. Der Nutzer stellte stattdessen die offizielle **RiSU-Stoffliste**
("Richtlinie Sicheres Experimentieren im Unterricht") als PDF bereit — ein für den Schuleinsatz
zusammengestelltes Verzeichnis mit exakt den benötigten Spalten (Name, CAS-Nr., GHS-Piktogramme,
Signalwort, H-/P-Sätze UND Tätigkeitsbeschränkung), aus dem `data/stoffliste.json` (~1800 Stoffe)
erzeugt wurde. `substance.php` ist damit auf einen reinen, kostenlosen Datenbank-Lookup reduziert.
**Deployment:** neue Dateien `substance.php` und `stoffliste.php` hochladen, sowie
`data/stoffliste.json` (~760 KB, nur lesend benötigt). Kein Migrations-Schritt, keine
`config.php`-Änderung nötig — der Endpunkt prüft weiterhin das Bearer-Token wie jeder andere
(`ai_backend_authenticate`), braucht aber keinerlei KI-/Abrechnungs-Infrastruktur mehr.
**Legende der Tätigkeitsbeschränkungs-Codes** (steht in keiner Form im PDF selbst, vom Nutzer
bestätigt — Übersetzung passiert zur Abfragezeit in `stoffliste.php`
(`STOFFLISTE_RESTRICTION_LEGEND`), bewusst NICHT in `data/stoffliste.json` gespeichert, damit eine
künftige Korrektur nur eine Codeänderung statt einer neuen Datenbank-Generierung braucht — genau
das war hier schon einmal nötig, die erste Fassung dieser Legende war falsch):
| Code | Bedeutung |
| --- | --- |
| `+` | Generelle Erlaubnis |
| `X` | Generelles Tätigkeitsverbot an Schulen |
| `L+` | Tätigkeitsverbot für Lehrkräfte, Ausnahme siehe RiSU (I 3.5) |
| `S` | Tätigkeitsverbot für Schülerinnen und Schüler |
| `S4K` | Tätigkeitsverbot für Schülerinnen und Schüler bis einschließlich Jahrgangsstufe 4 |
| `S9K` | Tätigkeitsverbot für Schülerinnen und Schüler bis einschließlich Jahrgangsstufe 9 |
| `W` | Tätigkeitsverbot für werdende oder stillende Mütter |
| `ESP` | Gesonderte Ersatzstoffprüfung notwendig |
**Bekannte Lücken:**
- Diese Stoffliste enthält keine Entsorgungshinweise (anders als der frühere GESTIS-Ansatz) — das
`disposal`-Feld im Wizard bleibt bei einem Datenbank-Treffer leer und muss weiterhin von Hand
ausgefüllt werden.
- Für ca. 40 der ca. 1800 Stoffe im PDF (~2 %) konnte die Tabellenerkennung die Spalten nicht
zuverlässig auseinanderhalten (ungewöhnlich lange Zellinhalte lassen `pdfplumber`s
Spaltenerkennung für einzelne Zeilen kollabieren) — betroffene Zeilen wurden NICHT mit
geratenen/möglicherweise falschen Werten aufgenommen, sondern bewusst ausgelassen (ein falscher
Stoffname bei sonst korrekten Gefahrendaten wäre gefährlicher als ein fehlender Eintrag). Bei
Bedarf lässt sich die Liste der fehlenden Stoffe aus dem PDF neu ermitteln.
**Aktualisieren der Datenbank:** wenn die RiSU-Stoffliste künftig überarbeitet wird, das PDF neu
herunterladen und `data/stoffliste.json` neu erzeugen (Parsing-Logik ist nicht Teil des Repos,
da einmalig genutzt — bei Bedarf erneut über `pdfplumber` extrahieren, Spaltenreihenfolge siehe
`stoffliste.php`-Kommentar). Die Tätigkeitsbeschränkungs-**Codes** werden weiterhin mit
gespeichert, nur ihre Übersetzung lebt separat in `STOFFLISTE_RESTRICTION_LEGEND`.
## Prompt Caching
Der Systemprompt in `plan.php` ist vollständig statisch (identisch bei jeder Anfrage, jedes
@@ -143,6 +198,21 @@ grundsätzlich nicht) — dann hilft nur eine serverseitige Konfiguration durch
- Ob Prompt Caching tatsächlich greift (`cache_read_input_tokens` > 0 bei einer zweiten Anfrage
innerhalb von 5 Minuten) — der `FakeProvider` simuliert kein Caching, das lässt sich nur gegen
die echte Anthropic-API beobachten (z.B. per Blick in die `transactions`-Tabelle).
- **`substance.php`/`stoffliste.php` (Chemikalien-Recherche):** braucht keinen echten API-Key und
keine Netzwerkverbindung mehr, ist also grundsätzlich einfacher zu verifizieren als der Rest
dieser Liste — trotzdem NICHT in einer echten PHP-Umgebung gelaufen (kein PHP auf der
Implementierungs-Maschine verfügbar). Die Lookup-/Normalisierungslogik in `stoffliste.php` sowie
der PDF→JSON-Umwandlungsschritt für `data/stoffliste.json` wurden in Python nachgebaut und
gegen Stichproben verifiziert (u.a. Natrium, Kaliumdichromat, Zinn-Verbindungen), nicht aber der
tatsächlich ausgeführte PHP-Code selbst — vor dem produktiven Einsatz einmal mit ein paar
bekannten Stoffnamen durchtesten.
**Nachtrag (Nutzer-Testlauf):** eine harte `allowed_domains`-Einschränkung auf GESTIS lieferte
für tatsächlich vorhandene Stoffe trotzdem "nicht sicher auffindbar" — vermutlich liefert die
Websuche für GESTIS' dynamisch aufgebaute Seiten zu wenige brauchbar indexierte Treffer.
`allowed_domains` wurde daraufhin entfernt; die Quellenrangfolge (GESTIS zuerst, sonst andere
anerkannte Quellen) steht jetzt nur noch im Systemprompt, mit Pflichtangabe der tatsächlich
genutzten Quelle im `source`-Feld. Ob GESTIS damit in der Praxis öfter tatsächlich als Quelle
gefunden wird, ist weiterhin nur mit echten Testläufen zu beurteilen.
## Guthaben aufladen