# v11: AI 인사이트 섹션 구현 ## 이번 단계에서 할 일 1. AI 분석 결과를 시트에 작성 2. 핵심 발견사항 하이라이트 3. 개선 제안 섹션 4. 월간 목표 및 추적 시스템 ## 1. AI 인사이트 개요 ### 대시보드 레이아웃 ``` ┌──────────────────────────────────────────────────────────────────────────────┐ │ Dashboard (기존 차트/KPI 영역) │ ├──────────────────────────────────────────────────────────────────────────────┤ │ │ │ 🤖 AI 인사이트 섹션 (BD1:BN40) │ │ ┌────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ 📊 종합 분석 요약 │ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ │ │ 연간 저축률 20.9%로 양호한 재무 상태입니다. │ │ │ │ │ │ 주거비와 식비가 전체 지출의 48%를 차지하며, │ │ │ │ │ │ 외식 비용 절감으로 추가 저축이 가능합니다. │ │ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ 🔍 핵심 발견사항 💡 개선 제안 │ │ │ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │ │ │ │ ✅ 저축률 20% 이상 달성 │ │ 1. 외식 빈도 줄이기 │ │ │ │ │ │ ⚠️ 외식비 예산 초과 │ │ 2. 구독 서비스 정리 │ │ │ │ │ │ 📈 12월 보너스 효과 │ │ 3. 대중교통 활용 │ │ │ │ │ └─────────────────────────┘ └─────────────────────────┘ │ │ │ │ │ │ │ │ 🎯 월간 목표 트래커 │ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ │ │ 카테고리 현재 목표 진행률 │ │ │ │ │ │ 식비 ¥45,000 ¥40,000 ████████░░ 112% │ │ │ │ │ │ 교통비 ¥15,000 ¥12,000 ████████████░░ 125% │ │ │ │ │ │ 유흥비 ¥30,000 ¥25,000 ████████████░░ 120% │ │ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────────────────┘ ``` ### AI 인사이트 타입 ```typescript interface AIInsight { summary: string; keyFindings: string[]; recommendations: string[]; monthlyGoals: { category: string; targetAmount: number; currentAmount: number; }[]; } ``` ## 2. 인사이트 작성 모듈 ### InsightWriter 클래스 ```typescript /** * v11: AI 인사이트 시트 작성 모듈 */ import { SheetsClient } from '../sheets/client'; import { sheets_v4 } from 'googleapis'; import { AIInsight, MonthlySummary, CategoryBreakdown } from '../types'; export class InsightWriter { private client: SheetsClient; constructor(client: SheetsClient) { this.client = client; } /** * AI 인사이트 전체 섹션 작성 */ async writeInsightSection( spreadsheetId: string, sheetId: number, insight: AIInsight, monthlySummaries: MonthlySummary[], categoryBreakdown: CategoryBreakdown[] ): Promise { console.log('\n🤖 AI 인사이트 섹션 작성 시작...\n'); const sheetName = 'Dashboard'; // 1. 섹션 헤더 await this.writeSectionHeader(spreadsheetId, sheetName); console.log(' ✅ 섹션 헤더'); // 2. 종합 분석 요약 await this.writeSummary(spreadsheetId, sheetName, insight.summary); console.log(' ✅ 종합 분석 요약'); // 3. 핵심 발견사항 await this.writeKeyFindings(spreadsheetId, sheetName, insight.keyFindings); console.log(' ✅ 핵심 발견사항'); // 4. 개선 제안 await this.writeRecommendations(spreadsheetId, sheetName, insight.recommendations); console.log(' ✅ 개선 제안'); // 5. 월간 목표 트래커 await this.writeMonthlyGoals(spreadsheetId, sheetName, insight.monthlyGoals); console.log(' ✅ 월간 목표 트래커'); // 6. 서식 적용 await this.applyInsightFormatting(spreadsheetId, sheetId); console.log(' ✅ 서식 적용'); console.log('\n✅ AI 인사이트 섹션 작성 완료!\n'); } } ``` ## 3. 섹션 헤더 작성 ```typescript /** * 인사이트 섹션 헤더 작성 */ private async writeSectionHeader( spreadsheetId: string, sheetName: string ): Promise { const header = [ ['🤖 AI 재무 분석 인사이트', '', '', '', '', '', '', '', '', ''], ['', '', '', '', '', '', '', '', '', ''], ['Powered by Vertex AI (Gemini)', '', '', '', '', '', '', '', '', ''], ]; await this.client.writeSheet(spreadsheetId, `${sheetName}!BD1`, header); } ``` ## 4. 종합 분석 요약 ### 요약 박스 작성 ```typescript /** * 종합 분석 요약 작성 */ private async writeSummary( spreadsheetId: string, sheetName: string, summary: string ): Promise { // 요약을 여러 줄로 분할 (40자 기준) const lines = this.wrapText(summary, 50); const summarySection = [ ['📊 종합 분석 요약', '', '', '', '', '', '', '', '', ''], ['', '', '', '', '', '', '', '', '', ''], ...lines.map((line) => [line, '', '', '', '', '', '', '', '', '']), ['', '', '', '', '', '', '', '', '', ''], ]; await this.client.writeSheet(spreadsheetId, `${sheetName}!BD5`, summarySection); } /** * 텍스트 줄바꿈 유틸리티 */ private wrapText(text: string, maxLength: number): string[] { const words = text.split(' '); const lines: string[] = []; let currentLine = ''; for (const word of words) { if ((currentLine + ' ' + word).trim().length <= maxLength) { currentLine = (currentLine + ' ' + word).trim(); } else { if (currentLine) lines.push(currentLine); currentLine = word; } } if (currentLine) lines.push(currentLine); return lines; } ``` ### 요약 박스 시각화 ``` ┌────────────────────────────────────────────────────────┐ │ 📊 종합 분석 요약 │ │ │ │ 연간 저축률 20.9%로 양호한 재무 상태입니다. │ │ 주거비와 식비가 전체 지출의 48%를 차지하며, │ │ 외식 비용 절감으로 추가 저축이 가능합니다. │ │ │ └────────────────────────────────────────────────────────┘ ``` ## 5. 핵심 발견사항 ### 발견사항 리스트 ```typescript /** * 핵심 발견사항 작성 */ private async writeKeyFindings( spreadsheetId: string, sheetName: string, findings: string[] ): Promise { const findingsSection = [ ['🔍 핵심 발견사항', '', '', '', ''], ['', '', '', '', ''], ...findings.map((finding, i) => { const emoji = this.getFindingEmoji(finding); return [`${emoji} ${finding}`, '', '', '', '']; }), ]; await this.client.writeSheet(spreadsheetId, `${sheetName}!BD12`, findingsSection); } /** * 발견사항 성격에 따른 이모지 선택 */ private getFindingEmoji(finding: string): string { const lowerFinding = finding.toLowerCase(); if (lowerFinding.includes('달성') || lowerFinding.includes('양호') || lowerFinding.includes('증가')) { return '✅'; } if (lowerFinding.includes('초과') || lowerFinding.includes('높') || lowerFinding.includes('주의')) { return '⚠️'; } if (lowerFinding.includes('감소') || lowerFinding.includes('절감') || lowerFinding.includes('줄')) { return '📉'; } if (lowerFinding.includes('증가') || lowerFinding.includes('상승')) { return '📈'; } return '📌'; } ``` ### 발견사항 레이아웃 | 이모지 | 의미 | 예시 | |--------|------|------| | ✅ | 긍정적 | 저축률 20% 달성 | | ⚠️ | 주의 필요 | 외식비 예산 초과 | | 📈 | 상승 추이 | 12월 보너스 효과 | | 📉 | 하락 추이 | 교통비 절감 성공 | | 📌 | 일반 정보 | 월평균 지출 현황 | ## 6. 개선 제안 ### 제안 리스트 작성 ```typescript /** * 개선 제안 작성 */ private async writeRecommendations( spreadsheetId: string, sheetName: string, recommendations: string[] ): Promise { const recsSection = [ ['💡 개선 제안', '', '', '', ''], ['', '', '', '', ''], ...recommendations.map((rec, i) => [`${i + 1}. ${rec}`, '', '', '', '']), ]; await this.client.writeSheet(spreadsheetId, `${sheetName}!BI12`, recsSection); } ``` ### 제안 카드 스타일 ``` ┌──────────────────────────────────┐ │ 💡 개선 제안 │ │ │ │ 1. 외식 빈도를 주 2회로 제한 │ │ 2. 미사용 구독 서비스 해지 │ │ 3. 대중교통 정기권 활용 │ │ 4. 카페 방문 대신 텀블러 사용 │ │ │ └──────────────────────────────────┘ ``` ## 7. 월간 목표 트래커 ### 목표 테이블 작성 ```typescript /** * 월간 목표 트래커 작성 */ private async writeMonthlyGoals( spreadsheetId: string, sheetName: string, goals: { category: string; targetAmount: number; currentAmount: number }[] ): Promise { const header = ['카테고리', '현재 (월평균)', '목표', '달성률', '진행바', '상태']; const rows = goals.map((goal) => { const percentage = (goal.currentAmount / goal.targetAmount) * 100; const status = percentage <= 100 ? '✅' : percentage <= 120 ? '⚠️' : '🔴'; // REPT 함수로 진행바 생성 const progressBar = `=REPT("█",MIN(10,ROUND(${percentage}/10)))&REPT("░",MAX(0,10-ROUND(${percentage}/10)))`; return [ goal.category, goal.currentAmount, goal.targetAmount, `${percentage.toFixed(0)}%`, progressBar, status, ]; }); const goalsSection = [ ['🎯 월간 목표 트래커', '', '', '', '', ''], ['', '', '', '', '', ''], header, ...rows, ['', '', '', '', '', ''], ['* 목표는 현재 지출의 10% 절감 기준', '', '', '', '', ''], ]; await this.client.writeSheet(spreadsheetId, `${sheetName}!BD20`, goalsSection); } ``` ### 목표 트래커 시각화 ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ 🎯 월간 목표 트래커 │ ├──────────┬─────────────┬──────────┬────────┬──────────────┬──────┤ │ 카테고리 │ 현재(월평균) │ 목표 │ 달성률 │ 진행바 │ 상태 │ ├──────────┼─────────────┼──────────┼────────┼──────────────┼──────┤ │ 식비 │ ¥45,000 │ ¥40,000 │ 112% │ ████████████░│ ⚠️ │ │ 교통비 │ ¥15,000 │ ¥12,000 │ 125% │ █████████████│ 🔴 │ │ 유흥비 │ ¥30,000 │ ¥25,000 │ 120% │ █████████████│ 🔴 │ │ 쇼핑 │ ¥20,000 │ ¥22,000 │ 91% │ █████████░░░░│ ✅ │ │ 통신비 │ ¥8,000 │ ¥10,000 │ 80% │ ████████░░░░░│ ✅ │ └──────────┴─────────────┴──────────┴────────┴──────────────┴──────┘ ``` ## 8. 서식 적용 ### 인사이트 영역 서식 ```typescript /** * 인사이트 섹션 서식 적용 */ private async applyInsightFormatting( spreadsheetId: string, sheetId: number ): Promise { const requests: sheets_v4.Schema$Request[] = [ // 섹션 헤더 서식 { repeatCell: { range: { sheetId, startRowIndex: 0, endRowIndex: 1, startColumnIndex: 55, // BD열 endColumnIndex: 65, }, cell: { userEnteredFormat: { backgroundColor: { red: 0.2, green: 0.4, blue: 0.8 }, textFormat: { foregroundColor: { red: 1, green: 1, blue: 1 }, bold: true, fontSize: 14, }, horizontalAlignment: 'CENTER', }, }, fields: 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)', }, }, // 요약 박스 배경 { repeatCell: { range: { sheetId, startRowIndex: 4, endRowIndex: 11, startColumnIndex: 55, endColumnIndex: 65, }, cell: { userEnteredFormat: { backgroundColor: { red: 0.95, green: 0.95, blue: 1 }, borders: { top: { style: 'SOLID', color: { red: 0.7, green: 0.7, blue: 0.9 } }, bottom: { style: 'SOLID', color: { red: 0.7, green: 0.7, blue: 0.9 } }, left: { style: 'SOLID', color: { red: 0.7, green: 0.7, blue: 0.9 } }, right: { style: 'SOLID', color: { red: 0.7, green: 0.7, blue: 0.9 } }, }, }, }, fields: 'userEnteredFormat(backgroundColor,borders)', }, }, // 목표 트래커 조건부 서식 (달성률 기준) { addConditionalFormatRule: { rule: { ranges: [{ sheetId, startRowIndex: 22, endRowIndex: 30, startColumnIndex: 58, // 달성률 열 endColumnIndex: 59, }], booleanRule: { condition: { type: 'NUMBER_LESS_THAN_EQ', values: [{ userEnteredValue: '100' }], }, format: { backgroundColor: { red: 0.85, green: 0.95, blue: 0.85 }, }, }, }, index: 0, }, }, { addConditionalFormatRule: { rule: { ranges: [{ sheetId, startRowIndex: 22, endRowIndex: 30, startColumnIndex: 58, endColumnIndex: 59, }], booleanRule: { condition: { type: 'NUMBER_GREATER', values: [{ userEnteredValue: '100' }], }, format: { backgroundColor: { red: 1, green: 0.9, blue: 0.85 }, }, }, }, index: 1, }, }, ]; await this.client.batchUpdate(spreadsheetId, requests); } ``` ## 9. AI 분석 통합 ### 전체 플로우 ```typescript /** * AI 인사이트 생성 및 작성 통합 */ async generateAndWriteInsights( spreadsheetId: string, sheetId: number, summaries: MonthlySummary[], breakdown: CategoryBreakdown[], annualStats: { totalIncome: number; totalExpense: number; netSavings: number; savingsRate: number; } ): Promise { console.log('\n🧠 AI 분석 시작...\n'); // 1. AI 분석 실행 const analyzer = new FinancialAIAnalyzer(); const insight = await analyzer.analyzeFinancials(summaries, breakdown, annualStats); // 2. 인사이트 시트 작성 await this.writeInsightSection(spreadsheetId, sheetId, insight, summaries, breakdown); // 3. 자연어 요약 추가 const narrativeSummary = await analyzer.generateNarrativeSummary(annualStats, breakdown); await this.writeNarrativeSummary(spreadsheetId, 'Dashboard', narrativeSummary); console.log('✅ AI 인사이트 생성 및 작성 완료!'); } /** * 자연어 요약 작성 */ private async writeNarrativeSummary( spreadsheetId: string, sheetName: string, summary: string ): Promise { const lines = this.wrapText(summary, 60); const narrativeSection = [ ['📝 AI 생성 요약', '', '', '', '', '', '', '', '', '', '', ''], ['', '', '', '', '', '', '', '', '', '', '', ''], ...lines.map((line) => [line, '', '', '', '', '', '', '', '', '', '', '']), ]; await this.client.writeSheet(spreadsheetId, `${sheetName}!BD32`, narrativeSection); } ``` ## 10. 에러 처리 및 폴백 ### AI 실패 시 기본 인사이트 ```typescript /** * AI 호출 실패 시 기본 인사이트 사용 */ async writeInsightSectionWithFallback( spreadsheetId: string, sheetId: number, summaries: MonthlySummary[], breakdown: CategoryBreakdown[], annualStats: { totalIncome: number; totalExpense: number; netSavings: number; savingsRate: number; } ): Promise { let insight: AIInsight; try { const analyzer = new FinancialAIAnalyzer(); insight = await analyzer.analyzeFinancials(summaries, breakdown, annualStats); console.log(' ✅ AI 분석 성공'); } catch (error) { console.log(' ⚠️ AI 분석 실패, 기본 인사이트 사용'); insight = this.generateFallbackInsight(annualStats, breakdown); } await this.writeInsightSection(spreadsheetId, sheetId, insight, summaries, breakdown); } /** * 폴백 인사이트 생성 */ private generateFallbackInsight( annualStats: { totalIncome: number; totalExpense: number; netSavings: number; savingsRate: number; }, breakdown: CategoryBreakdown[] ): AIInsight { const savingsRate = annualStats.savingsRate; const topCategories = breakdown.slice(0, 3); return { summary: savingsRate >= 20 ? `연간 저축률 ${savingsRate.toFixed(1)}%로 건전한 재무 상태를 유지하고 있습니다.` : `연간 저축률 ${savingsRate.toFixed(1)}%입니다. 20% 이상을 목표로 지출 관리가 필요합니다.`, keyFindings: [ `최대 지출 카테고리: ${topCategories[0].category} (${topCategories[0].percentage.toFixed(1)}%)`, `월평균 지출: ¥${Math.round(annualStats.totalExpense / 12).toLocaleString()}`, `월평균 저축: ¥${Math.round(annualStats.netSavings / 12).toLocaleString()}`, ], recommendations: [ '외식 빈도를 줄이고 집밥 비율 늘리기', '정기 구독 서비스 점검 및 미사용 해지', '대중교통 정기권으로 교통비 절감', ], monthlyGoals: breakdown.slice(0, 5).map((b) => ({ category: b.category, currentAmount: Math.round(b.amount / 12), targetAmount: Math.round((b.amount / 12) * 0.9), })), }; } ``` ## 현재 프로젝트 구조 ``` project/src/ ├── charts/ │ ├── trend-charts.ts ✅ │ ├── ratio-charts.ts ✅ │ └── kpi-widgets.ts ✅ ├── insights/ │ └── writer.ts ✅ AI 인사이트 작성 (v11) ├── vertexai/ │ ├── client.ts ✅ │ └── analyzer.ts ✅ └── ... ``` ## 다음 단계 v12에서는: - CLI 도구 완성 - 파라미터 처리 - 에러 핸들링 - 최종 통합 및 실행 --- **작성일**: 2025-12-01 **상태**: ✅ 완료 **다음**: v12 - CLI 도구 완성 및 프로젝트 마무리