where('fuel_type', $context->fuelType->value) ->where('price_effective_at', '>=', now()->subDays(30)) ->selectRaw("station_id, COUNT(*) as changes, {$diffExpr} as span_days") ->groupBy('station_id') ->having('changes', '>', 1) ->having('span_days', '>', 0) ->get(); if ($rows->count() < 10) { return $this->disabledSignal('Insufficient stickiness data (need 10+ stations with price history)'); } $avgHoldDays = $rows->avg(fn ($r) => $r->span_days / ($r->changes - 1)); $avgHoldDays = round((float) $avgHoldDays, 1); $score = match (true) { $avgHoldDays < 2 => -0.1, $avgHoldDays > 5 => 0.1, default => 0.0, }; $detail = match (true) { $avgHoldDays < 2 => "Volatile prices (avg hold: {$avgHoldDays} days) — harder to predict.", $avgHoldDays > 5 => "Sticky prices (avg hold: {$avgHoldDays} days) — more predictable.", default => "Normal hold period (avg: {$avgHoldDays} days).", }; return [ 'score' => $score, 'confidence' => min(1.0, $rows->count() / 200), 'direction' => 'stable', 'detail' => $detail, 'data_points' => $rows->count(), 'enabled' => true, ]; } }