feat: weekly digest + local-time quiet hours
Weekly Digest (send_weekly_digest Celery task): - Runs every Monday 8:30 AM UTC via beat schedule - Queries all followed bills updated in the past 7 days per user - Sends low-priority ntfy push (Priority: low, Tags: newspaper,calendar) - Creates a NotificationEvent (weekly_digest type) for RSS feed visibility - Admin can trigger immediately via POST /api/admin/trigger-weekly-digest - Manual Controls panel now includes "Send Weekly Digest" button Local-time quiet hours: - Browser auto-detects IANA timezone via Intl.DateTimeFormat().resolvedOptions().timeZone - Timezone saved to notification_prefs alongside quiet_hours_start/end on Save - Dispatcher converts UTC → user's local time (zoneinfo stdlib) before hour comparison - Falls back to UTC if timezone absent or unrecognised - Quiet hours UI: 12-hour AM/PM selectors, shows detected timezone as hint - Clearing quiet hours also clears stored timezone Co-Authored-By: Jack Levy
This commit is contained in:
@@ -238,6 +238,14 @@ async def resume_analysis(current_user: User = Depends(get_current_admin)):
|
||||
return {"task_id": task.id, "status": "queued"}
|
||||
|
||||
|
||||
@router.post("/trigger-weekly-digest")
|
||||
async def trigger_weekly_digest(current_user: User = Depends(get_current_admin)):
|
||||
"""Send the weekly bill activity summary to all eligible users now."""
|
||||
from app.workers.notification_dispatcher import send_weekly_digest
|
||||
task = send_weekly_digest.delay()
|
||||
return {"task_id": task.id, "status": "queued"}
|
||||
|
||||
|
||||
@router.post("/trigger-trend-scores")
|
||||
async def trigger_trend_scores(current_user: User = Depends(get_current_admin)):
|
||||
from app.workers.trend_scorer import calculate_all_trend_scores
|
||||
|
||||
@@ -31,6 +31,7 @@ _EVENT_LABELS = {
|
||||
"new_document": "New Bill Text",
|
||||
"new_amendment": "Amendment Filed",
|
||||
"bill_updated": "Bill Updated",
|
||||
"weekly_digest": "Weekly Digest",
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +49,7 @@ def _prefs_to_response(prefs: dict, rss_token: str | None) -> NotificationSettin
|
||||
digest_frequency=prefs.get("digest_frequency", "daily"),
|
||||
quiet_hours_start=prefs.get("quiet_hours_start"),
|
||||
quiet_hours_end=prefs.get("quiet_hours_end"),
|
||||
timezone=prefs.get("timezone"),
|
||||
)
|
||||
|
||||
|
||||
@@ -96,10 +98,13 @@ async def update_notification_settings(
|
||||
prefs["quiet_hours_start"] = body.quiet_hours_start
|
||||
if body.quiet_hours_end is not None:
|
||||
prefs["quiet_hours_end"] = body.quiet_hours_end
|
||||
if body.timezone is not None:
|
||||
prefs["timezone"] = body.timezone
|
||||
# Allow clearing quiet hours by passing -1
|
||||
if body.quiet_hours_start == -1:
|
||||
prefs.pop("quiet_hours_start", None)
|
||||
prefs.pop("quiet_hours_end", None)
|
||||
prefs.pop("timezone", None)
|
||||
|
||||
user.notification_prefs = prefs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user