[Fix] 원문 기준 공고명 추출 및 생성 결과 보정 - #218
Conversation
- 공고 추출 응답에 postingName 필드 추가 - 원문에 명시된 공고 제목만 그대로 추출하도록 프롬프트 수정 - 공고명이 없거나 불확실한 경우 빈 문자열을 유지하도록 처리 - 생성 단계에서 회사명과 직무명으로 공고명을 임의 생성하지 않도록 수정 - 기존 요청 DTO 생성자 호환성 유지 - 원문 공고명 전달 및 빈 공고명 유지 테스트 추가
|
Warning Review limit reached
Next review available in: 47 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthrough추출 응답과 생성 요청에 Changes공고명 처리 흐름
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant JobPostingIngestService
participant JobPostingAiService
participant OpenAI
JobPostingIngestService->>JobPostingAiService: postingNameHint 포함 생성 요청
JobPostingAiService->>OpenAI: postingName 생성 규칙 포함 프롬프트 전송
OpenAI-->>JobPostingAiService: 생성 응답 반환
JobPostingAiService->>JobPostingAiService: postingNameHint 우선 정규화
JobPostingAiService-->>JobPostingIngestService: postingName 포함 결과 반환
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiService.java (2)
385-410: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftAI가 만든 공고명을 원문 대조 없이 저장하지 마세요.
Line 401은
response.postingName()을 빈 값 여부만 정규화합니다. 따라서 모델이 원문에 없는"회사명 직무명 채용"을 반환해도 그대로 ingest 단계의postingNameHint가 되어 저장될 수 있습니다. 프롬프트는 지시일 뿐 보장 장치가 아니므로, 텍스트 원문 또는 이미지 OCR 원문과 정규화된 제목을 대조하고 불일치하면 빈 문자열로 처리해야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiService.java` around lines 385 - 410, Update normalizeResponse so response.postingName() is validated against rawText (including the OCR source represented by it) before being used as the postingNameHint; retain it only when the normalized title is supported by the source text, otherwise pass an empty string. Keep the existing defaultString normalization and other response fields unchanged.
681-707: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
postingNameHint == null로 공고명 합성을 하지 마세요.
/generateAPI는JobPostingGenerateRequest의postingNameHint를 검증 없이 받아null도 전달하고, 이 상태가 기존 11자 매개 생성자 호출 경로와 똑같이 처리됩니다. 실제 기존 11자 생성자는postingNameHint를null로 넘기도록 되어 있으니,null과"무엇이든"을 같은 “공고명 유지” 신호로 쓰면 빈 공고명 요청이 AI 응답/jobTitleHint로 합성됩니다. 누락/빈 공고명 요청만큼은 빈 문자열을 유지하도록 명시적으로 처리하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiService.java` around lines 681 - 707, Update normalizeGeneratedResponse so a null postingNameHint produces an empty posting name instead of falling back to response.postingName() or request.jobTitleHint(). Preserve every non-null postingNameHint value, including blank strings, unchanged, and leave the remaining normalization logic intact.src/test/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingIngestServiceTest.java (1)
130-138: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win테스트 원문에 공고명을 포함하세요.
Line 131의 postingName은
"2026 해커스 클라우드 엔지니어 공개채용"인데, 테스트 원문인 Line 137의"채용 공고 원문"에는 해당 제목이 없습니다. 이 fixture는 원문에 없는 공고명도 정상 추출된 것으로 간주하므로, source-only 요구사항을 검증하지 못합니다. rawText에 실제 제목을 포함하거나 이미지 OCR 원문을 별도로 구성하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingIngestServiceTest.java` around lines 130 - 138, Update the test fixture used by JobPostingIngestServiceTest so the rawText passed to JobPostingExtractResponse includes the extracted postingName, “2026 해커스 클라우드 엔지니어 공개채용”; alternatively, use a separate image-OCR source containing that title. Ensure the fixture validates source-only extraction rather than accepting a title absent from the original text.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/test/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiServiceTest.java`:
- Around line 274-346: Update the tests around generateJobPosting to exercise
the public failure-recovery path instead of invoking private
normalizeGeneratedResponse directly. Mock or otherwise force the external AI
call to fail, then verify that createFallbackGeneratedResponse preserves the
request hint in postingName for both a non-empty original posting name and an
empty one, using the asynchronous result from generateJobPosting.
---
Outside diff comments:
In
`@src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiService.java`:
- Around line 385-410: Update normalizeResponse so response.postingName() is
validated against rawText (including the OCR source represented by it) before
being used as the postingNameHint; retain it only when the normalized title is
supported by the source text, otherwise pass an empty string. Keep the existing
defaultString normalization and other response fields unchanged.
- Around line 681-707: Update normalizeGeneratedResponse so a null
postingNameHint produces an empty posting name instead of falling back to
response.postingName() or request.jobTitleHint(). Preserve every non-null
postingNameHint value, including blank strings, unchanged, and leave the
remaining normalization logic intact.
In
`@src/test/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingIngestServiceTest.java`:
- Around line 130-138: Update the test fixture used by
JobPostingIngestServiceTest so the rawText passed to JobPostingExtractResponse
includes the extracted postingName, “2026 해커스 클라우드 엔지니어 공개채용”; alternatively,
use a separate image-OCR source containing that title. Ensure the fixture
validates source-only extraction rather than accepting a title absent from the
original text.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: b1549408-0e02-4149-9a34-8fd02a56b20a
📒 Files selected for processing (6)
src/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/request/JobPostingGenerateRequest.javasrc/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/response/JobPostingExtractResponse.javasrc/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiService.javasrc/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingIngestService.javasrc/test/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiServiceTest.javasrc/test/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingIngestServiceTest.java
| @Test | ||
| @DisplayName("공고 생성 결과는 AI가 바꾼 제목 대신 원문에서 추출한 공고명을 유지한다") | ||
| void normalizeGeneratedResponsePreservesExtractedPostingName() { | ||
| JobPostingGenerateRequest request = new JobPostingGenerateRequest( | ||
| "테스트 기업", | ||
| null, | ||
| 100L, | ||
| "채용 요약", | ||
| "", | ||
| "주요 업무", | ||
| "자격 요건", | ||
| "우대 사항", | ||
| null, | ||
| "백엔드 개발자", | ||
| "2026 백엔드 개발자 공개채용" | ||
| ); | ||
| JobPostingGenerateResponse generated = new JobPostingGenerateResponse( | ||
| "테스트 기업 백엔드 채용", | ||
| "테스트 기업", | ||
| "백엔드 개발자", | ||
| "주요 업무", | ||
| "자격 요건", | ||
| "우대 사항", | ||
| "요약" | ||
| ); | ||
|
|
||
| JobPostingGenerateResponse normalized = ReflectionTestUtils.invokeMethod( | ||
| jobPostingAiService, | ||
| "normalizeGeneratedResponse", | ||
| generated, | ||
| request | ||
| ); | ||
|
|
||
| assertThat(normalized).isNotNull(); | ||
| assertThat(normalized.postingName()).isEqualTo("2026 백엔드 개발자 공개채용"); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("원문에 공고명이 없으면 생성 결과의 공고명도 빈 문자열로 유지한다") | ||
| void normalizeGeneratedResponseKeepsMissingPostingNameEmpty() { | ||
| JobPostingGenerateRequest request = new JobPostingGenerateRequest( | ||
| "테스트 기업", | ||
| null, | ||
| 100L, | ||
| "채용 요약", | ||
| "", | ||
| "주요 업무", | ||
| "자격 요건", | ||
| "우대 사항", | ||
| null, | ||
| "백엔드 개발자", | ||
| "" | ||
| ); | ||
| JobPostingGenerateResponse generated = new JobPostingGenerateResponse( | ||
| "테스트 기업 백엔드 채용", | ||
| "테스트 기업", | ||
| "백엔드 개발자", | ||
| "주요 업무", | ||
| "자격 요건", | ||
| "우대 사항", | ||
| "요약" | ||
| ); | ||
|
|
||
| JobPostingGenerateResponse normalized = ReflectionTestUtils.invokeMethod( | ||
| jobPostingAiService, | ||
| "normalizeGeneratedResponse", | ||
| generated, | ||
| request | ||
| ); | ||
|
|
||
| assertThat(normalized).isNotNull(); | ||
| assertThat(normalized.postingName()).isEmpty(); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
생성 실패 fallback을 공개 메서드 기준으로 검증하세요.
추가된 테스트는 private normalizeGeneratedResponse만 직접 호출하며, generateJobPosting의 외부 호출 실패 후 createFallbackGeneratedResponse로 들어가는 경로는 검증하지 않습니다. 빈 공고명과 원문 공고명이 있는 요청 각각에 대해 생성 실패를 유도하고, 공개 메서드 결과의 postingName이 힌트를 그대로 유지하는지 확인해야 합니다.
As per path instructions, 실패 복구 경로를 포함한 비동기·외부 호출 시나리오를 충분히 검증해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/test/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingAiServiceTest.java`
around lines 274 - 346, Update the tests around generateJobPosting to exercise
the public failure-recovery path instead of invoking private
normalizeGeneratedResponse directly. Mock or otherwise force the external AI
call to fail, then verify that createFallbackGeneratedResponse preserves the
request hint in postingName for both a non-empty original posting name and an
empty one, using the asynchronous result from generateJobPosting.
Source: Path instructions
- 추출된 공고명이 원문에 존재하는지 정규화 후 검증 - 원문에서 확인되지 않는 공고명은 빈 문자열로 처리 - 공고명 힌트가 null인 경우 임의 제목 생성을 차단 - AI 호출 실패 시 공고명 힌트 보존 여부를 public API 경로로 검증 - 공고명 추출 테스트 fixture에 원문 근거 추가
✨ 어떤 이유로 PR를 하셨나요?
📋 세부 내용 - 왜 해당 PR이 필요한지 작업 내용을 자세하게 설명해주세요
📸 작업 화면 스크린샷
🚨 관련 이슈 번호 [ ]
Summary by CodeRabbit
새로운 기능
버그 수정