/** * Project Master Analysis JS * AVI (Activity Vitality Index) & VCI (Value Contribution Index) 분석 엔진 */ // Chart.js 플러그인 전역 등록 if (typeof ChartDataLabels !== 'undefined') { Chart.register(ChartDataLabels); } document.addEventListener('DOMContentLoaded', () => { console.log("Business Analysis Engine initialized..."); loadProjectAnalysisData(); }); async function loadProjectAnalysisData() { try { const response = await fetch('/api/analysis/p-war'); const data = await response.json(); if (data.error) throw new Error(data.error); renderVitalityLeaderboard(data); renderValueCharts(data); if (data.length > 0 && data[0].avg_info) { const avg = data[0].avg_info; const infoEl = document.getElementById('avg-system-info'); if (infoEl) infoEl.textContent = `* 시스템 종합 자산 건전도: ${avg.avg_risk}% (운영 표준 70.0% 대비)`; } } catch (e) { console.error("분석 데이터 로딩 실패:", e); } } function getStatusInfo(avi, isAutoDelete) { if (isAutoDelete || avi < 10) return { label: '중단/방치', class: 'badge-system', key: 'dead' }; if (avi < 30) return { label: '위험 노출', class: 'badge-danger', key: 'danger' }; if (avi < 70) return { label: '관리 주의', class: 'badge-warning', key: 'warning' }; return { label: '정상 운영', class: 'badge-active', key: 'active' }; } // VCI 등급 판정 로직 (Sabermetrics WAR 등급 기준 응용) function getVciGrade(vci) { if (vci >= 10) return { label: 'Masterpiece', class: 'grade-mvp', desc: '시스템 가치를 견인하는 핵심 자산 (MVP급)' }; if (vci >= 2) return { label: 'Blue Chip', class: 'grade-allstar', desc: '꾸준한 활력의 우량 자산 (주전급)' }; if (vci >= -2) return { label: 'Steady', class: 'grade-starter', desc: '표준 수준의 현상 유지 (보결급)' }; if (vci >= -10) return { label: 'Underperform', class: 'grade-bench', desc: '운영 미비로 인한 가치 하락 (마이너급)' }; return { label: 'Liability', class: 'grade-out', desc: '가치를 훼손 중인 방치 자산 (방출급)' }; } function renderValueCharts(data) { if (!data || data.length === 0) return; // 1. 운영 활력 분포 (Doughnut) try { const stats = { active: [], warning: [], danger: [], dead: [] }; data.forEach(p => { const status = getStatusInfo(p.p_war, p.is_auto_delete); stats[status.key].push(p); }); const statusCtx = document.getElementById('statusChart').getContext('2d'); if (window.myStatusChart) window.myStatusChart.destroy(); window.myStatusChart = new Chart(statusCtx, { type: 'doughnut', data: { labels: ['정상 운영', '관리 주의', '위험 노출', '중단/방치'], datasets: [{ data: [stats.active.length, stats.warning.length, stats.danger.length, stats.dead.length], backgroundColor: ['#1E5149', '#22c55e', '#f59e0b', '#ef4444'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, layout: { padding: 15 }, plugins: { legend: { position: 'right', labels: { boxWidth: 10, font: { size: 11, weight: '700' }, usePointStyle: true } }, datalabels: { display: false } }, cutout: '65%', onClick: (e, elements) => { if (elements.length > 0) { const idx = elements[0].index; openProjectListModal(['정상 운영', '관리 주의', '위험 노출', '중단/방치'][idx], stats[['active', 'warning', 'danger', 'dead'][idx]]); } } } }); } catch (err) { console.error("도넛 차트 에러:", err); } // 2. 전략적 자산 매트릭스 (Scatter) try { const sortedByAVI = [...data].sort((a, b) => b.p_war - a.p_war); const top5Ids = sortedByAVI.slice(0, 5).map(p => p.project_nm); const bottom5Ids = sortedByAVI.slice(-5).map(p => p.project_nm); const largeProjects = data.filter(p => p.file_count > 450).map(p => p.project_nm); const vipProjectNames = new Set([...top5Ids, ...bottom5Ids, ...largeProjects]); const scatterData = data.map(p => { const vci = p.risk_count || 0; const absVci = Math.abs(vci); return { x: Math.min(500, p.file_count), y: p.p_war, label: p.project_nm, isVip: vipProjectNames.has(p.project_nm), vci: vci, radius: Math.max(5, Math.min(25, 5 + (absVci / 10))) }; }); const vitalityCtx = document.getElementById('forecastChart').getContext('2d'); if (window.myVitalityChart) window.myVitalityChart.destroy(); window.myVitalityChart = new Chart(vitalityCtx, { type: 'scatter', data: { datasets: [{ data: scatterData, backgroundColor: (ctx) => { const p = ctx.raw; if (!p) return '#94a3b8'; if (p.x >= 250 && p.y >= 50) return '#1E5149'; if (p.x < 250 && p.y >= 50) return '#22c55e'; if (p.x < 250 && p.y < 50) return '#94a3b8'; return '#ef4444'; }, pointRadius: (ctx) => ctx.raw ? ctx.raw.radius : 5, hoverRadius: (ctx) => (ctx.raw ? ctx.raw.radius : 5) + 3 }] }, options: { responsive: true, maintainAspectRatio: false, layout: { padding: { top: 30, right: 45, left: 10, bottom: 10 } }, scales: { x: { type: 'linear', min: 0, max: 500, title: { display: true, text: '파일 수 (Files)', font: { size: 11, weight: '700' } }, grid: { display: false }, ticks: { stepSize: 125, callback: (v) => v >= 500 ? '500+' : v } }, y: { min: 0, max: 100, title: { display: true, text: '운영 활력 (AVI %)', font: { size: 11, weight: '700' } }, grid: { display: false } } }, plugins: { legend: { display: false }, datalabels: { backgroundColor: 'rgba(255, 255, 255, 0.8)', borderRadius: 4, padding: 4, align: (ctx) => (ctx.raw && ctx.raw.y > 80 ? 'bottom' : 'top'), offset: (ctx) => (ctx.raw ? ctx.raw.radius : 5) + 2, font: { size: 10, weight: '800' }, color: '#1e293b', formatter: (v) => v ? v.label : '', display: (ctx) => ctx.raw && ctx.raw.isVip, clip: false }, tooltip: { callbacks: { label: (ctx) => ` [${ctx.raw.label}] 활력(AVI): ${ctx.raw.y.toFixed(1)}% | 가치 기여(VCI): ${ctx.raw.vci.toFixed(1)}` } } } }, plugins: [{ id: 'quadrants', beforeDraw: (chart) => { const { ctx, chartArea: { left, top, right, bottom }, scales: { x, y } } = chart; const midX = x.getPixelForValue(250); const midY = y.getPixelForValue(50); ctx.save(); ctx.fillStyle = 'rgba(34, 197, 94, 0.03)'; ctx.fillRect(left, top, midX - left, midY - top); ctx.fillStyle = 'rgba(30, 81, 73, 0.03)'; ctx.fillRect(midX, top, right - midX, midY - top); ctx.fillStyle = 'rgba(148, 163, 184, 0.03)'; ctx.fillRect(left, midY, midX - left, bottom - midY); ctx.fillStyle = 'rgba(239, 68, 68, 0.05)'; ctx.fillRect(midX, midY, right - midX, bottom - midY); ctx.lineWidth = 2; ctx.strokeStyle = 'rgba(0,0,0,0.1)'; ctx.beginPath(); ctx.moveTo(midX, top); ctx.lineTo(midX, bottom); ctx.moveTo(left, midY); ctx.lineTo(right, midY); ctx.stroke(); ctx.font = 'bold 12px Pretendard'; ctx.textAlign = 'center'; ctx.fillStyle = 'rgba(0,0,0,0.2)'; ctx.fillText('활력 양호', (left + midX) / 2, (top + midY) / 2); ctx.fillText('핵심 가치', (midX + right) / 2, (top + midY) / 2); ctx.fillText('정체/소규모', (left + midX) / 2, (midY + bottom) / 2); ctx.fillStyle = 'rgba(239, 68, 68, 0.4)'; ctx.fillText('자산 손실 위험', (midX + right) / 2, (midY + bottom) / 2); ctx.restore(); } }] }); } catch (err) { console.error("전략 매트릭스 에러:", err); } } function renderVitalityLeaderboard(data) { const container = document.getElementById('p-war-table-container'); if (!container) return; const sortedData = [...data].sort((a, b) => a.p_war - b.p_war); container.innerHTML = `
| 프로젝트명 | 파일 수 | 정체 일수 | 상태 판정 | 가치 기여 (VCI) | 활력 지수 (AVI) | 업무 집중도 | 상태 예보 (14d) |
|---|---|---|---|---|---|---|---|
| ${p.project_nm} | ${p.file_count.toLocaleString()}개 | ${p.days_stagnant}일 | ${status.label === '사망' ? '중단' : status.label} | ${vci > 0 ? '+' : ''}${vci.toFixed(1)} | ${avi.toFixed(1)}% |
${p.work_effort}%
|
${p.predicted_soi !== null ? p.predicted_soi.toFixed(1) + '%' : '-'} |
|
⚙️ AI 자산 건전성 분석 시뮬레이션 (AAS Metrics)
📊 실질 업무 집중도 Analysis
${p.work_effort}%
최근 수집 로그 중 실질적 자산 증분이 포착된 밀도입니다.
VCI GRADE
${grade.label}
${grade.desc}
1
동적 감쇄 계수(λ) 산출
자산 규모 및 조직 위험을 합산하여 개별 활력 곡선을 생성합니다.
λ = ${p.ai_lambda.toFixed(4)}
2
활동 진정성 검증
Factor = ${(p.log_quality * 100).toFixed(0)}%
3
가동 보존율 (AVI)
Result = ${avi.toFixed(1)}%
4
가치 기여 영향력 (VCI)
VCI = ${vci.toFixed(1)}
|
|||||||
대상 프로젝트 없음
' : `| 프로젝트명 | 부서 | 관리자 | 정체일 | 활력(AVI) |
|---|---|---|---|---|
| ${p.project_nm} | ${p.dept || '-'} | ${p.master || '-'} | ${p.days_stagnant}일 | ${p.p_war.toFixed(1)}% |
자산의 가동 상태와 생존율을 나타내는 지표입니다.
| 지수 (AVI) | 등급 | 운영 상태 |
|---|---|---|
| 90%↑ | Live | 실시간 성과물이 도출되는 최상급 가동 |
| 70~90% | Stable | 주기적 업데이트가 이뤄지는 표준 안정 |
| 30~70% | Idle | 관리가 필요한 유휴/정체 상태 |
| 10~30% | Risk | 자산 가치 소멸 직전의 위험 상태 |
| 10%↓ | Frozen | 운영이 중단된 동결/방치 상태 |
운영 표준(AVI 70%) 대비 자산 가치 기여도에 따른 프로젝트 위상 분류입니다.
| 점수 (VCI) | 등급 | 운영 의미 |
|---|---|---|
| +10.0↑ | Masterpiece | 시스템 가치를 견인하는 핵심 자산 (MVP급) |
| +2.0 ~ +10.0 | Blue Chip | 꾸준한 활력의 우량 자산 (주전급) |
| -2.0 ~ +2.0 | Steady | 표준 수준의 현상 유지 (보결급) |
| -10.0 ~ -2.0 | Underperform | 운영 미비로 인한 가치 하락 (마이너급) |
| -10.0↓ | Liability | 가치를 훼손 중인 방치 자산 (방출급) |
단순 관리 로그를 제외한 실질적인 산출물 변화의 밀도입니다.
| 비율 (%) | 등급 | 활동 성격 |
|---|---|---|
| 80%↑ | Intensive | 성과물 위주의 고밀도 집중 작업 |
| 50~80% | Active | 성과와 관리가 균형 잡힌 원활한 실행 |
| 20~50% | Maintenance | 설정/행정 등 단순 관리 중심의 작업 |
| 20%↓ | Surface | 실체적 변화가 적은 형식적 로그 중심 |
단순한 현재 점수 나열이 아닌, 최근 활동의 가속도(Acceleration)와 변화 패턴을 AI가 분석하여 미래의 활력 지수(AVI)를 예보합니다.
| 분석 결과 | 상태 등급 | 관리 가이드라인 |
|---|---|---|
| AVI 상승↑ | 성장 가속 | 활동 모멘텀이 상승 중인 우수 자산 |
| AVI 유지 | 안정 유지 | 현재의 리듬을 유지하는 표준 운영 상태 |
| AVI 하락↓ | 활력 저하 | 정체 징후 포착, 관리 리소스 투입 검토 |
| AVI 10%↓ | 중단 위기 | 단기 내 완전 방치 및 가치 소멸 위험 |