/** * Project Master Analysis JS * AVI (Activity Vitality Index) & VCI (Value Contribution Index) 분석 엔진 * OCI (Operational Consistency 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' }; } function getVciGrade(vci) { if (vci >= 10) return { label: 'Masterpiece', class: 'grade-mvp', desc: '시스템 가치를 견인하는 최우량 핵심 자산' }; 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: '자산 규모 (파일 수)', font: { size: 11, weight: '700' } }, grid: { display: false } }, 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, font: { size: 10, weight: '800' }, 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 = `
${sortedData.map((p, idx) => { const status = getStatusInfo(p.p_war, p.is_auto_delete); const avi = p.p_war; const vci = p.risk_count; const oci = p.oci_score || 0; const rowId = `project-${idx}`; const grade = getVciGrade(vci); let rhythmLabel = oci >= 80 ? "정기적" : oci >= 50 ? "안정적" : oci >= 20 ? "간헐적" : "불규칙"; let rhythmColor = oci >= 80 ? "#059669" : oci >= 50 ? "#1e5149" : oci >= 20 ? "#f59e0b" : "#dc2626"; // 존재 신뢰도 패널티 (ECV) 상세 설명 복구 let ecvText = "100% (데이터 실체 검증)"; let ecvClass = "highlight-val"; let ecvDesc = `현재 ${p.file_count}개의 유효 성과물이 확인됩니다. 시스템적으로 실체가 완벽히 존재하는 상태입니다.`; if (p.file_count === 0) { ecvText = "5% (유령 프로젝트 판명)"; ecvClass = "highlight-penalty"; ecvDesc = "데이터가 전무하여 프로젝트의 디지털 실체가 없습니다. 모든 분석에서 최하위 패널티가 적용됩니다."; } else if (p.file_count < 10) { ecvText = "40% (형식적 껍데기 판명)"; ecvClass = "highlight-penalty"; ecvDesc = "최소 수준의 문서만 존재하며, 실질적인 운영 가치를 인정하기 어려운 소규모 상태입니다."; } // 활동 품질 텍스트 복구 const qualityLabel = p.log_quality >= 1.0 ? '성과물 중심의 실무 활동' : p.log_quality >= 0.7 ? '구조 관리를 위한 시스템 활동' : '단순 행정 기반의 형식 활동'; const qualityDetail = p.log_quality >= 1.0 ? '최근 로그에서 파일 업로드/수정 등 가치 증분 활동이 명확히 포착되었습니다.' : p.log_quality >= 0.7 ? '폴더 생성/이동 등 구조적 관리는 이뤄지고 있으나, 직접적 결과물 생산은 부족합니다.' : '메일 확인, 권한 변경 등 시스템 유지성 활동 위주로 파악되어 품질 가중치가 낮게 적용되었습니다.'; return ` `; }).join('')}
프로젝트명 파일 수 정체 일수 상태 판정 가치 기여 (VCI) 운영 활력 (AVI) 업무 집중도 운영 일관성 (OCI)
${p.project_nm} ${p.file_count.toLocaleString()}개 ${p.days_stagnant}일 ${status.label} ${vci > 0 ? '+' : ''}${vci.toFixed(1)} ${avi.toFixed(1)}%
${p.work_effort}%
${oci}% ${rhythmLabel}
⚙️ AI 위험 적응형 모델(AAS) 기반 인과관계 분석
📊 실질 업무 집중도 (Job Focus) ${p.work_effort}%
최근 30회 수집 이력 중 단순 로그 갱신이 아닌 실제 성과물의 변동이 포착된 날의 비율입니다. 이는 운영의 '진정성'을 보여주는 핵심 지표입니다.
VCI GRADE
${grade.label}
${grade.desc}
1
동적 위험 계수(λ) 산출
프로젝트 규모가 클수록 정보 망실 시의 충격을 반영하여 데이터의 하락 속도가 가속됩니다. 현재 λ=${p.ai_lambda.toFixed(4)}는 귀하의 자산 규모가 정밀하게 투영된 결과입니다.
Dynamic λ = ${p.ai_lambda.toFixed(4)}
4
활동 품질 검증 (Quality)
최근 로그 분석 결과 ${qualityLabel}으로 판명되었습니다. ${qualityDetail}
Quality Factor = ${(p.log_quality * 100).toFixed(0)}%
2
방치 시간 감쇄 적용
마지막 유효 활동 이후 ${p.days_stagnant}일간의 누적 정체 시간은 지수 감쇄 곡선을 따라 데이터의 최신성과 가치를 상쇄시켰습니다.
Decay Result = ${((avi / (p.file_count === 0 ? 0.05 : p.file_count < 10 ? 0.4 : 1) / p.log_quality) || 0).toFixed(1)}%
3
존재 진정성 (ECV)
${ecvDesc} 파일 수 자체가 분석의 데이터 진정성을 보정하는 핵심 팩터로 작용합니다.
Entity Factor = ${ecvText}
가치 기여도 (VCI) 진단: ${vci >= 0 ? '+' : ''}${vci.toFixed(2)}
현재 프로젝트는 운영 표준(AVI 70%) 대비 ${Math.abs(avi - 70).toFixed(1)}%p ${avi >= 70 ? '상회' : '하회'}하고 있으며, ${p.file_count}개의 자산 규모에 따른 ${((p.file_count / 200) + 0.5).toFixed(2)}배의 가중치가 적용되었습니다. 이는 시스템 전체 관점에서 ${vci >= 0 ? '순자산 가치를 증대' : '잠재적 기회비용을 손실'}시키고 있는 상태로 분석됩니다.
최종 AVI: ${avi.toFixed(1)}%
`; } function toggleProjectDetail(rowId) { const container = document.querySelector('.table-scroll-wrapper'); const mainRow = document.querySelector(`tr[onclick*="toggleProjectDetail('${rowId}')"]`); const detailRow = document.getElementById(`detail-${rowId}`); if (detailRow && container) { if (!detailRow.classList.contains('active')) { document.querySelectorAll('.detail-row').forEach(row => row.classList.remove('active')); detailRow.classList.add('active'); // 정밀 스크롤 이동 로직 복구 setTimeout(() => { const headerH = container.querySelector('thead').offsetHeight || 45; const targetScrollTop = mainRow.offsetTop - headerH; container.scrollTo({ top: targetScrollTop, behavior: 'smooth' }); }, 100); } else { detailRow.classList.remove('active'); } } } function openProjectListModal(label, projects) { const modal = document.getElementById('analysisModal'); const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); title.innerText = `[${label}] 프로젝트 리스트 (${projects.length}건)`; body.innerHTML = `
${projects.map(p => ``).join('')}
프로젝트명관리자정체일AVI
${p.project_nm}${p.master || '-'}${p.days_stagnant}일${p.p_war.toFixed(1)}%
`; modal.style.display = 'flex'; } function openAnalysisModal(type) { const modal = document.getElementById('analysisModal'); const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); if (type === 'avi') { title.innerText = '운영 활력 지수 (AVI) 등급 가이드'; body.innerHTML = `
AVI = exp(-λ × days) × Quality × 100

자산의 가동 상태와 생존율을 나타내는 지표입니다.

지수 (AVI)등급운영 상태
90%↑Live실시간 성과물이 도출되는 최상급 가동
70~90%Stable주기적 업데이트가 이뤄지는 표준 안정
30~70%Idle관리가 필요한 유휴/정체 상태
10~30%Risk자산 가치 소멸 직전의 위험 상태
10%↓Frozen운영이 중단된 사망/방치 상태
`; } else if (type === 'vci') { title.innerText = '자산 가치 기여도 (VCI) 등급 가이드'; body.innerHTML = `
VCI = (AVI - 70.0) × (Files / 200 + 0.5)

운영 표준(AVI 70%) 대비 자산 가치 기여도에 따른 프로젝트 위상 분류입니다.

점수 (VCI)등급운영 의미
+10.0↑Masterpiece시스템 가치를 견인하는 최우량 핵심 자산
+2.0 ~ +10.0Blue Chip꾸준한 활력으로 가치를 창출하는 우량 자산
-2.0 ~ +2.0Steady표준 수준의 운영을 유지 중인 안정 자산
-10.0 ~ -2.0Underperform규모 대비 활력 부족으로 가치 하락 중인 자산
-10.0↓Liability가치를 훼손 중인 고위험 방치 자산
`; } else if (type === 'oci') { title.innerText = '운영 일관성 지수 (OCI) 분석 가이드'; body.innerHTML = `
"얼마나 꾸준하게 관리되고 있는가?"

미래 예측이 아닌, 최근 30일간의 활동 리듬관리의 규칙성을 분석하여 성실도를 점수화합니다.

분석 결과일관성 등급관리 신뢰도
80%↑매우 우수주 단위의 정기적 관리가 완벽히 이뤄짐
50~80%양호간헐적 정체는 있으나 꾸준히 관리됨
20~50%주의돌발적 활동 위주, 관리의 리듬이 깨짐
20%↓매우 불량장기 정체 중이거나 관리 의지 확인 불가
`; } else { title.innerText = '업무 집중도 (Job Focus) 등급 가이드'; body.innerHTML = `

최근 수집 로그 중 단순 행정 로그를 제외하고 실질적인 성과물(파일) 변동이 포착된 비율입니다.

비율 (%)등급활동 성격
80%↑Intensive성과물 위주의 고밀도 집중 작업
50~80%Active성과와 관리가 균형 잡힌 원활한 실행
20~50%Maintenance설정/행정 등 단순 관리 중심의 작업
20%↓Surface실체적 변화가 적은 형식적 로그 중심
`; } modal.style.display = 'flex'; } function closeAnalysisModal() { document.getElementById('analysisModal').style.display = 'none'; }