Files
saqel/backend/app/Services/WatchIntervalPolicy.php
T

36 lines
1.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services;
/** Pure watch-v1 measurement rules. This measures playback evidence, not attention. */
final class WatchIntervalPolicy
{
public static function evaluate(
string $previousType,
string $eventType,
int $previousPositionMs,
int $positionMs,
int $serverDeltaSeconds,
int $durationMs,
string $fundingSource
): array {
if ($eventType !== 'heartbeat' || !in_array($previousType, ['start', 'heartbeat', 'resume'], true)) {
return ['seconds' => 0, 'status' => 'excluded', 'reason' => 'non_contiguous_event'];
}
if ($fundingSource === 'none') {
return ['seconds' => 0, 'status' => 'excluded', 'reason' => 'unfunded_session'];
}
if ($durationMs <= 0 || $positionMs > $durationMs) {
return ['seconds' => 0, 'status' => 'review', 'reason' => 'position_out_of_bounds'];
}
$clientDeltaMs = $positionMs - $previousPositionMs;
if ($clientDeltaMs < 1000 || $clientDeltaMs > 90000 || $serverDeltaSeconds < 1 || $serverDeltaSeconds > 90) {
return ['seconds' => 0, 'status' => 'review', 'reason' => 'implausible_delta'];
}
$seconds = min((int)floor($clientDeltaMs / 1000), $serverDeltaSeconds, 60);
if ($seconds <= 0) return ['seconds' => 0, 'status' => 'excluded', 'reason' => 'zero_interval'];
return ['seconds' => $seconds, 'status' => 'measured', 'reason' => 'contiguous_heartbeat'];
}
}