presseportale/app/Services/Image/Precheck/ImagePrecheckEvaluation.php
Kevin Adametz 8bb7049bdc images: KI-Vorab-Check für Titelbilder — Vision-Sichtung mit definiertem Ausgang je Befund, Admin-Queue, Protokoll und Archiv-Snapshot (Bildkennzeichnung Block K)
Decision-Update Abschnitt 12 (E15, S24): Sichtung auf offensichtliche
Probleme, keine Rechteprüfung, öffentlich weiterhin „automatisierte Prüfung".

- ImagePrecheckManager mit openai- und deterministic-Treiber (Muster der
  Text-Prüfstrecke), Bild auf 1024 px, Metadaten gestrippt, JPEG als Data-URI,
  Antwortschema 12.4; Fehler → Ausgang `error`, durchlassen mit Vermerk.
- Auswertung 12.3: reject (Stock-Wasserzeichen, Nacktheit/Gewalt, Dokument
  ab 0,85), queue (Screenshot, fremdes Logo, Kennzeichen ab 0,6 sowie
  Ablehnungskategorien 0,6–0,85), hint (Personen, „wirkt KI" nur bei „ohne
  KI", technischer Mangel). Stufe 1 wertet nur das Wasserzeichen; die KI setzt
  nie die Kennzeichnung (E9). Herabstufung per Config (12.8).
- Uploader: Check vor jedem Schreiben, Ablehnung speichert nichts und
  landet als ki_audits `image_precheck` an der PM; Hash-Bindung zwischen
  Vorprüfung im Formular und Speichern; Spalten precheck*, Snapshot
  `precheck_snapshot` im Bestätigungsprotokoll, Archiv-Payload erweitert.
- Queue: Bild zurückgehalten (withheld_at), Meldung läuft weiter; Admin
  entscheidet mit Vermerk (Freigabe/Ablehnung → ki_audits
  `image_precheck_review`); Ansicht „Bild-Sichtung" in der PM-Liste,
  Sichtungsblock auf der PM-Seite, Systemstatus-Zähler.
- Formular v2: Ablehnungsgrund unter dem Dateifeld, Hinweise nach der
  Dateiauswahl, „wird kurz gesichtet" an der Karte; API 422 mit Grund,
  Warnung `image_under_review`.
- Config images.precheck (IMAGE_PRECHECK aus, STAGE 1); 17 Tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 13:27:31 +00:00

81 lines
2.6 KiB
PHP

<?php
namespace App\Services\Image\Precheck;
use App\Enums\ImagePrecheckCategory;
use App\Enums\ImagePrecheckOutcome;
/**
* Ausgewerteter Befund (DU 12.3): ein Ausgang, die auslösenden Kategorien
* je Ausgang und die Sätze für den Einreicher.
*/
final readonly class ImagePrecheckEvaluation
{
/**
* @param array<int, array{category: ImagePrecheckCategory, confidence: float, outcome: ImagePrecheckOutcome, note: string|null}> $triggers
*/
public function __construct(
public ImagePrecheckOutcome $outcome,
public array $triggers,
public int $stage,
) {}
/**
* @return array<int, array{category: ImagePrecheckCategory, confidence: float, outcome: ImagePrecheckOutcome, note: string|null}>
*/
public function triggersFor(ImagePrecheckOutcome $outcome): array
{
return array_values(array_filter($this->triggers, fn (array $t): bool => $t['outcome'] === $outcome));
}
/** Grund in einem Satz bei automatischer Ablehnung. */
public function rejectionMessage(): ?string
{
$first = $this->triggersFor(ImagePrecheckOutcome::Reject)[0] ?? null;
return $first !== null ? $first['category']->rejectionMessage() : null;
}
/**
* Hinweise fürs Formular, in der Reihenfolge der Kategorien.
*
* @return array<int, string>
*/
public function hints(): array
{
return array_map(
fn (array $t): string => $t['category']->hintMessage($t['note']),
$this->triggersFor(ImagePrecheckOutcome::Hint),
);
}
/** Kurzfassung fürs Audit: "stock_watermark 0.92, persons_visible 0.80". */
public function summary(): string
{
if ($this->triggers === []) {
return $this->outcome === ImagePrecheckOutcome::Error ? 'nicht geprüft' : 'ohne Befund';
}
return implode(', ', array_map(
fn (array $t): string => sprintf('%s %.2f → %s', $t['category']->value, $t['confidence'], $t['outcome']->value),
$this->triggers,
));
}
/**
* @return array{outcome: string, stage: int, triggers: array<int, array{category: string, confidence: float, outcome: string, note: string|null}>}
*/
public function toArray(): array
{
return [
'outcome' => $this->outcome->value,
'stage' => $this->stage,
'triggers' => array_map(fn (array $t): array => [
'category' => $t['category']->value,
'confidence' => $t['confidence'],
'outcome' => $t['outcome']->value,
'note' => $t['note'],
], $this->triggers),
];
}
}