Conversation
… stacking warning
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds “Daily Intelligence” surfaces and APIs: Safe-to-Spend (cached + live compute), Pay Timing recommendations, merchant logos, and a unified Smart Payment Calendar feed/page.
Changes:
- Frontend: adds
/calendarnavigation + new Calendar page and Safe-to-Spend hero on the dashboard. - Backend: introduces Safe-to-Spend + Pay Timing engines/endpoints, a
/calendarfeed, merchant logo lookup/caching, and a nightly recompute Celery task. - Tests/migrations: adds cache + logo tables and test coverage for the new engines/endpoints/services.
Reviewed changes
Copilot reviewed 44 out of 45 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/app/(app)/layout.tsx | Adds Calendar entry to app navigation. |
| frontend/app/(app)/dashboard/page.tsx | Renders new Safe-to-Spend hero on dashboard. |
| frontend/app/(app)/dashboard/_components/SafeToSpendHero.tsx | Client component fetching and displaying safe-to-spend + breakdown. |
| frontend/app/(app)/calendar/page.tsx | New Smart Payment Calendar UI consuming /calendar + pay-timing insights. |
| backend/tests/test_safe_to_spend_tool.py | Validates Safe-to-Spend Argus tool registration/behavior. |
| backend/tests/test_safe_to_spend_engine.py | Unit tests for Safe-to-Spend engine computation. |
| backend/tests/test_safe_to_spend_endpoint.py | Endpoint tests for cached vs live Safe-to-Spend. |
| backend/tests/test_recompute_safe_to_spend.py | Tests Celery recompute task upserts cache row. |
| backend/tests/test_pay_timing_engine.py | Unit tests for pay timing logic (utilization + stacking). |
| backend/tests/test_pay_timing_endpoint.py | Endpoint tests for /insights/pay-timing. |
| backend/tests/test_merchant_logos.py | Unit tests for logo cache hit/miss behavior. |
| backend/tests/test_calendar_endpoint.py | Endpoint tests for /calendar sorting + urgency computation. |
| backend/tasks/recompute_safe_to_spend.py | Celery task to recompute and upsert Safe-to-Spend cache. |
| backend/services/merchant_logos.py | Clearbit-based logo lookup with Supabase cache. |
| backend/routers/pay_timing.py | New insights router for pay-timing recommendations. |
| backend/routers/insights.py | Adds /insights/safe-to-spend endpoint (cached + live compute). |
| backend/routers/calendar.py | New /calendar feed merging bills + subscriptions + logos + urgency. |
| backend/migrations/017_merchant_logos.sql | Adds merchant_logos table for logo caching. |
| backend/migrations/016_safe_to_spend_cache.sql | Adds safe_to_spend_cache table + RLS policy. |
| backend/main.py | Registers new pay_timing and calendar routers. |
| backend/engines/safe_to_spend.py | Implements Safe-to-Spend computation engine. |
| backend/engines/pay_timing.py | Implements pay timing + bill stacking detection engine. |
| backend/celery_app.py | Registers recompute task and schedules nightly beat job. |
| backend/agents/tools.py | Registers get_safe_to_spend + get_pay_timing as Argus tools. |
Comments suppressed due to low confidence (2)
backend/services/merchant_logos.py:35
- 🟡 risk: This upserts
logo_url=Noneon any miss/timeout/404, permanently caching failures and preventing future retries (even if Clearbit/network recovers).
except requests.RequestException:
logo_url = None
supabase.table("merchant_logos").upsert(
{"merchant": merchant, "logo_url": logo_url},
on_conflict="merchant",
).execute()
return logo_url
backend/tasks/recompute_safe_to_spend.py:44
- 🟡 risk:
safe_to_spend_cache.computed_atnever updates on nightly recompute because the upsert payload omits it (DB default only applies on insert).
on_conflict="user_id",
).execute()
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+33
to
+38
| celery.conf.beat_schedule = { | ||
| "nightly-safe-to-spend-recompute": { | ||
| "task": "tasks.recompute_safe_to_spend.recompute_safe_to_spend_for_user", | ||
| "schedule": crontab(hour=2, minute=0), | ||
| "args": [], | ||
| }, |
Comment on lines
+36
to
+41
| subscriptions = ( | ||
| supabase.table("subscriptions") | ||
| .select("id, merchant, avg_amount, next_due_date") | ||
| .eq("user_id", user_id) | ||
| .eq("is_active", True) | ||
| .execute() |
Comment on lines
+46
to
+56
| for b in bills: | ||
| entries.append({ | ||
| "id": b["id"], | ||
| "type": "bill", | ||
| "merchant": b["merchant"], | ||
| "amount": b.get("avg_amount"), | ||
| "due_date": b.get("next_due_date"), | ||
| "logo_url": get_logo_url(b["merchant"], supabase), | ||
| "urgency": _urgency(b.get("next_due_date") or ""), | ||
| "ai_reasoning": None, | ||
| }) |
Comment on lines
+24
to
+28
| bills_due = sum( | ||
| b["avg_amount"] | ||
| for b in bills | ||
| if b.get("next_due_date") and date.fromisoformat(b["next_due_date"]) <= cutoff | ||
| ) |
Comment on lines
+39
to
+44
| card_recommendations.append({ | ||
| "account_id": acct["id"], | ||
| "pay_amount": pay_amount, | ||
| "target_utilization": _TARGET_UTILIZATION, | ||
| "closing_day": acct.get("closing_date"), | ||
| }) |
Comment on lines
+41
to
+48
| <div | ||
| onClick={() => setOpen((o) => !o)} | ||
| style={{ | ||
| background: "var(--surface-1)", borderRadius: "var(--r-xl)", | ||
| border: "1px solid var(--surface-3)", padding: "20px 24px", | ||
| cursor: "pointer", userSelect: "none", | ||
| }} | ||
| > |
Comment on lines
+69
to
+73
| [ | ||
| ["Current balance", data.breakdown.balance, false], | ||
| ["Bills due this window", data.breakdown.bills_due, true], | ||
| ["Buffer reserve", data.breakdown.buffer_reserve, true], | ||
| ] as [string, number, boolean][] |
Comment on lines
+67
to
+73
| Promise.all([ | ||
| api.get<{ entries: CalendarEntry[] }>("/calendar"), | ||
| api.get<PayTiming>("/insights/pay-timing"), | ||
| ]).then(([cal, pt]) => { | ||
| setEntries(cal.entries); | ||
| setPayTiming(pt); | ||
| }).finally(() => setLoading(false)); |
Comment on lines
+105
to
+107
| if cached: | ||
| return cached[0] | ||
| return {"safe_amount": None, "breakdown": {}, "computed_at": None} |
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/calendarfeed merging bills + subscriptions, sorted by date, urgency-coded, filter chips; replaces the old bills calendarTest plan
/calendarpage loads with bills and subscriptions sorted by due date/insights/pay-timingreturns card recommendations with correct pay amounts🤖 Generated with Claude Code