feat(ai): 통계 화면 AI 재무 코멘트

이번 달 집계(수입·지출·전월대비·예산·분류별)만 Claude Haiku에 보내 2~3개 코멘트 생성.
POST /account/ai-comment(유료 게이트), 거래 원문 미전송. 미설정/실패 시 빈 코멘트→카드 숨김.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
sb
2026-07-05 15:05:32 +09:00
co-authored by Claude Opus 4.8
parent 655af7af63
commit 4aeac2ac0f
6 changed files with 110 additions and 0 deletions
@@ -10,6 +10,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
@@ -107,6 +108,51 @@ public class AiEntryParser {
return callClaude(system, content, 400, today, categoryHints, walletHints);
}
/** 이번 달 집계(JSON) → 2~3개의 한국어 재무 코멘트. 미설정/실패 시 빈 리스트. */
public List<String> financeComment(String summaryJson) {
if (!enabled() || summaryJson == null || summaryJson.isBlank()) return List.of();
try {
String system = """
너는 한국어 개인 가계부 코치다. 아래 사용자의 이번 달 집계(JSON, 금액 단위 원)를 보고
도움이 되는 코멘트를 2~3개 만든다. 각 코멘트는 한 문장, 가능하면 구체적 숫자를 인용하고,
잔소리·과장 없이 담백하게. 반드시 아래 JSON '하나만' 출력한다(마크다운/설명 금지):
{"bullets":["문장1","문장2"]}
관점 예: 전월 대비 지출 증감, 예산 대비 소비 속도, 특정 분류 과다, 수입 대비 지출 비율.
데이터가 빈약하거나 코멘트할 게 없으면 bullets 를 빈 배열로.
""";
Map<String, Object> reqBody = Map.of(
"model", model,
"max_tokens", 400,
"system", system,
"messages", List.of(Map.of("role", "user", "content", summaryJson)));
JsonNode resp = rest.post()
.uri("/v1/messages")
.header("x-api-key", apiKey)
.header("anthropic-version", "2023-06-01")
.contentType(MediaType.APPLICATION_JSON)
.body(reqBody)
.retrieve()
.body(JsonNode.class);
String out = stripFences(resp.path("content").path(0).path("text").asText("")).trim();
log.info("[ai-comment] 원문='{}'", out);
String json = extractJson(out);
if (json == null) return List.of();
JsonNode arr = om.readTree(json).path("bullets");
List<String> bullets = new ArrayList<>();
if (arr.isArray()) {
for (JsonNode n : arr) {
String s = n.asText("").trim();
if (!s.isEmpty()) bullets.add(s);
}
}
log.info("[ai-comment] 결과 — {}개", bullets.size());
return bullets;
} catch (Exception e) {
log.warn("[ai-comment] 실패: {}", e.toString());
return List.of();
}
}
/** 분류·계좌 후보와 오늘 날짜를 시스템 프롬프트 템플릿에 주입. */
private String withHints(String body, LocalDate today, List<String> categoryHints, List<String> walletHints) {
String catList = (categoryHints == null || categoryHints.isEmpty()) ? "(없음)" : String.join(", ", categoryHints);