summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--routers/stats.py26
-rw-r--r--templates/stats.html30
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) {