Skip to content

Commit 7de3997

Browse files
Jihyun3478Nuung
andauthored
[25.07.11 / TASK-226] Hotfix - 사용자 트렌드 배치 LLM 분석 실패 이슈 & 좋아요 및 조회수 계산 이슈 대응 (#35)
* hotfix: 사용자 모두 summary 요약 실패로 반환되는 이슈 해결 * refactor: LLM 분석 결과 로깅 추가 * hotfix: 일주일 전 통계와의 차로 계산하도록 수정 * refactor: 린팅 적용 * refactor: 중복된 로직 제거 * feature: base class 추가와 기본 batch 형태 classed base 로 리뉴얼 1차 * feature: user 주간 분석 통계 분석과 토큰 만료 로직 보완 * feature: 주간 분석 배치 완료 * modify: type 강화 * feature: 주간 사용자 분석 배치 전체 리뉴얼 * modify: 메일 배치, 전체 데이터 활용 업데이트 * modify: 뉴스레터 데이터셋과 그에 따른 html 수정 * modify: prompt import 경로 상대로직 삭제 * modify: user filter pk 테스트용 원복 --------- Co-authored-by: Nuung <qlgks1@naver.com>
1 parent b5886a7 commit 7de3997

File tree

10 files changed

+947
-377
lines changed

10 files changed

+947
-377
lines changed

insight/models.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,29 @@ class TrendAnalysis(SerializableMixin):
3030
@dataclass
3131
class WeeklyTrendInsight(SerializableMixin):
3232
trending_summary: list[TrendingItem] = field(default_factory=list)
33-
trend_analysis: TrendAnalysis = None
34-
"""
35-
user trend인 경우 아래 필드가 필요합니다.
36-
(템플릿 의존성 존재, 현우님께서 작업하시면서 변경 가능)
37-
reminder: { // 해당하는 주에 작성한 글이 없는 경우
38-
title: str
39-
days_ago: int
40-
}
41-
user_weekly_stats: { // 토큰 정상인 모든 유저가 갖고 있는 필드
42-
posts: int
43-
views: int
44-
likes: int
45-
}
46-
"""
33+
trend_analysis: TrendAnalysis | None = None
34+
35+
36+
@dataclass
37+
class WeeklyUserStats(SerializableMixin):
38+
posts: int # 전체 게시글 수
39+
new_posts: int # 게시글 증가 수
40+
views: int # 조회수 증가 수
41+
likes: int # 좋아요 수 증가 수
42+
43+
44+
@dataclass
45+
class WeeklyUserReminder(SerializableMixin):
46+
"""해당하는 주에 작성한 글이 없는 경우"""
47+
48+
title: str # 마지막 작성한 글 title
49+
days_ago: int # 마지막 작성한 글 작성일자
50+
51+
52+
@dataclass
53+
class WeeklyUserTrendInsight(WeeklyTrendInsight):
54+
user_weekly_stats: WeeklyUserStats | None = None
55+
user_weekly_reminder: WeeklyUserReminder | None = None
4756

4857

4958
class WeeklyTrend(TimeStampedModel):
@@ -54,7 +63,7 @@ class WeeklyTrend(TimeStampedModel):
5463
week_start_date = models.DateField(verbose_name="주 시작일")
5564
week_end_date = models.DateField(verbose_name="주 종료일")
5665

57-
# 인사이트 데이터
66+
# 인사이트 데이터 - "WeeklyTrendInsight" 를 따라야 함
5867
insight = models.JSONField(
5968
verbose_name="핵심 키워드",
6069
help_text="주간 트렌드에 대한 핵심 키워드 및 인사이트 데이터, schema 변동에 유연하게 대응하기 위해 JSONField 사용",
@@ -96,7 +105,7 @@ class UserWeeklyTrend(TimeStampedModel):
96105
week_start_date = models.DateField(verbose_name="주 시작일")
97106
week_end_date = models.DateField(verbose_name="주 종료일")
98107

99-
# 인사이트 데이터
108+
# 인사이트 데이터 - "WeeklyUserTrendInsight" 를 따라야 함
100109
insight = models.JSONField(
101110
verbose_name="핵심 키워드",
102111
help_text="주간 트렌드에 대한 핵심 키워드 및 인사이트 데이터, schema 변동에 유연하게 대응하기 위해 JSONField 사용",

insight/tasks/base_analysis.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import logging
2+
from abc import ABC, abstractmethod
3+
from dataclasses import dataclass
4+
from datetime import datetime
5+
from typing import Any, Generic, TypeVar
6+
7+
import aiohttp
8+
9+
from scraping.velog.client import VelogClient
10+
from utils.utils import get_previous_week_range
11+
12+
T = TypeVar("T") # 분석 결과 타입
13+
14+
15+
@dataclass
16+
class AnalysisContext:
17+
"""분석 컨텍스트 정보"""
18+
19+
week_start: datetime
20+
week_end: datetime
21+
velog_client: VelogClient
22+
23+
24+
@dataclass
25+
class AnalysisResult(Generic[T]):
26+
"""분석 결과 래퍼"""
27+
28+
success: bool
29+
data: T | None = None
30+
error: Exception | None = None
31+
metadata: dict[str, Any] | None = None
32+
33+
34+
class BaseBatchAnalyzer(ABC, Generic[T]):
35+
"""배치 분석 작업의 기본 추상 클래스"""
36+
37+
def __init__(self):
38+
self.logger = logging.getLogger("newsletter")
39+
40+
async def run(self) -> AnalysisResult[list[T]]:
41+
"""메인 실행 메서드"""
42+
self.logger.info("Starting %s", self.__class__.__name__)
43+
44+
try:
45+
# 1. 컨텍스트 초기화
46+
context = await self._initialize_context()
47+
48+
# 2. 데이터 수집
49+
raw_data = await self._fetch_data(context)
50+
if not raw_data:
51+
self.logger.info("No data to process")
52+
return AnalysisResult(
53+
success=True, data=[], metadata={"reason": "no_data"}
54+
)
55+
56+
# 3. 분석 실행
57+
analysis_results = await self._analyze_data(raw_data, context)
58+
59+
# 4. 결과 저장
60+
await self._save_results(analysis_results, context)
61+
62+
self.logger.info(
63+
"Completed %s successfully", self.__class__.__name__
64+
)
65+
return AnalysisResult(
66+
success=True,
67+
data=analysis_results,
68+
metadata={"processed_count": len(analysis_results)},
69+
)
70+
71+
except Exception as e:
72+
self.logger.exception(
73+
"Failed to run %s: %s", self.__class__.__name__, e
74+
)
75+
return AnalysisResult(success=False, error=e)
76+
77+
async def _initialize_context(self) -> AnalysisContext:
78+
"""분석 컨텍스트 초기화"""
79+
week_start, week_end = get_previous_week_range()
80+
81+
session = aiohttp.ClientSession()
82+
velog_client = VelogClient.get_client(
83+
session=session,
84+
access_token="dummy_access_token",
85+
refresh_token="dummy_refresh_token",
86+
)
87+
88+
return AnalysisContext(
89+
week_start=week_start,
90+
week_end=week_end,
91+
velog_client=velog_client,
92+
)
93+
94+
@abstractmethod
95+
async def _fetch_data(self, context: AnalysisContext) -> list[Any]:
96+
"""데이터 수집 (구현 필요)"""
97+
pass
98+
99+
@abstractmethod
100+
async def _analyze_data(
101+
self, raw_data: list[Any], context: AnalysisContext
102+
) -> list[T]:
103+
"""데이터 분석 (구현 필요)"""
104+
pass
105+
106+
@abstractmethod
107+
async def _save_results(
108+
self, results: list[T], context: AnalysisContext
109+
) -> None:
110+
"""결과 저장 (구현 필요)"""
111+
pass

insight/tasks/user_weekly_trend_analysis.py

Lines changed: 0 additions & 191 deletions
This file was deleted.

0 commit comments

Comments
 (0)