presseportale/docs/user-admin/Entwicklungsplan KI-Pruefung und Veroeffentlichung.md
Kevin Adametz 8d8d957884 Doku: Status-Sync 11./12.06., Decision-Update Preisstruktur und Phase-9-Plan
- Decision-Update Preisstruktur & Veroeffentlichungs-Flow aufgenommen
  (Launch-Tarife, Slot-Verbrauch bei Veroeffentlichung, Submit-Gate,
  Launch-Credits) inkl. Klarstellung 12.06.: Gelb geht direkt live,
  keine manuelle Pruef-Queue, nur Rot wird abgelehnt
- Alle Status-Dokumente auf den Code-Stand gezogen: README-Index,
  STATUS-ABGLEICH (KI-Pipeline, Bilder/Lizenzen, Pricing), Checkliste
  (KI- und Titelbild-Bloecke, Launch-To-dos), Admin-User,
  user-zusammenhaenge (Datenmodell-Delta), Entwicklungsplan KI-Pruefung
  (Phase 0 abgehakt, Decision-Abgleich)
- Ueberschriebene Tarif-Abschnitte in Konzept-Update 1/2 und
  Relaunch-Konzept mit Superseded-/IST-Hinweisen markiert
- Neues Plan-Dokument PHASE-9-FLOW-UND-TARIFE-PLAN.md (9A-9J)
- Phase-8-Roadmap-Doku (20-PHASE-8-USER-PANEL.md) + PROGRESS-Eintraege

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:20:22 +00:00

524 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Entwicklungsplan: KI-Prüfung & Veröffentlichungs-Pipeline
Stand: 11.06.2026 — **Phasen 05 abgeschlossen**, Phase 6 (Trust-Score) offen.
Dieser Plan definiert die schrittweise Umsetzung der automatisierten Prüfung
und Veröffentlichung von Pressemitteilungen (PM). Er ist so geschnitten, dass
jede Phase einzeln umgesetzt, getestet und ausgeliefert werden kann.
> **Abgleich mit dem Decision-Update (11.06.2026)**: Das
> [`Decision-Update Preisstruktur & Veröffentlichungs-Flow`](../Decision-Update%20Preisstruktur%20&%20Ver%C3%B6ffentlichungs-Flow.md)
> setzt auf dieser Pipeline auf und ergänzt zum Launch drei noch offene
> Flow-Regeln, die **nicht** Teil dieses Plans waren:
>
> 1. **Submit-Gate**: „Zur Prüfung einreichen" wird hinter eine aktive
> Buchung gelegt (das Modal zeigt ohne Buchung einen Buchungs-Hinweis).
> 2. **Slot-Verbrauch bei Veröffentlichung** statt bei Einreichung —
> rot abgelehnte PMs verbrauchen keinen Slot. Der aktuelle Quota-Stub
> zählt noch beim Einreichen (`submitForReview`) und muss umgestellt werden.
> 3. **Kein Re-Check zum Launch**: eine Einreichung = eine Prüfung;
> Nachbessern + erneut prüfen kommt erst in Phase 2.
> 4. **Gelb-Routing geändert (Entscheidung 12.06.2026)**: Gelb geht zum
> Launch **direkt live** wie Grün — keine manuelle Review-Queue mehr.
> Nur Rot wird abgelehnt (mit Begründung an den Autor). Phase 4 unten
> beschreibt das ursprünglich gebaute Verhalten (Gelb → manuelle Queue);
> die Umstellung erfolgt im Phase-9-Plan
> (`docs/PHASE-9-FLOW-UND-TARIFE-PLAN.md`, Päckchen 9A).
## Ziel & Leitprinzip
Jede eingehende PM wird **automatisch von einer KI geprüft**. Nur in
**äußersten Fällen** erfolgt eine manuelle redaktionelle Prüfung. Zwei
Einreichungsstellen müssen denselben Prüf-Pfad durchlaufen:
1. **Web-Formular** (Customer- und Admin-Editor)
2. **API** (`/api/v1/press-releases`)
Es gibt zwei voneinander unabhängige Bewertungen (Konzept-Update 1, Abschnitt 15):
- **Klassifikations-Score (Grün/Gelb/Rot)** — der „Red Flag": entscheidet, ob
überhaupt veröffentlicht wird. **Jetzt umzusetzen.**
- **Content-Score (0100) → Stufe (Standard/Geprüft/Hochwertig)** — die
Qualitätsbewertung („Scoring"). **Spätere Phase.**
## Konzept-Grundlage
- `docs/konzept/Konzept-Update 1 Überarbeitete Abschnitte.md`, §15.1
(Klassifikations-Score) und §15.2 (Content-Score).
- `docs/konzept/Konzept-Update 2 Score-Stufen-System.md` (Stufen-Mapping,
`content_tier`, Außenkommunikation).
Klassifikations-Score laut Konzept §15.1:
| Klassifikation | Bedeutung | Auswirkung |
|---|---|---|
| **Grün** | unauffällig | direkte Veröffentlichung (optional 510 Min. Verzögerung) |
| **Gelb** | unklar/grenzwertig | manuelle Review-Queue (nicht boostbar) |
| **Rot** | unzulässig | zurück an Autor mit Begründung, keine Veröffentlichung |
Faktoren (Red Flags): Werbung statt PM, beleidigend/diskriminierend, rechtlich
heikel, Spam-Muster, unseriöse Versprechen. Speicherung laut Konzept:
`press_releases.classification` plus Audit-Log `ki_audits`.
## Ist-Zustand (Bestandsaufnahme)
- **Statuswerte** (`App\Enums\PressReleaseStatus`): `draft`, `review`,
`published`, `rejected`, `archived`.
- **Web-Einreichung**: `App\Services\PressRelease\PressReleaseService::submitForReview()`
prüft nur eine wortbasierte Blacklist (`config/blacklist.php` via
`BlacklistService`), setzt sonst Status `review`, erhöht das Quota und
schreibt ein `PressReleaseStatusLog`.
- **Veröffentlichung**: `PressReleaseService::publish()` (Admin-Aktion) und der
Cron `App\Console\Commands\PublishScheduledPressReleases` (publiziert
`review`-PMs mit fälligem `scheduled_at`). Beide prüfen erneut nur die
Blacklist.
- **API**: `App\Http\Controllers\Api\V1\PressReleaseController::store()` /
`update()` schreiben `status` direkt aus dem Request (erlaubt: `draft`,
`review`) und rufen `submitForReview` **nicht** auf. Eine API-PM mit
`status=review` landet damit **ohne** Blacklist-/Quota-/Log-Prüfung in der
Queue.
- **UI-Einreichung (Prozess-Start)**:
- Detailansicht ([show.blade.php]): vollständiges Modal `confirm-submit-review`
mit rechtlichen Hinweisen, Quota-Anzeige und Bestätigungs-Checkboxen →
ruft `submitForReview`.
- Bearbeiten ([edit.blade.php]): Button „Speichern & zur Prüfung" mit nur
`wire:confirm` (Browser-Dialog), **kein** Modal.
- Erstellen ([create.blade.php]): Button „Zur Prüfung senden" ohne Modal.
- **Kein** Datenmodell für Klassifikation/Score: keine Spalten
`classification`, `content_score`, `content_tier`, keine Tabelle `ki_audits`.
## Lücken & Risiken
- **L1 — API-Bypass**: Einreichung über die API umgeht jede Prüfung.
- **L2 — Keine echte Inhaltsprüfung**: nur eine triviale Wort-Blacklist; keine
Erkennung von Werbung, Spam, rechtlich heiklen oder unseriösen Inhalten.
- **L3 — Auto-Publish ohne Klassifikation**: geplante PMs werden vom Cron
veröffentlicht, ohne dass eine inhaltliche Bewertung stattgefunden hat.
- **L4 — Uneinheitlicher Prozess-Start**: das Bestätigungs-Modal existiert nur
in der Detailansicht, nicht beim Bearbeiten/Erstellen.
- **L5 — Kein Audit**: KI-Entscheidungen wären ohne `ki_audits` nicht
nachvollziehbar (DSGVO / Nachweispflicht).
## Zielarchitektur
```
Einreichung (Formular ODER API)
SubmissionService.submit() ← ein einziger Funnel
├─ Hard-Filter: Blacklist (synchron, deterministisch)
ClassificationService.classify() ← KI (Claude), mit Fallback
├─ Rot → status=rejected, Begründung an Autor
├─ Gelb → status=review (manuelle Queue, „äußerste Fälle")
└─ Grün → Veröffentlichungspfad (sofort / geplant)
ki_audits (vollständiges Audit-Log jeder KI-Entscheidung)
+
press_releases.classification / classified_at
+
(später) content_score / content_tier
```
Kernregeln:
- Formular **und** API rufen ausschließlich `SubmissionService.submit()` auf.
Die API darf `status` nicht mehr frei setzen; `published` ist über die API
nie erreichbar.
- Re-Klassifikation bei jeder Änderung einer PM (Konzept §15.1: „Bei Änderung
der PM wird neu klassifiziert").
- Schwellen/Verhalten sind konfigurierbar (`config/scoring.php`), damit sie
ohne Code-Änderung kalibriert werden können.
---
## Entwicklungsschritte
### Phase 0 — Prozess-Start im UI vereinheitlichen — ✅ erledigt (11.06.2026)
**Ziel:** Das bestehende Einreichungs-Modal erscheint überall dort, wo eine PM
eingereicht wird — auch beim Bearbeiten (Button „Speichern & zur Prüfung") und
beim Erstellen (Button „Zur Prüfung senden"). Reiner UI-Schritt, kein Backend.
**Umsetzung:** Das Modal `confirm-submit-review` (rechtliche Hinweise, Quota,
Bestätigungs-Checkboxen) wird in Customer-Show, -Create **und** -Edit über
`flux:modal.trigger` geöffnet; bestätigt ruft es wie geplant
`submitForReview` bzw. `saveAndSubmit`/`save('review')`.
**Umfang:**
- Modal `confirm-submit-review` aus `show.blade.php` in eine wiederverwendbare
Blade-/Volt-Komponente extrahieren (z. B.
`resources/views/livewire/components/press-release-submit-modal.blade.php`).
- In `edit.blade.php` den `wire:confirm`-Button durch einen
`flux:modal.trigger` ersetzen; bei Bestätigung wird wie bisher
`saveAndSubmit` ausgeführt (erst speichern, dann einreichen).
- In `create.blade.php` denselben Modal-Trigger vor `save('review')` schalten.
- Texte/Checkboxen identisch zur Detailansicht halten (rechtliche Hinweise,
Quota, Bestätigungen).
**Betroffene Dateien:** `resources/views/livewire/customer/press-releases/{show,edit,create}.blade.php`,
neue Komponente unter `resources/views/livewire/components/`.
**Done:** In allen drei Ansichten (Customer: show/edit/create) öffnet derselbe
Bestätigungsdialog; Tests für Edit/Create-Submit grün.
**Admin-Editor (`/admin/press-releases/`) — bewusst ausgenommen:** Der
Admin-Editor behält sein bisheriges Verhalten (`wire:confirm`). Begründung: Wenn
eine PM beim Admin landet, hat die vorgelagerte User-Prüfung (Einreichungs-Modal
im Customer-Flow) bereits stattgefunden. Der Admin braucht hier keinen erneuten
Bestätigungsdialog. Stattdessen erhält der Admin-Editor in einer späteren Phase
einen zusätzlichen **„Prüfung"-Button** (siehe Phase 4: On-Demand-KI-Prüfung).
**Tests:** Volt-Tests, die das Öffnen des Modals und den Submit-Pfad
(`saveAndSubmit` / `save('review')`) abdecken.
### Phase 1 — Einreichungs-Funnel & API-Absicherung — ✅ erledigt (11.06.2026)
**Ziel:** Beide Einreichungsstellen laufen durch einen Pfad; die API-Lücke (L1)
wird geschlossen. Noch ohne KI — nur Vereinheitlichung.
**Umsetzung:**
- `PressReleaseService::submitForReview()` ist der alleinige Einreichungs-Einstieg
(Web-Formular **und** API rufen dieselbe Methode). Auf eine separate
`SubmissionService`-Fassade wurde bewusst verzichtet — `submitForReview` ist
bereits die stabile Schnittstelle, in die Phase 3 die KI-Klassifikation
einhängt.
- API: `status` aus den Validierungsregeln von `StorePressReleaseRequest` und
`UpdatePressReleaseRequest` entfernt (inkl. ungenutzter Imports). `store()`
erzeugt jetzt **immer** `PressReleaseStatus::Draft`; ein übergebenes `status`
wird ignoriert. `update()` kann den Status nicht mehr setzen.
- Neue explizite Route `POST /api/v1/press-releases/{pressRelease}/submit`
(`press-releases.submit`) → `PressReleaseController::submit()`. Diese prüft
`press-releases:write`, Ownership und erlaubt nur `draft`/`rejected`
(sonst 409); ruft `submitForReview()`; eine `BlacklistViolationException`
wird als **422** mit Begründung zurückgegeben. Damit greifen Blacklist-,
Quota- und Status-Log-Behandlung auch für API-Einreichungen.
- `published` ist über die API weiterhin nie erreichbar (nur Admin-Aktion/Cron).
**Betroffene Dateien:** `app/Http/Controllers/Api/V1/PressReleaseController.php`,
`app/Http/Requests/Api/V1/{Store,Update}PressReleaseRequest.php`,
`routes/api.php`. `PressReleaseService` blieb unverändert (Schnittstelle
ausreichend).
**Done:** API kann keine PM mehr ungeprüft in `review` heben; eine PM-Einreichung
verhält sich über API und Formular identisch.
**Tests:** `tests/Feature/Api/V1/PressReleaseSubmitApiTest.php` (Create erzeugt
immer Draft & ignoriert `status`; Submit-Route hebt nach `review`, zählt Quota,
schreibt Log; Blacklist → 422 + `rejected`; fehlende Schreibrechte → 403;
bereits in `review` → 409; fremde PM → 403). Alle grün.
### Phase 2 — Datenmodell & Audit — ✅ erledigt (11.06.2026)
**Ziel:** Persistenz für Klassifikation und vollständiges KI-Audit. Noch ohne
Verhaltensänderung (alle Felder nullable).
**Umsetzung:**
- Migration `add_classification_to_press_releases`: Spalten `classification`
(string(16), nullable, nach `status`) und `classified_at` (timestamp,
nullable) plus Index auf `classification`. `content_score`/`content_tier`
bewusst **erst in Phase 5** (siehe Datenmodell-Anhang).
- Migration `create_ki_audits_table`: `press_release_id` (FK, cascade),
`type`, `provider` (nullable), `model` (nullable), `result` (nullable),
`reason` (text, nullable), `raw_response` (json, nullable),
`created_at` (useCurrent), Index `(press_release_id, type)`. Kein
`updated_at` (append-only Log).
- Model `App\Models\KiAudit` (`$timestamps = false`, Cast `raw_response`
array, Konstanten `TYPE_CLASSIFICATION`/`TYPE_CONTENT_SCORE`, Relation
`pressRelease()`), Relation `PressRelease::kiAudits()` (neueste zuerst).
- Enum `App\Enums\PressReleaseClassification` (Green/Yellow/Red + `label()`),
in `PressRelease::casts()` für `classification` registriert.
- `config/scoring.php`: Anbieter/Modell-Auswahl (`CLASSIFICATION_PROVIDER`,
Default `deterministic`, `CLASSIFICATION_MODEL`), Timeout, Grün-Verzögerung
(Minuten), Gelb→manuelle-Queue-Flag sowie Content-Score-Stufen-Schwellen
(Phase 5).
- `KiAuditFactory` mit States `classification()` / `contentScore()`.
**Betroffene Dateien:** zwei neue Migrationen unter `database/migrations/`,
`app/Models/PressRelease.php`, `app/Models/KiAudit.php`,
`app/Enums/PressReleaseClassification.php`, `config/scoring.php`,
`database/factories/KiAuditFactory.php`.
**Done:** Migrationen laufen; Modelle/Casts/Relation vorhanden; keine bestehende
Funktionalität verändert (alle Felder nullable).
**Tests:** `tests/Feature/PressReleaseClassificationModelTest.php` (Enum-/
Datetime-Cast, Default null, `kiAudits()`-Reihenfolge, `raw_response`-Array-Cast
+ Relation, Cascade-Delete). Alle grün.
### Phase 3 — KI-Klassifikation (Red Flag) — ✅ erledigt (11.06.2026)
**Ziel:** Echte inhaltliche Prüfung jeder Einreichung; Ergebnis asynchron als
Klassifikation gespeichert und auditiert.
**Entscheidungen (11.06.2026):** Erster aktiver Anbieter ist **OpenAI** (Key/
Budget vorhanden); Anthropic/Gemini folgen über dieselbe Treiber-Schnittstelle.
Klassifikation läuft **asynchron über die Queue** (synchron wäre später nicht
handelbar). Zum Testen ohne Dauer-Worker gibt es einen Drain-Befehl.
**Umsetzung:**
- **Provider-agnostische Treiber-Architektur** unter
`app/Services/PressRelease/Classification/`:
- Interface `Contracts\ClassificationDriver::classify(PressRelease): ClassificationResult`.
- `ClassificationResult` (Value Object: Enum-Klassifikation, `reasons[]`,
`provider`, `model`, `rawResponse`, `reasonText()`).
- `Drivers\OpenAiClassificationDriver` — OpenAI Chat-Completions via
`Http`-Client, liest `config/services.openai` (Key/URL/Modell/Timeout),
erzwingt `response_format: json_object` und parst
`{classification, reasons[]}`. Wirft bei fehlendem Key / HTTP-Fehler /
ungültigem JSON.
- `Drivers\DeterministicClassificationDriver` — Blacklist → Rot/Grün
(nie Gelb), als Fallback ohne externe API.
- `ClassificationManager` (Laravel-Manager) löst den Treiber aus
`config('scoring.classification.provider')` auf
(`createOpenaiDriver`/`createDeterministicDriver`).
- **Asynchroner Job** `app/Jobs/ClassifyPressRelease` (Queue `classification`,
`tries=3`): klassifiziert über den aktiven Treiber, bei Ausfall **Fallback**
auf den deterministischen Treiber (mit `Log::warning`), schreibt
`press_releases.classification`/`classified_at` und einen `ki_audits`-Eintrag
(inkl. `provider`/`model`/`reason`/`raw_response`).
- **Einbindung in den Funnel:** `PressReleaseService::submitForReview()` stößt
nach dem synchronen Blacklist-Hard-Filter und dem Statuswechsel den Job an
(`ClassifyPressRelease::dispatch(...)->onQueue('classification')`). Greift für
Formular **und** API (gemeinsamer Einstieg aus Phase 1).
- **Drain-Befehl** `php artisan classification:work` (Option `--once`): arbeitet
die Queue einmalig ab und beendet sich (`queue:work --stop-when-empty`) — zum
Testen ohne permanenten Worker.
- **Konfig:** `config/scoring.php` Default-Provider auf `openai` gesetzt
(`CLASSIFICATION_PROVIDER`); Modell leer ⇒ `config('services.openai.model')`.
- **Test-Isolation:** `phpunit.xml` erzwingt `CLASSIFICATION_PROVIDER=deterministic`
und leeren `OPENAI_API_KEY`, damit die Suite keine echten OpenAI-Calls macht;
der OpenAI-Pfad wird gezielt mit `Http::fake()` getestet.
**Noch offen (bewusst):** Re-Klassifikation bei jeder PM-Änderung (Update über
Formular/API) ist noch **nicht** verdrahtet — Phase 3 klassifiziert beim
Einreichen. Nachzuziehen, wenn das Status-Routing (Phase 4) steht. Anthropic-/
Gemini-Treiber + SDK folgen separat.
**Betroffene Dateien:** `app/Services/PressRelease/Classification/*`,
`app/Jobs/ClassifyPressRelease.php`,
`app/Console/Commands/RunClassificationQueue.php`,
`app/Services/PressRelease/PressReleaseService.php`, `config/scoring.php`,
`phpunit.xml`.
**Done:** Jede Einreichung (Formular + API) stößt asynchron eine Klassifikation
an, erzeugt einen `ki_audits`-Eintrag; bei KI-Ausfall greift der deterministische
Fallback nachvollziehbar. Status-Routing folgt in Phase 4.
**Tests:** `tests/Feature/PressReleaseClassificationJobTest.php` (OpenAI grün/gelb
mit `Http::fake`, Fallback bei HTTP-500, deterministisch Rot bei Blacklist,
Dispatch auf Queue `classification` via `Queue::fake`). Alle grün; volle Suite
416 grün (2 vorbestehende WIP-Failures unverändert).
### Phase 4 — Routing, Auto-Publish & Review-Queue — ✅ erledigt (11.06.2026)
**Ziel:** Die Klassifikation steuert den Status. Manuelle Prüfung nur noch bei
Gelb.
**Umsetzung:**
- **Routing im Job** über `PressReleaseService::routeByClassification()`
(vom `ClassifyPressRelease`-Job nach dem Klassifizieren aufgerufen):
- **Rot** → `reject(..., source: 'ki')`: `status=rejected`, KI-Begründung per
Mail an den Autor (`PressReleaseRejected`, wie bei Blacklist).
- **Gelb** → keine Aktion, bleibt `review` (manuelle Admin-Queue).
- **Grün** → `autoPublishGreen()`: ohne Termin sofort veröffentlichen,
optional mit Sicherheitsfenster `scoring.classification.green_delay_minutes`
(über `published_at`-Override); mit zukünftigem `scheduled_at` bleibt die PM
in `review` und der Scheduler publiziert zum Termin.
- Greift nur, solange die PM noch `review` ist (manuelle Admin-Eingriffe haben
Vorrang). `publish()` erhielt einen `?Carbon $publishedAtOverride`-Parameter,
`reject()` einen `string $source`-Parameter.
- **Scheduler** `PublishScheduledPressReleases`: Kandidaten-Query um
`where('classification', 'green')` erweitert — nur **grüne** fällige PMs
werden automatisch publiziert; gelbe warten immer auf den Admin. Geplante
Termine werden weiterhin respektiert.
- **Admin-Review-Queue:** Index- und Show-Ansicht zeigen ein KI-Klassifikations-
Badge (grün/gelb/rot); der Index hat einen **Klassifikations-Filter**
(`classificationFilter`, inkl. URL-Param, Active-Chip, Reset) — damit „nur
Gelb" filterbar. Die Show-Ansicht blendet im Review-Block den **KI-Hinweis**
(Begründung aus dem jüngsten `ki_audits`-Eintrag) ein.
**Test-Isolation (wichtig):** Da Tests mit `sync`-Queue den Job inline ausführen,
wurde der Klassifikations-Job in den „submit→review"-Tests via `Queue::fake()`
entkoppelt (Workflow, PublishModal, API-Submit). Die Scheduler-Tests setzen jetzt
`classification = green` für Publish-Kandidaten; neuer Test: fällige **gelbe** PM
bleibt `review`.
**Betroffene Dateien:** `app/Services/PressRelease/PressReleaseService.php`,
`app/Jobs/ClassifyPressRelease.php`,
`app/Console/Commands/PublishScheduledPressReleases.php`, Admin-Views
`resources/views/livewire/admin/press-releases/{index,show}.blade.php`.
**Done:** Grüne PMs gehen automatisch live (sofort/zum Termin), rote werden
abgelehnt + Autor benachrichtigt, nur gelbe landen in der manuellen Queue; Admin
sieht Klassifikation + KI-Begründung und kann nach Gelb filtern.
**Tests:** Routing in `PressReleaseClassificationJobTest` (Rot→rejected+Mail,
Grün-sofort→published+Mail, Grün-geplant→bleibt review, Gelb→bleibt review);
Scheduler in `PressReleaseSchedulingTest` (grün fällig→published, gelb
fällig→review); Admin-UI in `PressReleaseIndexPhase8bTest` (KI-Badge,
Klassifikations-Filter). Volle Suite 423 grün (2 vorbestehende WIP-Failures).
**Admin „Prüfung"-Button (On-Demand-KI-Prüfung)** — ✅ erledigt (11.06.2026):
- Im Admin-Editor gibt es oben den Button **„Prüfung"**, der ein Modal
`admin-ki-check` öffnet: auswählbare Klassifikation (Content-Score als
„in Vorbereitung" deaktiviert) und ein **Anbieter-Override** (Konfiguriert /
OpenAI / Deterministisch).
- `runKiCheck()` dispatcht `ClassifyPressRelease` auf der Queue `classification`
**mit `route: false`** und optionalem `providerOverride`. Das ist eine
nachgelagerte Re-Check-Prüfung: sie aktualisiert nur `classification` +
`ki_audits`, **ohne** den Status zu ändern (kein Auto-Publish/Reject) — die
Entscheidung bleibt beim Admin (Ergebnis sichtbar in der Detailansicht).
- Dafür erhielt `ClassifyPressRelease` die Parameter `bool $route = true` und
`?string $providerOverride = null`.
**Tests:** `tests/Feature/Admin/AdminKiCheckTest.php` (Button/Modal sichtbar;
Dispatch mit `route=false` + Provider-Override; Abbruch ohne Auswahl;
Re-Check-Job aktualisiert Bewertung, lässt Status unverändert).
**Re-Klassifikation bei Änderung** (Konzept §15.1) — ✅ erledigt (11.06.2026):
- Neue Service-Methode `reclassifyIfClassified()`: dispatcht nur wenn die PM
bereits klassifiziert ist `ClassifyPressRelease` mit `route: false`
(Re-Check ohne Statusänderung).
- Eingehängt überall dort, wo Inhalt geändert wird, und nur bei tatsächlicher
Änderung von Titel/Text (`wasChanged(['title', 'text'])`):
Customer-Editor `save()`, Admin-Editor `save()`, API `update()`.
Beim Einreichen übernimmt weiterhin `submitForReview` die (routende)
Klassifikation.
**Tests:** `tests/Feature/PressReleaseReclassifyTest.php` (Service dispatcht nur
bei vorhandener Klassifikation; API-Update klassifiziert neu bei Text-Änderung,
nicht bei reiner Keyword-Änderung).
**Noch offen / Folgearbeiten:**
- **Live-Aktualisierung der Ansicht** nach Abschluss des Hintergrund-Jobs
(Polling/Event) wäre ein optionales UX-Upgrade; aktuell erscheint das Ergebnis
nach Reload/Navigation.
- **Content-Score-Option** im Prüfungs-Modal — ✅ mit Phase 5 aktiviert (s. u.).
### Phase 5 — Content-Score & Stufen — ✅ erledigt (11.06.2026)
**Ziel:** Qualitätsbewertung 0100 → Stufe Standard/Geprüft/Hochwertig
(Konzept-Update 2).
**Umsetzung:**
- **Datenmodell:** Migration `add_content_score_to_press_releases`
`content_score` (tinyint, nullable), `content_tier` (string, nullable, Index),
`scored_at`. Enum `App\Enums\PressReleaseContentTier`
(Standard/Geprueft/Hochwertig) mit `fromScore()` (Schwellen aus
`config/scoring.php`), `label()` und `isPubliclyBadged()` (Standard wird laut
Update 2 nicht beworben). In `PressRelease` als Cast registriert.
- **Schwellen** (`config/scoring.php`): Geprüft ≥ 60, Hochwertig ≥ 80 (Update 2),
kalibrierbar; plus Anbieter/Modell/Timeout für den Score.
- **Treiber-Architektur** unter `app/Services/PressRelease/ContentScore/`
analog zur Klassifikation: `Contracts\ContentScoreDriver`, `ContentScoreResult`,
`Drivers\OpenAiContentScoreDriver` (gewichtete Faktoren §15.2 als JSON
`{score, breakdown}`), `Drivers\DeterministicContentScoreDriver`
(regelbasierte Heuristik: Länge, Bild, Quelle, Headline, Vollständigkeit),
`ContentScoreManager`.
- **Job** `app/Jobs/ScorePressRelease` (Queue `classification`, Fallback auf
deterministisch): schreibt `content_score` + abgeleitete `content_tier` +
`scored_at` und `ki_audits` (type=content_score). Optionaler
`providerOverride`.
- **Berechnung** bei Einreichung (`submitForReview` dispatcht Klassifikation
**und** Score) und bei Inhaltsänderung (`rescoreIfScored()` in Customer-/
Admin-Editor und API-`update()`, analog zur Re-Klassifikation).
- **Anzeige:**
- Customer-Editor: Score-Panel (Punktzahl, Stufe, „noch X Punkte bis zur
nächsten Stufe") — der produktive Editor-Score laut Update 2.
- Admin-Index & -Show: Stufen-/Score-Badge (intern inkl. Punktzahl).
- Customer-Detailansicht: öffentliches Stufen-Badge (✓ Geprüft / ★ Hochwertig;
Standard ohne Badge).
- **Admin-Prüfungs-Modal:** Content-Score-Option aktiviert; `runKiCheck()`
dispatcht zusätzlich `ScorePressRelease`.
**Done:** Score wird bei Einreichung/Änderung berechnet, Stufe abgeleitet,
auditiert und überall sichtbar.
**Tests:** `tests/Feature/PressReleaseContentScoreTest.php` (Tier-Mapping,
öffentliche Badges, OpenAI-Score→Tier+Audit, Fallback, Dispatch bei Submit,
Re-Score nur wenn bereits bewertet); Editor-Panel in
`CustomerPressReleaseEditPhase7Test`; Stufen-Badge in `PressReleaseIndexPhase8bTest`;
Content-Score-Dispatch in `AdminKiCheckTest`. Volle Suite 440 grün.
**Noch offen / Folgearbeiten:**
- **Public Web-Frontend** (presseecho/businessportal24): Stufen-Badges in den
öffentlichen Listen/Detailseiten gemäß Update 2 ergänzen (bisher nur im
Portal/Backend und der Customer-Ansicht).
- **Score-History & Breakdown-Ansicht** (Publisher-Dashboard) und Boost-
Eligibilität (Abschnitt 16) sind eigene spätere Ausbaustufen.
### Phase 6 — Trust-Score (später)
Account-/Firmen-Ebene (Konzept §15.3): lockert die KI-Freigabe-Schwelle für
zuverlässige Publisher. Eigene spätere Ausbaustufe; hier nur als Ausblick
vermerkt.
---
## Datenmodell-Anhang (Zielzustand)
```
press_releases (Ergänzungen)
+ classification enum(green,yellow,red) NULL
+ classified_at timestamp NULL
+ content_score tinyint NULL (Phase 5)
+ content_tier enum(standard,gepruft,hochwertig) NULL (Phase 5)
ki_audits (neu)
- id
- press_release_id FK
- type enum(classification,content_score)
- provider string (z. B. anthropic)
- model string (z. B. claude-opus-4-8)
- result string/json
- reason text NULL
- raw_response json/longtext
- created_at timestamp
```
## Offene Entscheidungen
- **Anbieter & Modell** — ✅ entschieden (11.06.2026): Erster aktiver Anbieter
ist **OpenAI** (`CLASSIFICATION_PROVIDER=openai`, Modell aus
`config/services.openai`). Architektur provider-agnostisch; Anthropic/Gemini
folgen. Offen bleibt, ob später mehrere Anbieter parallel (Primär + Fallback
jenseits des deterministischen) laufen sollen.
- **Synchron vs. Queue** — ✅ entschieden (11.06.2026): **Queue** (asynchron,
Queue-Name `classification`). Drain zum Testen: `php artisan classification:work`.
- **Dependency**: OpenAI-Treiber nutzt den nativen `Http`-Client (kein neues
Composer-Paket). Anthropic PHP-SDK (`anthropic-ai/sdk`) ist **freigegeben**;
für Gemini je ein offizielles/etabliertes SDK oder HTTP-Client bei Umsetzung
des Treibers.
- **Grün-Verzögerung**: 0 Min. (sofort) oder 510 Min. (Konzept-Option) als
Sicherheitsfenster — konfigurierbar über
`scoring.classification.green_delay_minutes`, Default noch festzulegen.
- **Gelb-Verhalten**: ausschließlich manuelle Queue, oder zusätzlich
automatische Benachrichtigung des Autors.
- **DSGVO**: Aufbewahrung/Anonymisierung der `raw_response` in `ki_audits`.
## Nächste Schritte
Phasen 05 sind umgesetzt (Suite grün). Es folgen:
1. **Launch-Block aus dem Decision-Update** (siehe Abgleich-Box oben):
Submit-Gate hinter Buchung, Slot-Verbrauch bei Veröffentlichung,
Tarif-/Zahlungs-Modul.
2. **Betrieb**: Queue-Worker für `classification` im Produktions-Setup
(Test-Drain: `php artisan classification:work`).
3. **Folgearbeiten**: Live-Aktualisierung des KI-Ergebnisses in der UI,
Stufen-Badges im öffentlichen Web-Frontend, Anthropic-/Gemini-Treiber.
4. **Phase 6**: Trust-Score (eigene Ausbaustufe).