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>
This commit is contained in:
parent
48993f5506
commit
b2b5ff06c2
17 changed files with 2231 additions and 172 deletions
|
|
@ -0,0 +1,524 @@
|
|||
# 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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue