fix: KPI uses real enrichment data instead of AI guesses
buildKpiDashboard now reads channelEnrichment (real API data from Phase 2) with fallback to channelAnalysis (AI-generated). YouTube subscribers, Instagram followers, 강남언니 rating/reviews all use verified data when available. Fixed || ?? operator precedence. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>claude/bold-hawking
parent
ff82c9f9d5
commit
9d06272073
|
|
@ -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<string, unknown>).channelEnrichment as Record<string, unknown> | 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<string, unknown> | 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<string, unknown>[]) || [];
|
||||
const igPrimary = igAccounts[0] || (enrichment?.instagram as Record<string, unknown>) || 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,13 +385,16 @@ function buildKpiDashboard(r: ApiReport): import('../types/report').KPIMetric[]
|
|||
target3Month: '3,000',
|
||||
target12Month: '10,000',
|
||||
});
|
||||
if (igSecondary) {
|
||||
const enFollowers = (igSecondary.followers as number) || 0;
|
||||
metrics.push({
|
||||
metric: 'Instagram EN 팔로워',
|
||||
current: channels.instagram.followers ? fmt(Math.round(channels.instagram.followers * 4.5)) : '68.8K',
|
||||
target3Month: '75K',
|
||||
target12Month: '100K',
|
||||
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
|
||||
if (channels.naverBlog) {
|
||||
|
|
@ -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<string, unknown> | 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)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue