Skip to content

[FIX] 칼럼 추천 로직 수정#11

Merged
goblurry merged 5 commits into
deployfrom
main
Nov 16, 2025
Merged

[FIX] 칼럼 추천 로직 수정#11
goblurry merged 5 commits into
deployfrom
main

Conversation

@goblurry

@goblurry goblurry commented Nov 16, 2025

Copy link
Copy Markdown
Member

🎯 목적

정확한 뉴스 매칭 및 상관종목 칼럼 생성 개선을 위해 칼럼 추천 로직을 수정함.

📝 주요 변경사항

  • Massive API 정확한 일치 뉴스 검색 기능 추가
  • Pass 1에서 정확히 일치하는 뉴스만 사용하도록 개선
  • 상관종목 뉴스 사용 시 source_ticker 정보 추가
  • ChatGPT 프롬프트에 source_ticker 컨텍스트 추가

🔗 관련 이슈/PR

🔄 버전 검토

  • 배포 버전 변경 시 config.py 업데이트 필요

🧪 테스트

  • 로컬 테스트 완료(Swagger UI에서 API 동작 확인)

📋 체크리스트

  • API 스펙 변경사항 문서화 (Swagger 업데이트)
  • 프론트엔드에 필요한 환경 변수 안내
  • 데이터베이스 스키마 변경사항 공유 (schema.sql 업데이트)
  • API 응답 형식 변경 여부 확인
  • CodeRabbit 제안사항 검토 완료

📝 커밋 메시지 규칙
타입 설명
feat 새로운 기능 추가 (API, 서비스 로직 등)
fix 버그 수정 (예외 처리, 로직 오류 등)
refactor 리팩토링 (기능 변화 없이 구조 개선)
style 코드 스타일 수정 (공백, 들여쓰기 등)
docs 문서 수정 (README, Swagger, 주석 등)
test 테스트 코드 추가/수정
chore 설정/빌드 관련 변경 (requirements.txt, .gitignore 등)
perf 성능 개선 (쿼리 최적화 등)
ci CI/CD 관련 설정 변경 (GitHub Actions 등)
config 설정 파일 변경 (config.py 등)

예시: feat: 뉴스 칼럼 추천 API 추가

Summary by CodeRabbit

릴리즈 노트

  • 새로운 기능

    • 정확한 뉴스 데이터 필터링 기능이 추가되었습니다.
    • 관리자 대상 뉴스 컬럼 초기화 기능이 추가되었습니다.
    • 컬럼 생성 시 관련 종목 정보를 참고하여 더 나은 맥락 반영이 가능해졌습니다.
    • 추천 흐름에 상세한 로깅이 추가되어 추적성이 개선되었습니다.
  • Chores

    • 버전이 1.2.2로 업데이트되었습니다.

@coderabbitai

coderabbitai Bot commented Nov 16, 2025

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

뉴스 칼럼 관리 기능을 확장하는 변경사항입니다. 정확한 뉴스 매칭을 위해 MassiveService에 새로운 메서드를 추가하고, 관련 티커 참조를 지원하도록 ChatGPTClient를 개선했습니다. 모든 칼럼 삭제 엔드포인트와 배치 재생성 스크립트가 추가되었습니다.

Changes

Cohort / File(s) Summary
Configuration & Version
app/config.py
버전 1.2.1에서 1.2.2로 업그레이드, Optional import 추가
Router Endpoints
app/routers/news.py
관리자 전용 DELETE /news/columns 엔드포인트 추가, GET /news/massive/{ticker} 엔드포인트 추가(옵션: published_utc, limit), 칼럼 추천 로직에 디버그/정보 로깅 추가
External API Integration
app/services/external/massive_service.py
get_news_exact_match() 메서드 추가: 티커의 첫 번째 항목과 정확히 일치하는 뉴스 필터링, 속도 제한 및 오류 처리 포함
AI Column Generation
app/services/news/chatgpt_client.py
source_ticker 선택 파라미터를 _create_user_prompt()generate_column()에 추가, 다양한 티커 맥락을 지원하는 조건부 프롬프트 생성
Service Layer Integration
app/services/news/news_column_service.py
Pass 1 뉴스 검색에서 get_news_exact_match() 사용, generate_column() 호출 시 source_ticker 전달
Repository Operations
app/services/news/news_db_repository.py
delete_all_columns() 메서드 추가: 모든 뉴스 칼럼 삭제 및 삭제된 행 수 반환
Utility Script
regenerate_all_columns.py
모든 칼럼 삭제 및 재생성을 자동화하는 새로운 스크립트, 비동기 API 호출 및 타임아웃 설정 포함

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • 주의 필요 영역:
    • app/services/external/massive_service.py: 동일한 get_news_exact_match() 메서드의 중복 정의 확인 필요 (런타임 오류 유발 가능)
    • app/routers/news.py: 새 엔드포인트의 MassiveService 지역 import 및 오류 처리 검증
    • app/services/news/chatgpt_client.py: 조건부 프롬프트 로직과 source_ticker 흐름의 정확성 확인
    • regenerate_all_columns.py: 타임아웃 값(30초, 600초) 및 순차 실행 로직 적절성 검토

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.84% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 '칼럼 추천 로직 수정'으로 PR의 주요 변경사항인 정확한 뉴스 매칭 및 상관종목 칼럼 생성 개선을 반영하고 있습니다.
Description check ✅ Passed PR 설명이 목적, 주요 변경사항, 관련 이슈, 버전 검토, 테스트, 체크리스트 등 템플릿의 대부분의 필수 섹션을 포함하고 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch main

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (4)
regenerate_all_columns.py (2)

9-9: BASE_URL을 설정 가능하도록 개선 권장

현재 BASE_URL이 하드코딩되어 있어 프로덕션 환경이나 다른 환경에서 실행할 때 코드 수정이 필요합니다. 환경 변수나 CLI 인자로 받도록 개선하는 것을 권장합니다.

다음과 같이 개선할 수 있습니다:

+import os
+
-BASE_URL = "http://localhost:8000"
+BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8000")

27-27: 불필요한 f-string 접두사 제거

Line 27의 문자열에 플레이스홀더가 없으므로 f 접두사가 불필요합니다.

-        print(f"✅ 칼럼 재생성 완료:")
+        print("✅ 칼럼 재생성 완료:")
app/services/news/news_db_repository.py (1)

164-166: 에러 발생 시 0 반환이 실패를 숨김

에러가 발생했을 때 0을 반환하면, 호출자가 삭제가 실패했는지 아니면 삭제할 행이 없었는지 구분할 수 없습니다. 예외를 재발생시키거나 별도의 에러 표시를 반환하는 것을 고려해보세요.

다음과 같이 개선할 수 있습니다:

     async def delete_all_columns(self) -> int:
         try:
             query = "DELETE FROM news_columns"
             result = await self.db_service.execute(query)
             deleted_count = int(result.split()[-1]) if result else 0
             logger.info("all_columns_deleted", deleted_count=deleted_count)
             return deleted_count
         except Exception as e:
-            logger.error("delete_all_columns_failed", error=str(e))
-            return 0
+            logger.exception("delete_all_columns_failed", error=str(e))
+            raise
app/routers/news.py (1)

271-273: 요청마다 서비스 인스턴스 생성 비효율

각 요청마다 새로운 MassiveService 인스턴스를 생성하는 것은 비효율적입니다. 모듈 레벨의 전역 서비스 인스턴스를 사용하거나 init_services를 통해 초기화하는 것을 권장합니다.

다음과 같이 개선할 수 있습니다:

+# 모듈 상단의 전역 서비스 인스턴스 섹션에 추가
+massive_service: MassiveService = None

+# init_services 함수에 추가
 def init_services(
     db: DatabaseService,
     liquid_stocks: LiquidStocksService,
     correlation: CorrelationService,
     news_column: NewsColumnService,
-    rate_limiter: Limiter
+    rate_limiter: Limiter,
+    massive: MassiveService
 ):
-    global db_service, liquid_stocks_service, correlation_service, news_column_service, limiter
+    global db_service, liquid_stocks_service, correlation_service, news_column_service, limiter, massive_service
+    massive_service = massive
     # ... 기존 코드 ...

 # 엔드포인트에서
 async def get_massive_news_exact(...):
     try:
-        from app.services.external import MassiveService
-        
-        massive_service = MassiveService()
-        
+        if not massive_service:
+            raise Exception("Massive 서비스가 초기화되지 않았습니다.")
+
         news_list = await massive_service.get_news_exact_match(
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fae224c and a6cd4a6.

📒 Files selected for processing (7)
  • app/config.py (2 hunks)
  • app/routers/news.py (5 hunks)
  • app/services/external/massive_service.py (1 hunks)
  • app/services/news/chatgpt_client.py (2 hunks)
  • app/services/news/news_column_service.py (2 hunks)
  • app/services/news/news_db_repository.py (1 hunks)
  • regenerate_all_columns.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
app/services/news/news_db_repository.py (3)
regenerate_all_columns.py (1)
  • delete_all_columns (11-18)
app/routers/news.py (1)
  • delete_all_columns (58-85)
app/services/database/db_service.py (1)
  • execute (41-43)
app/routers/news.py (2)
app/services/news/news_db_repository.py (1)
  • delete_all_columns (157-166)
app/services/external/massive_service.py (1)
  • get_news_exact_match (306-402)
app/services/news/news_column_service.py (1)
app/services/external/massive_service.py (1)
  • get_news_exact_match (306-402)
app/services/news/chatgpt_client.py (1)
app/models/column_schema.py (1)
  • ColumnContent (16-20)
regenerate_all_columns.py (2)
app/services/news/news_db_repository.py (1)
  • delete_all_columns (157-166)
app/services/news/news_column_service.py (1)
  • generate_all_columns (38-83)
🪛 Ruff (0.14.4)
app/services/news/news_db_repository.py

163-163: Consider moving this statement to an else block

(TRY300)


164-164: Do not catch blind exception: Exception

(BLE001)


165-165: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

app/routers/news.py

70-70: Abstract raise to an inner function

(TRY301)


70-70: Create your own exception

(TRY002)


70-70: Avoid specifying long messages outside the exception class

(TRY003)


74-78: Consider moving this statement to an else block

(TRY300)


80-80: Do not catch blind exception: Exception

(BLE001)


81-81: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


82-85: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


84-84: Use explicit conversion flag

Replace with conversion flag

(RUF010)


296-296: Do not catch blind exception: Exception

(BLE001)


297-297: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


298-301: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


300-300: Use explicit conversion flag

Replace with conversion flag

(RUF010)


396-396: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)

regenerate_all_columns.py

27-27: f-string without any placeholders

Remove extraneous f prefix

(F541)

app/services/external/massive_service.py

388-393: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


395-395: Do not catch blind exception: Exception

(BLE001)


396-401: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test-and-build
🔇 Additional comments (10)
app/config.py (1)

2-2: LGTM!

Optional 타입 임포트 추가 및 버전 업데이트가 정확합니다. 변경사항이 PR의 다른 파일들(source_ticker 파라미터 추가)과 일관성 있게 연동됩니다.

Also applies to: 44-44

app/services/news/chatgpt_client.py (2)

84-112: LGTM!

source_ticker 파라미터 추가와 조건부 프롬프트 생성 로직이 잘 구현되었습니다. 상관종목의 뉴스를 사용하는 경우와 직접 뉴스를 사용하는 경우를 명확히 구분하여 ChatGPT에 컨텍스트를 제공하는 것이 적절합니다.


115-123: LGTM!

generate_column 메서드에 source_ticker 파라미터가 올바르게 추가되고 프롬프트 생성에 전달됩니다.

app/services/news/news_column_service.py (2)

141-146: LGTM!

Pass 1에서 get_news_exact_match로 변경하여 정확히 일치하는 뉴스만 검색하도록 개선되었습니다. PR 목표에 부합합니다.


199-203: LGTM!

source_ticker 파라미터가 Pass 1과 Pass 2에서 일관되게 전달되고, Line 382에서 동일한 티커인 경우 None으로 설정하여 중복 정보를 방지하는 로직이 적절합니다.

Also applies to: 362-363, 382-382

app/services/external/massive_service.py (1)

305-402: LGTM!

get_news_exact_match 메서드가 잘 구현되었습니다. 주요 특징:

  • tickers[0]이 검색 종목과 정확히 일치하는 뉴스만 필터링
  • 필터링을 위해 3배 많은 결과를 가져온 후 제한 (Line 324)
  • 429 Rate Limit 에러 적절히 처리
  • 정확한 매칭 결과 로깅

PR의 핵심 기능이 올바르게 구현되었습니다.

app/routers/news.py (3)

53-85: LGTM!

전체 칼럼 삭제 엔드포인트가 잘 구현되었습니다. 관리자용 기능으로 적절하며, 에러 처리와 로깅이 포함되어 있습니다.


246-301: LGTM!

Massive API 뉴스 조회 엔드포인트가 잘 구현되었습니다. 정확한 매칭 기능을 테스트하기 위한 유용한 엔드포인트입니다. (단, 서비스 인스턴스 생성 방식은 별도 코멘트를 참조하세요)


386-393: LGTM!

칼럼 추천 플로우의 로깅이 개선되어 디버깅과 모니터링이 더욱 용이해졌습니다.

Also applies to: 415-419

app/services/news/news_db_repository.py (1)

157-166: 파싱 로직은 asyncpg 명세에 부합하며 정상 작동

검증 결과, db_service.execute()는 asyncpg의 conn.execute()를 직접 위임하며, DELETE 쿼리에 대해 "DELETE <n>" 형식의 문자열을 반환합니다. 예: "DELETE 5". 현재 코드의 result.split()[-1] 파싱은 이 형식에서 숫자를 올바르게 추출하고 있습니다.

결과가 없는 경우를 처리하는 조건부 검사(if result else 0)도 적절합니다. 코드는 asyncpg의 문서화된 동작과 일치하며 정상 작동합니다.

Comment thread app/routers/news.py
from pydantic import BaseModel
import structlog
import random
from datetime import datetime, timedelta

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

미사용 임포트 확인 필요

datetimetimedelta 임포트가 추가되었으나 현재 코드에서 사용되지 않는 것으로 보입니다.

다음 스크립트로 사용 여부를 확인해주세요:

사용되지 않는다면 임포트를 제거하세요.


🏁 Script executed:

#!/bin/bash
# Description: Check if datetime and timedelta are used in this file

# Search for datetime usage
rg -n "\bdatetime\b" app/routers/news.py

# Search for timedelta usage  
rg -n "\btimedelta\b" app/routers/news.py

Length of output: 85


미사용 임포트 제거 필요

검증 결과, datetimetimedelta는 파일 내에서 사용되지 않고 있습니다. 임포트 검색에서 line 7의 임포트 문만 반환되었으며, 다른 라인에서의 사용은 없습니다.

app/routers/news.py line 7에서 미사용 임포트를 제거하세요:

from datetime import datetime, timedelta
🤖 Prompt for AI Agents
In app/routers/news.py around line 7, the imports "from datetime import
datetime, timedelta" are unused; remove that import line to clean up dead code
and avoid linter warnings—simply delete the "from datetime import datetime,
timedelta" statement and run tests/lint to confirm no other references remain.

@goblurry
goblurry merged commit e15dcd1 into deploy Nov 16, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant