"""mockup 7개 역분석 — 채널 규모별 3개월/12개월 target 성장률 공식.""" from integrations.llm.schemas.report import KPIMetric def _round_clean(n: int) -> int: if n < 100: return n if n < 1000: return round(n / 100) * 100 if n < 10_000: return round(n / 500) * 500 if n < 100_000: return round(n / 1000) * 1000 if n < 1_000_000: return round(n / 5000) * 5000 return round(n / 50_000) * 50_000 def _target_multiplier(current: int) -> tuple[float, float]: if current < 1_000: return (2.5, 9.0) if current < 5_000: return (1.7, 4.0) if current < 25_000: return (1.5, 2.5) if current < 50_000: return (1.3, 2.2) return (1.1, 1.9) def _follower_kpi(metric: str, val: int | None, unit: str = "명") -> dict | None: if not val: return None m3, m12 = _target_multiplier(val) return { "metric": metric, "current": f"{val:,}{unit}", "target_3_month": f"{_round_clean(int(val * m3)):,}{unit}", "target_12_month": f"{_round_clean(int(val * m12)):,}{unit}", } def _blog_frequency(posts: list) -> tuple[str, str, str] | None: """RSS posts timestamp로 (current, target_3m, target_12m) 라벨 반환. target은 절대 downgrade 안 함.""" from common.utils import parse_ts dts = sorted((d for d in (parse_ts(p.get("postDate")) for p in posts) if d), reverse=True) if len(dts) < 2: return None avg_gap = (dts[0] - dts[-1]).days / (len(dts) - 1) if avg_gap > 90: current = f"방치 ({dts[0].strftime('%Y-%m')})" elif avg_gap <= 1: current = f"주 {7 // max(int(avg_gap), 1)}회" elif avg_gap <= 3: current = "주 2~3회" elif avg_gap <= 14: current = "주 1~2회" elif avg_gap <= 30: current = f"월 {max(30 // int(avg_gap), 1)}회" else: current = "월 1회 미만" if avg_gap > 3: return current, "주 2회", "주 3회" if avg_gap > 2: return current, "주 3회", "주 5회" if avg_gap > 1: return current, "주 5회", "주 7회" return current, f"{current} 유지", f"{current} 유지" def build_kpi_dashboard( instagram: dict, facebook: dict, youtube: dict, gangnam_unni: dict, hospital: dict, naver_blog: dict | None = None, ) -> list[dict]: ig_en = hospital.get("instagramEn") or {} fb_en = hospital.get("facebookEn") or {} tiktok = hospital.get("tiktok") or {} cafe = hospital.get("naverCafe") or {} kpis: list[dict] = [] for k in [ _follower_kpi("YouTube 구독자", youtube.get("subscribers")), _follower_kpi("Instagram KR 팔로워", instagram.get("followers")), _follower_kpi("Instagram EN 팔로워", ig_en.get("followers")), _follower_kpi("Facebook KR 팔로워", facebook.get("followers")), _follower_kpi("Facebook EN 팔로워", fb_en.get("followers")), _follower_kpi("TikTok 팔로워", tiktok.get("followers")), _follower_kpi("Naver Cafe 회원 수", cafe.get("memberCount")), ]: if k: kpis.append(k) if naver_blog: freq = _blog_frequency(naver_blog.get("posts") or []) if freq: cur, t3, t12 = freq kpis.append({ "metric": "네이버 블로그 포스팅 빈도", "current": cur, "target_3_month": t3, "target_12_month": t12, }) gu_reviews = gangnam_unni.get("totalReviews") if gu_reviews: if gu_reviews < 1000: rm3, rm12 = 2.0, 6.0 elif gu_reviews < 5000: rm3, rm12 = 1.10, 1.50 else: rm3, rm12 = 1.07, 1.27 kpis.append({ "metric": "강남언니 리뷰", "current": f"{gu_reviews:,}개", "target_3_month": f"{_round_clean(int(gu_reviews * rm3)):,}개", "target_12_month": f"{_round_clean(int(gu_reviews * rm12)):,}개", }) return [KPIMetric.model_validate(k).model_dump() for k in kpis]