Files
test-mcp/js/analysis.js_fragment_leaderboard
Taehoon b864d615ea feat: 운영 일관성(OCI) 지표 도입 및 분석 UI/UX 정밀 복구
- analysis_service.py: 운영 일관성(OCI) 산출 로직 구현 및 장기 정체 패널티(100일 기준) 적용
- js/analysis.js: OCI 통합, 아코디언 심층 분석 텍스트 보강, SWOT 사분면 및 스크롤 로직 정밀 복구
- style/*.css: 유색 border-left/top 스타일 제거 및 흑백/그레이 계열로 디자인 정제
- templates/analysis.html: 분석 모델 명칭 원복 및 지표 정의 UI 업데이트
- ANALYSIS_REPORT.md: OCI 지표 정의 추가 및 가치 기여도(VCI) 등급 설명 정제 (야구 용어 삭제)
2026-03-25 17:58:58 +09:00

179 lines
14 KiB
Plaintext

function renderPWarLeaderboard(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 = `
<div class="table-scroll-wrapper">
<table class="data-table p-war-table">
<thead>
<tr>
<th style="position: sticky; top: 0; z-index: 10; width: 280px;">프로젝트명</th>
<th style="position: sticky; top: 0; z-index: 10;">파일 수</th>
<th style="position: sticky; top: 0; z-index: 10;">방치일</th>
<th style="position: sticky; top: 0; z-index: 10;">상태 판정</th>
<th style="position: sticky; top: 0; z-index: 10;">
활력 지수 (AVI) <button class="btn-help" onclick="event.stopPropagation(); openAnalysisModal('avi')">?</button>
</th>
<th style="position: sticky; top: 0; z-index: 10; text-align:right;">가치 기여 (VCI)</th>
<th style="position: sticky; top: 0; z-index: 10; text-align:center;">업무 집중도</th>
<th style="position: sticky; top: 0; z-index: 10;">
운영 일관성 (OCI) <button class="btn-help" onclick="event.stopPropagation(); openAnalysisModal('oci')">?</button>
</th>
</tr>
</thead>
<tbody>
${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}`;
let rhythmLabel = "";
let rhythmColor = "";
if (oci >= 80) { rhythmLabel = "정기적"; rhythmColor = "#059669"; }
else if (oci >= 50) { rhythmLabel = "안정적"; rhythmColor = "#1e5149"; }
else if (oci >= 20) { rhythmLabel = "간헐적"; rhythmColor = "#f59e0b"; }
else { rhythmLabel = "불규칙"; rhythmColor = "#dc2626"; }
// 존재 신뢰도 패널티 (ECV) 텍스트 준비
let ecvText = "100% (데이터 신뢰)";
let ecvClass = "highlight-val";
let ecvDesc = "충분한 성과물이 존재합니다.";
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 ? '성과물 직결 <b>실무 활동</b>' : p.log_quality >= 0.7 ? '시스템 <b>구조적 활동</b>' : '단순 <b>행정적 활동</b>';
return `
<tr class="project-row ${status.key === 'danger' ? 'row-danger' : status.key === 'warning' ? 'row-warning' : ''}"
onclick="toggleProjectDetail('${rowId}')">
<td class="font-bold">${p.project_nm}</td>
<td>${p.file_count.toLocaleString()}개</td>
<td>${p.days_stagnant}일</td>
<td><span class="${status.class}">${status.label}</span></td>
<td class="p-war-value ${avi >= 70 ? 'text-plus' : 'text-minus'}">
${avi.toFixed(1)}%
</td>
<td style="text-align:right; font-weight:700; color:${vci >= 0 ? '#059669' : '#dc2626'};">
${vci >= 0 ? '+' : ''}${vci.toFixed(2)}
</td>
<td style="text-align:center;">
<div style="display:flex; flex-direction:column; align-items:center; gap:4px;">
<span style="font-weight:800; font-size:12px; color:${p.work_effort >= 70 ? '#059669' : p.work_effort <= 30 ? '#dc2626' : '#6366f1'};">
${p.work_effort}%
</span>
<div style="width:40px; height:4px; background:#f1f5f9; border-radius:2px; overflow:hidden;">
<div style="width:${p.work_effort}%; height:100%; background:${p.work_effort >= 70 ? '#059669' : p.work_effort <= 30 ? '#dc2626' : '#6366f1'}; transition: width 0.5s;"></div>
</div>
</div>
</td>
<td style="text-align:center;">
<div style="display:flex; align-items:center; justify-content:center; gap:8px;">
<span style="font-weight:800; font-size:13px; color:${rhythmColor};">
${oci}%
</span>
<span style="font-size:10px; padding:2px 6px; border-radius:10px; background:${rhythmColor}15; color:${rhythmColor}; border:1px solid ${rhythmColor}30;">
${rhythmLabel}
</span>
</div>
</td>
</tr>
<tr id="detail-${rowId}" class="detail-row">
<td colspan="8">
<div class="detail-container">
<div class="formula-explanation-card">
<div style="font-size: 13px; font-weight: 700; color: #6366f1; margin-bottom: 15px;">
⚙️ AI 위험 적응형 모델(AAS) 산출 시뮬레이션
</div>
<!-- 업무 집중도 분석 (상단 배치) -->
<div style="background: #f8fafc; padding: 15px; border-radius: 8px; margin-bottom: 20px; border: 1px solid #eef2f6;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-size: 13px; font-weight: 800; color: #1e5149;">📊 실질 업무 집중도 분석 (Job Focus)</span>
<span style="font-size: 14px; font-weight: 800; color: ${p.work_effort >= 70 ? '#059669' : p.work_effort <= 30 ? '#dc2626' : '#6366f1'};">
집중도 ${p.work_effort}%
</span>
</div>
<div style="width: 100%; height: 6px; background: #e2e8f0; border-radius: 3px; overflow: hidden; margin-bottom: 10px;">
<div style="width: ${p.work_effort}%; height: 100%; background: ${p.work_effort >= 70 ? '#059669' : p.work_effort <= 30 ? '#dc2626' : '#6366f1'}; transition: width 0.5s;"></div>
</div>
<div style="font-size: 11.5px; color: #64748b; line-height: 1.5;">
최근 30개 수집 이력 중 단순 로그 갱신이 아닌 <b>실제 파일 수의 변동</b>이 포착된 날의 비율입니다.
현재 이 프로젝트는 <b>${p.work_effort >= 70 ? '매우 밀도 높은 실무' : p.work_effort <= 30 ? '형식적 관리 위주의 정체' : '간헐적인 성과물'}</b> 상태를 보이고 있습니다.
</div>
</div>
<!-- 수식 단계 2x2 그리드 -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div class="formula-step">
<div class="step-num">1</div>
<div class="step-content">
<div class="step-title">동적 위험 계수(λ) 산출</div>
<div style="font-size:11px; color:#64748b; margin-bottom:5px;">자산 규모(${p.file_count}개) 및 부서 위험도를 합산한 하락 속도입니다.</div>
<div class="math-logic">λ = <span class="highlight-var">${p.ai_lambda.toFixed(4)}</span></div>
</div>
</div>
<div class="formula-step">
<div class="step-num">4</div>
<div class="step-content">
<div class="step-title">활동 품질 검증 (Quality)</div>
<div class="step-desc" style="font-size:11px; margin-bottom:5px;">
최근 로그 분석 결과 ${qualityLabel}으로 판명되었습니다.
</div>
<div class="math-logic">Factor = <span class="${p.log_quality < 0.7 ? 'highlight-penalty' : 'highlight-val'}">${(p.log_quality * 100).toFixed(0)}%</span></div>
</div>
</div>
<div class="formula-step">
<div class="step-num">2</div>
<div class="step-content">
<div class="step-title">방치 시간 감쇄 적용</div>
<div style="font-size:11px; color:#64748b; margin-bottom:5px;">${p.days_stagnant}일간의 정체로 인한 가치 보존율입니다.</div>
<div class="math-logic">Result = <span class="highlight-val">${((avi / (p.file_count === 0 ? 0.05 : p.file_count < 10 ? 0.4 : 1) / p.log_quality) || 0).toFixed(1)}%</span></div>
</div>
</div>
<div class="formula-step">
<div class="step-num">3</div>
<div class="step-content">
<div class="step-title">존재 진정성 (ECV)</div>
<div style="font-size:11px; color:#64748b; margin-bottom:5px;">${ecvDesc}</div>
<div class="math-logic">Factor = <span class="${ecvClass}">${ecvText}</span></div>
</div>
</div>
</div>
<div style="margin-top: 20px; padding-top: 15px; border-top: 2px solid #1e5149; text-align: right; display: flex; justify-content: space-between; align-items: center;">
<div style="text-align: left;">
<div style="font-size: 12px; font-weight: 700; color: ${vci >= 0 ? '#059669' : '#dc2626'};">
가치 기여도 (VCI): ${vci >= 0 ? '+' : ''}${vci.toFixed(2)}
</div>
<div style="font-size: 10px; color: #94a3b8;">* AVI 70% 대비 프로젝트의 실질적 자산 하중 반영</div>
</div>
<div>
<span style="font-size: 13px; color: #64748b; font-weight: 700;">최종 AVI: </span>
<span style="color: #1e5149; font-size: 22px; font-weight: 900;">${avi.toFixed(1)}%</span>
</div>
</div>
</div>
</div>
</td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
`;
}