presseportale/app/Services/PressRelease/CrawlerHitReport.php
Kevin Adametz c26bdff681 admin: Bericht „Indexierung & Abrufe" — Rolle Erstquelle/Kopie, Quellenbefund, Crawler-Abrufe; Schlüsselpfad ab Projektwurzel
Der Bericht meldete auf Live „nicht konfiguriert", während der Lauf
längst Daten holte: der relative Pfad der Schlüsseldatei war im Cron
(Projektwurzel) lesbar, im Web-Request (public/) nicht. Relative Pfade
werden jetzt ab base_path() aufgelöst.

Die Verteilung trennt zusätzlich nach Rolle in der kanonischen Zuordnung
(1c.8): bei einer Kopie mit Verweis ist „ausgeschlossen" der erwartete
Befund. Neu: Quellenbefund über Erstquellen (welche Adresse Google führt,
fremde Hosts), Abrufe durch Such- und KI-Dienste je Dienst, Tag und
Meldung mit CSV. Herkunftsmarker als Join statt korreliertem EXISTS,
Aggregate 15 Minuten gecacht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 12:53:22 +00:00

112 lines
4.2 KiB
PHP

<?php
namespace App\Services\PressRelease;
use App\Models\PressReleaseCrawlerHit;
use App\Support\CrawlerIdentifier;
use Illuminate\Support\Carbon;
/**
* Admin-Auswertung des Crawler-Abruf-Protokolls (Roadmap 1c.0): welche
* Such- und KI-Dienste in welchem Umfang Meldungen geholt haben. Zählt
* Abrufe, nicht Zitate — das Protokoll weiß nicht, was ein Dienst mit der
* Seite gemacht hat.
*/
class CrawlerHitReport
{
/**
* Je Dienst: Abrufe und betroffene Meldungen im Zeitraum, Abrufe seit
* Beginn des Protokolls, letzter Abruf. Sortiert nach Abrufen im Zeitraum.
*
* @return list<array{service: string, label: string, kind: string, fetches: int, releases: int, fetches_total: int, last_at: Carbon}>
*/
public function byService(int $days = 30): array
{
$since = now()->subDays($days);
$recent = PressReleaseCrawlerHit::query()
->where('created_at', '>=', $since)
->selectRaw('service, count(*) as fetches, count(distinct press_release_id) as releases')
->groupBy('service')
->toBase()
->get()
->keyBy('service');
return PressReleaseCrawlerHit::query()
->selectRaw('service, count(*) as fetches_total, max(created_at) as last_at')
->groupBy('service')
->toBase()
->get()
->map(fn (object $row): array => [
'service' => (string) $row->service,
'label' => CrawlerIdentifier::label((string) $row->service),
'kind' => CrawlerIdentifier::kind((string) $row->service),
'fetches' => (int) ($recent[$row->service]->fetches ?? 0),
'releases' => (int) ($recent[$row->service]->releases ?? 0),
'fetches_total' => (int) $row->fetches_total,
'last_at' => Carbon::parse((string) $row->last_at),
])
->sortByDesc(fn (array $row): array => [$row['fetches'], $row['fetches_total']])
->values()
->all();
}
/**
* Abrufe je Tag über die letzten N Tage, lückenlos, getrennt nach Suche
* und KI (Art aus config/crawlers.php).
*
* @return list<array{date: Carbon, search: int, ai: int}>
*/
public function daily(int $days = 14): array
{
$start = now()->startOfDay()->subDays($days - 1);
$counts = PressReleaseCrawlerHit::query()
->where('created_at', '>=', $start)
->selectRaw('date(created_at) as day, service, count(*) as fetches')
->groupBy('day', 'service')
->toBase()
->get()
->groupBy('day');
$series = [];
for ($i = 0; $i < $days; $i++) {
$day = $start->copy()->addDays($i);
$row = ['date' => $day, 'search' => 0, 'ai' => 0];
foreach ($counts->get($day->toDateString(), collect()) as $entry) {
$row[CrawlerIdentifier::kind((string) $entry->service) === 'search' ? 'search' : 'ai'] += (int) $entry->fetches;
}
$series[] = $row;
}
return $series;
}
/**
* Am häufigsten abgerufene Meldungen im Zeitraum.
*
* @return list<array{press_release_id: int, title: string, fetches: int, services: int}>
*/
public function topReleases(int $days = 30, int $limit = 15): array
{
return PressReleaseCrawlerHit::query()
->join('press_releases', 'press_releases.id', '=', 'press_release_crawler_hits.press_release_id')
->where('press_release_crawler_hits.created_at', '>=', now()->subDays($days))
->selectRaw('press_release_crawler_hits.press_release_id, press_releases.title, count(*) as fetches, count(distinct service) as services')
->groupBy('press_release_crawler_hits.press_release_id', 'press_releases.title')
->orderByDesc('fetches')
->limit($limit)
->toBase()
->get()
->map(fn (object $row): array => [
'press_release_id' => (int) $row->press_release_id,
'title' => (string) $row->title,
'fetches' => (int) $row->fetches,
'services' => (int) $row->services,
])
->all();
}
}