[BE-58] 이력서 프로필 도메인 모델 · 축 가중치 · 평가 기준 버전 (FR-95·97 기반)
작업 내용 (설계 의도)
근거 TDD: 20260808-지원관리-확장-tdd.md — “방안 5 (recommendation 신규 컨텍스트)”, “방안 9 지원 추천도”, “recommendation 인터페이스 시그니처”
변경 사항
- 신규
recommendation컨텍스트를 만듭니다.matching에 합류시키지 않는 이유: matching은 “알림을 보낼 공고인가”(키워드·근무형태)를 판정하고, 추천도는 “지원할 만한가”(내 역량 대비 적합도)를 판정합니다 — 입력·출력·기준 버전 축이 전부 다릅니다. FR-97이 “기존match_criteria_revisions와는 별개 축”을 명시적으로 요구했습니다. - 프로필은
document가 아니라recommendation이 소유합니다 — 프로필은 추천도 계산의 입력 기준이며 독립적 의미가 약합니다.document에 두면 확정·기준 버전 증가가 두 컨텍스트에 걸쳐 조율돼야 합니다.sourceDocumentVersionId: Long만 참조합니다. - 초안 vs 확정을 타입으로 드러냅니다 —
ResumeProfile.isConfirmed. 확정 전 초안은 추천도 계산에 쓰이지 않습니다(FR-95).confirm()은 이미 확정이면false를 반환해 기준 버전을 올리지 않습니다(멱등). - 평가 기준 버전(
recommendation_criteria_revisions)은 단일 축입니다 — 프로필 확정(PROFILE_CONFIRMED)과 축 가중치 변경(AXIS_WEIGHT) 둘 다 이 revision을 증가시킵니다.change_target으로 구분합니다. - 축 가중치(
recommendation_axis_weights) —REQUIRED_SKILL45 /PREFERRED_SKILL20 /CAREER_ROLE20 /WORK_CONDITION15가 기본값입니다. 합계 100 검증은RecommendationAxisWeights값 객체가 캡슐화합니다. ProfileSkill이 충족률을 스스로 판정합니다 —fulfillmentAgainst(요구 기술, 요구 기간)이FULL/PARTIAL/NONE을 반환합니다. “일부 충족 60%“는 기술명 일치 && (최근 사용 3년 초과 || 사용 기간 < 요구 기간의 1/2)입니다(FR-96 확정 규칙). 판정 로직을 외부 함수에 두지 않습니다(no-expose-value-for-external-logic).- 시간 타입은
ZonedDateTime, 연월은YearMonth를 씁니다.
이 티켓은 계산·API를 만들지 않습니다 — 모델·저장·기준 버전만 만듭니다. 계산은 BE-63, API는 BE-62·65입니다.
의존
- BE-56 (예외·플래그 시드)
다이어그램
처리 흐름
sequenceDiagram participant U as ConfirmResumeProfileUseCase participant D as ResumeProfileDomainService participant P as ResumeProfile participant R as ResumeProfileRepository participant C as RecommendationCriteriaRepository U->>D: confirm(profileId) D->>R: findBy(profileId) D->>P: confirm() alt 이미 확정 P-->>D: false → 기준 버전 미증가 (멱등) else 신규 확정 P-->>D: true D->>R: save(profile) — 항목 cascade D->>C: bumpRevision(PROFILE_CONFIRMED, reason) C-->>D: 새 criteriaRevision end D-->>U: (profile, criteriaRevision)
클래스 의존
flowchart LR subgraph Domain["domain/recommendation"] PDS[ResumeProfileDomainService] Profile[ResumeProfile] Skill[ProfileSkill] Exp[ProfileExperience] Pref[ProfileWorkPreference] Axis[RecommendationAxis] Weights[RecommendationAxisWeights] PRepo[ResumeProfileRepository] WRepo[RecommendationAxisWeightRepository] CRepo[RecommendationCriteriaRepository] end PDS --> Profile PDS --> PRepo PDS --> CRepo Profile --> Skill Profile --> Exp Profile --> Pref Weights --> Axis Weights --> WRepo
테스트 케이스
- 초안 프로필을 만들면
isConfirmed=false다 - 프로필을 확정하면
isConfirmed=true가 되고 새 기준 버전이 생성된다 - 이미 확정된 프로필을 다시 확정하면 기준 버전이 증가하지 않는다 (멱등)
- 확정된 프로필의 항목을 수정하려 하면 예외가 발생한다
- 항목(기술·경력·선호)이 cascade로 한 단위 저장된다
- 축 가중치 4종 합계가 100이면 저장된다
- 합계가 99면
InvalidRecommendationWeightException이다 (경계값) - 축이 3종만 전달되면 예외다 (4축 필수)
- 가중치를 변경하면 기준 버전이
AXIS_WEIGHT로 증가한다 - 기본 가중치가 45/20/20/15로 시드된다
- 기술명이 프로필에 없으면 충족률이
NONE이다 - 기술명이 일치하고 최근 사용 4년 전이면
PARTIAL이다 - 기술명이 일치하고 최근 사용 정확히 3년 전이면
FULL이다 (경계값) - 기술명이 일치하고 사용 기간이 요구의 40%면
PARTIAL이다 - 기술명이 일치하고 사용 기간이 요구의 정확히 50%면
FULL이다 (경계값) - 기술명 정규화(대소문자·공백·하이픈)가 적용되어
Spring Boot와spring-boot가 같은 기술로 판정된다