Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -101,106 +101,67 @@ public List<ChatBudgetInfoDto> getCategoryBudgetRemainingAmount(User user) {
);
}).collect(Collectors.toList());
}
// private String generateScenarioBudgetAnalysisPrompt() {
// return """
// **반드시 정확히 상황을 판단하고 예산 카테고리를 명확히 파악해 응답하세요!**
//
// 사용자의 메시지를 분석하여 다음 절차를 순서대로 수행하세요:
//
// 1단계. [상황 판단]
// - 사용자가 명확히 두 가지 이상의 상품(서비스 포함) 중에서 고민 중이라면 → `CHOICE_RECOMMENDATION`
// - 사용자가 하나의 상품(서비스 포함)을 구매할지 여부만을 고민 중이라면 → `YES_NO_DECISION`
//
// 2단계. [예산 카테고리 판단]
// - 사용자가 구매를 고민하는 상품이 어떤 소비 카테고리에 포함되는지 정확히 판단하세요.
// - 소비 카테고리는 [예산 카테고리별 남은 금액]에 언급됩니다. (ex. 쇼핑, 식비)
// - 판단할 수 없으면 '기타'로 분류하세요.
//
// 3단계. [남은 예산과 상품 가격 분석]
// - 판단한 예산 카테고리의 남은 예산 금액을 확인하세요.
// - 상품의 가격은 사용자 메시지에 상품의 정확한 가격이 있으면 이를 활용하고, 없다면 일반적이고 합리적인 가격을 가정하세요.
// - 상품의 가격과 남은 예산을 비교하여 구매 가능 여부를 판단하세요.
// 사용자의 남은 예산을 고려하여 두 상품 모두 예산 범위 내에서 구매할 수 있더라도 아직 이번달이 많이 남았다면,
// 앞으로 지출이 생길 가능성을 추론하여 비교적 고가의 상품보다는 상대적으로 합리적인 가격의 상품을 선택하도록 도움
// 2단계에서 파악한 예산 카테고리를 답변 시 명시적으로 언급하세요. (예: "쇼핑 예산", "문화생활 예산")
//
// 2단계의 결과에 따라 다음 예시 답변 형식을 따르세요:
//
// [답변 예시]
// - 예산 범위 내에서 모두 구매 가능하지만 고가의 상품과 저렴한 상품 간의 고민이 있는 경우:
// "[이름]님, 현재 쇼핑 예산은 70,000원이 남아 있어서 A와 B 모두 선택 가능해요. 다만, B는 A에 비해 더 합리적인 가격이라 예산 관리 측면에서는 좋은 선택일 수 있어요. 이번엔 조금 절약하고, 다음 기회에 A를 선택하시는 것도 좋은 방법이에요. 어떤 결정을 하시든 항상 응원할게요!"
// - 예산은 충분하지만 가성비에 대한 고민이 있는 경우:
// "[이름]님, 현재 문화생활 예산은 30,000원이 남아 있어서 구매 가능해요. 다만 가격 대비 효용성을 중요하게 생각하시는 것 같으니, 가성비 좋은 다른 옵션을 고려하시는 것도 좋아요. 물론 이 상품이 너무 마음에 드신다면 자신을 위한 소소한 선물로 구매하셔도 괜찮답니다!"
// - 예산 초과로 구매가 불가능할 것으로 생각되는 경우:
// "남은 예산으로는 조금 어려워 보여요.😢 더 합리적인 대안을 추천해드릴게요."
// 충동성 지출을 절제하는 것이 장기적으로 더 만족스러운 결정이라는 점을 다정하고 설득력 있게 전달.
//
// 답변은 항상 친절하고 다정하게 사용자의 현명한 소비 의사를 응원하는 어조로 작성하세요.
// """;
// }

// 상황 판단 프롬프트 (GPT가 CHOICE_RECOMMENDATION 또는 YES_NO_DECISION 판단)
private String generateScenarioPrompt() {
return """
**정확하게 상황을 판단하세요!**
사용자의 메시지를 분석해 아래 중 정확한 상황을 판단:

- **CHOICE_RECOMMENDATION**: 사용자가 두 개 이상의 상품(A vs B)을 비교하여 하나를 선택하려는 경우.
사용자의 남은 예산을 고려하여 두 상품 모두 예산 범위 내에서 구매할 수 있더라도 아직 이번달이 많이 남았다면,
앞으로 지출이 생길 가능성을 추론하여 비교적 고가의 상품보다는 상대적으로 합리적인 가격의 상품을 선택하도록 도움
- **YES_NO_DECISION**: 사용자가 특정 상품 하나(혹은 여러 상품을 묶어서)를 살지 말지 결정하려는 경우.

**주의**:
- 사용자가 특정 상품을 단독으로 언급했어도, 명확한 다른 상품과의 비교가 없다면 YES_NO_DECISION으로 판단하세요.
## 1. 상황 판단
- **CHOICE_RECOMMENDATION**: 두 개 이상 상품 비교 요청 (상품 수 무관)
- 여러 상품이 예산 내에 있다면, 남은 기간 추가 지출을 고려해 더 합리적인 선택 제안

- **YES_NO_DECISION**: 특정 상품 구매 여부 결정 요청
""";
}

private String generateBudgetAnalysisPrompt() {
return """
**중요: 예산 분석과 구매 가능성 평가**

1. **예산 카테고리 판단**
- 사용자의 메시지에서 상품의 키워드를 분석해, 상품별로 관련있는 예산 카테고리를 매핑.
- 예산 카테고리 종류는 [예산 카테고리별 남은 금액]에 언급됨. (ex. 상품이 음식->식비, 상품이 옷->쇼핑)
- 관련 카테고리가 없으면 "기타"로 분류.

2. **남은 예산 확인**
- 해당 예산 카테고리의 현재 남은 예산 금액을 사용자에게 안내.

3. **상품 가격 분석**
- 사용자가 가격을 명시했다면 해당 가격을, 그렇지 않다면 일반적인 시장 가격을 가정.

4. **구매 가능성 평가 및 추천 답변**
**예산 내 충분히 구매 가능**:
- "충분히 예산 내에서 구매 가능해요! 😊 하지만 다른 지출 계획도 고려해보세요."

**예산 내 가능하지만 부담되는 경우**:
- "예산이 빠듯할 수 있으니 신중히 결정하거나, 절약할 수 있는 대안을 고려해보는 것도 좋아요."

**예산 초과로 불가능**:
- "남은 예산으로는 조금 어려워 보여요.😢 더 합리적인 대안을 추천해드릴게요."
- 충동성 지출을 절제하는 것이 장기적으로 더 만족스러운 결정이라는 점을 다정하고 설득력 있게 전달.
## 2. 예산 분석과 구매 가능성 평가
1) 메시지에서 상품별 관련 예산 카테고리를 추출. 예산 카테고리 종류는 [예산 카테고리별 남은 금액]에 언급됨. (ex. 상품이 음식->식비) (없으면 기타)
2) **[예산 카테고리별 남은 금액]**에서 해당 카테고리 잔여 금액 안내
3) 제시 가격 또는 시장가 가정
4) 지출 비율 계산(ex. 2만 원 = 잔여 예산의 50%)
5) 과거 지출 내역 기반 남은 이번 달 소비 경향 추론
[과거 지출 요약] 블록의 통계치를 참고해,
- 다음 기간 예상 지출 규모
- 주요 지출 패턴(빈도·증가·감소 추이)
등을 추론해서 반영
6) 남은 기간과 날짜 고려해 최종 소비 가능 여부·추천 옵션 산출

## 3. 답변 구성
- **충분히 가능**
- 구체적 수치(금액)로 설명
- 기대 효과 언급 (만족감, 편리함)
- **부담되는 경우**
- 현재 부담 정도 설명
- 대안 제시
- 미래 소비 예측 반영
- **불가능할 때**
- 친절하고 설득력 있게 공감
- 충동 지출 자제 권유
- 감성적 동기 부여 (“다음에 더 맛있게…” 등)
- **출력 구조**
1) 비교·수치 분석 설명 단락 \s
2) 최종 추천 + 감성 동기부여 단락
## 4. 톤 & 포맷
- 숫자를 정확하게
- 이모지 1~2개로 친근함 추가
- 마크다운 사용 금지
- 문장 끝은 모두 '~요'체로 마무리
""";
}

// GPT가 참고할 사용자 예시 응답
private String generateSampleResponse(String userNickname) {
return String.format(
"""
[상황 예시] 쇼핑 카테고리 예산이 70,000원 남은 상황을 가정
[적절한 응답 분량]: 250~350자 내외
[상황별 응답 예시]
- **if CHOICE_RECOMMENDATION** (구체적 상품명을 반드시 언급):
"%s님, 현재 쇼핑 예산은 70,000원이 남았어요. \n\
나이키 운동화와 아디다스 운동화 모두 예산 내에서 가능하지만 😊, 아디다스 제품이 더 저렴하고 부담이 적어요. 앞으로의 지출 계획까지 고려해 선택하는 게 현명할 것 같아요!"
- **if YES_NO_DECISION** (구체적 상품명을 반드시 언급):
"%s님, 쇼핑 예산 70,000원으로 무신사 티셔츠는 구매 가능해요. \n\
다만, 다른 필수 지출도 생각해보고 결정하면 좋겠어요!"

[적절한 응답 분량]:
{사용자}님, 현재 남은 예산 내에서 고민하시는 두 상품 모두 충분히 선택할 수 있어요. 다만 {A}쪽이 {B}보다 조금 더 고급스러워서 예산 관리 측면에서 살짝 고민될 수 있을 것 같아요. 내심 더 고가의 {A}이 끌리시지만, 조금 참고 상대적으로 합리적인 {B}로 선택의 균형을 잡으려 하시는 것 같네요.
이럴 때는 가격도 합리적이고, 만족감이나 실용성{B의 장점}에서도 절대 부족함 없는 {B}을 선택하시면 장기적으로 더 뿌듯하실 거예요. 다음에 더 고급스러운 상품을 선택할 때의 즐거움이 더 특별해질지도 모르죠! 😊 어떤 결정을 하시든 현명한 소비를 응원합니다!
- **if CHOICE_RECOMMENDATION** (구체적 상품명을 반드시 언급):
"%s님, 현재 식비 예산이 30,000원 남아있어요. 고기(20,000원)는 잔여 예산의 67%%를 차지해요. 조금 큰 비중이라, 하루 식사로 지출하기엔 조금 부담스러울 수 있어요. 컵밥(3,000원)을 선택하시면 다음 주 예상 식비 지출(적어도 4~5만 원 이상 예상)에 대한 부담이 훨씬 줄어들 거예요.\\n가끔은 작은 절약이 더 큰 만족으로 돌아와요. 오늘은 컵밥으로 아끼고, 고기는 다음에 조금 더 여유 있을 때 드시면 더 맛있고 기분 좋게 드실 수 있을 거라 생각해요!😊"
- **if YES_NO_DECISION** (구체적 상품명을 반드시 언급):
"%s님, 쇼핑 예산이 50,000원 남아있어요. 100,000원짜리 옷을 사면 예산을 50,000원 초과하게 됩니다. 아직 한 달이 많이 남았으니, 이번 달 다른 지출을 최소화해 예산을 재조정하면 가능할 수도 있어요.\\n예를 들어, 외식비나 카페비에서 50,000원만 절약하면 오늘 옷 구매가 부담 없이 이루어질 거예요. 😊 그렇지만, 조금 더 여유를 모아 다음 달 초에 구매하는 것도 한 방법이에요. %s님이 기분 좋게 쇼핑하실 수 있도록 응원할게요!"
""",
userNickname, userNickname
userNickname, userNickname, userNickname
);
}

Expand All @@ -215,7 +176,11 @@ public String generateGPTResponseForChat(User user, String userMessage, String b
String scenarioPrompt = generateScenarioPrompt();
String budgetAnalysisPrompt = generateBudgetAnalysisPrompt();
String sampleResponse = generateSampleResponse(user.getNickname());

// String expenseInfo = """
// - 식비: 월평균 400,000원, 주 3회 지출
// - 쇼핑: 월평균 80,000원, 월 2회 지출
// - 교통: 월평균 70,000원, 주 5회 이용
// """;
LocalDate today = LocalDate.now();

String message = String.format(
Expand All @@ -231,8 +196,8 @@ public String generateGPTResponseForChat(User user, String userMessage, String b

%s
---
[오늘 날짜]: %s
[사용자 %s의 메시지]: "%s"
[오늘]: %s
[사용자 %s]: "%s"

%s

Expand All @@ -242,12 +207,13 @@ public String generateGPTResponseForChat(User user, String userMessage, String b
scenarioPrompt,
budgetAnalysisPrompt,
budgetInfo,
// expenseInfo,
sampleResponse,
today,
user.getNickname(),
userMessage,
finalInstruction
);
return openAIService.generateGPTResponse(message, OpenAIOptionEnum.LOGICAL);
return openAIService.generateGPTResponse(message, OpenAIOptionEnum.BALANCED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.io.IOException;
import java.time.LocalDate;
import java.util.List;

@Tag(name = "Transaction")
@RestController
Expand All @@ -38,6 +39,17 @@ public ResponseEntity<String> createExpense(@RequestBody @Valid ExpenseAddReques
return ResponseEntity.ok(category);
}

@Operation(summary = "지출 내역 여러 건 추가")
@PostMapping("/expenses-multiple")
public ResponseEntity<Void> createMultipleExpenses(
@RequestBody @Valid List<ExpenseAddRequestDto> requests) {
// 각 DTO마다 서비스 호출
for (ExpenseAddRequestDto dto : requests) {
expenseService.createExpense(dto);
}
return ResponseEntity.ok().build();
}

@Operation(summary = "수입 내역 추가")
@PostMapping("/incomes")
public ResponseEntity<String> createIncome(@RequestBody @Valid IncomeAddRequestDto request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ public String generateGPTResponse(String message, OpenAIOptionEnum optionType) {
public String generateUserAgentPrompt(UserAgent userAgent) {
return String.format(
"""
너는 "%s"야. %s의 성격, %s한 어조를 가진 금융 어시스턴트야.
항상 이 성격과 말투를 유지하며 대답해야 해. 한국어로 대답하고, 마크다운은 절대 사용하지 마.
너는 "%s"야.
- %s의 성격, %s한 어조를 가진 금융 어시스턴트. 항상 이 성격과 말투를 유지.
- 한국어로 대답, 마크다운은 절대 사용 금지
""",
userAgent.getAgent().getAgentName(),
userAgent.getAgent().getPersonality(),
Expand Down
Loading