Skip to content

feat(digest): weekly spending digest — APScheduler + HTML email + REST endpoints#1069

Open
Entr0zy wants to merge 1 commit into
rohitdash08:mainfrom
Entr0zy:feat/weekly-digest-121
Open

feat(digest): weekly spending digest — APScheduler + HTML email + REST endpoints#1069
Entr0zy wants to merge 1 commit into
rohitdash08:mainfrom
Entr0zy:feat/weekly-digest-121

Conversation

@Entr0zy
Copy link
Copy Markdown

@Entr0zy Entr0zy commented May 24, 2026

/claim #121

What this PR adds

A complete weekly digest system for FinMind that aggregates each user's last 7 days of spending and delivers a formatted HTML summary email every Monday morning.


Changes

packages/backend/app/services/digest.py (new)

Function Purpose
build_weekly_digest(uid, reference_date) Aggregates EXPENSE rows for the past 7 days: total spend, category breakdown, top-3 categories, week-over-week % change vs the prior 7-day window, largest single expense
render_digest_html(data) Renders the digest dict into a styled HTML email (inline CSS, no extra deps)
send_weekly_digest(user) Builds + emails a digest to one user via the existing SMTP helper
send_all_weekly_digests() Fan-out across all users; returns {"sent": N, "failed": M}
init_digest_scheduler(app) Registers an APScheduler BackgroundScheduler cron job: every Monday at 08:00

packages/backend/app/routes/digest.py (new)

Endpoint Description
GET /digest/preview Returns this week's digest data as JSON (JWT required)
POST /digest/send-test Immediately emails the digest to the authenticated user; returns 202 if SMTP is not configured

Modified files

  • app/routes/__init__.py — registers digest blueprint at /digest
  • app/__init__.py — calls init_digest_scheduler(app) on startup (skipped when TESTING=true or DISABLE_SCHEDULER is set)

packages/backend/tests/test_digest.py (new — 14 tests)

  • Empty digest returns zero totals
  • Expense aggregation (total, count, largest)
  • Income transactions excluded
  • 7-day window boundary (expenses from day 8+ excluded)
  • Week-over-week change: zero when no prior data, correct % when data exists
  • Top-3 category cap
  • send-test endpoint (202 when SMTP unconfigured)
  • JWT protection on both endpoints
  • HTML renderer: basic render, empty-week fallback, negative WoW indicator

Verification

# Unit tests (no Redis/DB required)
cd packages/backend
TESTING=true pytest tests/test_digest.py -v -k "html or requires_auth"

# Full test suite (requires Docker Compose)
docker compose up -d
pytest tests/test_digest.py -v

# Preview digest JSON
curl -H "Authorization: Bearer <token>" http://localhost:5000/digest/preview

# Trigger a test email
curl -X POST -H "Authorization: Bearer <token>" http://localhost:5000/digest/send-test

Demo video: Short screen capture showing the /digest/preview JSON response and send-test endpoint in the local Docker environment will be attached within 24h.

…h08#121)

Implements the weekly digest feature requested in issue rohitdash08#121.

## What changed

### New: packages/backend/app/services/digest.py
- build_weekly_digest(uid, reference_date) - aggregates the past 7
  days of EXPENSE transactions: total spend, category breakdown (top 3),
  week-over-week % change vs the prior 7-day window, and the largest
  single expense.
- render_digest_html(data) - renders digest data into a styled HTML
  email (inline CSS, no extra template-engine dependency).
- send_weekly_digest(user) - builds and emails a digest to one user.
- send_all_weekly_digests() - fan-out over all users.
- init_digest_scheduler(app) - APScheduler job every Monday at 08:00.

### New: packages/backend/app/routes/digest.py
- GET  /digest/preview   - returns digest JSON for the authenticated user.
- POST /digest/send-test - immediately emails the digest (202 if SMTP not configured).

### Modified: routes/__init__.py and app/__init__.py
- Registers blueprint and initialises scheduler on startup.

### New: packages/backend/tests/test_digest.py
- 14 tests: aggregation, income exclusion, 7-day boundary, WoW change,
  top-3 cap, send-test, JWT protection, HTML renderer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Entr0zy Entr0zy requested a review from rohitdash08 as a code owner May 24, 2026 04:15
@Entr0zy
Copy link
Copy Markdown
Author

Entr0zy commented May 24, 2026

✅ Live Demo — Weekly Digest (GET /digest/preview)

Tested against the local Flask server (SQLite, no Docker required):

# Register + Login
POST /auth/register  → 201 Created
POST /auth/login     → 200  {"access_token": "eyJ..."}

# Auth gate
GET /digest/preview  (no JWT)  → 401 {"msg": "Missing Authorization Header"}

# With valid JWT — full digest response
GET /digest/preview  → 200 OK

Response:

{
    "all_categories": [
        { "amount": 98.49, "category_id": null, "name": "Uncategorised" }
    ],
    "currency": "INR",
    "date_range": "2026-05-18 – 2026-05-24",
    "largest_expense": {
        "amount": 85.5,
        "date": "2026-05-23",
        "notes": "Weekly groceries"
    },
    "num_transactions": 2,
    "top_categories": [
        { "amount": 98.49, "category_id": null, "name": "Uncategorised" }
    ],
    "total_previous_week": 0,
    "total_spent": 98.49,
    "user_id": 2,
    "week_end": "2026-05-24",
    "week_start": "2026-05-18",
    "wow_change_pct": 0.0
}

Also verified:

  • POST /digest/send-test returns 202 Accepted when SMTP is not configured (correct behaviour)
  • Scheduler initialises on create_app() and is skipped when TESTING=true or DISABLE_SCHEDULER is set (prevents double-scheduler in pytest)
  • All 14 tests in tests/test_digest.py written and passing (5 without Docker, full suite with Docker Compose)

Server: python demo_server.py (SQLite + redis no-op patch)
Environment: Windows 10 / Python 3.12.7 / Flask 3.x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant