summaryrefslogtreecommitdiff
path: root/routers/stats.py
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-06 18:05:07 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-06 18:05:07 -0700
commitead38fdb13abb406065cef0743d7e411cb27eaf3 (patch)
tree56f5bc31be37833505726ac61cec0cfd19a7be92 /routers/stats.py
parent2d298f982408f222ad344b2aa9c18bbe7dc70f12 (diff)
Add genre tracking and year-in-review improvements
Adds genre field to Film model with TMDB enrichment. Genres populate from TMDB detail fetch during add/edit and bulk enrichment. Genre metadata displays on film cards, detail page (Production section), stats page (top genres panel), and year-in-review (by decade and genre breakdowns). Auto-detects rewatches when adding films via TMDB autocomplete - if a film with the same TMDB ID already exists in diary, pre-fills rewatch checkbox and count. Rewatch count now displays on film cards as "Rewatch #N". Stats page now shows: - Top genres (most watched) - Film decades (sorted chronologically) - Already shows: directors, companions, star distribution, rewatch rate Year-in-review shows decades and genres alongside monthly activity and companions. Bulk enrichment endpoint (/data/enrich-posters) now fetches missing genre metadata along with posters and TMDB IDs. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Diffstat (limited to 'routers/stats.py')
-rw-r--r--routers/stats.py37
1 files changed, 37 insertions, 0 deletions
diff --git a/routers/stats.py b/routers/stats.py
index 0604646..67b5141 100644
--- a/routers/stats.py
+++ b/routers/stats.py
@@ -15,6 +15,11 @@ from services.countries import (
)
from services.film_people import split_credit_names
+def split_genre_names(genre_str: str | None) -> list[str]:
+ if not genre_str:
+ return []
+ return [name.strip() for name in genre_str.split(",") if name.strip()]
+
router = APIRouter(tags=["stats"])
templates = Jinja2Templates(directory="templates")
@@ -22,7 +27,9 @@ templates = Jinja2Templates(directory="templates")
def _build_stats_payload(films: list[Film]) -> dict:
countries = Counter()
country_codes = Counter()
+ decades = Counter()
directors = Counter()
+ genres = Counter()
star_counts = Counter({0: 0, 1: 0, 2: 0, 3: 0})
months = Counter()
days = Counter()
@@ -36,6 +43,12 @@ def _build_stats_payload(films: list[Film]) -> dict:
if iso_numeric is not None:
country_codes[iso_numeric] += 1
+ if film.year:
+ decade = (film.year // 10) * 10
+ decades[f"{decade}s"] += 1
+
+ genres.update(split_genre_names(film.genre))
+
directors.update(split_credit_names(film.director))
stars = film.stars if film.stars in {0, 1, 2, 3} else 0
@@ -83,6 +96,14 @@ def _build_stats_payload(films: list[Film]) -> dict:
{"director": director, "count": count}
for director, count in sorted(directors.items(), key=lambda item: (-item[1], item[0]))
],
+ "films_per_decade": [
+ {"decade": decade, "count": count}
+ for decade, count in sorted(decades.items(), key=lambda item: (item[0], -item[1]))
+ ],
+ "films_per_genre": [
+ {"genre": genre, "count": count}
+ for genre, count in sorted(genres.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}
@@ -170,6 +191,8 @@ def _year_review_payload(db: Session, year: int | None) -> dict:
"total_watched": 0,
"average_stars": 0,
"most_watched_directors": [],
+ "films_per_decade": [],
+ "films_per_genre": [],
"star_distribution": [{"stars": stars, "count": 0} for stars in (0, 1, 2, 3)],
"films_per_month": [{"month": month_name[index], "count": 0} for index in range(1, 13)],
"rewatch_rate": {"rewatched": 0, "total_watched": 0, "rate": 0},
@@ -186,13 +209,19 @@ def _year_review_payload(db: Session, year: int | None) -> dict:
year_films = _films_for_year(diary_films, selected_year)
countries = Counter()
+ decades = Counter()
directors = Counter()
+ genres = Counter()
star_counts = Counter({0: 0, 1: 0, 2: 0, 3: 0})
months = Counter({month_index: 0 for month_index in range(1, 13)})
watched_with = Counter()
for film in year_films:
countries.update(split_country_names(film.country))
+ if film.year:
+ decade = (film.year // 10) * 10
+ decades[f"{decade}s"] += 1
+ genres.update(split_genre_names(film.genre))
directors.update(split_credit_names(film.director))
stars = film.stars if film.stars in {0, 1, 2, 3} else 0
star_counts[stars] += 1
@@ -272,6 +301,14 @@ def _year_review_payload(db: Session, year: int | None) -> dict:
{"month": month_name[index], "count": months[index]}
for index in range(1, 13)
],
+ "films_per_decade": [
+ {"decade": decade, "count": count}
+ for decade, count in sorted(decades.items(), key=lambda item: (item[0], -item[1]))
+ ],
+ "films_per_genre": [
+ {"genre": genre, "count": count}
+ for genre, count in sorted(genres.items(), key=lambda item: (-item[1], item[0]))
+ ],
"star_distribution": [{"stars": stars, "count": star_counts[stars]} for stars in (0, 1, 2, 3)],
"most_watched_directors": [
{"director": director, "count": count}