Skip to content

예산 임계치 기반 판단 로직 및 알림 중복 방지 기능 추가#18

Closed
chokyungjin0504 wants to merge 4 commits into
mainfrom
feat/budget-module
Closed

예산 임계치 기반 판단 로직 및 알림 중복 방지 기능 추가#18
chokyungjin0504 wants to merge 4 commits into
mainfrom
feat/budget-module

Conversation

@chokyungjin0504

@chokyungjin0504 chokyungjin0504 commented May 11, 2026

Copy link
Copy Markdown
Contributor

📌 변경 요약

AI 호출 비용에 대해 예산 사용률 임계치(50% / 80% / 100%)를 기준으로
판단 및 알림을 처리하는 로직을 추가하고,
동일 임계치에 대해 알림이 중복 발송되지 않도록 개선했습니다.


✅ 주요 변경 사항

1. 예산 임계치 개념 도입 (budget 모듈)

  • 예산 사용률에 따라 다음과 같은 임계치를 정의했습니다.
    • 50% → HALF
    • 80% → WARNING
    • 100% → EXCEEDED
  • BudgetDecision에 임계치(BudgetThreshold) 정보를 포함하도록 확장
  • DefaultBudgetEvaluator에서 임계치 기반 상태 판단 로직 구현

2. 알림 책임 분리 (notification 모듈)

  • 예산 판단 로직(budget)과 알림 처리 로직(notification)을 분리했습니다.
  • 판단 결과(BudgetDecision)를 기반으로 알림을 처리하는
    BudgetNotificationService를 추가했습니다.

3. 알림 중복 방지 기능 구현

  • 동일한 임계치 상태에서 알림이 반복 발송되는 문제를 방지하기 위해
    NotificationStateStore를 도입했습니다.
  • 기본 구현으로 InMemoryNotificationStateStore를 제공하여
    로컬/테스트 환경에서 사용 가능하도록 했습니다.
  • 각 임계치(50/80/100%)에 대해 알림은 최초 1회만 발송됩니다.

4. 단위 테스트 추가

  • DefaultBudgetEvaluatorTest
    • 예산 사용량에 따른 임계치 판단 로직 검증
  • BudgetNotificationServiceTest
    • 임계치별 알림 발송
    • 동일 임계치 중복 알림 방지 여부 검증

✅ 설계 의도

  • budget 모듈
    • 비용 계산 및 상태 판단만 담당
  • notification 모듈
    • 판단 결과를 바탕으로 알림 정책 및 실행을 담당

책임을 명확히 분리하여 확장성(이메일, Slack, Webhook 등)과 테스트 용이성을 고려했습니다.


🔍 테스트 방법

./gradlew :spring-ai-ledger-budget:test

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a budget notification system that triggers alerts at 50%, 80%, and 100% usage thresholds. It includes the new BudgetThreshold enum, a BudgetNotificationService to manage alert frequency, and updates to DefaultBudgetEvaluator to support these new levels. Feedback focuses on improving the robustness of the notification logic, specifically by including budget cycle information in the state store keys to prevent state carry-over between months. Additionally, it is recommended to avoid relying on Enum.ordinal() for threshold comparisons, pre-calculate threshold values to improve performance, and remove development-related comments from the production code.

* 이미 알림을 보낸 임계치 이하의 상태인 경우
* 중복 알림을 방지하기 위해 아무 작업도 하지 않습니다.
*/
public void notifyIfNeeded(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

현재 알림 상태 저장소(NotificationStateStore)가 targetId만을 키로 사용하고 있어, 예산 주기가 바뀌어도(예: 다음 달) 이전 상태가 유지되는 문제가 있습니다. 주기가 바뀔 때 알림이 다시 발송될 수 있도록 키에 주기 정보(예: 연월)를 포함하거나 상태 초기화 메커니즘이 필요합니다.

Comment on lines +13 to 14
// 우리가 "의도한" 형태
public record BudgetDecision(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

이 주석(// 우리가 "의도한" 형태)은 개발 과정의 메모로 보입니다. 코드의 의도는 클래스 구조로 충분히 전달되므로, 프로덕션 코드의 깔끔함을 위해 삭제하는 것을 권장합니다.

public record BudgetDecision(

Comment on lines +6 to +12
public enum BudgetThreshold {

NONE, // 임계치 미도달
HALF, // 50%
WARNING, // 80%
EXCEEDED // 100%
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ordinal()에 의존하는 대신, 각 임계치에 명시적인 순서나 비교 로직을 제공하는 것이 안전합니다. 이를 통해 향후 Enum 상수의 순서가 바뀌더라도 로직이 깨지는 것을 방지할 수 있습니다.

Suggested change
public enum BudgetThreshold {
NONE, // 임계치 미도달
HALF, // 50%
WARNING, // 80%
EXCEEDED // 100%
}
public enum BudgetThreshold {
NONE(0), // 임계치 미도달
HALF(1), // 50%
WARNING(2), // 80%
EXCEEDED(3); // 100%
private final int level;
BudgetThreshold(int level) {
this.level = level;
}
public boolean isHigherThan(BudgetThreshold other) {
return this.level > other.level;
}
}

Comment on lines +43 to +45
if (current.ordinal() <= lastNotified.ordinal()) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ordinal()을 직접 비교하는 대신 BudgetThreshold에 비교 로직을 캡슐화하여 사용하는 것이 좋습니다(제공된 BudgetThreshold 관련 제안 참고). 또한, 현재 로직은 임계치 확인과 상태 갱신 사이에 원자성이 보장되지 않아 동시성 환경에서 중복 알림이 발생할 수 있는 구조입니다.

Suggested change
if (current.ordinal() <= lastNotified.ordinal()) {
return;
}
if (!current.isHigherThan(lastNotified)) {
return;
}

Comment on lines +42 to +43
BigDecimal halfThreshold = monthlyLimit.multiply(BigDecimal.valueOf(0.5));
BigDecimal warnThreshold = monthlyLimit.multiply(BigDecimal.valueOf(0.8));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

halfThresholdwarnThresholdmonthlyLimit이 결정되면 변하지 않는 값입니다. evaluate 메서드 호출 시마다 매번 계산하기보다 생성자에서 미리 계산하여 필드로 관리하는 것이 효율적입니다.

@chokyungjin0504

Copy link
Copy Markdown
Contributor Author

에러 해결 후 다시 올리겠습니다..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant