import { DEFAULT_VIRAL_THRESHOLD } from './types.js'; import type { CommentSnapshot, DailyStat, ViralConfig } from './types.js'; export type { ViralConfig } from './types.js'; /** * '떡상(Viral)' 여부 판별 함수: * 업로드 후 특정 시간(windowHours) 내에 설정된 조회수나 댓글 수에 도달했는지 확인합니다. * 이 함수는 순수 함수로, 데이터만 넣으면 결과를 반환합니다. */ export function isViral( uploadedAtIso: string, viewCount: number | null, commentCount: number, nowIso: string, cfg: ViralConfig, ): boolean { const ageHours = (new Date(nowIso).getTime() - new Date(uploadedAtIso).getTime()) / 3_600_000; // 설정된 분석 기간을 넘었으면 떡상이 아님 if (ageHours > cfg.windowHours) return false; // 조회수 기준 또는 댓글 수 기준을 만족하면 떡상으로 간주 return (viewCount ?? 0) >= cfg.minViews || commentCount >= cfg.minComments; } /** * '급상승(Spiking)' 여부 판별: * '떡상'이 전체 누적 데이터 기반이라면, 이 함수는 '최근 며칠간 얼마나 댓글이 늘었나'를 봅니다. * 실제 데이터가 실시간으로 급격히 튀어 오르는지를 감지하는 신호입니다. */ export function isSpiking(growth: number, threshold: number = DEFAULT_VIRAL_THRESHOLD): boolean { return growth >= threshold; } /** 특정 날짜(ISO 형식)에서 N일 전 날짜를 계산하는 유틸리티 함수입니다. */ function isoDateMinusDays(date: string, days: number): string { const d = new Date(`${date}T00:00:00Z`); d.setUTCDate(d.getUTCDate() - days); return d.toISOString().slice(0, 10); } /** * 댓글 성장률 계산 함수: * 마지막 `windowDays` 기간 동안 댓글이 얼마나 늘었는지 계산합니다. * 오래된 데이터가 없으면 가능한 한 가장 빠른 시점의 데이터를 기준으로 삼습니다. */ export function commentGrowth(stats: CommentSnapshot[], windowDays: number, asOfDate?: string): number { if (stats.length === 0) return 0; // 날짜순으로 정렬 const sorted = [...stats].sort((a, b) => a.date.localeCompare(b.date)); const latest = sorted[sorted.length - 1]!; // 기준 날짜로부터 N일 전까지의 구간 설정 const cutoff = isoDateMinusDays(asOfDate ?? latest.date, windowDays); let baseline = sorted[0]!; for (const s of sorted) { // 구간 안의 데이터 중 가장 마지막 기준점을 찾음 if (s.date <= cutoff) baseline = s; else break; } // (현재 - 기준) = 순수 성장량 return latest.commentCount - baseline.commentCount; }