Skip to content

Commit 3cdd83a

Browse files
docs(money-mirror): Sentry docs and portable README links (#16)
* docs(money-mirror): document Sentry and fix portable README links - Note Sentry integration in CODEBASE-CONTEXT and README stack table - Link README to CODEBASE-CONTEXT for agent-oriented reference - Use relative paths for schema.sql and vercel.json (remove absolute paths) Made-with: Cursor * feat(money-mirror): dashboard statements API, filters, and schema sync - Add GET /api/statements and dashboard client with nav, filters, insights - Persist statement nickname, account purpose, card_network; extend schema.sql - Split parse route and dashboard UI to satisfy file-size limits - Advisory engine, categorizer, and upload metadata updates; format-date helper - Docs, CODEBASE-CONTEXT, CHANGELOG, and linear-sync metadata Made-with: Cursor
1 parent a49e731 commit 3cdd83a

31 files changed

Lines changed: 1408 additions & 348 deletions

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## 2026-04-05 — MoneyMirror Phase 2 Linear + project-state
4+
5+
**What:** Recorded MoneyMirror Phase 2 (dashboard roadmap) delivery in **Linear** and refreshed [project-state.md](project-state.md).
6+
7+
**Linear (under [VIJ-11](https://linear.app/vijaypmworkspace/issue/VIJ-11/issue-009-moneymirror-ai-powered-personal-finance-coach-for-gen-z)):**
8+
9+
- [VIJ-23](https://linear.app/vijaypmworkspace/issue/VIJ-23/moneymirror-phase-2-shipped-dashboard-roadmap-a-h-baseline)**Done** — Phase 2 shipped in repo (epics A–H baseline).
10+
- [VIJ-24](https://linear.app/vijaypmworkspace/issue/VIJ-24/moneymirror-ops-run-neon-alter-for-statement-label-columns-nickname)**Todo** — run Neon `ALTER TABLE` for label columns if missing (`apps/money-mirror/schema.sql`).
11+
- [VIJ-25](https://linear.app/vijaypmworkspace/issue/VIJ-25/moneymirror-backlog-post-roadmap-f3-g2g3-h3)**Backlog** — F3, G2–G3, H3 follow-ups.
12+
13+
**Repo:** [experiments/linear-sync/issue-009.json](experiments/linear-sync/issue-009.json)`tasks` map + `last_sync_mode` updated for manual Phase 2 issue creation (not a full `/linear-sync` pipeline run).
14+
15+
**Update (same day):** Full **Sprint 1–3** (VIJ-26–28, Done), **Sprint 4 / Backlog** (VIJ-25), **Epics A–H** (VIJ-29–36, Done) created in Linear and mirrored in [project-state.md](project-state.md) § MoneyMirror PM roadmap — Linear map.
16+
17+
---
18+
319
## 2026-04-04 — GitHub PR #15 + project state (repo hygiene)
420

521
**What:** Pushed `feat/linear-workflow-sync` (commits `9f483ed`, `dced451`) and opened [**PR #15**](https://github.com/shadowdevcode/ai-product-os/pull/15) for review: Neon MCP secret removal, `.codex/config.toml`, CHANGELOG updates.
Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
# Codebase Context: MoneyMirror
22

3-
Last updated: 2026-04-03
3+
Last updated: 2026-04-04
44

55
## What This App Does
66

77
MoneyMirror is a mobile-first PWA AI financial coach for Gen Z Indians (₹20K–₹80K/month). Users sign in with Neon Auth email OTP, upload a password-free Indian bank-account or credit-card statement PDF, and Gemini 2.5 Flash parses and categorizes each transaction into needs/wants/investment/debt/other. The "Mirror moment" reveals the gap between self-reported spend from onboarding and actual spend from the statement. An advisory engine fires up to 5 consequence-first nudges, and a weekly recap email is sent by a Vercel cron fan-out every Monday at 8:00 AM IST. The primary North Star proxy is second-month statement upload rate (≥60%).
88

99
## Architecture Overview
1010

11-
- **Frontend**: Next.js 16 App Router (RSC by default, `"use client"` for interactive panels). Key pages: `/` (landing), `/onboarding` (5-question flow), `/score` (Money Health Score reveal), `/dashboard` (Mirror + advisory feed + upload).
11+
- **Frontend**: Next.js 16 App Router (RSC by default, `"use client"` for interactive panels). Key pages: `/` (landing), `/onboarding` (5-question flow), `/score` (Money Health Score reveal), `/dashboard` (Overview with perceived vs actual + categories, **Insights** tab for AI nudges, **Upload** tab, statement picker + month filter, URL `?statement_id=`).
1212
- **Backend**: Next.js API routes under `src/app/api/`. Neon Auth for session auth, Neon Postgres for persistence, Gemini 2.5 Flash for PDF parse + categorization, Resend for weekly recap emails, PostHog for server-side telemetry.
13-
- **Database**: Neon Postgres. 4 tables: `profiles`, `statements`, `transactions`, `advisory_feed`. `profiles` persists monthly income and perceived spend; `statements` now tracks `institution_name`, `statement_type`, and optional credit-card due metadata. All monetary values are stored as `BIGINT` in paisa (₹ × 100) to avoid float precision errors.
13+
- **Database**: Neon Postgres. 4 tables: `profiles`, `statements`, `transactions`, `advisory_feed`. `profiles` persists monthly income and perceived spend; `statements` tracks `institution_name`, `statement_type`, optional credit-card due metadata, optional `nickname`, `account_purpose`, `card_network` for multi-account labelling. All monetary values are stored as `BIGINT` in paisa (₹ × 100) to avoid float precision errors.
1414
- **AI Integration**: Gemini 2.5 Flash via `@google/genai`. Used for: (1) PDF text → structured bank-account or credit-card statement JSON, (2) transaction category normalization. The statement-parse route currently enforces a 25s timeout and returns JSON 504 on timeout.
1515
- **Analytics**: PostHog (server-side only, `posthog-node`). 10 events tracked: `onboarding_completed`, `statement_parse_started/rate_limited/success/timeout/failed`, `weekly_recap_triggered/completed`, `weekly_recap_email_sent/failed`. All calls fire-and-forget (`.catch(() => {})`).
16+
- **Error tracking**: Sentry via `@sentry/nextjs` (`sentry.server.config.ts`, `sentry.edge.config.ts`, `src/instrumentation.ts`, `src/instrumentation-client.ts`). Uses `NEXT_PUBLIC_SENTRY_DSN` plus org/project/auth token vars as in app `README.md` / `.env.local.example`.
1617

1718
## Key Files
1819

@@ -24,7 +25,9 @@ MoneyMirror is a mobile-first PWA AI financial coach for Gen Z Indians (₹20K
2425
| `src/app/api/dashboard/advisories/route.ts` | Authenticated GET — returns advisory_feed rows for the current user via the active Neon session cookie. |
2526
| `src/app/api/cron/weekly-recap/route.ts` | Master cron: scheduled GET entrypoint for Vercel Cron; accepts Bearer `CRON_SECRET` or local `x-cron-secret`, paginates users in 1000-row batches, and fans out to the worker via Promise.allSettled. |
2627
| `src/app/api/cron/weekly-recap/worker/route.ts` | Worker: sends Resend email per user. Returns HTTP 502 on failure so master counts it correctly. |
27-
| `src/lib/advisory-engine.ts` | Fires 5 advisory types based on spend ratios and thresholds. Writes to `advisory_feed` table. |
28+
| `src/lib/advisory-engine.ts` | Rule-based advisories (perception gap, subscriptions, food, no investment, debt ratio, high Other bucket, discretionary mix, avoidable estimate, CC minimum-due risk). |
29+
| `src/lib/format-date.ts` | UTC-safe date labels for statement periods (no raw ISO in UI). |
30+
| `src/app/api/statements/route.ts` | Authenticated GET — list processed statements for picker + month filter. |
2831
| `src/lib/scoring.ts` | Computes Money Health Score (0–100) from 5 onboarding question responses. |
2932
| `src/lib/statements.ts` | Defines statement types, parser prompts, metadata validation, and shared display labels for bank-account and credit-card uploads. |
3033
| `src/lib/pdf-parser.ts` | Extracts raw text from PDF buffer using `pdf-parse`. Uses `result.total` (not `result.pages?.length`) for page count — v2 API. |
@@ -33,21 +36,22 @@ MoneyMirror is a mobile-first PWA AI financial coach for Gen Z Indians (₹20K
3336
## Data Model
3437

3538
- **profiles**: One row per user. `id` = Neon Auth user id (TEXT). Stores `monthly_income_paisa`, `perceived_spend_paisa`, `target_savings_rate`, `money_health_score`.
36-
- **statements**: One per uploaded PDF. Tracks `institution_name`, `statement_type` (`bank_account` or `credit_card`), statement period, optional card due metadata, and `status`. Status never set to `processed` before `transactions` child insert succeeds.
39+
- **statements**: One per uploaded PDF. Tracks `institution_name`, `statement_type` (`bank_account` or `credit_card`), statement period, optional card due metadata, optional `nickname` / `account_purpose` / `card_network`, and `status`. Status never set to `processed` before `transactions` child insert succeeds.
3740
- **transactions**: Many per statement. All amounts in paisa (BIGINT). `category` CHECK: `needs | wants | investment | debt | other` (lowercase).
3841
- **advisory_feed**: Advisory nudges generated per statement. `trigger` identifies which advisory type fired.
3942

4043
## API Endpoints
4144

42-
| Method | Path | Auth | Purpose |
43-
| ------ | ------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
44-
| POST | `/api/statement/parse` | Neon session cookie | Upload PDF plus `statement_type`, parse with Gemini, persist to DB, return mirror data |
45-
| GET | `/api/dashboard` | Neon session cookie | Rehydrate latest processed statement + advisory feed (refresh/deep link path) |
46-
| GET | `/api/dashboard/advisories` | Neon session cookie | Fetch advisory_feed rows for user |
47-
| POST | `/api/onboarding/complete` | Neon session cookie | Save onboarding income, score, and perceived spend to profiles |
48-
| GET | `/api/cron/weekly-recap` | `authorization: Bearer <CRON_SECRET>` or local `x-cron-secret` | Scheduled master fan-out |
49-
| POST | `/api/cron/weekly-recap/worker` | `x-cron-secret` header | Worker: send one recap email; returns 502 on failure |
50-
| ALL | `/api/auth/[...path]` || Neon Auth passthrough |
45+
| Method | Path | Auth | Purpose |
46+
| ------ | ------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
47+
| POST | `/api/statement/parse` | Neon session cookie | Upload PDF plus `statement_type` and optional `nickname`, `account_purpose`, `card_network` |
48+
| GET | `/api/dashboard` | Neon session cookie | Rehydrate processed statement + advisories; optional `?statement_id=` for a specific upload |
49+
| GET | `/api/statements` | Neon session cookie | List all processed statements for the user |
50+
| GET | `/api/dashboard/advisories` | Neon session cookie | Fetch advisory_feed rows for user |
51+
| POST | `/api/onboarding/complete` | Neon session cookie | Save onboarding income, score, and perceived spend to profiles |
52+
| GET | `/api/cron/weekly-recap` | `authorization: Bearer <CRON_SECRET>` or local `x-cron-secret` | Scheduled master fan-out |
53+
| POST | `/api/cron/weekly-recap/worker` | `x-cron-secret` header | Worker: send one recap email; returns 502 on failure |
54+
| ALL | `/api/auth/[...path]` || Neon Auth passthrough |
5155

5256
## Things NOT to Change Without Reading First
5357

@@ -61,11 +65,11 @@ MoneyMirror is a mobile-first PWA AI financial coach for Gen Z Indians (₹20K
6165

6266
## Known Limitations
6367

64-
- Statement history browsing not yet implemented — dashboard always shows the latest processed statement. `GET /api/dashboard` accepts `?statement_id=` as a future extension point.
68+
- Cross-month trend comparison and aggregated “all accounts” rollups are not implemented (single-statement view with picker only).
6569
- Password removal stays manual outside the app. Password-protected PDFs are rejected with a clear retry message.
6670
- Inbox ingestion from email is not implemented. Users must manually download the PDF and upload it.
6771
- PDF parsing reliability depends on the PDF being text-based (not scanned/image). Scanned PDFs return 400.
6872
- Rate limit for uploads is 3/day per user (in-memory, resets on server restart) — not durable across deployments.
6973
- Weekly recap email only triggers if the user has at least one processed statement. New users without statements are silently skipped.
7074
- Share button (`navigator.share`) is hidden on desktop browsers — only rendered when Web Share API is available.
71-
- Current automated validation count is 45 tests across route and library coverage.
75+
- Run `npm test` in `apps/money-mirror` for current library and API test counts.

apps/money-mirror/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ AI-powered personal finance coach for Gen Z Indians that uses Neon Auth plus Neo
44

55
**Sign in with your email, upload a statement, and get a brutally honest view of where your money actually goes.**
66

7+
For AI- and agent-oriented architecture, data model, and API reference, see [`CODEBASE-CONTEXT.md`](./CODEBASE-CONTEXT.md).
8+
79
## What it does
810

911
1. User signs in with Neon Auth email OTP and lands in a private onboarding flow.
@@ -22,6 +24,7 @@ AI-powered personal finance coach for Gen Z Indians that uses Neon Auth plus Neo
2224
| Database | Neon Postgres (`@neondatabase/serverless`) |
2325
| AI | Google Gemini 2.5 Flash |
2426
| Analytics | PostHog (`posthog-node`) |
27+
| Errors | Sentry (`@sentry/nextjs`) |
2528
| Email | Resend |
2629
| Hosting | Vercel |
2730

@@ -70,7 +73,7 @@ Fill in these values:
7073

7174
### 4. Apply database schema
7275

73-
Run the full contents of [`schema.sql`](/Users/vijaysehgal/Downloads/02-Portfolio/ai-product-os/apps/money-mirror/schema.sql) against your Neon database.
76+
Run the full contents of [`schema.sql`](./schema.sql) against your Neon database.
7477

7578
Tables created:
7679

@@ -170,7 +173,7 @@ Returns the advisory subset for the authenticated user and statement.
170173

171174
### `GET /api/cron/weekly-recap`
172175

173-
Fan-out master route that finds all users with processed statements and triggers worker jobs. This is the scheduled entrypoint configured in [`vercel.json`](/Users/vijaysehgal/Downloads/02-Portfolio/ai-product-os/apps/money-mirror/vercel.json).
176+
Fan-out master route that finds all users with processed statements and triggers worker jobs. This is the scheduled entrypoint configured in [`vercel.json`](./vercel.json).
174177

175178
**Auth**: `authorization: Bearer <CRON_SECRET>` from Vercel Cron. Local/manual triggering may also use `x-cron-secret: <CRON_SECRET>`.
176179

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { describe, expect, it } from 'vitest';
2+
import {
3+
formatMonthKeyForDisplay,
4+
formatPeriodRange,
5+
formatStatementDate,
6+
monthKeyFromPeriodEnd,
7+
statementMonthLabel,
8+
} from '@/lib/format-date';
9+
10+
describe('format-date', () => {
11+
it('formats ISO date strings without raw T00:00:00', () => {
12+
expect(formatStatementDate('2026-04-11T00:00:00.000Z')).toMatch(/11/);
13+
expect(formatStatementDate('2026-04-11T00:00:00.000Z')).toMatch(/2026/);
14+
expect(formatStatementDate('2026-04-11T00:00:00.000Z')).not.toContain('T');
15+
});
16+
17+
it('formats YYYY-MM-DD', () => {
18+
expect(formatStatementDate('2026-03-22')).toMatch(/22/);
19+
});
20+
21+
it('formatPeriodRange joins start and end', () => {
22+
const r = formatPeriodRange('2026-02-23', '2026-03-22');
23+
expect(r).toContain('–');
24+
expect(r).not.toContain('T');
25+
});
26+
27+
it('statementMonthLabel uses period end', () => {
28+
expect(statementMonthLabel('2026-03-22')).toMatch(/March/);
29+
expect(statementMonthLabel('2026-03-22')).toMatch(/2026/);
30+
});
31+
32+
it('monthKeyFromPeriodEnd', () => {
33+
expect(monthKeyFromPeriodEnd('2026-03-22')).toBe('2026-03');
34+
});
35+
36+
it('formatMonthKeyForDisplay', () => {
37+
expect(formatMonthKeyForDisplay('2026-03')).toMatch(/March/);
38+
});
39+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# MoneyMirror coaching tone
2+
3+
Educational, India-first personal finance copy. Used for in-app insights and any future LLM prompts.
4+
5+
## Principles
6+
7+
1. **Clarity over jargon** — Explain the “why” in one sentence before the number.
8+
2. **No shame** — Frame behaviour as common and fixable; avoid moralising.
9+
3. **Consequence-first** — Tie spend to a concrete tradeoff (e.g. annualised food delivery, EMI load).
10+
4. **Not advice** — Insights are observations from statement data, not buy/sell or tax guidance.
11+
12+
## Disclaimers (always available in UI)
13+
14+
- MoneyMirror does not provide personalised investment, tax, or legal advice.
15+
- Users should consult a qualified professional for regulated decisions.
16+
- Do **not** impersonate real individuals or creators; use archetypes (educator, myth-buster) if needed, not names.
17+
18+
## Prompt templates (if using Gemini for narrative)
19+
20+
- Structure: **Observation****Why it matters****One practical next step** (optional).
21+
- Append: “This is general information based on your uploaded statement, not a recommendation.”
22+
23+
## Archetypes (optional user preference)
24+
25+
- **Educator** — Neutral, step-by-step.
26+
- **Myth-buster** — Short reframe of a common money myth tied to their data.
27+
- **Long-term** — Emphasise compounding and small recurring cuts.
28+
29+
Do not label these after real influencers or YouTubers.

apps/money-mirror/schema.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ CREATE TABLE IF NOT EXISTS public.statements (
2929
minimum_due_paisa BIGINT,
3030
credit_limit_paisa BIGINT,
3131
status TEXT NOT NULL CHECK (status IN ('processing', 'processed', 'failed')) DEFAULT 'processing',
32+
nickname TEXT,
33+
account_purpose TEXT,
34+
card_network TEXT,
3235
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
3336
);
3437

@@ -66,3 +69,8 @@ CREATE INDEX IF NOT EXISTS idx_transactions_user_statement
6669

6770
CREATE INDEX IF NOT EXISTS idx_advisory_feed_user_created_at
6871
ON public.advisory_feed(user_id, created_at DESC);
72+
73+
-- Existing Neon DBs: add statement label columns (idempotent)
74+
ALTER TABLE public.statements ADD COLUMN IF NOT EXISTS nickname TEXT;
75+
ALTER TABLE public.statements ADD COLUMN IF NOT EXISTS account_purpose TEXT;
76+
ALTER TABLE public.statements ADD COLUMN IF NOT EXISTS card_network TEXT;

0 commit comments

Comments
 (0)