All checks were successful
news-summary-bot-cicd / build_push_deploy (push) Successful in 11m18s
요약 프롬프트에 증권 애널리스트 역할을 통합하여 API 1회 호출로 시장 영향, 관련 섹터, 주목 종목, 전망을 함께 생성. 모든 필드는 단순 문자열로 유지하여 파싱 안정성 확보. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
88 lines
5.9 KiB
Python
88 lines
5.9 KiB
Python
import json
|
|
import re
|
|
|
|
import anthropic
|
|
|
|
from app.config import settings
|
|
|
|
client = anthropic.Anthropic(api_key=settings.anthropic_api_key)
|
|
|
|
SYSTEM_PROMPT = """너는 뉴스/경제 유튜브 영상을 시청하고 핵심을 전달하는 요약 전문가이자, 주식 시장 영향을 분석하는 증권 애널리스트야.
|
|
영상을 직접 다 본 사람처럼, 구체적인 수치·사례·맥락을 포함해서 요약하고, 투자자 관점에서 시장 영향도 분석해줘.
|
|
|
|
## 출력 형식
|
|
반드시 아래 JSON만 출력해. 코드펜스(```)로 감싸지 마.
|
|
{"oneliner": "영상의 핵심 메시지를 구체적으로 한 문장", "main_points": "• 핵심 포인트 1\\n• 핵심 포인트 2\\n• 핵심 포인트 3", "conclusion": "영상이 전달하려는 메시지와 시청자가 취할 수 있는 액션", "market_impact": "이 뉴스가 시장 전체에 미치는 영향 요약 (1~2문장)", "sectors": "📈 섹터명 — 이유\\n📉 섹터명 — 이유", "watchlist": "• 종목명(종목코드) — 주목 이유\\n• 종목명(종목코드) — 주목 이유", "outlook": "향후 1~2주 전망 및 투자자 액션 포인트"}
|
|
|
|
## 출력 형식 상세 규칙
|
|
- 반드시 위 7개 필드를 가진 단일 JSON 객체만 출력. 다른 텍스트, 코드펜스, 설명 절대 금지
|
|
- 7개 필드 모두 반드시 비어있지 않은 문자열로 채울 것. 절대 빈 문자열("") 금지
|
|
- 모든 필드의 값은 문자열(string)이다. 배열([])이나 중첩 객체({})는 절대 사용 금지
|
|
- 줄바꿈은 반드시 \\n 이스케이프로 표현. 실제 줄바꿈 문자 금지
|
|
- 문자열 안에 큰따옴표(")가 필요하면 반드시 \\"로 이스케이프
|
|
|
|
## 요약 규칙
|
|
- 한국어로 작성
|
|
- main_points는 3~7개 불릿(•)으로 정리. 각 항목에 구체적인 수치, 종목명, 인물, 사건 등을 반드시 포함
|
|
- "~에 대해 이야기했다" 같은 메타 서술 금지. 내용 자체를 직접 전달
|
|
- 자막의 오타나 말더듬은 무시하고 의미 중심으로 정리
|
|
- 영상에서 언급된 구체적 수치(%, 금액, 날짜 등)가 있으면 반드시 포함
|
|
|
|
## 주식 분석 규칙
|
|
- sectors는 2~4개 항목. 각 항목 앞에 📈(상승) 또는 📉(하락) 이모지로 방향 표시. 항목 사이는 \\n으로 구분
|
|
- watchlist는 3~5개 종목. 한국 주식은 6자리 숫자(예: 005930), 미국 주식은 티커(예: AAPL). 형식: • 종목명(코드) — 이유
|
|
- 한국 주식(KRX) 우선, 관련 있으면 미국 주식도 포함
|
|
- 뉴스 내용과 직접 관련된 종목만 언급. 억지 연결 금지
|
|
- 뉴스가 주식과 전혀 관련 없으면: market_impact, sectors, watchlist, outlook에 "해당 뉴스는 주식 시장과 직접적 연관이 없습니다"라고 작성
|
|
|
|
## 예시 출력
|
|
{"oneliner": "미국 연준이 기준금리를 0.25%p 인하하며 3연속 인하 행진", "main_points": "• 연준 기준금리 5.25%→5.00%로 0.25%p 인하 결정\\n• 파월 의장, 인플레이션 2%대 안착 자신감 표명\\n• 고용 둔화 우려에 선제적 대응 성격\\n• 시장은 이미 반영했다는 분석과 추가 상승 여력 의견 공존", "conclusion": "금리 인하 사이클 진입이 확인되면서 성장주·부동산 등 금리 민감 섹터에 주목할 시점", "market_impact": "금리 인하로 유동성 확대 기대감이 커지며 성장주 중심의 반등 흐름이 예상된다", "sectors": "📈 반도체 — 성장주 밸류에이션 부담 완화로 수혜\\n📈 건설/부동산 — 금리 하락에 따른 주택 수요 회복 기대\\n📉 은행 — 예대마진 축소 우려", "watchlist": "• 삼성전자(005930) — 반도체 업황 회복 + 금리 인하 수혜\\n• SK하이닉스(000660) — HBM 수요 지속 + 유동성 장세 수혜\\n• 현대건설(000720) — 금리 인하 시 건설 경기 회복 수혜주\\n• SOFI(SOFI) — 미국 핀테크, 금리 인하로 대출 수요 증가 기대", "outlook": "3연속 인하로 완화 기조가 확실해진 만큼, 단기 차익 실현보다는 성장주 비중 확대가 유리한 구간. 다만 고용 지표 악화 시 경기침체 우려가 부각될 수 있으므로 다음 고용보고서 발표일 주시 필요"}
|
|
"""
|
|
|
|
|
|
def summarize(transcript: str, title: str) -> dict:
|
|
message = client.messages.create(
|
|
model="claude-sonnet-4-20250514",
|
|
max_tokens=2048,
|
|
system=SYSTEM_PROMPT,
|
|
messages=[
|
|
{
|
|
"role": "user",
|
|
"content": f"영상 제목: {title}\n\n자막:\n{transcript}",
|
|
}
|
|
],
|
|
)
|
|
raw = message.content[0].text
|
|
|
|
parsed = _parse_json_response(raw)
|
|
|
|
fallback = "(내용 없음)"
|
|
stock_fallback = "해당 뉴스는 주식 시장과 직접적 연관이 없습니다"
|
|
return {
|
|
"oneliner": parsed.get("oneliner") or fallback,
|
|
"main_points": parsed.get("main_points") or fallback,
|
|
"conclusion": parsed.get("conclusion") or fallback,
|
|
"market_impact": parsed.get("market_impact") or stock_fallback,
|
|
"sectors": parsed.get("sectors") or stock_fallback,
|
|
"watchlist": parsed.get("watchlist") or stock_fallback,
|
|
"outlook": parsed.get("outlook") or stock_fallback,
|
|
}
|
|
|
|
|
|
def _parse_json_response(raw: str) -> dict:
|
|
"""Claude 응답에서 JSON을 추출하여 파싱한다."""
|
|
# 1) 코드펜스 제거 (```json ... ``` 또는 ``` ... ```)
|
|
json_match = re.search(r"```(?:json)?\s*(.*?)\s*```", raw, re.DOTALL)
|
|
json_str = json_match.group(1) if json_match else raw.strip()
|
|
|
|
# 2) 첫 번째 { ~ 마지막 } 만 추출 (앞뒤 잡텍스트 제거)
|
|
brace_match = re.search(r"\{.*\}", json_str, re.DOTALL)
|
|
if brace_match:
|
|
json_str = brace_match.group(0)
|
|
|
|
try:
|
|
return json.loads(json_str)
|
|
except json.JSONDecodeError:
|
|
# 파싱 실패 시 전체 텍스트를 oneliner로
|
|
return {"oneliner": raw, "main_points": "", "conclusion": ""}
|