diff --git a/src/lib/transformReport.ts b/src/lib/transformReport.ts index 616d78f..6e090e5 100644 --- a/src/lib/transformReport.ts +++ b/src/lib/transformReport.ts @@ -333,24 +333,30 @@ function buildRoadmap(r: ApiReport): import('../types/report').RoadmapMonth[] { function buildKpiDashboard(r: ApiReport): import('../types/report').KPIMetric[] { // Always build comprehensive KPIs from channel data. - // AI-provided kpiTargets are merged in (fill gaps) but don't replace our metrics. + // Prefer real enrichment data over AI-guessed channelAnalysis. const channels = r.channelAnalysis || {}; + const enrichment = (r as Record).channelEnrichment as Record | undefined; const metrics: import('../types/report').KPIMetric[] = []; - // YouTube metrics - if (channels.youtube) { - const subs = channels.youtube.subscribers ?? 0; + // YouTube metrics — prefer enrichment (real API data) over AI guess + const ytEnrich = enrichment?.youtube as Record | undefined; + const ytSubs = (ytEnrich?.subscribers as number) || ((channels.youtube?.subscribers as number) ?? 0); + const ytViews = (ytEnrich?.totalViews as number) || 0; + const ytVideos = (ytEnrich?.totalVideos as number) || 0; + + if (channels.youtube || ytEnrich) { metrics.push({ metric: 'YouTube 구독자', - current: subs > 0 ? fmt(subs) : '-', - target3Month: subs > 0 ? fmt(Math.round(subs * 1.1)) : '115K', - target12Month: subs > 0 ? fmt(Math.round(subs * 2)) : '200K', + current: ytSubs > 0 ? fmt(ytSubs) : '-', + target3Month: ytSubs > 0 ? fmt(Math.round(ytSubs * 1.1)) : '10K', + target12Month: ytSubs > 0 ? fmt(Math.round(ytSubs * 2)) : '50K', }); + const monthlyViews = ytViews > 0 && ytVideos > 0 ? Math.round(ytViews / Math.max(ytVideos / 12, 1)) : 0; metrics.push({ metric: 'YouTube 월 조회수', - current: '~270K', - target3Month: '500K', - target12Month: '1.5M', + current: monthlyViews > 0 ? `~${fmt(monthlyViews)}` : '-', + target3Month: monthlyViews > 0 ? fmt(Math.round(monthlyViews * 2)) : '500K', + target12Month: monthlyViews > 0 ? fmt(Math.round(monthlyViews * 5)) : '1.5M', }); metrics.push({ metric: 'YouTube Shorts 평균 조회수', @@ -360,14 +366,18 @@ function buildKpiDashboard(r: ApiReport): import('../types/report').KPIMetric[] }); } - // Instagram metrics - if (channels.instagram) { - const followers = channels.instagram.followers ?? 0; + // Instagram metrics — prefer enrichment data + const igAccounts = (enrichment?.instagramAccounts as Record[]) || []; + const igPrimary = igAccounts[0] || (enrichment?.instagram as Record) || null; + const igSecondary = igAccounts[1] || null; + const igFollowers = (igPrimary?.followers as number) || ((channels.instagram?.followers as number) ?? 0); + + if (channels.instagram || igPrimary) { metrics.push({ metric: 'Instagram KR 팔로워', - current: followers > 0 ? fmt(followers) : '-', - target3Month: followers > 0 ? fmt(Math.round(followers * 1.4)) : '20K', - target12Month: followers > 0 ? fmt(Math.round(followers * 3.5)) : '50K', + current: igFollowers > 0 ? fmt(igFollowers) : '-', + target3Month: igFollowers > 0 ? fmt(Math.round(igFollowers * 1.4)) : '20K', + target12Month: igFollowers > 0 ? fmt(Math.round(igFollowers * 3.5)) : '50K', }); metrics.push({ metric: 'Instagram KR Reels 평균 조회수', @@ -375,12 +385,15 @@ function buildKpiDashboard(r: ApiReport): import('../types/report').KPIMetric[] target3Month: '3,000', target12Month: '10,000', }); - metrics.push({ - metric: 'Instagram EN 팔로워', - current: channels.instagram.followers ? fmt(Math.round(channels.instagram.followers * 4.5)) : '68.8K', - target3Month: '75K', - target12Month: '100K', - }); + if (igSecondary) { + const enFollowers = (igSecondary.followers as number) || 0; + metrics.push({ + metric: 'Instagram EN 팔로워', + current: enFollowers > 0 ? fmt(enFollowers) : '-', + target3Month: enFollowers > 0 ? fmt(Math.round(enFollowers * 1.1)) : '75K', + target12Month: enFollowers > 0 ? fmt(Math.round(enFollowers * 1.5)) : '100K', + }); + } } // Naver Blog @@ -393,22 +406,25 @@ function buildKpiDashboard(r: ApiReport): import('../types/report').KPIMetric[] }); } - // 강남언니 - if (channels.gangnamUnni) { - const rating = channels.gangnamUnni.rating ?? 0; - const correctedRating = typeof rating === 'number' && rating > 0 && rating <= 5 ? rating * 2 : rating; + // 강남언니 — prefer enrichment data + const guEnrich = enrichment?.gangnamUnni as Record | undefined; + const guRating = (guEnrich?.rating as number) || ((channels.gangnamUnni?.rating as number) ?? 0); + const guCorrected = typeof guRating === 'number' && guRating > 0 && guRating <= 5 ? guRating * 2 : guRating; + const guReviews = (guEnrich?.totalReviews as number) || ((channels.gangnamUnni?.reviews as number) ?? 0); + + if (channels.gangnamUnni || guEnrich) { metrics.push({ metric: '강남언니 평점', - current: correctedRating > 0 ? `${correctedRating}/10` : '-', - target3Month: correctedRating > 0 ? `${Math.min(correctedRating + 0.5, 10).toFixed(1)}/10` : '8.0/10', - target12Month: correctedRating > 0 ? `${Math.min(correctedRating + 1.0, 10).toFixed(1)}/10` : '9.0/10', + current: guCorrected > 0 ? `${guCorrected}/10` : '-', + target3Month: guCorrected > 0 ? `${Math.min(guCorrected + 0.5, 10).toFixed(1)}/10` : '8.0/10', + target12Month: guCorrected > 0 ? `${Math.min(guCorrected + 1.0, 10).toFixed(1)}/10` : '9.0/10', }); - if (channels.gangnamUnni.reviews) { + if (guReviews > 0) { metrics.push({ metric: '강남언니 리뷰 수', - current: fmt(channels.gangnamUnni.reviews as number), - target3Month: fmt(Math.round((channels.gangnamUnni.reviews as number) * 1.15)), - target12Month: fmt(Math.round((channels.gangnamUnni.reviews as number) * 1.5)), + current: fmt(guReviews), + target3Month: fmt(Math.round(guReviews * 1.15)), + target12Month: fmt(Math.round(guReviews * 1.5)), }); } }