diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-07 00:02:25 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-07 00:02:25 -0700 |
| commit | 84ac42e5be00fe7591a6946f27a33770b0270a41 (patch) | |
| tree | 5f0072c63e3d216149606d211b343698fc38472e | |
| parent | 845843a76c318c4894c111e4019e53af6bb0b3f2 (diff) | |
Add rewatch patterns stats panel
Adds a "Rewatches" section to /stats that displays films you've watched multiple times, grouped by title. For each rewatched film, shows:
- Watch count (e.g., "3×")
- Rating history (e.g., ✦ → ✦✦ → ✦) with accent highlight when ratings drift
- Days between first and last watch
Changes:
- routers/stats.py: Group films by title in _build_stats_payload, compute rewatch details (ratings, days between, rating change flag), add rewatch_patterns key
- templates/stats.html: Add "Rewatches" panel HTML and JS renderer (renderLists)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
| -rw-r--r-- | routers/stats.py | 26 | ||||
| -rw-r--r-- | templates/stats.html | 30 |
2 files changed, 55 insertions, 1 deletions
diff --git a/routers/stats.py b/routers/stats.py index 67b5141..2abc493 100644 --- a/routers/stats.py +++ b/routers/stats.py @@ -1,4 +1,4 @@ -from collections import Counter +from collections import Counter, defaultdict from calendar import month_name from datetime import date, timedelta @@ -67,6 +67,29 @@ def _build_stats_payload(films: list[Film]) -> dict: total_watched = len(films) rewatched = sum(1 for film in films if film.rewatch or film.rewatch_count > 0) + title_groups = defaultdict(list) + for film in films: + title_groups[film.title].append(film) + + rewatch_details = [] + for title, entries in title_groups.items(): + if len(entries) < 2: + continue + sorted_entries = sorted(entries, key=lambda f: f.date_watched or date.min) + ratings = [e.stars for e in sorted_entries] + first_date = sorted_entries[0].date_watched + last_date = sorted_entries[-1].date_watched + days_between = (last_date - first_date).days if first_date and last_date else None + rewatch_details.append({ + "title": title, + "watches": len(sorted_entries), + "ratings": ratings, + "days_between": days_between, + "rating_changed": len({r for r in ratings if r > 0}) > 1, + }) + + rewatch_details.sort(key=lambda x: (-x["watches"], x["title"])) + today = date.today() start_day = today - timedelta(days=364) trailing_days = [] @@ -119,6 +142,7 @@ def _build_stats_payload(films: list[Film]) -> dict: "total_watched": total_watched, "rate": round(rewatched / total_watched, 4) if total_watched else 0, }, + "rewatch_patterns": rewatch_details, "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])) diff --git a/templates/stats.html b/templates/stats.html index 9b71679..6f271bd 100644 --- a/templates/stats.html +++ b/templates/stats.html @@ -98,6 +98,16 @@ </div> <ol id="film-decades" class="stats-list"></ol> </section> + + <section class="stats-panel stats-panel-wide"> + <div class="stats-panel-header"> + <div> + <p class="eyebrow">Films</p> + <h2>Rewatches</h2> + </div> + </div> + <div id="rewatch-patterns-list" class="stats-list"></div> + </section> </section> {% endblock %} @@ -164,6 +174,26 @@ <strong>${rate}%</strong> <span>${data.rewatch_rate.rewatched} of ${data.rewatch_rate.total_watched} watched films were rewatches.</span> `; + + const rewatchPatterns = document.getElementById("rewatch-patterns-list"); + if (data.rewatch_patterns && data.rewatch_patterns.length > 0) { + rewatchPatterns.innerHTML = data.rewatch_patterns.map((item) => { + const stars = (n) => (n > 0 ? "✦".repeat(n) : "—"); + const ratingHistory = item.ratings.map(stars).join(" → "); + const drift = item.rating_changed + ? `<span style="color:var(--accent)">${ratingHistory}</span>` + : `<span style="color:var(--muted)">${ratingHistory}</span>`; + const gap = item.days_between != null + ? `<span style="color:var(--subtle);font-size:13px">${item.days_between}d apart</span>` + : ""; + return `<div class="stats-list-row"> + <span>${item.title}</span> + <span style="display:flex;gap:12px;align-items:center">${gap}${drift}<strong>${item.watches}×</strong></span> + </div>`; + }).join(""); + } else { + rewatchPatterns.innerHTML = "<p style=\"color:var(--muted);font-size:14px;margin:0\">No rewatches yet.</p>"; + } } function renderHeatmap(days) { |
