audience_data 범위 및 기타 처리수정
parent
8fe0512608
commit
c705ce40f8
|
|
@ -267,7 +267,7 @@ async def get_dashboard_stats(
|
|||
Dashboard.platform == "youtube",
|
||||
Dashboard.platform_user_id == social_account.platform_user_id,
|
||||
Dashboard.uploaded_at >= start_dt,
|
||||
Dashboard.uploaded_at <= end_dt,
|
||||
Dashboard.uploaded_at < today + timedelta(days=1),
|
||||
)
|
||||
)
|
||||
period_video_count = count_result.scalar() or 0
|
||||
|
|
@ -451,7 +451,7 @@ async def get_dashboard_stats(
|
|||
# 12. 업로드 영상 수 및 trend 주입 (캐시 저장 후 — 항상 DB에서 직접 집계)
|
||||
for metric in dashboard_data.content_metrics:
|
||||
if metric.id == "uploaded-videos":
|
||||
metric.value = str(period_video_count)
|
||||
metric.value = float(period_video_count)
|
||||
video_trend = float(period_video_count - prev_period_video_count)
|
||||
metric.trend = video_trend
|
||||
metric.trend_direction = "up" if video_trend > 0 else ("down" if video_trend < 0 else "-")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ YouTube Analytics 데이터 가공 프로세서
|
|||
YouTube Analytics API의 원본 데이터를 프론트엔드용 Pydantic 스키마로 변환합니다.
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Literal
|
||||
|
||||
|
|
@ -19,6 +20,46 @@ from app.utils.logger import get_logger
|
|||
|
||||
logger = get_logger("dashboard")
|
||||
|
||||
_COUNTRY_CODE_MAP: dict[str, str] = {
|
||||
"KR": "대한민국",
|
||||
"US": "미국",
|
||||
"JP": "일본",
|
||||
"CN": "중국",
|
||||
"GB": "영국",
|
||||
"DE": "독일",
|
||||
"FR": "프랑스",
|
||||
"CA": "캐나다",
|
||||
"AU": "호주",
|
||||
"IN": "인도",
|
||||
"ID": "인도네시아",
|
||||
"TH": "태국",
|
||||
"VN": "베트남",
|
||||
"PH": "필리핀",
|
||||
"MY": "말레이시아",
|
||||
"SG": "싱가포르",
|
||||
"TW": "대만",
|
||||
"HK": "홍콩",
|
||||
"BR": "브라질",
|
||||
"MX": "멕시코",
|
||||
"NL": "네덜란드",
|
||||
"BE": "벨기에",
|
||||
"SE": "스웨덴",
|
||||
"NO": "노르웨이",
|
||||
"FI": "핀란드",
|
||||
"DK": "덴마크",
|
||||
"IE": "아일랜드",
|
||||
"PL": "폴란드",
|
||||
"CZ": "체코",
|
||||
"RO": "루마니아",
|
||||
"HU": "헝가리",
|
||||
"SK": "슬로바키아",
|
||||
"SI": "슬로베니아",
|
||||
"HR": "크로아티아",
|
||||
"GR": "그리스",
|
||||
"PT": "포르투갈",
|
||||
"ES": "스페인",
|
||||
"IT": "이탈리아",
|
||||
}
|
||||
|
||||
class DataProcessor:
|
||||
"""YouTube Analytics 데이터 가공 프로세서
|
||||
|
|
@ -90,6 +131,7 @@ class DataProcessor:
|
|||
monthly_data = self._merge_monthly_data(
|
||||
raw_data.get("trend_recent", {}),
|
||||
raw_data.get("trend_previous", {}),
|
||||
end_date=end_date,
|
||||
)
|
||||
daily_data: list[DailyData] = []
|
||||
else: # mode == "day"
|
||||
|
|
@ -271,49 +313,49 @@ class DataProcessor:
|
|||
self,
|
||||
data_recent: dict[str, Any],
|
||||
data_previous: dict[str, Any],
|
||||
end_date: str = "",
|
||||
) -> list[MonthlyData]:
|
||||
"""최근 12개월과 이전 12개월의 월별 데이터를 병합
|
||||
|
||||
최근 12개월 대비 이전 12개월의 월별 조회수 비교 차트를 위한 데이터를 생성합니다.
|
||||
실제 API 응답의 월 데이터를 기준으로 매핑합니다.
|
||||
end_date 기준 12개월을 명시 생성하여 API가 반환하지 않은 월(당월 등)도 0으로 포함합니다.
|
||||
|
||||
Args:
|
||||
data_recent: 최근 12개월 월별 조회수 데이터
|
||||
rows = [["2026-01", 150000], ["2026-02", 180000], ...]
|
||||
data_previous: 이전 12개월 월별 조회수 데이터
|
||||
rows = [["2025-01", 120000], ["2025-02", 140000], ...]
|
||||
end_date: 기준 종료일 (YYYY-MM-DD). 미전달 시 오늘 사용
|
||||
|
||||
Returns:
|
||||
list[MonthlyData]: 월별 비교 데이터 (최대 12개)
|
||||
list[MonthlyData]: 월별 비교 데이터 (12개, API 미반환 월은 0)
|
||||
"""
|
||||
logger.debug("[DataProcessor._merge_monthly_data] START")
|
||||
|
||||
rows_recent = data_recent.get("rows", [])
|
||||
rows_previous = data_previous.get("rows", [])
|
||||
|
||||
# 월별 맵 생성: {"2025-02": 150000, "2025-03": 180000}
|
||||
map_recent = {row[0]: row[1] for row in rows_recent if len(row) >= 2}
|
||||
map_previous = {row[0]: row[1] for row in rows_previous if len(row) >= 2}
|
||||
|
||||
# 최근 기간의 월 키만 기준으로 정렬 (24개 합집합 방지)
|
||||
# 각 월의 이전 연도 키는 1년 전으로 계산: "2025-02" → "2024-02"
|
||||
recent_months = sorted(map_recent.keys())
|
||||
# end_date 기준 12개월 명시 생성 (API 미반환 당월도 0으로 포함)
|
||||
if end_date:
|
||||
end_dt = datetime.strptime(end_date, "%Y-%m-%d")
|
||||
else:
|
||||
end_dt = datetime.today()
|
||||
|
||||
# 월별 데이터 생성
|
||||
result = []
|
||||
for month_key in recent_months:
|
||||
year, month = month_key.split("-")
|
||||
month_num = int(month)
|
||||
month_label = f"{month_num}월"
|
||||
|
||||
# 이전 연도 동일 월: "2025-02" → "2024-02"
|
||||
prev_year_key = f"{int(year) - 1}-{month}"
|
||||
|
||||
for i in range(11, -1, -1):
|
||||
m = end_dt.month - i
|
||||
y = end_dt.year
|
||||
if m <= 0:
|
||||
m += 12
|
||||
y -= 1
|
||||
month_key = f"{y}-{m:02d}"
|
||||
result.append(
|
||||
MonthlyData(
|
||||
month=month_label,
|
||||
month=f"{m}월",
|
||||
this_year=map_recent.get(month_key, 0),
|
||||
last_year=map_previous.get(prev_year_key, 0),
|
||||
last_year=map_previous.get(f"{y - 1}-{m:02d}", 0),
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -437,9 +479,17 @@ class DataProcessor:
|
|||
if gender in gender_map_f:
|
||||
gender_map_f[gender] += viewer_pct
|
||||
|
||||
# 연령대 5개로 통합: 13-17+18-24 → 13-24, 55-64+65- → 55+
|
||||
merged_age: dict[str, float] = {
|
||||
"13-24": age_map.get("13-17", 0.0) + age_map.get("18-24", 0.0),
|
||||
"25-34": age_map.get("25-34", 0.0),
|
||||
"35-44": age_map.get("35-44", 0.0),
|
||||
"45-54": age_map.get("45-54", 0.0),
|
||||
"55+": age_map.get("55-64", 0.0) + age_map.get("65-", 0.0),
|
||||
}
|
||||
age_groups = [
|
||||
{"label": age, "percentage": int(round(pct))}
|
||||
for age, pct in sorted(age_map.items())
|
||||
for age, pct in merged_age.items()
|
||||
]
|
||||
gender_map = {k: int(round(v)) for k, v in gender_map_f.items()}
|
||||
|
||||
|
|
@ -447,15 +497,17 @@ class DataProcessor:
|
|||
geo_rows = geography_data.get("rows", [])
|
||||
total_geo_views = sum(row[1] for row in geo_rows if len(row) >= 2)
|
||||
|
||||
merged_geo: defaultdict[str, int] = defaultdict(int)
|
||||
for row in geo_rows:
|
||||
if len(row) >= 2:
|
||||
merged_geo[self._translate_country_code(row[0])] += row[1]
|
||||
|
||||
top_regions = [
|
||||
{
|
||||
"region": self._translate_country_code(row[0]),
|
||||
"percentage": int(
|
||||
(row[1] / total_geo_views * 100) if total_geo_views > 0 else 0
|
||||
),
|
||||
"region": region,
|
||||
"percentage": int((views / total_geo_views * 100) if total_geo_views > 0 else 0),
|
||||
}
|
||||
for row in geo_rows[:5] # 상위 5개
|
||||
if len(row) >= 2
|
||||
for region, views in sorted(merged_geo.items(), key=lambda x: x[1], reverse=True)[:5]
|
||||
]
|
||||
|
||||
logger.debug(
|
||||
|
|
@ -487,26 +539,4 @@ class DataProcessor:
|
|||
>>> _translate_country_code("US")
|
||||
"미국"
|
||||
"""
|
||||
country_map = {
|
||||
"KR": "대한민국",
|
||||
"US": "미국",
|
||||
"JP": "일본",
|
||||
"CN": "중국",
|
||||
"GB": "영국",
|
||||
"DE": "독일",
|
||||
"FR": "프랑스",
|
||||
"CA": "캐나다",
|
||||
"AU": "호주",
|
||||
"IN": "인도",
|
||||
"ID": "인도네시아",
|
||||
"TH": "태국",
|
||||
"VN": "베트남",
|
||||
"PH": "필리핀",
|
||||
"MY": "말레이시아",
|
||||
"SG": "싱가포르",
|
||||
"TW": "대만",
|
||||
"HK": "홍콩",
|
||||
"BR": "브라질",
|
||||
"MX": "멕시코",
|
||||
}
|
||||
return country_map.get(code, code)
|
||||
return _COUNTRY_CODE_MAP.get(code, "기타")
|
||||
|
|
|
|||
Loading…
Reference in New Issue