- 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>
524 lines
26 KiB
Markdown
524 lines
26 KiB
Markdown
# Entwicklungsplan: KI-Prüfung & Veröffentlichungs-Pipeline
|
||
|
||
Stand: 11.06.2026 — **Phasen 0–5 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 (0–100) → 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 5–10 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 0–100 → 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 5–10 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 0–5 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).
|