Stats: GitHub-style watch-time heatmap

- api/stats gains by_day: local-date -> seconds (same watch-time
  heuristic as by_weekday)
- ?stats renders a Mon..Sun x week grid (30d window, matches the
  page header) with 4 intensity levels + tooltip per cell + legend;
  replaces the by-weekday bar chart the heatmap subsumes
- test_stats covers by_day (sums, slug-scoped, empty case)
This commit is contained in:
jozamudi committed 2026-08-24 22:24:16 +00:00
1 parent 7d9fab6bd9
commit 7b670cff24
4 files changed
+52 -20

No files matched your search

+3 -1
View File
@@ -198,6 +198,7 @@ def test_stats(base=BASE):
assert d["days_active"] == 1 and d["best_streak"] == 1
assert d["on_hold"] == 0
assert sum(d["by_weekday"]) == 250 # seconds, Mon..Sun
assert sum(d["by_day"].values()) == 250 and all(isinstance(v, int) and v > 0 for v in d["by_day"].values())
a0 = d["top_anime"][0]
assert a0["title"] == "Stat Show" and a0["status"] == "ongoing", a0
assert a0["cur_ep"] == "2" and a0["total"] == 24 and a0["poster"] == "https://p/1.jpg"
@@ -207,8 +208,9 @@ def test_stats(base=BASE):
# slug filter scopes to one anime
ds = _json("GET", "/api/stats?days=7&slug=stat-a", jar=jar, base=base)
assert len(ds["top_anime"]) == 1 and ds["watch_minutes"] == 4
assert sum(ds["by_day"].values()) == 250
dn = _json("GET", "/api/stats?days=7&slug=none", jar=jar, base=base)
assert dn["top_anime"] == [] and dn["watch_minutes"] == 0
assert dn["top_anime"] == [] and dn["watch_minutes"] == 0 and dn["by_day"] == {}
# per-user isolation: alice never watched stat-a
a = login("alice", "alicepw12345", base)
da = _json("GET", "/api/stats?days=7", jar=a, base=base)