feat: add freshness decay to tackle analytics
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from math import exp, log
|
||||
|
||||
from fastapi import APIRouter, Query
|
||||
from sqlalchemy import select
|
||||
@@ -12,6 +13,12 @@ from ..models import CatchReport, Fish, ModerationStatus, Spot, Waterbody
|
||||
from ..schemas import TackleCombinationOut
|
||||
|
||||
router = APIRouter()
|
||||
FRESHNESS_HALF_LIFE_HOURS = 12.5
|
||||
|
||||
|
||||
def _age_hours(value: datetime, now: datetime) -> float:
|
||||
observed_at = value if value.tzinfo is not None else value.replace(tzinfo=timezone.utc)
|
||||
return max(0.0, (now - observed_at).total_seconds() / 3600)
|
||||
|
||||
|
||||
@router.get("/api/v1/analytics/tackle", response_model=list[TackleCombinationOut])
|
||||
@@ -53,14 +60,19 @@ def tackle_combinations(
|
||||
catches = len(unique_reports)
|
||||
unique_players = len(players)
|
||||
last_seen = max(report.reported_at for report in unique_reports.values())
|
||||
freshness_score = round(sum(
|
||||
exp(-_age_hours(report.reported_at, now) * log(2) / FRESHNESS_HALF_LIFE_HOURS)
|
||||
for report in unique_reports.values()
|
||||
) / catches * 100)
|
||||
enough = catches >= min_samples and unique_players >= min_players
|
||||
result.append(TackleCombinationOut(
|
||||
role=role, value=value, catches=catches, unique_players=unique_players,
|
||||
last_seen_at=last_seen, status="recommendation" if enough else "insufficient_data",
|
||||
last_seen_at=last_seen, freshness_score=freshness_score,
|
||||
status="recommendation" if enough else "insufficient_data",
|
||||
explanation=(
|
||||
"Достаточно независимых наблюдений для рекомендации."
|
||||
if enough else
|
||||
f"Данных мало: нужно минимум {min_samples} наблюдения и {min_players} независимых игрока."
|
||||
),
|
||||
))
|
||||
return sorted(result, key=lambda item: (item.status != "recommendation", -item.catches, -item.unique_players, item.role, item.value))
|
||||
return sorted(result, key=lambda item: (item.status != "recommendation", -item.freshness_score, -item.catches, -item.unique_players, item.role, item.value))
|
||||
|
||||
Reference in New Issue
Block a user