feat(insights): committed/discretionary chart, recurring charge detection, fees & interest audit
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getCurrentUser } from "@/lib/auth";
|
||||
import { queryRaw } from "@/lib/db";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const user = await getCurrentUser(req);
|
||||
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
|
||||
|
||||
// Statement-level fees and interest (aggregated by Gemini from the PDF)
|
||||
const stmtRows = await queryRaw<{
|
||||
bank_name: string;
|
||||
fees: string;
|
||||
interest: string;
|
||||
}>(
|
||||
`SELECT
|
||||
bank_name,
|
||||
SUM(COALESCE(fees_charged, 0))::numeric(12,2) AS fees,
|
||||
SUM(COALESCE(interest_charged, 0))::numeric(12,2) AS interest
|
||||
FROM statements
|
||||
WHERE owner_id = $1
|
||||
GROUP BY bank_name
|
||||
HAVING SUM(COALESCE(fees_charged, 0)) + SUM(COALESCE(interest_charged, 0)) > 0
|
||||
ORDER BY (SUM(COALESCE(fees_charged, 0)) + SUM(COALESCE(interest_charged, 0))) DESC`,
|
||||
[user.id]
|
||||
);
|
||||
|
||||
// Transaction-level fee and interest line items (split-adjusted)
|
||||
const txnRows = await queryRaw<{
|
||||
id: number;
|
||||
transaction_date: string;
|
||||
description: string;
|
||||
merchant_name: string | null;
|
||||
transaction_type: string;
|
||||
my_amount: string;
|
||||
bank_name: string;
|
||||
}>(
|
||||
`SELECT
|
||||
t.id,
|
||||
t.transaction_date,
|
||||
t.description,
|
||||
t.merchant_name,
|
||||
t.transaction_type,
|
||||
CASE
|
||||
WHEN ts.share_percent IS NOT NULL THEN COALESCE(t.amount_aud, t.amount) * ts.share_percent / 100
|
||||
ELSE COALESCE(t.amount_aud, t.amount)
|
||||
END::numeric(12,2) AS my_amount,
|
||||
s.bank_name
|
||||
FROM transactions t
|
||||
LEFT JOIN transaction_splits ts ON ts.transaction_id = t.id AND ts.participant_id = $1
|
||||
JOIN statements s ON s.id = t.statement_id
|
||||
WHERE s.owner_id = $1
|
||||
AND t.transaction_type IN ('fee', 'interest')
|
||||
ORDER BY t.transaction_date DESC`,
|
||||
[user.id]
|
||||
);
|
||||
|
||||
const by_bank = stmtRows.map((r) => ({
|
||||
bank_name: r.bank_name,
|
||||
fees: Number(r.fees),
|
||||
interest: Number(r.interest),
|
||||
total: Number(r.fees) + Number(r.interest),
|
||||
}));
|
||||
|
||||
const transactions = txnRows.map((r) => ({
|
||||
...r,
|
||||
my_amount: Number(r.my_amount),
|
||||
}));
|
||||
|
||||
// Totals from statement-level data (more complete — Gemini reads the statement summary)
|
||||
const total_fees = by_bank.reduce((s, r) => s + r.fees, 0);
|
||||
const total_interest = by_bank.reduce((s, r) => s + r.interest, 0);
|
||||
|
||||
return NextResponse.json({ by_bank, transactions, total_fees, total_interest });
|
||||
}
|
||||
Reference in New Issue
Block a user