🧩 BE

대시보드 수집 데이터 추가

Assigned To
Date
2026/02/26
Status
Done
Type
Feature
Server
DB
Table of contents

Issue Point

기능 도입 성과 (11/17 기준 전후 4주)

1.
부진 기사 메인 노출 시간 변화
2.
메인 기사 교체 빈도 변화
3.
운영 시간대 vs 비운영 시간대 메인 기사 성과 차이

디자인 적용 성과 (3월 초 쯤)

1.
부진기사 메인 노출시간 추가 감소 여부
2.
메인기사 초기 반응속도 변화

Task

API 설계

엔드포인트
→ GET /api/v2/articles/effectiveness?launch_date=2026-01-15&period_days=30
파라미터
타입
필수
설명
launch_date
string
Y
대시보드 도입일
period_days
int
N
비교 기간 일수
비교 구간
before: launch_date - period_days ~ launch_date (도입 전)
after: launch_date ~ launch_date + period_days (도입 후)
응답 구조
{ "launch_date": "2026-01-15", "period_days": 30, "before_period": { "start": "2025-12-16", "end": "2026-01-15" }, "after_period": { "start": "2026-01-15", "end": "2026-02-14" }, "underperforming_exposure": { "before": { "avg_exposure_minutes": 180.5, "median_exposure_minutes": 155.0, "article_count": 45 }, "after": { "avg_exposure_minutes": 120.3, "median_exposure_minutes": 98.0, "article_count": 52 }, "change_percent": -33.3 }, "replacement_frequency": { "before": { "avg_daily_replacements": 15.2, "total_articles": 456 }, "after": { "avg_daily_replacements": 22.8, "total_articles": 684 }, "change_percent": 50.0 }, "time_slot_comparison": { "operating": { "before": { "avg_exposure_minutes": 150.0, "avg_final_view_count": 5000, "article_count": 280 }, "after": { "avg_exposure_minutes": 100.0, "avg_final_view_count": 6500, "article_count": 310 } }, "non_operating": { "before": { "avg_exposure_minutes": 200.0, "avg_final_view_count": 3000, "article_count": 176 }, "after": { "avg_exposure_minutes": 195.0, "avg_final_view_count": 3200, "article_count": 174 } } } }
JSON
복사

지표 산출 로직

부진 기사 노출 시간 (underperforming_exposure)
기사별 최종 조회수 = MAX(view_count) from naver_main_articles_view_count
기사별 노출 시간 = updated_at - created_at from naver_main_articles
부진 기사 정의: 해당 기간 내 최종 조회수 하위 20% (PERCENT_RANK)
before/after 각각 하위 20% 기사의 평균 노출 시간 비교
메인 기사 교체 빈도 (replacement_frequency)
일별 신규 메인 기사 수 = COUNT(*) by DATE(created_at) from naver_main_articles
before / after 일 평균 비교
운영 / 비운영 시간대 비교 (time_slot_comparison)
운영 : HOUR(created_at) 9~17시
비운영 : 나머지 시간대
각 시간대별 평균 노출 시간 + 평균 최종 조회수 비교

파일 추가

__init__.py
router import
schema.py - 엔티티 정의
PeriodRange: start, end ExposureMetrics: avg_exposure_minutes, median_exposure_minutes, article_countUnderperformingExposure: before, after, change_percent ReplacementMetrics: avg_daily_replacements, total_articles ReplacementFrequency: before, after, change_percent TimeSlotMetrics: avg_exposure_minutes, avg_final_view_count, article_count TimeSlotPeriod: before, after TimeSlotComparison: operating, non_operating EffectivenessResponse: 전체 응답
Plain Text
복사
query.py - 도메인 로직
get_article_stats (db_session, start_date, end_date)
get_daily_article_counts (db_session, start_date, end_date)
get_time_slot_stats (db_session, start_date, end_date)
service.py - 비즈니스 로직
1.
before/after 기간 계산
2.
각 기간별로 3개 쿼리 함수 호출 (총 6회)
3.
부진 기사: article_stats에서 하위 20% 필터 → 평균 노출 시간
4.
교체 빈도: daily_counts 평균
5.
시간대: time_slot_stats 집계
6.
change_percent 계산: (after - before) / before * 100
7.
EffectivenessResponse 반환
router.py - 컨트롤러