fix: KPI dashboard always generates comprehensive 10+ metrics
Previously AI-provided kpiTargets (often only 3-4 items) would completely replace our channel-based KPI generation. Now we always build the full set (YouTube, Instagram, Naver, 강남언니, Google Maps, cross-platform) and merge AI extras that don't overlap. Also adds 강남언니 평점/리뷰, 네이버 플레이스 평점 as standard KPIs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>claude/bold-hawking
parent
5239ad7382
commit
ff82c9f9d5
|
|
@ -332,19 +332,8 @@ function buildRoadmap(r: ApiReport): import('../types/report').RoadmapMonth[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildKpiDashboard(r: ApiReport): import('../types/report').KPIMetric[] {
|
function buildKpiDashboard(r: ApiReport): import('../types/report').KPIMetric[] {
|
||||||
// If AI provided explicit KPIs, use them
|
// Always build comprehensive KPIs from channel data.
|
||||||
if (r.kpiTargets?.length) {
|
// AI-provided kpiTargets are merged in (fill gaps) but don't replace our metrics.
|
||||||
return r.kpiTargets
|
|
||||||
.filter((k): k is { metric?: string; current?: string; target3Month?: string; target12Month?: string } => !!k?.metric)
|
|
||||||
.map(k => ({
|
|
||||||
metric: k.metric || '',
|
|
||||||
current: k.current || '-',
|
|
||||||
target3Month: k.target3Month || '-',
|
|
||||||
target12Month: k.target12Month || '-',
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build comprehensive KPI from channel data
|
|
||||||
const channels = r.channelAnalysis || {};
|
const channels = r.channelAnalysis || {};
|
||||||
const metrics: import('../types/report').KPIMetric[] = [];
|
const metrics: import('../types/report').KPIMetric[] = [];
|
||||||
|
|
||||||
|
|
@ -404,6 +393,36 @@ 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;
|
||||||
|
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',
|
||||||
|
});
|
||||||
|
if (channels.gangnamUnni.reviews) {
|
||||||
|
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)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Google Maps
|
||||||
|
if (channels.naverPlace) {
|
||||||
|
metrics.push({
|
||||||
|
metric: '네이버 플레이스 평점',
|
||||||
|
current: channels.naverPlace.rating ? `${channels.naverPlace.rating}/5` : '-',
|
||||||
|
target3Month: '4.5/5',
|
||||||
|
target12Month: '4.8/5',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Cross-platform
|
// Cross-platform
|
||||||
metrics.push({
|
metrics.push({
|
||||||
metric: '웹사이트 + SNS 유입',
|
metric: '웹사이트 + SNS 유입',
|
||||||
|
|
@ -419,6 +438,33 @@ function buildKpiDashboard(r: ApiReport): import('../types/report').KPIMetric[]
|
||||||
target12Month: '월 50건',
|
target12Month: '월 50건',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Merge AI-provided KPIs that we didn't already cover
|
||||||
|
if (r.kpiTargets?.length) {
|
||||||
|
const existingNames = new Set(metrics.map(m => m.metric.toLowerCase()));
|
||||||
|
for (const k of r.kpiTargets) {
|
||||||
|
if (!k?.metric) continue;
|
||||||
|
// Skip if we already have a similar metric
|
||||||
|
const lower = k.metric.toLowerCase();
|
||||||
|
if (existingNames.has(lower)) continue;
|
||||||
|
if (lower.includes('youtube') && existingNames.has('youtube 구독자')) continue;
|
||||||
|
if (lower.includes('instagram') && existingNames.has('instagram kr 팔로워')) continue;
|
||||||
|
if (lower.includes('강남언니') || lower.includes('gangnam')) {
|
||||||
|
// Use AI's gangnamunni data — update existing or add
|
||||||
|
const guIdx = metrics.findIndex(m => m.metric.includes('강남언니'));
|
||||||
|
if (guIdx >= 0) {
|
||||||
|
metrics[guIdx] = { metric: k.metric, current: k.current || '-', target3Month: k.target3Month || '-', target12Month: k.target12Month || '-' };
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metrics.push({
|
||||||
|
metric: k.metric,
|
||||||
|
current: k.current || '-',
|
||||||
|
target3Month: k.target3Month || '-',
|
||||||
|
target12Month: k.target12Month || '-',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue