Compare commits

...

4 Commits

8 changed files with 92 additions and 44 deletions

View File

@ -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 <= kpi_end_dt,
Dashboard.uploaded_at <= end_dt,
)
)
period_video_count = count_result.scalar() or 0

View File

@ -418,8 +418,8 @@ class DataProcessor:
# === 연령/성별 데이터 처리 ===
demo_rows = demographics_data.get("rows", [])
age_map: dict[str, int] = {}
gender_map = {"male": 0, "female": 0}
age_map: dict[str, float] = {}
gender_map_f: dict[str, float] = {"male": 0.0, "female": 0.0}
for row in demo_rows:
if len(row) < 3:
@ -427,27 +427,21 @@ class DataProcessor:
age_group = row[0] # "age18-24"
gender = row[1] # "male" or "female"
views = row[2]
viewer_pct = row[2] # viewerPercentage (이미 % 값, 예: 45.5)
# 연령대별 집계 (age18-24 → 18-24)
# 연령대별 집계: 남녀 비율 합산 (age18-24 → 18-24)
age_label = age_group.replace("age", "")
age_map[age_label] = age_map.get(age_label, 0) + views
age_map[age_label] = age_map.get(age_label, 0.0) + viewer_pct
# 성별 집계
if gender in gender_map:
gender_map[gender] += views
if gender in gender_map_f:
gender_map_f[gender] += viewer_pct
# 연령대별 비율 계산
total_demo_views = sum(age_map.values())
age_groups = [
{
"label": age,
"percentage": int(
(count / total_demo_views * 100) if total_demo_views > 0 else 0
),
}
for age, count in sorted(age_map.items())
{"label": age, "percentage": int(round(pct))}
for age, pct in sorted(age_map.items())
]
gender_map = {k: int(round(v)) for k, v in gender_map_f.items()}
# === 지역 데이터 처리 ===
geo_rows = geography_data.get("rows", [])

View File

@ -124,7 +124,7 @@ class YouTubeAnalyticsService:
self._fetch_monthly_data(video_ids, previous_start, previous_end, access_token),
self._fetch_top_videos(video_ids, start_date, _kpi_end, access_token),
self._fetch_demographics(start_date, _kpi_end, access_token),
self._fetch_region(video_ids, start_date, _kpi_end, access_token),
self._fetch_region(start_date, _kpi_end, access_token),
]
else: # mode == "day"
tasks = [
@ -134,7 +134,7 @@ class YouTubeAnalyticsService:
self._fetch_daily_data(video_ids, day_previous_start, day_previous_end, access_token),
self._fetch_top_videos(video_ids, start_date, end_date, access_token),
self._fetch_demographics(start_date, end_date, access_token),
self._fetch_region(video_ids, start_date, end_date, access_token),
self._fetch_region(start_date, end_date, access_token),
]
# 병렬 실행
@ -206,8 +206,6 @@ class YouTubeAnalyticsService:
estimatedMinutesWatched, averageViewDuration,
subscribersGained]
Note:
annotationClickThroughRate는 2019 annotations 기능 제거로 deprecated.
"""
logger.debug(
f"[YouTubeAnalyticsService._fetch_kpi] START - video_count={len(video_ids)}"
@ -218,7 +216,7 @@ class YouTubeAnalyticsService:
"startDate": start_date,
"endDate": end_date,
"metrics": "views,likes,comments,shares,estimatedMinutesWatched,averageViewDuration,subscribersGained",
# "filters": f"video=={','.join(video_ids)}",
"filters": f"video=={','.join(video_ids)}",
}
result = await self._call_api(params, access_token)
@ -393,7 +391,6 @@ class YouTubeAnalyticsService:
async def _fetch_region(
self,
video_ids: list[str],
start_date: str,
end_date: str,
access_token: str,
@ -403,7 +400,6 @@ class YouTubeAnalyticsService:
지역별 조회수 분포를 조회합니다 (상위 5).
Args:
video_ids: YouTube 영상 ID 리스트
start_date: 조회 시작일 (YYYY-MM-DD)
end_date: 조회 종료일 (YYYY-MM-DD)
access_token: OAuth 2.0 액세스 토큰
@ -421,9 +417,7 @@ class YouTubeAnalyticsService:
"endDate": end_date,
"dimensions": "country",
"metrics": "views",
"filters": f"video=={','.join(video_ids)}",
"sort": "-views",
"maxResults": "5",
}
result = await self._call_api(params, access_token)

View File

@ -95,7 +95,7 @@ async def make_youtube_seo_description(
"language" : project.language,
"target_keywords" : hashtags
}
chatgpt = ChatgptService()
chatgpt = ChatgptService(timeout = 180)
yt_seo_output = await chatgpt.generate_structured_output(yt_upload_prompt, yt_seo_input_data)
result_dict = {
"title" : yt_seo_output.title,

View File

@ -43,6 +43,7 @@ class ChatgptService:
text_format=output_format
)
# Response 디버그 로깅
logger.debug(f"[ChatgptService] attempt: {attempt}")
logger.debug(f"[ChatgptService] Response ID: {response.id}")
logger.debug(f"[ChatgptService] Response status: {response.status}")
logger.debug(f"[ChatgptService] Response model: {response.model}")

View File

@ -550,6 +550,7 @@ async def get_video_status(
}
message = status_messages.get(status, f"상태: {status}")
video_id = None
# succeeded 상태인 경우 백그라운드 태스크 실행
if status == "succeeded" and video_url:
# creatomate_render_id로 Video 조회하여 task_id 가져오기
@ -561,6 +562,8 @@ async def get_video_status(
)
video = video_result.scalar_one_or_none()
video_id = video.id
if video and video.status != "completed":
# 이미 완료된 경우 백그라운드 작업 중복 실행 방지
# 백그라운드 태스크로 MP4 다운로드 → Blob 업로드 → DB 업데이트 → 임시 파일 삭제
@ -584,6 +587,7 @@ async def get_video_status(
status=status,
url=video_url,
snapshot_url=result.get("snapshot_url"),
video_id = video_id if video_id else None
)
logger.info(

View File

@ -49,6 +49,7 @@ class VideoRenderData(BaseModel):
status: Optional[str] = Field(None, description="렌더 상태")
url: Optional[str] = Field(None, description="영상 URL")
snapshot_url: Optional[str] = Field(None, description="스냅샷 URL")
video_id: Optional[int] = Field(None, description="Video id(DB)")
class PollingVideoResponse(BaseModel):

View File

@ -70,7 +70,7 @@ API 요청 시 `dimensions` 파라미터에 들어갈 수 있는 값들입니다
| -------------- | ------- | --------------------------- | -------------- |
| `**ageGroup`** | **연령대** | 시청자 연령 분포 (18-24, 25-34...) | `video`와 혼용 불가 |
| `**gender`** | **성별** | 남녀 성비 (male, female) | `video`와 혼용 불가 |
| `**country`** | **국가** | 국가별 시청자 수 (KR, US...) | 지도 차트용 |
| `**country`** | **국가** | 국가별 시청자 수 (KR, US...) | 지도 차트용, 채널 전체 기준 |
### C. 유입 및 기기 (Traffic Device)
@ -84,22 +84,76 @@ API 요청 시 `dimensions` 파라미터에 들어갈 수 있는 값들입니다
---
## 3. 자주 쓰는 조합 (Best Practice)
## 3. 현재 사용 중인 API 호출 조합
대시보드에서 실제로 사용하는 7가지 호출 조합입니다. 모두 `ids=channel==MINE`으로 고정합니다.
### 1. KPI 요약 (`_fetch_kpi`) — 현재/이전 기간 각 1회
| 파라미터 | 값 |
| ---------- | ------------------------------------------------------------------------------------- |
| dimensions | (없음) |
| metrics | `views, likes, comments, shares, estimatedMinutesWatched, averageViewDuration, subscribersGained` |
| filters | `video==ID1,ID2,...` (업로드된 영상 ID 최대 30개) |
> 현재/이전 기간을 각각 호출하여 trend(증감률) 계산에 사용.
---
### 2. 월별 추이 차트 (`_fetch_monthly_data`) — 최근 12개월 / 이전 12개월 각 1회
| 파라미터 | 값 |
| ---------- | -------------------- |
| dimensions | `month` |
| metrics | `views` |
| filters | `video==ID1,ID2,...` |
| sort | `month` |
---
### 3. 일별 추이 차트 (`_fetch_daily_data`) — 최근 30일 / 이전 30일 각 1회
| 파라미터 | 값 |
| ---------- | -------------------- |
| dimensions | `day` |
| metrics | `views` |
| filters | `video==ID1,ID2,...` |
| sort | `day` |
---
### 4. 인기 영상 TOP 4 (`_fetch_top_videos`)
| 파라미터 | 값 |
| ---------- | ------------------------ |
| dimensions | `video` |
| metrics | `views, likes, comments` |
| filters | `video==ID1,ID2,...` |
| sort | `-views` |
| maxResults | `4` |
---
### 5. 시청자 연령/성별 분포 (`_fetch_demographics`) — 채널 전체 기준
| 파라미터 | 값 |
| ---------- | ----------------------- |
| dimensions | `ageGroup, gender` |
| metrics | `viewerPercentage` |
> `ageGroup`, `gender` 차원은 `video` 필터와 혼용 불가 → 채널 전체 시청자 기준.
---
### 6. 지역별 조회수 TOP 5 (`_fetch_region`) — 채널 전체 기준
| 파라미터 | 값 |
| ---------- | -------------------- |
| dimensions | `country` |
| metrics | `views` |
| sort | `-views` |
| maxResults | `5` |
1. **프로젝트 전체 요약 (KPI)**
- `dimensions`: (없음)
- `metrics`: `views,likes,estimatedRevenue`
- `filters`: `video==ID1,ID2...`
2. **일별 성장 그래프 (Line Chart)**
- `dimensions`: `day`
- `metrics`: `views`
- `filters`: `video==ID1,ID2...`
- `sort`: `day`
3. **인기 영상 랭킹 (Table)**
- `dimensions`: `video`
- `metrics`: `views,averageViewDuration`
- `filters`: `video==ID1,ID2...`
- `sort`: `-views`
---