[BE-72] 추천도 축 가중치 수정 API · 59점 상한 해제 API (FR-97)
작업 내용 (설계 의도)
근거 TDD: 20260808-지원관리-확장-tdd.md — “방안 9 (상한 해제는 결과 행과 분리)”, “API 계약 3단계 가중치 수정·상한 해제”
변경 사항
GET·PUT /api/recommendations/weights,POST·DELETE /api/job-postings/{jobPostingId}/recommendation/cap-release를 신설합니다.- 가중치 변경은 평가 기준 버전을 증가시킵니다(FR-97) —
change_target=AXIS_WEIGHT. 이 버전은match_criteria_revisions(키워드 매칭)와 별개 축입니다. - 합계 100 검증은 값 객체가 수행합니다 —
RecommendationAxisWeights.of()가 4축 전부·합계 100을 검증하고, 실패 시InvalidRecommendationWeightException(400)입니다. UseCase에if + throw를 두지 않습니다. - 상한 해제는 별도 테이블(
job_posting_recommendation_cap_overrides, 공고당 1행)에 영속화합니다 — 결과 행은 재평가 때 덮어써지므로 같은 행에 두면 해제가 날아갑니다. FR-97의 “재평가 시에도 유지”가 이 분리의 이유입니다. - 해제 후에도 원 상한 사유를 계속 노출합니다(FR-97) — 응답의
capReleased=true+capReason. 사용자가 “왜 상한이 걸렸었는지”를 잊지 않게 합니다. - 해제·재적용 API는 즉시 재계산한 결과를 반환합니다 — 사용자가 다음 재평가를 기다리지 않아도 점수 변화를 봅니다.
- 신규 컨트롤러 파일(
RecommendationWeightApiController.kt)로 만들어 BE-65의RecommendationApiController.kt와 충돌하지 않습니다(다른 wave이지만 파일 분리를 유지).
의존
- BE-63 (추천도 계산 도메인 — 2단계 산출물)
- BE-68 (에러 코드)
다이어그램
처리 흐름
sequenceDiagram participant FE as web(SPA) participant C as RecommendationWeightApiController participant U as UpdateRecommendationWeightsUseCase participant D as RecommendationDomainService participant W as RecommendationAxisWeights participant R as RecommendationCriteriaRepository FE->>C: PUT /api/recommendations/weights C->>U: execute(command) U->>D: updateWeights(weights) D->>W: of(weights) — 4축·합계 100 검증 alt 검증 실패 W-->>D: InvalidRecommendationWeightException (400) else 통과 D->>R: bumpRevision(AXIS_WEIGHT, reason) R-->>D: 새 criteriaRevision end D-->>U: criteriaRevision + weights C-->>FE: 200
클래스 의존
flowchart LR subgraph Presentation["presentation/recommendation"] Api[RecommendationWeightApiController] end subgraph Application["application/recommendation"] Get[GetRecommendationWeightsUseCase] Upd[UpdateRecommendationWeightsUseCase] Rel[ReleaseRecommendationCapUseCase] Res[RestoreRecommendationCapUseCase] end subgraph Domain["domain/recommendation"] DS[RecommendationDomainService] Weights[RecommendationAxisWeights] CapRepo[CapOverrideRepository] CRepo[RecommendationCriteriaRepository] end Api --> Get Api --> Upd Api --> Rel Api --> Res Upd --> DS Rel --> DS DS --> Weights DS --> CapRepo DS --> CRepo
테스트 케이스
- 현재 가중치를 조회하면 4축과 기준 버전이 반환된다
- 합계 100인 가중치를 저장하면 200과 증가된 기준 버전이 반환된다
- 합계가 99면 400
RECOMMENDATION_WEIGHT_INVALID다 (경계값) - 합계가 101이면 400이다 (경계값)
- 축이 3종만 전달되면 400이다
- 알 수 없는 축 이름이면 400이다
- 가중치 변경 후 재평가하면 새 비중으로 점수가 계산된다
- 가중치 변경이
match_criteria_revisions를 증가시키지 않는다 (별개 축 검증) - 상한이 걸린 공고에 해제를 요청하면 점수가 재계산되고
capReleased=true다 - 해제 후 재평가해도 해제가 유지된다
- 해제된 결과에 원 상한 사유(
capReason)가 계속 노출된다 - 해제를 취소하면 다시 59점 상한이 적용된다
- 상한이 걸리지 않은 공고에 해제를 요청해도 200이고 점수가 그대로다 (멱등)
- 평가 결과가 없는 공고에 해제를 요청하면 404
RECOMMENDATION_NOT_FOUND다