press-releases: begrenzter Pruefkreislauf mit Admin-Queue (Roadmap 1d.4, E3)
Automatische Pruefung je Meldung hoechstens max_automatic_attempts (3); das letzte Rot blockt statt abzulehnen: Meldung bleibt in review, Admin sieht sie unter "Pruefung noetig" mit allen KI-Begruendungen und entscheidet (Freischalten / Mit Kommentar zurueck). Danach ist die Automatik fuer diese Meldung aus - jede Freigabe geht direkt zum Menschen. Gelb: yellow_to_manual_queue wird jetzt ausgewertet (Vorgabe false). Kundenseite: "Zur Pruefung freigeben" mit Erklaerung der drei Schritte, Versuchszaehler an der Ablehnung, Hinweis auf menschliche Pruefung. Blade: inline @php(...) vor einem @php...@endphp-Block wird als Rohblock gelesen - vier Stellen auf Blockform umgestellt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c08c9e5de0
commit
0692d892e0
17 changed files with 487 additions and 47 deletions
|
|
@ -94,20 +94,35 @@ class PressReleaseService
|
|||
// Zeitpunkt wird hier zur Nachweisbarkeit protokolliert. (Spalte
|
||||
// heißt historisch withdrawal_waiver_consented_at; die frühere
|
||||
// Erlöschens-Checkbox entfiel mit der B2B-Umstellung, E2.)
|
||||
// Begrenzter Prüfkreislauf (E3, 16.09.2026): Ist die Automatik für
|
||||
// diese Meldung erschöpft, geht die Freigabe ohne KI-Lauf direkt an
|
||||
// einen Menschen. Sonst zählt dieser Lauf.
|
||||
$manual = $pressRelease->requiresManualReview();
|
||||
|
||||
$pressRelease->update([
|
||||
'status' => PressReleaseStatus::Review->value,
|
||||
'withdrawal_waiver_consented_at' => now(),
|
||||
'auto_check_attempts' => $manual ? $pressRelease->auto_check_attempts : $pressRelease->auto_check_attempts + 1,
|
||||
]);
|
||||
$this->logStatusChange($pressRelease, $previous, PressReleaseStatus::Review, null, $source);
|
||||
$this->logStatusChange(
|
||||
$pressRelease,
|
||||
$previous,
|
||||
PressReleaseStatus::Review,
|
||||
$manual ? 'Erneute Freigabe nach erschöpfter automatischer Prüfung — wartet auf manuelle Entscheidung.' : null,
|
||||
$source,
|
||||
);
|
||||
|
||||
// Versionierter Audit-Trail der KI-Deklaration: jeder Submit erzeugt
|
||||
// einen neuen, unveränderlichen Eintrag (Abs.-50-Verteidigung —
|
||||
// "Kunde hat am Datum X Option Y deklariert und bestätigt").
|
||||
$this->logAiDeclaration($pressRelease);
|
||||
|
||||
// KI-Klassifikation asynchron anstoßen (Red Flag). Das Routing anhand
|
||||
// des Ergebnisses übernimmt der Job über routeByClassification().
|
||||
ClassifyPressRelease::dispatch($pressRelease->id)->onQueue('classification');
|
||||
if (! $manual) {
|
||||
// KI-Klassifikation asynchron anstoßen (Red Flag). Das Routing
|
||||
// anhand des Ergebnisses übernimmt der Job über
|
||||
// routeByClassification().
|
||||
ClassifyPressRelease::dispatch($pressRelease->id)->onQueue('classification');
|
||||
}
|
||||
|
||||
// Content-Score parallel berechnen (Qualität, ohne Statuswirkung).
|
||||
ScorePressRelease::dispatch($pressRelease->id)->onQueue('classification');
|
||||
|
|
@ -183,14 +198,56 @@ class PressReleaseService
|
|||
}
|
||||
|
||||
if ($classification === PressReleaseClassification::Red) {
|
||||
$max = max(1, (int) config('scoring.classification.max_automatic_attempts', 3));
|
||||
|
||||
// E3: Nach dem letzten erlaubten Rot nicht noch einmal ablehnen,
|
||||
// sondern geblockt in die Admin-Queue — ab hier entscheidet ein
|
||||
// Mensch, und die Automatik ist für diese Meldung aus.
|
||||
if ($pressRelease->auto_check_attempts >= $max) {
|
||||
$this->holdForManualReview($pressRelease, sprintf(
|
||||
'Automatische Prüfung erschöpft (%d von %d): %s',
|
||||
$pressRelease->auto_check_attempts,
|
||||
$max,
|
||||
$reason ?: 'Rot ohne Begründung.',
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->reject($pressRelease, $reason ?: 'Automatische Ablehnung durch die KI-Prüfung.', 'ki');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Gelb geht durch (Entscheidung 16.09.2026) — außer der Schalter für
|
||||
// die manuelle Queue ist bewusst eingeschaltet.
|
||||
if ($classification === PressReleaseClassification::Yellow && config('scoring.classification.yellow_to_manual_queue')) {
|
||||
$this->holdForManualReview($pressRelease, 'Gelb — manuelle Prüfung laut Konfiguration.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->autoPublishApproved($pressRelease);
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldung bleibt in `review`, wird als „Prüfung nötig" markiert und
|
||||
* wartet auf die Entscheidung im Admin. Kein Slot-Verbrauch, keine Mail
|
||||
* an den Autor — der sieht den Stand in seinem Konto.
|
||||
*/
|
||||
private function holdForManualReview(PressRelease $pressRelease, string $reason): void
|
||||
{
|
||||
$pressRelease->update(['manual_review_required_at' => now()]);
|
||||
$this->logStatusChange($pressRelease, PressReleaseStatus::Review, PressReleaseStatus::Review, $reason, 'ki');
|
||||
|
||||
$this->activityLog->record(
|
||||
ActivityType::PressReleaseStatus,
|
||||
sprintf('PM #%d wartet auf manuelle Prüfung: %s', $pressRelease->id, mb_substr($reason, 0, 160)),
|
||||
subject: $pressRelease,
|
||||
actorLabel: 'ki',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Veröffentlicht eine als Gelb oder Grün klassifizierte PM automatisch.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue