Skip to content

Conversation

@sudo-Terry
Copy link
Member

@sudo-Terry sudo-Terry commented Jan 8, 2026

Desc

  • OpenAiChatService를 사용하여 LLM API를 호출하고, 분석 결과를 받아와 DTO로 직렬화하는 AiHealthDataExtractorService를 수정하고자 합니다.
  • LLM API에 의존적인 코드들은 OpenAiChatService에서, 그리고 추상화된 LLM API를 호출하여 분석하는 것은 CareCallAnalysisService에서 담당하니 이에 맞게 구조를 재구성합니다.
    • AiHealthDataExtractorService를 삭제
    • 프롬프트 빌더만 별도로 CareCallDataExtractionPrompt으로 분리
    • OpenAiChatService를 호출하여 분석을 수행하는 주체가 CareCallAnalysisService가 되도록 변경
    • Spring AI 마이그레이션 이후 불필요한 cleanContent과 같은 메소드 정리
  • HealthDataController쪽도 후속 작업 예정입니다

Summary by CodeRabbit

리팩토르

  • 헬스 데이터 추출 및 저장 로직의 내부 아키텍처 최적화를 통해 시스템 안정성 개선
  • 관련 자동화 테스트 스위트 업데이트 및 강화

✏️ Tip: You can customize this high-level summary in your review settings.

### Desc
- `OpenAiChatService`를 사용하여 LLM API를 호출하고, 분석 결과를 받아와 DTO로 직렬화하는 `AiHealthDataExtractorService`를 수정하고자 합니다.
- LLM API에 의존적인 코드들은 `OpenAiChatService`에서, 그리고 추상화된 LLM API를 호출하여 분석하는 것은 `CareCallAnalysisService`에서 담당하니 이에 맞게 구조를 재구성합니다.
  - `AiHealthDataExtractorService`를 삭제
  - 프롬프트 빌더만 별도로 `CareCallDataExtractionPrompt`으로 분리
  - `OpenAiChatService`를 호출하여 분석을 수행하는 주체가 `CareCallAnalysisService`가 되도록 변경
  - Spring AI 마이그레이션 이후 불필요한 `cleanContent`과 같은 메소드 정리
- `HealthDataController`쪽도 후속 작업 예정입니다
@github-actions github-actions bot added the refactoring Refactoring should not change Test codes label Jan 8, 2026
Base automatically changed from refactor/care-call-meal-record to main January 8, 2026 15:48
@sudo-Terry
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Medicare-Call Medicare-Call deleted a comment from coderabbitai bot Jan 8, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":404,"request":{"method":"PATCH","url":"https://api.github.com/repos/Medicare-Call/Medicare-Call-Backend/issues/comments/3724456237","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n## Walkthrough\n\n헬스 데이터 추출 흐름을 재설계했습니다. `AiHealthDataExtractorService`를 제거하고, `CareCallAnalysisService`에 직접 OpenAI 통합을 추가했으며, 새로운 `CareCallAnalysisResultSaveService`를 분리하여 데이터 저장을 담당하게 했습니다. 프롬프트 템플릿을 `CareCallDataExtractionPrompt` 상수로 이동했습니다.\n\n## Changes\n\n| Cohort / File(s) | 요약 |\n|---|---|\n| **컨트롤러 업데이트** <br> `src/main/java/com/example/medicare_call/controller/HealthDataController.java` | `AiHealthDataExtractorService` 의존성을 `CareCallAnalysisResultSaveService`로 교체. `extractHealthData` 및 `saveTestHealthData` 메서드가 새로운 서비스 방식 사용 |\n| **AI 프롬프트 상수화** <br> `src/main/java/com/example/medicare_call/service/ai/CareCallDataExtractionPrompt.java` | `AiHealthDataExtractorService` 제거 및 `CareCallDataExtractionPrompt` 클래스 생성. `SYSTEM_MESSAGE`와 `PROMPT_TEMPLATE` 정적 상수만 포함 |\n| **데이터 처리 서비스 핵심 로직** <br> `src/main/java/com/example/medicare_call/service/data_processor/CareCallAnalysisService.java` | OpenAI 직접 통합 추가. 새로운 공개 메서드 `extractHealthData(LocalDate, String, List<String>)` 생성. 기존 추출기 대신 `OpenAiChatService` 사용. `BeanOutputConverter` 및 동적 모델 설정 추가 |\n| **기존 추출기 테스트 제거** <br> `src/test/java/com/example/medicare_call/service/ai/AiHealthDataExtractorServiceTest.java` | 파일 제거 (309줄 삭제). `AiHealthDataExtractorService`에 대한 7개 테스트 케이스 및 Mockito 설정 제거 |\n| **서비스 테스트 업데이트** <br> `src/test/java/com/example/medicare_call/service/data_processor/CareCallAnalysisServiceTest.java` | `AiHealthDataExtractorService` 대신 `OpenAiChatService` 목(mock) 사용. Spring AI 채팅 클래스(`ChatResponse`, `Generation`, `AssistantMessage`) 임포트. 테스트 흐름을 OpenAI 채팅 기반으로 조정 |\n| **통합 테스트 업데이트** <br> `src/test/java/com/example/medicare_call/service/data_processor/HealthDataExtractionIntegrationTest.java` | `AiHealthDataExtractorService` 대신 `CareCallAnalysisService` 사용. 3개 테스트 케이스 모두 새로운 `extractHealthData(LocalDate, String, List<?>)` 시그니처 호출로 변경 |\n\n## Sequence Diagram(s)\n\n```mermaid\nsequenceDiagram\n    participant Controller as HealthDataController\n    participant Service as CareCallAnalysisService\n    participant OpenAI as OpenAiChatService\n    participant Converter as BeanOutputConverter\n    participant SaveService as CareCallAnalysisResultSaveService\n    \n    Controller->>Service: extractHealthData(callDate, transcriptionText, medicationNames)\n    Service->>Service: buildPrompt(CareCallDataExtractionPrompt)\n    Service->>OpenAI: openAiChat(prompt, OpenAiChatOptions)\n    OpenAI-->>Service: ChatResponse\n    Service->>Service: validateResponse()\n    Service->>Converter: convert(responseContent, HealthDataExtractionResponse)\n    Converter-->>Service: HealthDataExtractionResponse\n    Service->>SaveService: processAndSaveHealthData(response)\n    SaveService-->>Controller: 저장 완료\n```\n\n## Estimated code review effort\n\n🎯 4 (Complex) | ⏱️ ~75 minutes\n\n## Possibly related PRs\n\n- **Medicare-Call/Medicare-Call-Backend#162**: Spring AI 마이그레이션 변경사항(OpenAiChatService, PromptBuilder, OpenAiConfig)으로 본 PR의 OpenAI 통합 기반 구성과 직접 연계됨\n- **Medicare-Call/Medicare-Call-Backend#193**: 추출 흐름에 medicationNames 파라미터 추가 및 새로운 메서드 시그니처 변경과 관련\n- **Medicare-Call/Medicare-Call-Backend#184**: 헬스 데이터 추출 흐름 및 HealthDataExtractionResponse 포맷 변경으로 연계됨\n\n## Suggested reviewers\n\n- jyun-KIM\n- kisusu115\n- mjkhub\n\n## Poem\n\n> 🔄 낡은 추출기는 물러나고 <br>\n> ✨ OpenAI의 새 흐름이 들어섰네 <br>\n> 🎯 프롬프트는 상수가 되어 <br>\n> 💾 저장은 분리되어 맑아졌고 <br>\n> 🚀 아키텍처 춤을 추리라~\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 1 | ❌ 2</summary>\n\n<details>\n<summary>❌ Failed checks (1 warning, 1 inconclusive)</summary>\n\n|     Check name     | Status         | Explanation                                                                                                                                    | Resolution                                                                                                                                                                     |\n| :----------------: | :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Docstring Coverage | ⚠️ Warning     | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.                                                           | Write docstrings for the functions missing them to satisfy the coverage threshold.                                                                                             |\n|     Title check    | ❓ Inconclusive | PR 제목이 주요 변경 사항을 명확하게 반영하지만, 구체성이 부족함. 'refactor'는 변경의 성질만 나타내고 'AiHealthDataExtractorService'는 제거된 클래스만 언급할 뿐, 실제 핵심은 책임 구조 재조정과 서비스 간 역할 변경. | 제목을 더 구체적으로 개선 제안: 'refactor: restructure health data extraction with OpenAiChatService and CareCallAnalysisService' 또는 'refactor: move LLM interactions to OpenAiChatService' |\n\n</details>\n<details>\n<summary>✅ Passed checks (1 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                 |\n| :---------------: | :------- | :---------------------------------------------------------- |\n| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Medicare-Call/Medicare-Call-Backend&utm_content=213)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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= -->\n\n<!-- internal state end -->"},"request":{"retryCount":1}},"response":{"url":"https://api.github.com/repos/Medicare-Call/Medicare-Call-Backend/issues/comments/3724456237","status":404,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Thu, 08 Jan 2026 15:51:53 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept-Encoding, Accept, X-Requested-With","x-accepted-github-permissions":"issues=write; pull_requests=write","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-api-version-selected":"2022-11-28","x-github-media-type":"github.v3; format=json","x-github-request-id":"4052:172E9D:10638DD:4755F43:695FD299","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4928","x-ratelimit-reset":"1767890564","x-ratelimit-resource":"core","x-ratelimit-used":"72","x-xss-protection":"0"},"data":{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#update-an-issue-comment","status":"404"}}}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/java/com/example/medicare_call/service/ai/CareCallDataExtractionPrompt.java (1)

3-5: 유틸리티 클래스로서 인스턴스화 방지 고려.

정적 상수만 포함하는 유틸리티 클래스이므로, private 생성자를 추가하여 불필요한 인스턴스화를 방지하는 것이 좋습니다.

♻️ 유틸리티 클래스 패턴 적용
-public class CareCallDataExtractionPrompt {
+public final class CareCallDataExtractionPrompt {
+    
+    private CareCallDataExtractionPrompt() {
+        // Utility class - prevent instantiation
+    }
     
     public static final String SYSTEM_MESSAGE = "당신은 의료 통화 내용에서 건강 데이터를 추출하는 전문가입니다. 주어진 통화 내용에서 건강 관련 정보를 정확히 추출하여 JSON 형태로 응답해주세요.";
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f01df2 and a24f64e.

📒 Files selected for processing (6)
  • src/main/java/com/example/medicare_call/controller/HealthDataController.java
  • src/main/java/com/example/medicare_call/service/ai/CareCallDataExtractionPrompt.java
  • src/main/java/com/example/medicare_call/service/data_processor/CareCallAnalysisService.java
  • src/test/java/com/example/medicare_call/service/ai/AiHealthDataExtractorServiceTest.java
  • src/test/java/com/example/medicare_call/service/data_processor/CareCallAnalysisServiceTest.java
  • src/test/java/com/example/medicare_call/service/data_processor/HealthDataExtractionIntegrationTest.java
💤 Files with no reviewable changes (1)
  • src/test/java/com/example/medicare_call/service/ai/AiHealthDataExtractorServiceTest.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/test/java/com/example/medicare_call/service/data_processor/CareCallAnalysisServiceTest.java (1)
src/main/java/com/example/medicare_call/service/ai/CareCallDataExtractionPrompt.java (1)
  • CareCallDataExtractionPrompt (3-94)
src/main/java/com/example/medicare_call/service/data_processor/CareCallAnalysisService.java (1)
src/main/java/com/example/medicare_call/service/ai/CareCallDataExtractionPrompt.java (1)
  • CareCallDataExtractionPrompt (3-94)
🔇 Additional comments (10)
src/test/java/com/example/medicare_call/service/data_processor/HealthDataExtractionIntegrationTest.java (1)

35-35: 변경 사항이 잘 반영되었습니다.

새로운 CareCallAnalysisService.extractHealthData(LocalDate, String, List<String>) 시그니처에 맞춰 테스트가 올바르게 업데이트되었습니다. 모킹 설정과 검증 로직이 일관성 있게 유지되고 있네요.

Also applies to: 99-103

src/test/java/com/example/medicare_call/service/data_processor/CareCallAnalysisServiceTest.java (3)

59-60: @Value 필드 주입을 위한 ReflectionTestUtils 사용 확인.

@Value("${openai.model}") 필드를 테스트에서 설정하기 위해 ReflectionTestUtils를 사용하는 것은 괜찮은 접근입니다. 다만, 테스트 프로파일의 application-test.yml에 해당 프로퍼티를 설정하는 방법도 고려해볼 수 있어요.


69-81: 테스트 흐름이 명확합니다.

OpenAI 응답 모킹과 검증 로직이 새로운 서비스 구조에 맞게 잘 작성되었네요. contains(callRecord.getTranscriptionText())를 사용해 프롬프트에 통화 내용이 포함되는지 검증하는 부분이 좋습니다.


102-114: 예외 처리 테스트가 추가된 점 좋습니다.

OpenAI 응답이 null일 때 RuntimeException이 발생하고 저장 로직이 호출되지 않는 것을 검증하네요. 서비스 코드에 TODO 주석으로 표시된 대로, 추후 커스텀 예외로 개선되면 테스트도 함께 업데이트하면 될 것 같아요.

src/main/java/com/example/medicare_call/controller/HealthDataController.java (2)

46-50: medicationNames에 빈 리스트를 전달하는 것이 의도된 동작인지 확인 필요.

extractHealthData 호출 시 Collections.emptyList()를 전달하고 있는데, 이 경우 프롬프트에 "등록된 약 없음"으로 표시됩니다. 만약 이 엔드포인트에서도 Elder의 복약 스케줄을 활용해야 한다면, 별도로 조회하여 전달해야 할 수 있어요.

단순 추출 API라면 현재 구현이 맞지만, 사용자의 복약 정보를 활용한 정확한 추출이 필요하다면 검토해 주세요.


78-86: 테스트 엔드포인트의 책임 분리가 잘 되었습니다.

추출과 저장이 명확하게 분리되어 CareCallAnalysisService는 추출만, CareCallAnalysisResultSaveService는 저장만 담당하도록 구조화되었네요.

src/main/java/com/example/medicare_call/service/ai/CareCallDataExtractionPrompt.java (1)

7-93: 프롬프트 템플릿이 체계적으로 구성되었습니다.

추출 대상 데이터, 응답 형식, 특수 규칙(복약 데이터 분리 등)이 명확하게 정의되어 있어 LLM의 일관된 응답을 유도할 수 있겠네요.

src/main/java/com/example/medicare_call/service/data_processor/CareCallAnalysisService.java (3)

84-84: JSON 파싱 실패 시 예외 처리 고려.

beanOutputConverter.convert(content)가 OpenAI 응답이 예상 JSON 형식과 다를 경우 예외를 발생시킬 수 있습니다. @Retryable이 적용되어 재시도는 되지만, 파싱 실패와 API 실패를 구분하여 처리하면 디버깅에 도움이 될 수 있어요.


54-61: 리팩토링이 깔끔하게 완료되었습니다.

복약 스케줄 조회 및 extractHealthDataprocessAndSaveHealthData 흐름이 명확하게 분리되었네요. 스트림을 통한 중복 제거(distinct())도 적절합니다.


32-35: 필드 초기화 및 설정 주입 확인.

BeanOutputConverter를 필드에서 직접 초기화하는 것은 stateless한 변환기이므로 문제없습니다. openaiModel@Value를 통해 주입받아 유연하게 설정 가능하네요.

Comment on lines +80 to +88
if (response != null && response.getResult() != null) {
String content = response.getResult().getOutput().getText();
log.info("OpenAI 응답: {}", content);

return beanOutputConverter.convert(content);
} else {
// TODO: Custom Error Code 추가해서 예외 처리
throw new RuntimeException("OpenAI API 응답이 비어있습니다");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

응답 로깅 시 민감한 건강 정보 노출 주의.

Line 82에서 OpenAI 응답 전체를 로깅하고 있는데, 이 응답에는 혈당 수치, 복약 정보 등 민감한 건강 데이터가 포함될 수 있습니다. 프로덕션 환경에서는 로그 레벨을 DEBUG로 변경하거나, 민감 정보를 마스킹하는 것을 고려해 주세요.

🔒 로그 레벨 변경 제안
         if (response != null && response.getResult() != null) {
             String content = response.getResult().getOutput().getText();
-            log.info("OpenAI 응답: {}", content);
+            log.debug("OpenAI 응답: {}", content);

             return beanOutputConverter.convert(content);
         } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (response != null && response.getResult() != null) {
String content = response.getResult().getOutput().getText();
log.info("OpenAI 응답: {}", content);
return beanOutputConverter.convert(content);
} else {
// TODO: Custom Error Code 추가해서 예외 처리
throw new RuntimeException("OpenAI API 응답이 비어있습니다");
}
if (response != null && response.getResult() != null) {
String content = response.getResult().getOutput().getText();
log.debug("OpenAI 응답: {}", content);
return beanOutputConverter.convert(content);
} else {
// TODO: Custom Error Code 추가해서 예외 처리
throw new RuntimeException("OpenAI API 응답이 비어있습니다");
}

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

Labels

refactoring Refactoring should not change Test codes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants