summaryrefslogtreecommitdiff
path: root/routers/stats.py
blob: 78e9621581fb2a026b80150f830e478f8fd8bb5f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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))