from __future__ import annotations from collections import defaultdict from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Query from sqlalchemy import select from sqlalchemy.orm import Session, joinedload, selectinload from ..dependencies import Db from ..models import CatchReport, Fish, ModerationStatus, Spot, Waterbody from ..schemas import TackleCombinationOut router = APIRouter() @router.get("/api/v1/analytics/tackle", response_model=list[TackleCombinationOut]) def tackle_combinations( db: Db, waterbody: str | None = None, fish: str | None = None, method: str | None = None, hours: int = Query(72, ge=24, le=168), min_samples: int = Query(3, ge=1, le=100), min_players: int = Query(2, ge=1, le=100), ) -> list[TackleCombinationOut]: now = datetime.now(timezone.utc) query = select(CatchReport).options( joinedload(CatchReport.fish), joinedload(CatchReport.waterbody), selectinload(CatchReport.tackle_components), ).where( CatchReport.moderation_status == ModerationStatus.approved, CatchReport.deleted_at.is_(None), CatchReport.reported_at >= now - timedelta(hours=hours), ) if waterbody: query = query.join(Waterbody, CatchReport.waterbody_id == Waterbody.id).where(Waterbody.slug == waterbody) if fish: query = query.join(Fish, CatchReport.fish_id == Fish.id).where(Fish.slug == fish) if method: query = query.where(CatchReport.fishing_method == method) groups: dict[tuple[str, str], list[CatchReport]] = defaultdict(list) for report in db.scalars(query): for component in report.tackle_components: if component.raw_value.strip(): groups[(component.role, component.raw_value.strip())].append(report) result = [] for (role, value), reports in groups.items(): unique_reports = {report.id: report for report in reports} players = {report.player_name.strip().casefold() for report in unique_reports.values() if report.player_name and report.player_name.strip()} catches = len(unique_reports) unique_players = len(players) last_seen = max(report.reported_at for report in unique_reports.values()) 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", 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))