[BE-59] 공고 요구사항 추출 — JD 섹션 파싱·요구 경력·직무 분류 (FR-96 입력)
작업 내용 (설계 의도)
근거 TDD: 20260808-지원관리-확장-tdd.md — “방안 9 축 정의와 기본 가중치”, “JD 없는 공고의 처리”
변경 사항
추천도 4축 중 3축(REQUIRED_SKILL·PREFERRED_SKILL·CAREER_ROLE)의 입력은 공고 요구사항입니다. 이 티켓은 JD 본문·구조화 태그에서 요구사항을 추출하는 순수 함수만 만듭니다 — 프로필과 무관해 BE-58과 병렬 실행됩니다.
- 섹션 헤더 사전 기반 분할 —
자격 요건·지원 자격·필수·Requirements·Qualifications→ 필수 섹션 /우대사항·우대 조건·Preferred·Nice to have·Plus→ 우대 섹션. 헤더를 못 찾으면 그 축은 판단 불가로 떨어집니다(예외를 던지지 않습니다). - 기술명 추출 — 섹션 텍스트에서 불릿·쉼표·개행으로 항목을 쪼개고, 항목마다 기술명 후보를 정규화합니다. 문장이 너무 길면(40자 초과) 기술명이 아니라 서술로 보고 제외합니다 — 오탐 억제.
- 요구 경력 —
경력 N년 이상·N년+·N~M년·N years정규식으로 개월 수를 추출합니다.신입·무관이면 0개월. 표기가 없으면null(판단 불가 기여). - 직무 분류 — 구조화 태그(
job_posting_source_tags)에서 직무 성격 태그를 뽑습니다. 소스마다 태그 입도가 달라 별도 필드를 신설하지 않고 태그를 그대로 씁니다(TDD 방안 8 — 직무 분류 전용 필드 신설은 어댑터 9종 수정을 부르므로 미채택). JobRequirement.isEmpty()— 필수·우대·경력이 전부 비면true. 이 값이NOT_EVALUABLE(JD_UNAVAILABLE)판정의 근거입니다(검수 미결 #5 — JD 없는 공고는 평가 제외).- 순수 함수입니다 — Repository·Gateway를 주입받지 않고 텍스트만 입력받습니다. 테스트가 전부 단위 테스트로 커버됩니다.
의존
- BE-56 (예외)
다이어그램
처리 흐름
sequenceDiagram participant R as RecommendationDomainService participant E as JobRequirementExtractor participant Req as JobRequirement R->>E: extract(descriptionBody, tagValues) E->>E: 섹션 헤더 사전으로 필수·우대 섹션 분할 alt 헤더 미발견 E->>E: 해당 섹션 기술 목록 비움 (판단 불가 기여) else 헤더 발견 E->>E: 불릿·쉼표·개행 분리 → 40자 초과 항목 제외 E->>E: 기술명 정규화 end E->>E: 요구 경력 정규식 추출 (신입/무관 → 0개월) E->>E: 태그에서 직무 분류 키워드 추출 E->>Req: JobRequirement(필수, 우대, 경력, 직무) Req-->>R: isEmpty()면 JD_UNAVAILABLE 판정
클래스 의존
flowchart LR subgraph Domain["domain/recommendation"] Extractor[JobRequirementExtractor] Req[JobRequirement] Section[JdSectionSplitter] Exp[ExperienceRequirementParser] end Extractor --> Section Extractor --> Exp Extractor --> Req
테스트 케이스
자격 요건헤더 아래 불릿 3개에서 기술 3건이 추출된다우대사항헤더 아래 항목이 우대 기술로 분리된다- 섹션 헤더가 없으면 필수·우대 기술이 빈 목록이다 (예외 아님)
Requirements·Preferred영문 헤더도 인식된다- 40자를 초과하는 서술 문장은 기술명으로 추출되지 않는다 (오탐 억제)
- 쉼표로 나열된 항목이 각각 분리된다
경력 3년 이상에서 36개월이 추출된다3~5년에서 하한 36개월이 추출된다신입표기면 0개월이다경력 무관이면 0개월이다- 경력 표기가 없으면
null이다 (판단 불가 기여) - 본문이 null이면 필수·우대·경력이 모두 비고
isEmpty()가 true다 - 본문이 있으나 섹션·경력이 전부 없으면
isEmpty()가 true다 - 태그에서 직무 분류 키워드가 추출된다
- 같은 기술명이 필수·우대에 중복되면 필수에만 남는다
- 기술명이 대소문자·하이픈만 다르면 하나로 정규화된다