| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- <?php
- declare(strict_types=1);
- namespace App\Domain\Reputation;
- /**
- * Pure decay math per SPEC §5. Stateless — easy to unit-test against
- * hand-computed values.
- *
- * - Linear : max(0, 1 − age_days / decay_param)
- * - Exponential: 0.5 ^ (age_days / decay_param) (half-life)
- *
- * `age_days` may be fractional. Negative ages (future-dated reports —
- * shouldn't happen, but guard anyway) clamp to zero so a fresh report
- * always counts at full weight.
- */
- final class Decay
- {
- public static function value(DecayFunction $function, float $ageDays, float $decayParam): float
- {
- if ($decayParam <= 0.0) {
- return 0.0;
- }
- $age = max(0.0, $ageDays);
- return match ($function) {
- DecayFunction::Linear => max(0.0, 1.0 - ($age / $decayParam)),
- DecayFunction::Exponential => 0.5 ** ($age / $decayParam),
- };
- }
- /**
- * Inverse of `value()`: roughly how many days until a current score
- * decays below `$threshold`, assuming no further reports.
- *
- * Exponential is exact (the sum scales by 0.5^(Δ/T) regardless of
- * report ages). Linear treats the current score as a lump at "now"
- * — an upper bound on time-to-threshold; the real curve drops faster
- * as individual reports age out. Used by the policy preview to give
- * operators an at-a-glance "when does this entry fall off?".
- *
- * Returns 0.0 if the score is already at or below the threshold,
- * `null` if the threshold is zero/negative (entry would never fall
- * off naturally).
- */
- public static function daysUntilThreshold(
- DecayFunction $function,
- float $decayParam,
- float $currentScore,
- float $threshold,
- ): ?float {
- if ($decayParam <= 0.0) {
- return 0.0;
- }
- if ($threshold <= 0.0) {
- return null;
- }
- if ($currentScore <= $threshold) {
- return 0.0;
- }
- return match ($function) {
- DecayFunction::Linear => $decayParam * (1.0 - $threshold / $currentScore),
- DecayFunction::Exponential => $decayParam * (log($currentScore / $threshold) / log(2.0)),
- };
- }
- }
|