diff --git a/cheddahbot/api.py b/cheddahbot/api.py index 0718aa2..b5c5d1f 100644 --- a/cheddahbot/api.py +++ b/cheddahbot/api.py @@ -6,11 +6,13 @@ All ClickUp data is cached for 5 minutes to avoid hammering the API. from __future__ import annotations +import calendar import json import logging import shutil import threading import time +from datetime import UTC, datetime from typing import TYPE_CHECKING from fastapi import APIRouter @@ -242,7 +244,8 @@ async def get_link_building_tasks(): {"name": name, "tasks": tasks, "count": len(tasks)} for name, tasks in sorted(by_company.items(), key=lambda x: -len(x[1])) ], - "status_counts": _count_statuses(active_lb), + "status_counts": _count_lb_statuses(active_lb), + "next_month_count": _count_next_month(active_lb), } _set_cached("lb_tasks", result) return result @@ -289,6 +292,76 @@ def _count_statuses(tasks: list[dict]) -> dict[str, int]: return counts +def _count_lb_statuses(tasks: list[dict]) -> dict[str, int]: + """Count only relevant statuses for tasks due in the current or previous month.""" + now = datetime.now(UTC) + # First day of current month + cur_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + # First day of previous month + if now.month == 1: + prev_start = cur_start.replace(year=now.year - 1, month=12) + else: + prev_start = cur_start.replace(month=now.month - 1) + + prev_start_ms = int(prev_start.timestamp() * 1000) + + allowed = {"to do", "in progress", "error", "automation underway"} + counts: dict[str, int] = {} + for t in tasks: + s = t.get("status", "unknown") + if s not in allowed: + continue + due = t.get("due_date") + if not due: + # No due date — still count it (it's active work) + counts[s] = counts.get(s, 0) + 1 + continue + try: + if int(due) >= prev_start_ms: + counts[s] = counts.get(s, 0) + 1 + except (ValueError, TypeError): + pass + return counts + + +def _count_next_month(tasks: list[dict]) -> int: + """Count tasks due next month.""" + now = datetime.now(UTC) + if now.month == 12: + next_start = now.replace( + year=now.year + 1, month=1, day=1, + hour=0, minute=0, second=0, microsecond=0, + ) + next_end_month = 1 + next_end_year = now.year + 1 + else: + next_start = now.replace( + month=now.month + 1, day=1, + hour=0, minute=0, second=0, microsecond=0, + ) + next_end_month = now.month + 1 + next_end_year = now.year + + last_day = calendar.monthrange(next_end_year, next_end_month)[1] + next_end = next_start.replace(day=last_day, hour=23, minute=59, second=59) + + start_ms = int(next_start.timestamp() * 1000) + end_ms = int(next_end.timestamp() * 1000) + + count = 0 + for t in tasks: + due = t.get("due_date") + if not due: + continue + try: + d = int(due) + if start_ms <= d <= end_ms: + count += 1 + except (ValueError, TypeError): + pass + return count + + # ── Agents ── diff --git a/dashboard/index.html b/dashboard/index.html index 29c334b..3ed3169 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -715,6 +715,7 @@ async function loadLinkBuilding() {