
SpendPulse
소비가 말해주는 나의 패턴, 온전히 기기 안에서
SpendPulse는 지출 기록을 분석해 소비 패턴과 감정·시간대 상관관계를 보여주는 온디바이스 소비 인사이트 앱입니다. 모든 금융 데이터는 SwiftData로 기기에만 저장되며 서버로 전송되지 않습니다. 인사이트는 LLM이나 네트워크 없이 통계 기반 결정론 엔진(StatisticalInsightGenerator·KeywordCategoryClassifier·SubscriptionAuditor)으로 산출되며, 카테고리·요일·시간대·기분별 통계와 구독 감사(반복·중복 탐지)를 Swift Charts로 시각화합니다. Pro 기능은 StoreKit 2 구독으로 잠금 해제하고, Face ID로 앱을 잠가 데이터를 보호합니다.
기존 가계부 앱은 데이터를 서버로 보내 프라이버시 우려가 크고, 단순 합계만 보여줄 뿐 '왜 그렇게 썼는지'는 알려주지 않습니다. SpendPulse는 데이터를 기기 밖으로 내보내지 않으면서 감정·시간 패턴까지 분석해 비난 없는 인사이트를 제공합니다.
앱 미리보기
무엇을 할 수 있나요
감정 × 소비 상관 분석
지출에 5가지 기분 태그(happy·neutral·stressed·tired·celebratory)를 붙이면 PatternAnalyzer.moodAverages가 기분별 평균 지출을 계산합니다. 최고/최저 기분 평균의 배수가 1.5배 이상일 때만 의미 있는 인사이트로 표면화해 노이즈를 거릅니다.
시간·요일 패턴 인사이트
요일별 지출 합계와 24시간 거래 분포를 버킷으로 집계해 피크 요일·시간을 찾습니다. 요일 피크는 비영(非零) 요일 평균 대비 1.8배 이상, 시간대 피크는 전체 거래 4건 이상이면서 한 시간대가 30%를 넘을 때만 인사이트로 노출하는 통계 임계값을 적용합니다.
구독 감사 (반복·중복 탐지)
SubscriptionAuditor가 가맹점·통화·금액으로 거래를 그룹화하고, 거래 간격 평균이 weekly(6–10일)·monthly(26–35일)·yearly(350–380일) 허용 범위에 들면 반복 결제로 자동 분류합니다. 동일 금액·통화의 서로 다른 구독을 묶어 중복 구독(누수 비용)을 별도로 표면화합니다.
키워드 자동 카테고리 분류
KeywordCategoryClassifier가 메모를 소문자 정규화한 뒤 한·영 키워드 사전(스타벅스/netflix/택시 등)을 최장 일치(longest-match) 방식으로 매칭해 13개 소비 카테고리 중 하나를 부여합니다. 겹치는 규칙을 추가해도 안전하며, 결정론적이라 오프라인 동작과 테스트가 보장됩니다.
주간 결정론 인사이트 카드
StatisticalInsightGenerator가 한 주의 거래에서 지배 카테고리(50%+), 기분 편차, 피크 요일, 피크 시간, 주간 하이라이트를 순수 함수로 산출해 최대 5개 카드로 묶습니다. 모든 규칙이 숫자만으로 동작해 LLM·네트워크가 필요 없고, 그대로 테스트 오라클로 재사용됩니다.
네이티브 차트 대시보드
Swift Charts로 카테고리 도넛, 주간 추이, 기분 히트맵, 요일·시간 패턴을 시각화합니다. 13개 카테고리는 색맹 안전 팔레트로 매핑하고 지출에 빨강을 쓰지 않아 가독성과 접근성을 확보했습니다.
StoreKit 2 구독 결제
StoreKitPurchasing actor가 월간·연간 Pro 상품을 실제 결제 시트로 구매하고, VerificationResult 검증·currentEntitlements 기반 자격 확인·AppStore.sync() 복원을 처리합니다. 이전 반려(2.1(b))의 원인이던 '플래그만 바꾸던' 가짜 결제 경로를 실제 StoreKit 호출로 교체했습니다.
Face ID 프라이버시 잠금
LocalAuthentication 기반 LocalAuthBiometricService로 앱 진입 시 Face ID 인증을 요구해 소비 데이터를 보호합니다. 생체 인증 사용 가능 여부 확인과 사용자 취소·시스템 취소를 구분 처리하며, 테스트용 Mock 구현으로 인증 흐름을 검증합니다.
어떻게 만들었나요
Language
UI
Data
Payments
Privacy & OS
Testing
Tooling
MVVM + Clean Architecture로 Domain/Data/Presentation을 분리하고, 모든 분석을 결정론적 순수 함수로 구현해 LLM·서버 없이 100% 온디바이스로 동작하도록 설계했습니다.
- 1
Repository 패턴: Domain 계층은 TransactionRepository·GoalRepository 프로토콜과 CategoryClassifier·InsightGenerator 추상에만 의존하고, SwiftData 구현(SwiftDataTransactionRepository)은 Data 계층에 격리해 저장소를 교체·모킹 가능하게 했습니다.
- 2
결정론적 인사이트 엔진: StatisticalInsightGenerator·KeywordCategoryClassifier·SubscriptionAuditor·PatternAnalyzer를 입출력 없는 순수 함수로 작성해 LLM·네트워크 없이 동작하고, 동일 코드가 그대로 테스트 오라클로 재사용됩니다.
- 3
불변 도메인 모델: Transaction은 let 프로퍼티만 가진 값 타입으로, validated(...) 팩토리에서 음수 금액·빈 통화·미래 날짜를 검증해 시스템 경계에서 잘못된 데이터를 차단합니다.
- 4
StoreKit 2 actor 설계: StoreKitPurchasing을 actor로 구현해 로드된 Product를 캐시·재사용하고(가격 표시 후 재조회 실패 방지), VerificationResult 검증·currentEntitlements 자격 확인·Transaction.updates 스트림으로 구독 상태를 동기화합니다.
- 5
반응형 구독 상태: @Observable SubscriptionStore가 결제 엔진을 주입받아 entitlementUpdates() 스트림을 구독하고, 자격 변동 시 currentTier를 갱신해 SwiftUI Paywall·게이팅 UI가 자동 반영되도록 했습니다.
- 6
온디바이스 프라이버시: 금융 데이터는 SwiftData(@Model TransactionModel·GoalModel)로 기기에만 저장되어 서버 의존성이 없고, LocalAuthentication 기반 Face ID 잠금으로 추가 보호합니다.
- 7
Siri 통합: App Intents(LogTransactionIntent·TodaySpendingIntent)와 AppShortcutsProvider로 지출 기록과 오늘 소비 조회를 시리·단축어에서 실행할 수 있습니다.
146
테스트 케이스 (@Test)
26
테스트 스위트 (@Suite)
13
소비 카테고리
5
기분 태그
6
지원 통화
100% on-device
데이터 저장 위치



