# v5: 기본 재무 분석 로직 구현 ## 이번 단계에서 할 일 1. 월별/카테고리별 집계 로직 구현 2. 전월 대비 증감률 계산 3. 저축률 및 예산 대비 실적 계산 4. 분석 결과를 시트에 자동 기록 ## 1. 재무 계산기 (src/analysis/calculator.ts) 핵심 분석 로직을 담당하는 클래스: ```typescript /** * 재무 분석 계산기 */ import { Transaction, MonthlySummary, CategoryBreakdown } from '../types'; export class FinancialCalculator { /** * 월별 요약 계산 */ calculateMonthlySummaries(transactions: Transaction[]): MonthlySummary[] { const monthlyGroups = this.groupByMonth(transactions); const months = Object.keys(monthlyGroups).sort(); return months.map((month) => { const monthTransactions = monthlyGroups[month]; return this.calculateMonthlySummary(month, monthTransactions); }); } /** * 단일 월 요약 계산 */ private calculateMonthlySummary(month: string, transactions: Transaction[]): MonthlySummary { const income = transactions .filter((t) => t.type === 'income') .reduce((sum, t) => sum + t.amount, 0); const expense = transactions .filter((t) => t.type === 'expense') .reduce((sum, t) => sum + t.amount, 0); const categoryBreakdown = this.calculateCategoryBreakdown( transactions.filter((t) => t.type === 'expense') ); return { month, totalIncome: income, totalExpense: expense, netSavings: income - expense, transactionCount: transactions.length, categoryBreakdown, }; } /** * 카테고리별 분석 */ calculateCategoryBreakdown(transactions: Transaction[]): CategoryBreakdown[] { const totalExpense = transactions.reduce((sum, t) => sum + t.amount, 0); // 카테고리별 그룹화 const categoryGroups: Record = {}; transactions.forEach((t) => { if (!categoryGroups[t.category]) { categoryGroups[t.category] = []; } categoryGroups[t.category].push(t); }); return Object.entries(categoryGroups) .map(([category, txs]) => { const amount = txs.reduce((sum, t) => sum + t.amount, 0); return { category: category as Category, amount, percentage: totalExpense > 0 ? (amount / totalExpense) * 100 : 0, transactionCount: txs.length, }; }) .sort((a, b) => b.amount - a.amount); // 금액 내림차순 } /** * 전월 대비 증감률 계산 */ calculateMonthOverMonthChange(summaries: MonthlySummary[]): Array { return summaries.map((summary, index) => { if (index === 0) { return { ...summary, expenseChange: null, incomeChange: null }; } const prevMonth = summaries[index - 1]; const expenseChange = prevMonth.totalExpense > 0 ? ((summary.totalExpense - prevMonth.totalExpense) / prevMonth.totalExpense) * 100 : null; const incomeChange = prevMonth.totalIncome > 0 ? ((summary.totalIncome - prevMonth.totalIncome) / prevMonth.totalIncome) * 100 : null; return { ...summary, expenseChange, incomeChange }; }); } /** * 저축률 계산 */ calculateSavingsRate(income: number, expense: number): number { if (income <= 0) return 0; return ((income - expense) / income) * 100; } /** * 예산 대비 실제 지출 계산 */ calculateBudgetVsActual( transactions: Transaction[], budgets: Record ): Array<{ category: string; budget: number; actual: number; difference: number; percentage: number; }> { const breakdown = this.calculateCategoryBreakdown( transactions.filter((t) => t.type === 'expense') ); return EXPENSE_CATEGORIES.map((category) => { const actual = breakdown.find((b) => b.category === category)?.amount || 0; const budget = budgets[category] || 0; return { category, budget, actual, difference: budget - actual, percentage: budget > 0 ? (actual / budget) * 100 : 0, }; }); } /** * 월별로 거래 그룹화 */ private groupByMonth(transactions: Transaction[]): Record { const groups: Record = {}; transactions.forEach((t) => { const month = t.date.substring(0, 7); // YYYY-MM if (!groups[month]) groups[month] = []; groups[month].push(t); }); return groups; } } ``` ## 2. 데이터 집계기 (src/analysis/aggregator.ts) 분석 결과를 시트에 기록하는 모듈: ```typescript /** * 데이터 집계 및 시트 기록 모듈 */ import { SheetsClient } from '../sheets/client'; import { SheetsReader } from '../sheets/reader'; import { FinancialCalculator } from './calculator'; export class DataAggregator { private client: SheetsClient; private reader: SheetsReader; private calculator: FinancialCalculator; constructor(client: SheetsClient) { this.client = client; this.reader = new SheetsReader(client); this.calculator = new FinancialCalculator(); } /** * 전체 분석 실행 및 시트에 기록 */ async runFullAnalysis(spreadsheetId: string) { console.log('\n📊 재무 분석 시작...\n'); // 1. 원본 데이터 읽기 const transactions = await this.reader.readTransactions(spreadsheetId, 'RawData'); console.log(`✅ ${transactions.length}건의 거래 데이터 로드`); // 2. 월별 요약 계산 const monthlySummaries = this.calculator.calculateMonthlySummaries(transactions); // 3. 카테고리별 분석 const expenseTransactions = transactions.filter((t) => t.type === 'expense'); const categoryBreakdown = this.calculator.calculateCategoryBreakdown(expenseTransactions); // 4. 연간 통계 const totalIncome = monthlySummaries.reduce((sum, m) => sum + m.totalIncome, 0); const totalExpense = monthlySummaries.reduce((sum, m) => sum + m.totalExpense, 0); // 5. 시트에 기록 await this.writeMonthlySummary(spreadsheetId, monthlySummaries); await this.writeCategoryAnalysis(spreadsheetId, categoryBreakdown, totalExpense); return { monthlySummaries, categoryBreakdown, annualStats: { totalIncome, totalExpense, netSavings: totalIncome - totalExpense, savingsRate: this.calculator.calculateSavingsRate(totalIncome, totalExpense), }, }; } } ``` ## 3. 월별 요약 시트 출력 예시 | 월 | 총 수입 | 총 지출 | 순 저축 | 저축률 | 거래 건수 | 지출 전월대비 | |----|---------|---------|---------|--------|----------|--------------| | 2024-01 | 358,432 | 352,456 | 5,976 | 1.7% | 45 | - | | 2024-02 | 345,678 | 298,432 | 47,246 | 13.7% | 42 | -15.3% | | 2024-03 | 362,111 | 315,890 | 46,221 | 12.8% | 48 | +5.8% | | ... | | | | | | | | 2024-12 | 856,234 | 412,345 | 443,889 | 51.8% | 52 | +8.2% | | **연간 합계** | **5,343,495** | **4,224,666** | **1,118,829** | **20.9%** | **544** | - | ## 4. 카테고리 분석 시트 출력 예시 | 카테고리 | 연간 지출 | 비율 | 월평균 | 거래수 | 예산 대비 | |----------|----------|------|--------|--------|----------| | 주거비 | 1,116,000 | 26.4% | 93,000 | 24 | 98% | | 식비 | 924,000 | 21.9% | 77,000 | 180 | 96% | | 유틸리티 | 456,000 | 10.8% | 38,000 | 60 | 95% | | 쇼핑 | 412,000 | 9.8% | 34,333 | 48 | 114% | | 교통비 | 324,000 | 7.7% | 27,000 | 120 | 135% | | ... | | | | | | | **합계** | **4,224,666** | **100%** | **352,056** | **510** | - | ## 5. 분석 결과 요약 출력 ``` ================================================== 📊 연간 재무 요약 ================================================== 총 수입: ¥5,343,495 총 지출: ¥4,224,666 순 저축: ¥1,118,829 저축률: 20.9% 📈 상위 지출 카테고리: 1. 주거비: ¥1,116,000 (26.4%) 2. 식비: ¥924,000 (21.9%) 3. 유틸리티: ¥456,000 (10.8%) 4. 쇼핑: ¥412,000 (9.8%) 5. 교통비: ¥324,000 (7.7%) ================================================== ``` ## 6. 테스트 스크립트 ### src/test-analysis.ts ```typescript import * as dotenv from 'dotenv'; dotenv.config(); import { SheetsClient } from './sheets/client'; import { DataAggregator } from './analysis/aggregator'; async function main() { const spreadsheetId = process.env.TEST_SPREADSHEET_ID; if (!spreadsheetId) { console.error('❌ TEST_SPREADSHEET_ID가 설정되지 않았습니다.'); process.exit(1); } const client = new SheetsClient(); const aggregator = new DataAggregator(client); const result = await aggregator.runFullAnalysis(spreadsheetId); aggregator.printAnalysisSummary(result); } main().catch(console.error); ``` ### 실행 ```bash npx ts-node src/test-analysis.ts ``` ## 7. 주요 계산 공식 정리 | 지표 | 공식 | |------|------| | 저축률 | `(수입 - 지출) / 수입 × 100` | | 전월 대비 증감률 | `(당월 - 전월) / 전월 × 100` | | 카테고리 비율 | `카테고리 지출 / 총 지출 × 100` | | 예산 대비 | `실제 지출 / 예산 × 100` | | 월평균 | `연간 합계 / 12` | ## 현재 프로젝트 구조 ``` project/src/ ├── sheets/ │ ├── client.ts ✅ │ ├── reader.ts ✅ │ ├── writer.ts ✅ │ └── template.ts ✅ ├── analysis/ │ ├── categories.ts ✅ 카테고리 설정 │ ├── calculator.ts ✅ 계산 로직 │ ├── aggregator.ts ✅ 집계 및 기록 │ └── validator.ts ✅ 데이터 검증 ├── types/ │ └── index.ts ✅ └── test-analysis.ts ✅ ``` ## 다음 단계 v6에서는: - Vertex AI API 설정 - Gemini 모델로 재무 데이터 분석 - AI 인사이트 생성 프롬프트 설계 --- **작성일**: 2025-12-01 **상태**: ✅ 완료 **다음**: v6 - Vertex AI 연동