phase-9: domain-driven architecture complete
- Restructured server layer with 5 domains: shared, screener, portfolio, calls, finance - Migrated 58 TypeScript files to domain-driven structure - Updated CLAUDE.md with new architecture documentation - Added .gitignore rules for .md files (except CLAUDE.md) - Removed unused CatalystAnalyst import from app.ts - Fixed lint errors: removed unused imports, fixed regex escape, added console suppressions - Verified no sensitive data in git history - Server code compiles cleanly with TypeScript strict mode
This commit is contained in:
committed by
saikiranvella
parent
83116baa3c
commit
96a752ecf7
@@ -0,0 +1,53 @@
|
||||
import type { CategoryBreakdown, FinanceAnalysis, SimpleFINAccount } from '../../domains/shared';
|
||||
|
||||
export class PersonalFinanceAnalyzer {
|
||||
analyze(accounts: SimpleFINAccount[]): FinanceAnalysis {
|
||||
const assets = accounts.filter((a) => !['CREDIT', 'LOAN'].includes(a.type));
|
||||
const liabilities = accounts.filter((a) => ['CREDIT', 'LOAN'].includes(a.type));
|
||||
|
||||
const totalAssets = assets.reduce((s, a) => s + Math.max(0, a.balance), 0);
|
||||
const totalLiabilities = liabilities.reduce((s, a) => s + Math.abs(Math.min(0, a.balance)), 0);
|
||||
const netWorth = totalAssets - totalLiabilities;
|
||||
|
||||
const cash = accounts.filter((a) => ['CHECKING', 'SAVINGS'].includes(a.type));
|
||||
const investments = accounts.filter((a) => a.type === 'INVESTMENT');
|
||||
const totalCash = cash.reduce((s, a) => s + Math.max(0, a.balance), 0);
|
||||
const totalInvest = investments.reduce((s, a) => s + Math.max(0, a.balance), 0);
|
||||
|
||||
const allTx = accounts.flatMap((a) => a.transactions);
|
||||
const spending = allTx.filter((tx) => tx.amount < 0 && tx.category !== 'Transfer');
|
||||
const income = allTx.filter((tx) => tx.amount > 0 && tx.category === 'Income');
|
||||
|
||||
const totalSpend = spending.reduce((s, tx) => s + Math.abs(tx.amount), 0);
|
||||
const totalIncome = income.reduce((s, tx) => s + tx.amount, 0);
|
||||
|
||||
const byCategory: Record<string, number> = {};
|
||||
for (const tx of spending) {
|
||||
byCategory[tx.category] = (byCategory[tx.category] ?? 0) + Math.abs(tx.amount);
|
||||
}
|
||||
|
||||
const categoryBreakdown: CategoryBreakdown[] = Object.entries(byCategory)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([category, amount]) => ({
|
||||
category,
|
||||
amount,
|
||||
pct: totalSpend > 0 ? ((amount / totalSpend) * 100).toFixed(1) : '0',
|
||||
}));
|
||||
|
||||
return {
|
||||
netWorth,
|
||||
totalAssets,
|
||||
totalLiabilities,
|
||||
totalCash,
|
||||
totalInvestments: totalInvest,
|
||||
cashPct: totalAssets > 0 ? ((totalCash / totalAssets) * 100).toFixed(1) : '0',
|
||||
investPct: totalAssets > 0 ? ((totalInvest / totalAssets) * 100).toFixed(1) : '0',
|
||||
totalIncome,
|
||||
totalSpend,
|
||||
savingsRate:
|
||||
totalIncome > 0 ? (((totalIncome - totalSpend) / totalIncome) * 100).toFixed(1) : null,
|
||||
categoryBreakdown,
|
||||
accounts,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user