Skip to content

feat: Smart Weekly Digest with AI Narrative (#121)#463

Open
qiridigital wants to merge 1 commit intorohitdash08:mainfrom
qiridigital:feat/weekly-digest
Open

feat: Smart Weekly Digest with AI Narrative (#121)#463
qiridigital wants to merge 1 commit intorohitdash08:mainfrom
qiridigital:feat/weekly-digest

Conversation

@qiridigital
Copy link

Summary

Implements the Smart Weekly Digest feature as described in #121.

Backend

  • services/digest.py – Computes weekly financial summary: total income, total expenses, net flow, transaction count, category breakdown, top expense, upcoming bills, and week-over-week spending change percentage.
  • **
    outes/digest.py** – Two endpoints:
    • \GET /digest/weekly?week=YYYY-Wnn\ – Returns the digest for a specific ISO week (defaults to current week). Responses are cached in Redis for 1 hour.
    • \GET /digest/weekly/history?count=N\ – Returns digests for the last N weeks (max 12, default 4).
  • AI Narrative – Uses Google Gemini to generate a 3-4 sentence plain-text coaching summary from the digest data. Falls back gracefully to a heuristic narrative when no API key is configured.
  • Blueprint registered at /digest\ prefix.

Frontend

  • *\�pi/digest.ts* – Typed API client with \getWeeklyDigest()\ and \getDigestHistory().
  • *\pages/Digest.tsx* – Full-featured dashboard page:
    • Summary cards (Income, Expenses, Net Flow, Week-over-Week change)
    • AI Insights card with period range
    • Category breakdown and upcoming bills panels
    • Biggest expense highlight
    • Clickable weekly history cards to navigate between weeks
    • Week picker input for arbitrary week selection
  • Route added at /digest\ (protected), nav link in Navbar.

Tests

  • *\ est_digest.py* – 7 tests covering:
    • Empty state returns valid structure
    • Seeded data reflects in summary
    • Invalid week format returns 400
    • Week-over-week is 0 with no prior data
    • History endpoint returns correct count
    • Auth required (401 without token)
    • Caching consistency

Tech Stack Alignment

  • Flask Blueprint + @jwt_required()\ + \get_jwt_identity()\
  • SQLAlchemy queries following existing dashboard pattern
  • Redis caching with \cache_get/\cache_set\
  • React 18 + TypeScript + shadcn/ui \FinancialCard\ components
  • \ ormatMoney()\ for currency formatting

/claim #121

- Backend: services/digest.py computes weekly income, expenses, net flow,
  category breakdown, top expense, upcoming bills, and week-over-week change
- Backend: 
outes/digest.py exposes GET /digest/weekly and
  GET /digest/weekly/history with JWT auth and Redis caching
- Backend: Gemini-powered narrative generation with heuristic fallback
- Frontend: �pi/digest.ts typed API client
- Frontend: pages/Digest.tsx full dashboard with summary cards,
  AI insights, category breakdown, upcoming bills, and weekly history
- Tests: 	est_digest.py covering empty state, seeded data, auth,
  validation, week-over-week, history, and caching
- Nav: added Digest link in Navbar

Closes rohitdash08#121
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “Smart Weekly Digest” feature spanning backend and frontend, providing weekly financial summaries (with optional Gemini-generated narrative) and a new UI page to view current + recent weekly digests.

Changes:

  • Backend: introduce digest computation service + /digest/weekly and /digest/weekly/history endpoints with Redis caching and AI narrative generation.
  • Frontend: add /digest protected route, digest API client, and a full digest dashboard page + navbar link.
  • Tests: add backend endpoint tests for empty state, seeded data, auth, history, and caching.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/backend/tests/test_digest.py Adds test coverage for weekly digest endpoints and caching.
packages/backend/app/services/digest.py Implements weekly digest aggregation + Gemini narrative generation.
packages/backend/app/routes/digest.py Adds weekly digest + history endpoints (JWT-protected) and caching.
packages/backend/app/routes/init.py Registers the digest blueprint under /digest.
app/src/pages/Digest.tsx New digest dashboard page (summary cards, narrative, breakdowns, history, week picker).
app/src/components/layout/Navbar.tsx Adds “Digest” nav link.
app/src/api/digest.ts Adds typed client for weekly digest + history endpoints.
app/src/App.tsx Adds protected /digest route.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +8
"""Weekly digest endpoints."""

from datetime import date
from flask import Blueprint, jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity

from ..services.digest import compute_digest, generate_narrative
from ..services.cache import cache_get, cache_set
) : (
<div className="space-y-2">
{digest.upcoming_bills.map((b) => (
<div key={b.name} className="flex justify-between text-sm">
@@ -0,0 +1,147 @@
"""Tests for the weekly digest endpoints."""

from datetime import date, timedelta
Comment on lines +22 to +23
return 2000 <= year <= 2100 and 1 <= week <= 53
except Exception:
def digest_history():
"""Return digests for the last *n* weeks (default 4)."""
uid = int(get_jwt_identity())
count = min(int(request.args.get("count", 4)), 12)
Comment on lines +68 to +83
Category.name,
func.coalesce(func.sum(Expense.amount), 0),
)
.outerjoin(Category, Expense.category_id == Category.id)
.filter(
Expense.user_id == user_id,
Expense.spent_at >= monday,
Expense.spent_at <= sunday,
Expense.expense_type != "INCOME",
)
.group_by(Category.name)
.all()
)
category_breakdown = [
{"category": name or "Uncategorized", "amount": round(float(amt), 2)}
for name, amt in sorted(cat_rows, key=lambda r: float(r[1]), reverse=True)
Comment on lines +198 to +208
with url_request.urlopen(req, timeout=15) as resp:
data = json.loads(resp.read())
return (
data.get("candidates", [{}])[0]
.get("content", {})
.get("parts", [{}])[0]
.get("text", _fallback_narrative(digest))
)
except Exception:
logger.warning("Gemini digest narrative failed, using fallback")
return _fallback_narrative(digest)
Comment on lines +18 to +21
const jan4 = new Date(now.getFullYear(), 0, 4);
const daysSinceJan4 = Math.floor((now.getTime() - jan4.getTime()) / 86400000);
const weekNum = Math.ceil((daysSinceJan4 + jan4.getDay() + 1) / 7);
return `${now.getFullYear()}-W${String(weekNum).padStart(2, '0')}`;
Comment on lines +24 to +46
export function Digest() {
const { toast } = useToast();
const [week, setWeek] = useState(currentISOWeek);
const [digest, setDigest] = useState<WeeklyDigest | null>(null);
const [history, setHistory] = useState<WeeklyDigest[]>([]);
const [loading, setLoading] = useState(true);

async function load() {
setLoading(true);
try {
const [d, h] = await Promise.all([
getWeeklyDigest(week),
getDigestHistory(4),
]);
setDigest(d);
setHistory(h);
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : 'Failed to load digest';
toast({ title: 'Error', description: msg });
} finally {
setLoading(false);
}
}
Comment on lines +63 to +70
year_s, week_s = week.split("-W")
y, w = int(year_s), int(week_s)
if w == 1:
y -= 1
w = 52
else:
w -= 1
week = f"{y}-W{w:02d}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants