DOCS/troubleshooting/251014_coldmail_ir_analysis_scenario.md
happybell80 ba38062b6a docs: LangGraph 워크플로우 코드 원칙 준수 (파일 분리, 위치 수정)
- app/workflows/ → app/services/workflows/ (311 FastAPI 구조 원칙)
- coldmail_email_fetcher.py 생성 (fetch 로직 분리)
- coldmail_processor.py 생성 (process 로직 분리)
- 노드에서 기존 services 함수 재사용 (DRY 원칙)
- coldmail_briefing.py 최종 206줄 (300줄 제한 준수)
- 98줄 유지

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 22:38:37 +09:00

5.2 KiB

콜드메일 IR 자동 분석 시나리오

날짜: 2025-10-14 작성자: happybell80 관련 파일: rb8001/app/scheduler/jobs/coldmail_briefing.py


최종 목표 시나리오

평일 오전 9시 5분 자동 실행

  1. NAVER WORKS 메일 중 콜드메일 감지 (Naive Bayes)
  2. IR 첨부파일(PDF) 다운로드
  3. skill-rag-file에 업로드 → 51123 HDD 영구 저장
  4. AI 분석 실행:
    • 페이지별 요약 (RAG 기반 핵심 내용 추출)
    • 기업 평가 (사업분야, 재무, 기술 우위)
    • 베이지안 밸류에이션 및 신뢰도 계산
  5. Slack Lists에 정리된 보고서 첨부:
    • 회사명, 이메일, IR 파일
    • 밸류에이션, 신뢰도, 핵심 평가
  6. Slack 채널에 요약 메시지 + 피드백 버튼

구현 완료 (2025-10-14)

  • coldmail_briefing.py:78-302: 전체 플로우
  • naverworks_file_processor.py:94-135: PDF → skill-rag-file 업로드
  • ir_analyzer.py:86-168: RAG 6회 쿼리 → LLM 요약
  • startup_valuation.py:63-173: 베이지안 VC Method
  • coldmail_briefing.py:191-221,241,246: Slack Lists 파일 첨부
  • coldmail_briefing.py:262-285: Slack 피드백 버튼
  • coldmail_filter.py:29-76: Naive Bayes 학습

분석 결과 예시

굿베이션 (IT 인테리어 플랫폼): 27페이지 → 14 chunks, 밸류에이션 100억원 (30-300억원), 신뢰도 90%


구현 계획

우선순위 1: Slack Lists 파일 첨부 ( 완료)

  • 상세: 251014_slack_lists_file_attachment.md
  • skill-rag-file/app/api/download.py: GET /api/download/{document_id}
  • skill-slack/app/api/endpoints/files.py: POST /files/upload (X-API-Key)
  • coldmail_briefing.py:191-221, 241, 246: document_id → file_id 변환

우선순위 2: LLM JSON 파싱 에러

  • ir_analyzer.py:155 다음 (156 전): llm_response 전처리
    • cleaned = llm_response.strip()
    • if cleaned.startswith("```json"): cleaned = cleaned[7:]
    • if cleaned.startswith("```"): cleaned = cleaned[3:]
    • if cleaned.endswith("```"): cleaned = cleaned[:-3]
    • cleaned = cleaned.strip()
  • ir_analyzer.py:156: parsed = json.loads(cleaned)

우선순위 3: Slack 메시지에 IR 분석 결과 추가

  • coldmail_briefing.py:144 다음: processed_results = [] 추가
  • coldmail_briefing.py:188 다음: processed_results.append({...}) 추가
    • company_name, ir_metrics.get("revenue"), valuation_result.median, valuation_result.confidence 포함
  • coldmail_briefing.py:291 교체:
    • 기존: f"콜드메일 {processed_count}건 처리 완료 (...)"
    • 변경: summary_lines = [f"콜드메일 {processed_count}건 처리 완료"]
    • for item in processed_results: summary_lines.append(f"- {item['company']}: {item['median']}억원 (신뢰도 {int(item['confidence']*100)}%)")
    • summary_text = "\n".join(summary_lines)

우선순위 4: LangGraph 워크플로우

  • requirements.txt: langgraph 추가
  • app/services/coldmail_email_fetcher.py 생성 (coldmail_briefing.py:93-121 분리, 300줄 제한 준수)
    • async def fetch_emails(user_id, start_time, now): skill-email API 호출, return emails
  • app/services/coldmail_processor.py 생성 (coldmail_briefing.py:145-202 분리)
    • async def process_coldmail(email, user_id): 첨부파일 처리, IR 분석, Slack Lists 생성, return result or None
  • app/services/workflows/init.py: 빈 파일 생성
  • app/services/workflows/coldmail_workflow.py 생성:
    • from typing import TypedDict; from langgraph.graph import StateGraph, END; from app.services import coldmail_email_fetcher, coldmail_processor, coldmail_hybrid_filter
    • class ColdmailState(TypedDict): emails: list, coldmail_candidates: list, processed_results: list, user_id: str, start_time: str, now: str
    • async def fetch_emails_node(state): emails = await coldmail_email_fetcher.fetch_emails(state["user_id"], state["start_time"], state["now"]), return {"emails": emails}
    • async def filter_coldmail_node(state): candidates = [], for email in state["emails"]: result = await coldmail_hybrid_filter.hybrid_coldmail_filter(...), if is_coldmail: candidates.append(email), return {"coldmail_candidates": candidates}
    • async def process_email_node(state): results = [], for email in state["coldmail_candidates"]: result = await coldmail_processor.process_coldmail(email, state["user_id"]), if result: results.append(result), return {"processed_results": results}
    • async def send_summary_node(state): start_time/now = datetime.fromisoformat(...), async with aiohttp.ClientSession() as session, summary 전송
    • def route_after_filter(state): return "process" if state["coldmail_candidates"] else END
    • graph = StateGraph(ColdmailState); graph.add_node/add_edge/add_conditional_edges
    • workflow = graph.compile()
  • coldmail_briefing.py:78-302 교체 (최종 206줄)
    • coldmail_briefing.py:86-101 유지 (now, start_time, user_id 계산)
    • from app.services.workflows.coldmail_workflow import workflow
    • result = await workflow.ainvoke({"emails": [], "coldmail_candidates": [], "processed_results": [], "user_id": user_id, "start_time": start_time.isoformat(), "now": now.isoformat()})

교훈

  • 전체 시나리오 문서 먼저 작성 후 세부 구현
  • LLM 응답은 항상 전처리 (마크다운 블록 제거)