feat: replace Claude-based extraction with a rule-based engine

Deklarix itself no longer depends on the Anthropic API — that was a
separate API key/billing relationship from Claude Code (used to develop
Deklarix), which the user did not intend to take on for the product
itself.

Consideration (Gegenleistung) is no longer guessed from text — it's a
required form field now, since only the submitter actually knows
whether a business relationship existed. A keyword-only system can't
tell a covertly-paid post from a genuinely organic one; they read
identically. What internal/extract *can* still determine reliably and
deterministically from the caption: whether a disclosure keyword is
present (werbung, anzeige, bezahlte partnerschaft, paid partnership,
#ad, #werbung, #anzeige, #sponsored, #sponsoredby, #sponsoredpost —
case-insensitive), its exact original-case wording, and whether it sits
before the platform's "mehr anzeigen" truncation point (~125 chars
Instagram, ~150 TikTok — rough estimates, platforms change these without
notice, verify before real customer use).

internal/extract's Anthropic HTTP client and tool-use schema are gone
(client.go/api.go deleted), replaced by engine.go — a stateless Engine
with no network calls. extract.Result/ParsePayload keep the exact same
JSON shape as before (gegenleistung/kennzeichnung_vorhanden/
kennzeichnung_wortlaut/kennzeichnung_vor_kuerzung), so internal/store and
internal/dossier needed no changes at all — only extract itself, the web
form/handler (new consideration field), and main.go (no more
ANTHROPIC_API_KEY requirement) changed.

Trade-off the user was told and accepted: without an LLM, the system can
no longer independently catch undisclosed paid content that carries no
recognizable keyword at all — that now rests on the submitter's honesty.
Creative or implicit disclosure phrasing outside the keyword list also
won't be recognized.

Verified against a real running instance with zero API keys configured:
register -> check (real rule engine, correctly triggered WK-004 for a
disclosure placed 130 characters in, past the Instagram threshold) ->
archive -> PDF dossier download, all against real Postgres.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
noroot
2026-08-27 17:15:39 +02:00
parent 8f638df387
commit 34b1d8d2a6
14 changed files with 431 additions and 615 deletions

View File

@@ -35,15 +35,22 @@ Login als Selbstzweck.
---
## Kernprinzip: das Modell entscheidet nicht
## Kernprinzip: Fakten trennen von Urteil
**Das LLM extrahiert Fakten. Das Regelwerk in Go fällt das Urteil.**
**Stufe 1 bestimmt Fakten (deterministisch, kein LLM). Das Regelwerk in
Go fällt das Urteil.**
- **Stufe 1 — Extraktion (Claude API):** Eingabe Caption plus Bild/Video,
Ausgabe striktes JSON (Plattform, Gegenleistung, Kennzeichnung
vorhanden/Kanal/Wortlaut/Position, Produktkategorie, Verlinkungen, ...).
Ist die Extraktion unsicher, liefert sie `unklar` — nie eine geratene
Bewertung.
- **Stufe 1 — Extraktion (`internal/extract`, regelbasiert):** Ob eine
Gegenleistung vorlag, kann aus dem Text nicht zuverlässig erraten
werden — ein verschwiegener bezahlter Post sieht textlich identisch
aus wie ein echter organischer Post. Deshalb gibt der/die Einreichende
die Gegenleistung selbst an (Formularfeld). Was sich objektiv aus der
Caption bestimmen lässt, wird per Zeichenketten-Logik erkannt: ob ein
Kennzeichnungswort vorkommt, welcher Wortlaut, und ob es vor der
plattformspezifischen "mehr anzeigen"-Kürzung steht. Kein externer
Dienst, keine API-Kosten, kein Sprachmodell — daher auch keine
Interpretation kreativer/impliziter Formulierungen; was nicht über ein
bekanntes Kennzeichnungswort läuft, wird nicht erkannt.
- **Stufe 2 — Bewertung (Go, deterministisch):** Regelwerk als
versionierte YAML-Dateien (`rules/`). Jedes Finding referenziert
Regel-ID und Version (z. B. `WK-004 v3`). Regeln sind
@@ -51,10 +58,17 @@ Login als Selbstzweck.
Darstellungsdetails unterscheiden sich; die gehören in
Regelbedingungen/Extraktion, nie in separate Regelsätze pro Plattform.
**Absolute Regel:** Das Modell nennt niemals selbst Normen, Urteile oder
Randnummern. Fundstellen stehen ausschließlich in den kuratierten
YAML-Regeln. Eine halluzinierte Fundstelle ist in diesem Markt ein
Totalschaden.
**Absolute Regel:** Fundstellen stehen ausschließlich in den kuratierten
YAML-Regeln, nie in Freitext oder Extraktionslogik erfunden. Eine
halluzinierte Fundstelle ist in diesem Markt ein Totalschaden.
**Historie:** Ursprünglich war Stufe 1 als Claude-API-Aufruf geplant
(Kennzeichnung *und* Gegenleistung vom Modell erraten). Das wurde
bewusst verworfen — der Nutzer will keine laufende Anthropic-Abhängigkeit
für Deklarix selbst (getrennt von Claude Code, mit dem an Deklarix
programmiert wird). Die Idee "LLM sieht Kontext-Hinweise wie
Rabattcodes/Affiliate-Links, die reines Keyword-Matching übersieht"
bleibt ein späterer Ausbaupunkt, kein aktueller Blocker.
---
@@ -69,7 +83,7 @@ Bewusst langweilig, ein Binary, lokal lauffähig:
| Datenbank | PostgreSQL über `pgx`, Migrationen über `golang-migrate` |
| PDF | serverseitig erzeugt |
| Zeitstempel | RFC-3161 von einer öffentlichen TSA |
| Extraktion | Claude API (nur Fakten-Extraktion, siehe oben) |
| Extraktion | regelbasiert, `internal/extract`, keine externe Abhängigkeit |
| Design | `design/enterprise.css` (Enterprise Light Theme, enconf-Pattern, Primärfarbe `#1677ff`) |
Kein Docker für die App selbst, kein React, keine Queue. Neue
@@ -94,7 +108,7 @@ Managed Postgres in der EU (DSGVO).
│ └── deklarix/
│ └── main.go # Entry Point, HTTP-Server
├── internal/
│ ├── extract/ # Claude-API-Aufruf, JSON-Schema, Validierung
│ ├── extract/ # regelbasierte Fakten-Erkennung (Keywords, Kürzungs-Position)
│ ├── rules/ # YAML-Loader, Auswertung, Versionierung
│ ├── evidence/ # Hashing, Zeitstempel, Append-only-Log
│ ├── dossier/ # PDF-Erzeugung
@@ -356,8 +370,8 @@ git push origin main
sudo systemctl start deklarix
sudo systemctl status deklarix
# Config: /etc/deklarix/deklarix.env (DATABASE_URL, PORT, ANTHROPIC_API_KEY,
# RULES_DIR, DOSSIER_DIR, TSA_URL)
# Config: /etc/deklarix/deklarix.env (DATABASE_URL, PORT, RULES_DIR,
# DOSSIER_DIR, TSA_URL)
# Logs prüfen
journalctl -u deklarix -f