from collections import Counter from datetime import date, timedelta from fastapi import APIRouter, Depends, Request from fastapi.templating import Jinja2Templates from sqlalchemy.orm import Session from database import get_db from models import Film from services.countries import ( ISO_NUMERIC_TO_COUNTRY_NAME, country_name_to_iso_numeric, split_country_names, ) from services.film_people import split_credit_names router = APIRouter(tags=["stats"]) templates = Jinja2Templates(directory="templates") def _build_stats_payload(films: list[Film]) -> dict: countries = Counter() country_codes = Counter() directors = Counter() star_counts = Counter({0: 0, 1: 0, 2: 0, 3: 0}) months = Counter() days = Counter() watched_with = Counter() for film in films: country_names = split_country_names(film.country) countries.update(country_names) for country in country_names: iso_numeric = country_name_to_iso_numeric(country) if iso_numeric is not None: country_codes[iso_numeric] += 1 directors.update(split_credit_names(film.director)) stars = film.stars if film.stars in {0, 1, 2, 3} else 0 star_counts[stars] += 1 if film.date_watched: months[film.date_watched.strftime("%Y-%m")] += 1 days[film.date_watched.isoformat()] += 1 companions = split_credit_names(film.watched_with) if companions: watched_with.update(companions) else: watched_with["solo"] += 1 total_watched = len(films) rewatched = sum(1 for film in films if film.rewatch or film.rewatch_count > 0) today = date.today() start_day = today - timedelta(days=364) trailing_days = [] cursor = start_day while cursor <= today: trailing_days.append({"date": cursor.isoformat(), "count": days[cursor.isoformat()]}) cursor += timedelta(days=1) return { "scope": { "shelf": "diary", "requires_date_watched": True, }, "total_watched": total_watched, "films_per_country": [ {"country": country, "count": count} for country, count in sorted(countries.items(), key=lambda item: (-item[1], item[0])) ], "films_per_country_codes": [ {"code": code, "count": count} for code, count in sorted(country_codes.items(), key=lambda item: (-item[1], item[0])) ], "country_labels_by_code": { str(code): ISO_NUMERIC_TO_COUNTRY_NAME.get(code, str(code)) for code in country_codes }, "most_watched_directors": [ {"director": director, "count": count} for director, count in sorted(directors.items(), key=lambda item: (-item[1], item[0])) ], "star_distribution": [{"stars": stars, "count": star_counts[stars]} for stars in (0, 1, 2, 3)], "films_per_month": [ {"month": month, "count": count} for month, count in sorted(months.items()) ], "films_per_day": [ {"date": watched_date, "count": count} for watched_date, count in sorted(days.items()) ], "films_per_day_365": trailing_days, "rewatch_rate": { "rewatched": rewatched, "total_watched": total_watched, "rate": round(rewatched / total_watched, 4) if total_watched else 0, }, "watched_with_breakdown": [ {"watched_with": watched_with_value, "count": count} for watched_with_value, count in sorted(watched_with.items(), key=lambda item: (-item[1], item[0])) ], } def _diary_films(db: Session) -> list[Film]: return ( db.query(Film) .filter(Film.shelf == "diary", Film.date_watched.is_not(None)) .all() ) @router.get("/stats") def stats_page(request: Request): return templates.TemplateResponse( request=request, name="stats.html", context={"request": request, "active_page": "stats"}, ) @router.get("/stats/data") def stats_data(db: Session = Depends(get_db)): return _build_stats_payload(_diary_films(db))