This commit has no recorded session.
diff
commit 327ba94c630a431ca2355e5f121e16498b6af2e0Author: Colin Powell <colin@unbl.ink>Date: Tue Jun 23 22:21:18 2026 -0400 [trends] Add new time of day trend@@ -604,6 +604,17 @@ independent of the email flow it was originally creatdd for ** TODO [#B] Is there way to create unique slugs for media instances :media_types: +** DONE [#B] Time of Day Categories trend :trends:+:PROPERTIES:+:ID: 6598074f-2290-46db-967b-29f45d30be29+:END:++*** Description++Added a "Time of Day Categories" trend that groups scrobbles for Books, Trails,+Birding Locations, and Board Games into Early Bird (5-10:59am), Day Jay (11am-6:59pm),+and Night Owl (7pm-4:59am) buckets. Shows both overall and per-media-type breakdowns.+ * Version 58.1 [1/1] ** DONE [#B] Add auto genre tagging for papers :books:papers:metadata: :PROPERTIES:@@ -0,0 +1,65 @@+<div class="row">+ <div class="col-12">+ {% if data.total and data.total > 0 %}+ <h5>Overall</h5>+ <div class="table-responsive mb-4">+ <table class="table table-striped table-sm">+ <thead>+ <tr>+ <th>Category</th>+ <th class="text-end">Scrobbles</th>+ <th class="text-end">%</th>+ </tr>+ </thead>+ <tbody>+ {% for slug, info in data.categories.items %}+ <tr>+ <td>{{ info.label }}</td>+ <td class="text-end">{{ info.count }}</td>+ <td class="text-end">{{ info.pct }}%</td>+ </tr>+ {% endfor %}+ <tr class="table-secondary">+ <td><strong>Total</strong></td>+ <td class="text-end"><strong>{{ data.total }}</strong></td>+ <td class="text-end"></td>+ </tr>+ </tbody>+ </table>+ </div>++ <h5>By Media Type</h5>+ {% for mt, mt_data in data.by_media_type.items %}+ <h6 class="mt-3">{{ mt }}</h6>+ <div class="table-responsive">+ <table class="table table-striped table-sm">+ <thead>+ <tr>+ <th>Category</th>+ <th class="text-end">Scrobbles</th>+ <th class="text-end">%</th>+ </tr>+ </thead>+ <tbody>+ {% for slug, info in mt_data.categories.items %}+ <tr>+ <td>{{ info.label }}</td>+ <td class="text-end">{{ info.count }}</td>+ <td class="text-end">{{ info.pct }}%</td>+ </tr>+ {% endfor %}+ <tr class="table-secondary">+ <td><strong>Total</strong></td>+ <td class="text-end"><strong>{{ mt_data.total }}</strong></td>+ <td class="text-end"></td>+ </tr>+ </tbody>+ </table>+ </div>+ {% endfor %}++ {% else %}+ <p class="text-muted">No data found for Books, Trails, Birding Locations, or Board Games in this period.</p>+ {% endif %}+ </div>+</div>@@ -55,6 +55,9 @@ {% elif trend.slug == "reading-pace-vs-activity" %} {% include "trends/_reading_pace.html" %} +{% elif trend.slug == "time-of-day-categories" %}+ {% include "trends/_time_of_day_categories.html" %}+ {% elif trend.slug == "trending-up" %} {% include "trends/_trending_up.html" %} @@ -15,6 +15,7 @@ from trends.trends.mood import ( compute_mood_weather, ) from trends.trends.reading import compute_reading_pace_vs_activity+from trends.trends.time_of_day import compute_time_of_day_categories from trends.trends.trending import compute_trending_up TREND_REGISTRY = {}@@ -44,5 +45,8 @@ compute_peak_hours = register("peak-hours")(compute_peak_hours) compute_reading_pace_vs_activity = register("reading-pace-vs-activity")( compute_reading_pace_vs_activity )+compute_time_of_day_categories = register("time-of-day-categories")(+ compute_time_of_day_categories+) compute_trending_up = register("trending-up")(compute_trending_up) compute_weekly_rhythm = register("weekly-rhythm")(compute_weekly_rhythm)@@ -0,0 +1,89 @@+from collections import OrderedDict++from django.db.models import Count, Q+from django.db.models.functions import Extract+from scrobbles.models import Scrobble+++TARGET_MEDIA_TYPES = ["Book", "Trail", "BirdingLocation", "BoardGame"]++CATEGORIES = OrderedDict(+ [+ ("early_bird", {"label": "Early Bird", "hours": {5, 6, 7, 8, 9, 10}}),+ ("day_jay", {"label": "Day Jay", "hours": {11, 12, 13, 14, 15, 16, 17, 18}}),+ ("night_owl", {"label": "Night Owl", "hours": {19, 20, 21, 22, 23, 0, 1, 2, 3, 4}}),+ ]+)+++def _categorize_hour(hour):+ for slug, cat in CATEGORIES.items():+ if hour in cat["hours"]:+ return slug+ return None+++def compute_time_of_day_categories(user, period="last_30"):+ from trends.utils import get_date_range++ start, end = get_date_range(period)+ filters = Q(user=user, media_type__in=TARGET_MEDIA_TYPES, timestamp__isnull=False)+ if start:+ filters &= Q(timestamp__gte=start)+ if end:+ filters &= Q(timestamp__lte=end)++ qs = (+ Scrobble.objects.filter(filters)+ .annotate(hour=Extract("timestamp", "hour"))+ .values("media_type", "hour")+ .annotate(count=Count("id"))+ .order_by("media_type", "hour")+ )++ raw = {}+ for row in qs:+ mt = row["media_type"]+ raw.setdefault(mt, {})[row["hour"]] = row["count"]++ by_media_type = {}+ grand_totals = {"early_bird": 0, "day_jay": 0, "night_owl": 0}+ grand_total = 0++ for mt in TARGET_MEDIA_TYPES:+ mt_data = raw.get(mt, {})+ cat_counts = {"early_bird": 0, "day_jay": 0, "night_owl": 0}+ mt_total = 0+ for hour, count in mt_data.items():+ slug = _categorize_hour(hour)+ if slug:+ cat_counts[slug] += count+ mt_total += count+ by_media_type[mt] = {+ "total": mt_total,+ "categories": {},+ }+ for slug in CATEGORIES:+ c = cat_counts[slug]+ by_media_type[mt]["categories"][slug] = {+ "count": c,+ "pct": round((c / mt_total * 100), 1) if mt_total else 0,+ "label": CATEGORIES[slug]["label"],+ }+ grand_totals[slug] += c+ grand_total += mt_total++ categories = {}+ for slug in CATEGORIES:+ c = grand_totals[slug]+ categories[slug] = {+ "count": c,+ "pct": round((c / grand_total * 100), 1) if grand_total else 0,+ "label": CATEGORIES[slug]["label"],+ }++ return {+ "categories": categories,+ "total": grand_total,+ "by_media_type": by_media_type,+ }@@ -25,6 +25,7 @@ TIME_BOUND_TRENDS = { "mood-weather", "peak-hours", "reading-pace-vs-activity",+ "time-of-day-categories", "trending-up", "weekly-rhythm", }@@ -55,6 +55,11 @@ TREND_METADATA = { "description": "Compare how long you read per session with and without concurrent music.", "icon": "๐", },+ "time-of-day-categories": {+ "title": "Time of Day Categories",+ "description": "Are you an early bird, day jay, or night owl? Categorized by Books, Trails, Birding Locations, and Board Games.",+ "icon": "๐ฆ",+ }, "trending-up": { "title": "Trending Media Types", "description": "Which media types have you been consuming more or less of recently?",