diff --git a/ANALYSIS_REPORT.md b/ANALYSIS_REPORT.md index 51b9c74..594edec 100644 --- a/ANALYSIS_REPORT.md +++ b/ANALYSIS_REPORT.md @@ -9,11 +9,12 @@ ### 1.1 운영 활력 지수 (AVI, Activity Vitality Index) 프로젝트가 현재 얼마나 '살아서 숨 쉬고 있는가'를 나타내는 생존 지수입니다. -* **산출 공식**: $AVI = exp(-\lambda \times days) \times Quality \times 100$ +* **산출 공식**: $AVI = exp(-\lambda \times days) \times Quality \times ECV \times 100$ * **핵심 데이터**: * **정체 일수(days)**: 마지막 유의미한 파일 업데이트 이후 경과 시간. - * **감쇄 계수($\lambda$)**: 자산 규모(파일 수)가 클수록, 소속 부서의 방치율이 높을수록 커지며 점수를 더 빠르게 하락시킵니다. - * **활동 품질(Quality)**: 단순 시스템 로그(단순 접속, 설정 변경)는 낮게 평가하고, 실질적인 파일 증분 활동에 가점을 부여합니다. + * **감쇄 계수($\lambda$)**: 기본 $0.04$에서 시작하여, 자산 규모(최대 $+0.04$)와 부서 정체율(최대 $+0.03$)을 동적으로 결합합니다. + * **활동 품질(Quality)**: 파일 증분 활동($1.0$), 구조적 관리($0.7$), 단순 행정 로그($0.4$)로 차등 배점합니다. + * **존재 신뢰도(ECV)**: 파일 수 $0$개($0.05$), $10$개 미만($0.4$) 등 유령 프로젝트에 패널티를 부여합니다. * **의미**: 100%에 가까울수록 실시간 가동 상태이며, 0%에 가까울수록 데이터 노후화가 완료된 '사망' 상태를 뜻합니다. ### 1.2 자산 가치 기여도 (VCI, Value Contribution Index) @@ -22,15 +23,21 @@ * **산출 공식**: $VCI = (AVI - 70.0) \times (\frac{Files}{200} + 0.5)$ * **핵심 로직**: * **건강 기준선(70.0%)**: 시스템 자산 가치를 유지하기 위한 최소 마지노선(Replacement Level)입니다. - * **규모 가중치**: 파일 수가 많은 대형 프로젝트일수록 동일한 방치 상황에서 시스템에 주는 충격(음수값)이 기하급수적으로 커집니다. + * **규모 가중치**: 파일 $200$개를 $1.0$ 가중치 기준으로 삼아, 대형 프로젝트일수록 시스템에 주는 충격을 기하급수적으로 반영합니다. * **의미**: 양수(+)는 가치 창출, 음수(-)는 시스템 기회비용을 갉아먹는 '가치 파괴' 상태임을 나타냅니다. ### 1.3 업무 집중도 (Job Focus) 단순 관리 행위를 제외하고, 실제 성과물(파일)을 생산하는 데 얼마나 몰입했는지를 판별합니다. -* **산출 공식**: $Job Focus = \frac{\text{최근 30회 중 실질 파일 변동 발생 횟수}}{\text{전체 데이터 수집 횟수}} \times 100$ +* **산출 공식**: $Job Focus = \frac{\text{최근 히스토리 중 실제 파일 변동 발생 횟수}}{\text{전체 데이터 수집 횟수}} \times 100$ * **의미**: 로그만 남기는 '보여주기식 활동'을 필터링하여 운영의 진정성을 확인합니다. +### 1.4 운영 일관성 지수 (OCI, Operational Consistency Index) +프로젝트 관리의 '리듬'과 '성실도'를 측정하는 지표입니다. + +* **산출 공식**: 최근 30일 데이터를 4개 주차로 분할하여 활동 여부 분석 (주차별 성실도 70% + 활동 밀도 30%) +* **의미**: 특정 시점에 몰아치기식 작업을 하는 프로젝트보다, 매주 꾸준히 관리되는 프로젝트에 더 높은 신뢰 점수를 부여합니다. + --- ## 2. 등급 체계 및 관리 가이드 (Grade System) @@ -38,19 +45,17 @@ ### 2.1 VCI 등급 (프로젝트 위상) | 등급 (Grade) | 점수 기준 | 운영 의미 및 관리 전략 | | :--- | :--- | :--- | -| **Masterpiece** | +10.0 이상 | **핵심 자산 (MVP)**: 시스템 가치를 견인하는 최우량 프로젝트 | -| **Blue Chip** | +2.0 ~ +10.0 | **우량 자산 (주전)**: 꾸준한 활력으로 가치를 창출하는 핵심군 | -| **Steady** | -2.0 ~ +2.0 | **현상 유지 (보결)**: 표준 수준의 운영을 유지 중인 안정군 | -| **Underperform** | -10.0 ~ -2.0 | **저성과 (마이너)**: 규모 대비 활력이 부족하여 리소스 투입 필요 | -| **Liability** | -10.0 이하 | **가치 파괴 (방출)**: 시스템 가치를 훼손 중인 좀비 프로젝트. 타절 검토 시급 | +| **Masterpiece** | +10.0 이상 | **최우량 자산**: 시스템 가치를 견인하는 핵심 프로젝트 | +| **Blue Chip** | +2.0 ~ +10.0 | **우량 자산**: 꾸준한 활력으로 가치를 창출하는 핵심군 | +| **Steady** | -2.0 ~ +2.0 | **안정 자산**: 표준 수준의 운영을 유지 중인 현상 유지군 | +| **Underperform** | -10.0 ~ -2.0 | **저성과 자산**: 규모 대비 활력이 부족하여 가치 하락 중인 그룹 | +| **Liability** | -10.0 이하 | **고위험 자산**: 시스템 가치를 훼손 중인 방치 프로젝트. 즉시 조치 필요 | -### 2.2 상태 예보 (AI Forecast) -최근 활동의 **가속도(Acceleration)**와 **관성(Momentum)**을 AI가 분석한 14일 뒤 전망입니다. - -* **성장 가속 (Bullish)**: 활동 에너지가 증가 추세이며 가치가 오를 전망. -* **안정 유지 (Neutral)**: 현재의 안정적인 운영 리듬을 지속할 전망. -* **활력 저하 (Bearish)**: 정체 징후 포착. 단기 내 가동률 하락 예상. -* **중단 위기 (Warning)**: 급격한 활동 저하로 인한 자산 소멸 위험 노출. +### 2.2 운영 일관성 (OCI) 판정 +* **정기적 (80%↑)**: 주 단위의 정기적 관리가 완벽히 이뤄지는 최우량 관리 상태. +* **안정적 (50~80%)**: 간헐적 정체는 있으나 전반적인 관리 리듬을 유지하는 상태. +* **간헐적 (20~50%)**: 관리 활동이 불규칙하며, 필요에 의한 일회성 작업 중심인 상태. +* **불규칙 (20%↓)**: 장기 정체 중이거나 관리의 영속성을 확인하기 어려운 위험 상태. --- @@ -61,7 +66,7 @@ * **Velocity**: 파일 수의 변화 속도 계산. * **Acceleration**: 활동의 가속/감속 여부 판별. * **Stagnation**: 마지막 활동 이후의 공백 기간 측정. -3. **AI 시뮬레이션**: 추출된 피처를 AAS(AI 위험 적응형 모델)에 입력하여 개별 프로젝트만의 **'위험 곡선'**을 생성합니다. +3. **AI 시뮬레이션**: 추출된 피처를 AI 위험 적응형 모델 (AAS)에 입력하여 개별 프로젝트만의 **'위험 곡선'**을 생성합니다. 4. **최종 판정**: AVI와 VCI를 결합하여 리더보드에 등급과 관리 가이드라인을 송출합니다. --- diff --git a/__pycache__/analysis_service.cpython-312.pyc b/__pycache__/analysis_service.cpython-312.pyc index df75f8a..d34751f 100644 Binary files a/__pycache__/analysis_service.cpython-312.pyc and b/__pycache__/analysis_service.cpython-312.pyc differ diff --git a/analysis_service.py b/analysis_service.py index 30ba1ef..2f8f1f0 100644 --- a/analysis_service.py +++ b/analysis_service.py @@ -1,13 +1,51 @@ import re import math import statistics -from datetime import datetime +from datetime import datetime, timedelta from sql_queries import DashboardQueries from prediction_service import SOIPredictionService class AnalysisService: """프로젝트 통계 및 활동성 분석 전문 서비스""" + @staticmethod + def calculate_operational_consistency(history_rows, days_stagnant): + """운영 일관성 지수(OCI) 산출 로직 (장기 정체 패널티 포함) + 최근 30일간 활동 리듬 분석 + 현재 방치 기간에 따른 강력한 감쇄 + """ + if not history_rows or len(history_rows) < 2: + return 0.0 + + # 1. 최근 30일 이력 기반 Base Score 산출 + now = datetime.now().date() + recent_30 = [h for h in history_rows if (now - h['crawl_date']).days <= 30] + + # 주차별 활동 여부 (4주) + weeks_active = [False, False, False, False] + for h in recent_30: + days_ago = (now - h['crawl_date']).days + week_idx = min(3, days_ago // 7) + weeks_active[week_idx] = True + + base_consistency = (sum(weeks_active) / 4) * 70 + + # 활동 밀도 (변화 발생일 비율) + effort_days = 0 + for i in range(1, len(recent_30)): + if recent_30[i]['file_count'] != recent_30[i-1]['file_count']: + effort_days += 1 + + density_score = (effort_days / max(1, len(recent_30))) * 30 + base_oci = base_consistency + density_score + + # 2. [핵심] 장기 정체 패널티 적용 + # 방치일이 100일 이상이면 OCI는 0점으로 수렴 (성실도 무효화) + stagnation_factor = max(0, (100 - days_stagnant) / 100.0) + + final_oci = base_oci * stagnation_factor + + return round(final_oci, 1) + @staticmethod def calculate_activity_status(target_date_dt, log, file_count): """개별 프로젝트의 활동 상태 및 방치일 산출""" @@ -59,7 +97,7 @@ class AnalysisService: @staticmethod def get_p_zsr_analysis_logic(cursor): - """절대적 방치 실태 고발 및 AI 위험 적응형(AAS) 분석 로직""" + """절대적 방치 실태 고발 및 운영 일관성(OCI) 분석 로직""" cursor.execute(DashboardQueries.GET_LAST_CRAWL_DATE) res_date = cursor.fetchone() if not res_date or not res_date['last_date']: @@ -77,31 +115,12 @@ class AnalysisService: if not projects: return [] - # [Step 1] AI 전처리: 부서별 평균 방치일 계산 (조직적 위험도 산출용) - dept_stats = {} - for p in projects: - log = p['recent_log'] - days = 14 # 기본값 - if log and log != "데이터 없음": - match = re.search(r'(\d{4})\.(\d{2})\.(\d{2})', log) - if match: - log_date = datetime.strptime(match.group(0), "%Y.%m.%d").date() - days = (last_date - log_date).days - - dept = p['department'] or "미분류" - if dept not in dept_stats: dept_stats[dept] = [] - dept_stats[dept].append(days) - - dept_avg_risk = {d: statistics.mean(days_list) for d, days_list in dept_stats.items()} - - # [Step 2] AI 위험 적응형 SOI 산출 (AAS 모델) results = [] total_soi = 0 for p in projects: file_count = int(p['file_count']) if p['file_count'] else 0 log = p['recent_log'] - dept = p['department'] or "미분류" # 방치일 계산 days_stagnant = 14 @@ -114,52 +133,33 @@ class AnalysisService: is_auto_delete = log and "폴더자동삭제" in log.replace(" ", "") # AI-Hazard 추론 로직 (Dynamic Lambda) - # 1. 자산 규모 리스크 (파일이 많을수록 방치 시 가치 하락 가속) scale_impact = min(0.04, math.log10(file_count + 1) * 0.008) if file_count > 0 else 0 + ai_lambda = 0.04 + scale_impact - # 2. 조직적 전염 리스크 (부서 전체가 방치 중이면 패널티 부여) - dept_risk_days = dept_avg_risk.get(dept, 14) - env_impact = min(0.03, (dept_risk_days / 30) * 0.01) - - # 최종 AI 위험 계수 산출 (기본 0.04에서 변동) - ai_lambda = 0.04 + scale_impact + env_impact - - # 지수 감쇄 적용 (AAS Score) + # 지수 감쇄 적용 soi_score = math.exp(-ai_lambda * days_stagnant) * 100 - # [AI 데이터 진정성 검증 로직 1 - ECV 패널티 (존재론적)] + # ECV 패널티 existence_confidence = 1.0 - if file_count == 0: - existence_confidence = 0.05 - elif file_count < 10: - existence_confidence = 0.4 + if file_count == 0: existence_confidence = 0.05 + elif file_count < 10: existence_confidence = 0.4 - # [AI 데이터 진정성 검증 로직 2 - Log Quality Scoring (활동의 질)] + # Log Quality Scoring log_quality_factor = 1.0 if log and log != "데이터 없음": - # 성과 중심 (High) - if any(k in log for k in ["업로드", "수정", "등록", "변환", "파일", "업데이트"]): - log_quality_factor = 1.0 - # 구조 관리 (Mid) - elif any(k in log for k in ["폴더", "생성", "삭제", "이동"]): - log_quality_factor = 0.7 - # 단순 행정/설정 (Low) - elif any(k in log for k in ["참가자", "권한", "추가", "변경", "메일"]): - log_quality_factor = 0.4 - else: - log_quality_factor = 0.6 # 기타 일반 로그 + if any(k in log for k in ["업로드", "수정", "등록", "변환", "파일", "업데이트"]): log_quality_factor = 1.0 + elif any(k in log for k in ["폴더", "생성", "삭제", "이동"]): log_quality_factor = 0.7 + elif any(k in log for k in ["참가자", "권한", "추가", "변경", "메일"]): log_quality_factor = 0.4 + else: log_quality_factor = 0.6 - # 최종 점수 산출 (AAS * ECV * LogQuality) soi_score = soi_score * existence_confidence * log_quality_factor - - if is_auto_delete: - soi_score = 0.1 + if is_auto_delete: soi_score = 0.1 - # [AI 미래 예측 및 실무 투입 에너지 분석] + # [운영 일관성 분석 (OCI)] history_rows = SOIPredictionService.get_historical_soi(cursor, p['project_id']) - predicted_soi = SOIPredictionService.predict_future_soi(soi_score, history_rows, days_ahead=14) + oci_score = AnalysisService.calculate_operational_consistency(history_rows, days_stagnant) - # 실무 투입 에너지 계산 (최근 30개 히스토리 기준 파일 변화일수) + # 실무 투입 에너지 계산 effort_days = 0 if len(history_rows) > 1: for i in range(1, len(history_rows)): @@ -167,28 +167,26 @@ class AnalysisService: effort_days += 1 work_effort_rate = round((effort_days / max(1, len(history_rows))) * 100, 1) - total_soi += soi_score - # [최종 세이버메트릭스 보정: P-WAR+ (Adjusted Score)] - # 절대 기준선(Replacement Level): 70.0% (이 이하는 자산 가치 파괴로 간주) + # VCI 산출 REPLACEMENT_LEVEL = 70.0 - asset_weight = (file_count / 200.0) + 0.5 # 파일 100개당 0.5배 가중 (최소 0.5배) + asset_weight = (file_count / 200.0) + 0.5 p_war_score = (soi_score - REPLACEMENT_LEVEL) * asset_weight results.append({ "project_nm": p['short_nm'] or p['project_nm'], "file_count": file_count, "days_stagnant": days_stagnant, - "risk_count": round(p_war_score, 2), # P-WAR+ 절대 기여도 점수 (평균의 함정 극복용) + "risk_count": round(p_war_score, 2), "p_war": round(soi_score, 1), - "predicted_soi": predicted_soi, + "oci_score": oci_score, # 운영 일관성 지수 추가 "is_auto_delete": is_auto_delete, "master": p['master'], "dept": p['department'], "ai_lambda": round(ai_lambda, 4), "log_quality": log_quality_factor, - "work_effort": work_effort_rate, # 신규 지표 추가 + "work_effort": work_effort_rate, "avg_info": { "avg_files": 0, "avg_stagnant": 0, diff --git a/js/analysis.js b/js/analysis.js index f5ad012..2f4ecf0 100644 --- a/js/analysis.js +++ b/js/analysis.js @@ -1,6 +1,7 @@ /** * Project Master Analysis JS * AVI (Activity Vitality Index) & VCI (Value Contribution Index) 분석 엔진 + * OCI (Operational Consistency Index) 통합 버전 */ // Chart.js 플러그인 전역 등록 @@ -33,19 +34,18 @@ async function loadProjectAnalysisData() { } function getStatusInfo(avi, isAutoDelete) { - if (isAutoDelete || avi < 10) return { label: '중단/방치', class: 'badge-system', key: 'dead' }; + 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: '가치를 훼손 중인 방치 자산 (방출급)' }; + 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) { @@ -65,7 +65,7 @@ function renderValueCharts(data) { window.myStatusChart = new Chart(statusCtx, { type: 'doughnut', data: { - labels: ['정상 운영', '관리 주의', '위험 노출', '중단/방치'], + labels: ['정상 운영', '관리 주의', '위험 노출', '사망'], datasets: [{ data: [stats.active.length, stats.warning.length, stats.danger.length, stats.dead.length], backgroundColor: ['#1E5149', '#22c55e', '#f59e0b', '#ef4444'], @@ -84,20 +84,19 @@ function renderValueCharts(data) { onClick: (e, elements) => { if (elements.length > 0) { const idx = elements[0].index; - openProjectListModal(['정상 운영', '관리 주의', '위험 노출', '중단/방치'][idx], stats[['active', 'warning', 'danger', 'dead'][idx]]); + openProjectListModal(['정상 운영', '관리 주의', '위험 노출', '사망'][idx], stats[['active', 'warning', 'danger', 'dead'][idx]]); } } } }); } catch (err) { console.error("도넛 차트 에러:", err); } - // 2. 전략적 자산 매트릭스 (Scatter) + // 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 => { @@ -140,9 +139,8 @@ function renderValueCharts(data) { 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 } + title: { display: true, text: '자산 규모 (파일 수)', font: { size: 11, weight: '700' } }, + grid: { display: false } }, y: { min: 0, max: 100, @@ -155,17 +153,14 @@ function renderValueCharts(data) { 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)}` + label: (ctx) => ` [${ctx.raw.label}] AVI: ${ctx.raw.y.toFixed(1)}% | VCI: ${ctx.raw.vci.toFixed(1)}` } } } @@ -210,25 +205,47 @@ function renderVitalityLeaderboard(data) { 정체 일수 상태 판정 가치 기여 (VCI) - 활력 지수 (AVI) + 운영 활력 (AVI) 업무 집중도 - 상태 예보 (14d) + 운영 일관성 (OCI) ${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 vci = p.risk_count || 0; - const avi = p.p_war || 0; 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 ` ${p.project_nm} ${p.file_count.toLocaleString()}개 ${p.days_stagnant}일 - ${status.label === '사망' ? '중단' : status.label} + ${status.label} ${vci > 0 ? '+' : ''}${vci.toFixed(1)} @@ -241,22 +258,27 @@ function renderVitalityLeaderboard(data) { - ${p.predicted_soi !== null ? p.predicted_soi.toFixed(1) + '%' : '-'} + +
+ ${oci}% + ${rhythmLabel} +
+
-
⚙️ AI 자산 건전성 분석 시뮬레이션 (AAS Metrics)
+
⚙️ AI 위험 적응형 모델(AAS) 기반 인과관계 분석
- 📊 실질 업무 집중도 Analysis + 📊 실질 업무 집중도 (Job Focus) ${p.work_effort}%
-
최근 수집 로그 중 실질적 자산 증분이 포착된 밀도입니다.
+
최근 30회 수집 이력 중 단순 로그 갱신이 아닌 실제 성과물의 변동이 포착된 날의 비율입니다. 이는 운영의 '진정성'을 보여주는 핵심 지표입니다.
@@ -271,32 +293,52 @@ function renderVitalityLeaderboard(data) {
1
-
동적 감쇄 계수(λ) 산출
-
자산 규모 및 조직 위험을 합산하여 개별 활력 곡선을 생성합니다.
-
λ = ${p.ai_lambda.toFixed(4)}
-
-
-
-
2
-
-
활동 진정성 검증
-
Factor = ${(p.log_quality * 100).toFixed(0)}%
-
-
-
-
3
-
-
가동 보존율 (AVI)
-
Result = ${avi.toFixed(1)}%
+
동적 위험 계수(λ) 산출
+
프로젝트 규모가 클수록 정보 망실 시의 충격을 반영하여 데이터의 하락 속도가 가속됩니다. 현재 λ=${p.ai_lambda.toFixed(4)}는 귀하의 자산 규모가 정밀하게 투영된 결과입니다.
+
Dynamic λ = ${p.ai_lambda.toFixed(4)}
4
-
가치 기여 영향력 (VCI)
-
VCI = ${vci.toFixed(1)}
+
활동 품질 검증 (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)}% +
@@ -317,6 +359,8 @@ function toggleProjectDetail(rowId) { 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; @@ -333,11 +377,11 @@ function openProjectListModal(label, projects) { const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); title.innerText = `[${label}] 프로젝트 리스트 (${projects.length}건)`; - body.innerHTML = projects.length === 0 ? '

대상 프로젝트 없음

' : ` + body.innerHTML = `
- - ${projects.map(p => ``).join('')} + + ${projects.map(p => ``).join('')}
프로젝트명부서관리자정체일활력(AVI)
${p.project_nm}${p.dept || '-'}${p.master || '-'}${p.days_stagnant}일${p.p_war.toFixed(1)}%
프로젝트명관리자정체일AVI
${p.project_nm}${p.master || '-'}${p.days_stagnant}일${p.p_war.toFixed(1)}%
`; @@ -361,29 +405,47 @@ function openAnalysisModal(type) { 70~90%Stable주기적 업데이트가 이뤄지는 표준 안정 30~70%Idle관리가 필요한 유휴/정체 상태 10~30%Risk자산 가치 소멸 직전의 위험 상태 - 10%↓Frozen운영이 중단된 동결/방치 상태 + 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시스템 가치를 견인하는 핵심 자산 (MVP급)
+2.0 ~ +10.0Blue Chip꾸준한 활력의 우량 자산 (주전급)
-2.0 ~ +2.0Steady표준 수준의 현상 유지 (보결급)
-10.0 ~ -2.0Underperform운영 미비로 인한 가치 하락 (마이너급)
-10.0↓Liability가치를 훼손 중인 방치 자산 (방출급)
+10.0↑Masterpiece시스템 가치를 견인하는 최우량 핵심 자산
+2.0 ~ +10.0Blue Chip꾸준한 활력으로 가치를 창출하는 우량 자산
-2.0 ~ +2.0Steady표준 수준의 운영을 유지 중인 안정 자산
-10.0 ~ -2.0Underperform규모 대비 활력 부족으로 가치 하락 중인 자산
-10.0↓Liability가치를 훼손 중인 고위험 방치 자산
`; - } else if (type === 'focus') { + } else if (type === 'oci') { + title.innerText = '운영 일관성 지수 (OCI) 분석 가이드'; + body.innerHTML = ` +
+ "얼마나 꾸준하게 관리되고 있는가?" +

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

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

단순 관리 로그를 제외한 실질적인 산출물 변화의 밀도입니다.

+

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

@@ -394,29 +456,6 @@ function openAnalysisModal(type) {
비율 (%)등급활동 성격
`; - } else { - title.innerText = '상태 예보 (AI Forecast) 분석 가이드'; - body.innerHTML = ` -
- "2주 뒤의 프로젝트 건강 상태를 예측합니다" -

단순한 현재 점수 나열이 아닌, 최근 활동의 가속도(Acceleration)변화 패턴을 AI가 분석하여 미래의 활력 지수(AVI)를 예보합니다.

-
- - - - - - - - - -
분석 결과상태 등급관리 가이드라인
AVI 상승↑성장 가속활동 모멘텀이 상승 중인 우수 자산
AVI 유지안정 유지현재의 리듬을 유지하는 표준 운영 상태
AVI 하락↓활력 저하정체 징후 포착, 관리 리소스 투입 검토
AVI 10%↓중단 위기단기 내 완전 방치 및 가치 소멸 위험
- -
- ※ 분석 알고리즘 안내:
- 파일 수의 실질적 증가가 없는 프로젝트는 '성장 가속' 예보를 받을 수 없도록 설계되어 있으며, 정체가 길어질수록 감쇄 가중치가 자동으로 강화됩니다. -
- `; } modal.style.display = 'flex'; } diff --git a/js/analysis.js_fragment_leaderboard b/js/analysis.js_fragment_leaderboard index f1de889..a00bd16 100644 --- a/js/analysis.js_fragment_leaderboard +++ b/js/analysis.js_fragment_leaderboard @@ -14,39 +14,46 @@ function renderPWarLeaderboard(data) { 방치일 상태 판정 - 현재 SOI + 활력 지수 (AVI) - 실무 투입 + 가치 기여 (VCI) + 업무 집중도 - AI 예보 (14d) + 운영 일관성 (OCI) ${sortedData.map((p, idx) => { const status = getStatusInfo(p.p_war, p.is_auto_delete); - const soi = p.p_war; - const pred = p.predicted_soi; + const avi = p.p_war; + const vci = p.risk_count; + const oci = p.oci_score || 0; const rowId = `project-${idx}`; - let trendIcon = ""; - if (pred !== null) { - const diff = pred - soi; - if (diff < -5) trendIcon = '▼ 급락'; - else if (diff < 0) trendIcon = '↘ 하락'; - else trendIcon = '↗ 유지'; + 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 baseLambda = 0.04; - const scaleImpact = Math.min(0.04, Math.log10(p.file_count + 1) * 0.008); - const envImpact = Math.max(0, p.ai_lambda - baseLambda - scaleImpact); - - // 존재 신뢰도 패널티 (ECV) - let ecvText = "100% (신뢰)"; - let ecvClass = "highlight-val"; - if (p.file_count === 0) { ecvText = "5% (유령 프로젝트 패널티)"; ecvClass = "highlight-penalty"; } - else if (p.file_count < 10) { ecvText = "40% (소규모 껍데기 패널티)"; ecvClass = "highlight-penalty"; } + // 활동 품질 텍스트 준비 + const qualityLabel = p.log_quality >= 1.0 ? '성과물 직결 실무 활동' : p.log_quality >= 0.7 ? '시스템 구조적 활동' : '단순 행정적 활동'; return ` ${p.file_count.toLocaleString()}개 ${p.days_stagnant}일 ${status.label} - - ${soi.toFixed(1)}% + + ${avi.toFixed(1)}% + + + ${vci >= 0 ? '+' : ''}${vci.toFixed(2)}
@@ -70,27 +80,29 @@ function renderPWarLeaderboard(data) {
- - ${pred !== null ? pred.toFixed(1) + '%' : '-'} + + ${oci}% + + + ${rhythmLabel} - ${trendIcon}
- +
⚙️ AI 위험 적응형 모델(AAS) 산출 시뮬레이션
- +
- 📊 실질 업무 활성화 분석 (Work Vitality) + 📊 실질 업무 집중도 분석 (Job Focus) - 투입률 ${p.work_effort}% + 집중도 ${p.work_effort}%
@@ -102,13 +114,13 @@ function renderPWarLeaderboard(data) {
- +
-
1
동적 위험 계수(λ) 산출
+
자산 규모(${p.file_count}개) 및 부서 위험도를 합산한 하락 속도입니다.
λ = ${p.ai_lambda.toFixed(4)}
@@ -117,34 +129,40 @@ function renderPWarLeaderboard(data) {
활동 품질 검증 (Quality)
- ${p.log_quality >= 1.0 ? '성과물 직결 실무 활동 감지' : p.log_quality >= 0.7 ? '시스템 구조적 활동 주류' : '단순 행정적 활동 판명'} + 최근 로그 분석 결과 ${qualityLabel}으로 판명되었습니다.
Factor = ${(p.log_quality * 100).toFixed(0)}%
-
2
방치 시간 감쇄 적용
-
Result = ${((soi / (p.file_count === 0 ? 0.05 : p.file_count < 10 ? 0.4 : 1) / p.log_quality) || 0).toFixed(1)}%
+
${p.days_stagnant}일간의 정체로 인한 가치 보존율입니다.
+
Result = ${((avi / (p.file_count === 0 ? 0.05 : p.file_count < 10 ? 0.4 : 1) / p.log_quality) || 0).toFixed(1)}%
3
존재 진정성 (ECV)
+
${ecvDesc}
Factor = ${ecvText}
- * 최종 점수는 위 4개 팩터의 연쇄 추론 결과입니다. +
+
+ 가치 기여도 (VCI): ${vci >= 0 ? '+' : ''}${vci.toFixed(2)} +
+
* AVI 70% 대비 프로젝트의 실질적 자산 하중 반영
+
- 최종 P-SOI: - ${soi.toFixed(1)}% + 최종 AVI: + ${avi.toFixed(1)}%
diff --git a/style/analysis.css b/style/analysis.css index 208ced2..e9d9690 100644 --- a/style/analysis.css +++ b/style/analysis.css @@ -204,7 +204,7 @@ .step-title { font-size: 12px; font-weight: 700; color: var(--text-main); margin-bottom: 4px; } .math-logic { font-family: 'Consolas', monospace; background: var(--bg-muted); padding: 4px 8px; border-radius: 4px; font-weight: 700; color: var(--text-main); font-size: 12px; display: inline-block; } -.final-result-area { margin-top: 20px; padding-top: 15px; border-top: 2px solid var(--primary-color); display: flex; justify-content: space-between; align-items: center; } +.final-result-area { margin-top: 20px; padding-top: 15px; display: flex; justify-content: space-between; align-items: center; } /* Modal Analysis Specific */ .modal-footer { diff --git a/style/common.css b/style/common.css index 9bbf6a0..00d5608 100644 --- a/style/common.css +++ b/style/common.css @@ -87,6 +87,7 @@ button { cursor: pointer; border: none; transition: all 0.2s ease; } .nav-item { padding: 4px 12px; border-radius: var(--radius-sm); color: rgba(255, 255, 255, 0.8); font-size: 14px; + cursor: pointer; } .nav-item:hover { background: var(--primary-lv-8); color: #fff; } .nav-item.active { background: var(--primary-lv-0); color: var(--primary-color) !important; font-weight: 700; } diff --git a/style/dashboard.css b/style/dashboard.css index 9d4331c..dd22710 100644 --- a/style/dashboard.css +++ b/style/dashboard.css @@ -43,10 +43,10 @@ header { display: flex; flex-direction: column; justify-content: center; gap: 2px; border-left: 5px solid transparent; } .activity-card:hover { transform: translateY(-2px); box-shadow: var(--box-shadow); } -.activity-card.active { background: #e8f5e9; border-left-color: #4DB251; } -.activity-card.warning { background: #fff8e1; border-left-color: #FFBF00; } -.activity-card.stale { background: #ffebee; border-left-color: var(--error-color); } -.activity-card.unknown { background: #f5f5f5; border-left-color: #9e9e9e; } +.activity-card.active { background: #e8f5e9; } +.activity-card.warning { background: #fff8e1; } +.activity-card.stale { background: #ffebee; } +.activity-card.unknown { background: #f5f5f5; } .activity-card .label { font-size: 11px; font-weight: 600; opacity: 0.7; } .activity-card .count { font-size: 20px; font-weight: 800; } @@ -98,7 +98,7 @@ header { .detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; } .detail-section h4 { font-size: 13px; margin-bottom: 12px; color: var(--text-main); - border-left: 3px solid var(--primary-color); padding-left: 10px; font-weight: 700; + padding-left: 10px; font-weight: 700; } /* Personnel & Activity Tables */ diff --git a/style/inquiries.css b/style/inquiries.css index b6711bf..164eede 100644 --- a/style/inquiries.css +++ b/style/inquiries.css @@ -49,17 +49,17 @@ .stat-value { font-size: 18px; font-weight: 700; color: #333; } /* Status Border Colors */ -.stat-item.total { border-top: 3px solid #1e5149; } +.stat-item.total { } .stat-item.total .stat-value { color: #1e5149; } -.stat-item.complete { border-top: 3px solid #2e7d32; } +.stat-item.complete { } .stat-item.complete .stat-value { color: #2e7d32; } -.stat-item.working { border-top: 3px solid #1565c0; } +.stat-item.working { } .stat-item.working .stat-value { color: #1565c0; } -.stat-item.checking { border-top: 3px solid #ef6c00; } +.stat-item.checking { } .stat-item.checking .stat-value { color: #ef6c00; } -.stat-item.pending { border-top: 3px solid #673ab7; } +.stat-item.pending { } .stat-item.pending .stat-value { color: #673ab7; } -.stat-item.unconfirmed { border-top: 3px solid #9e9e9e; } +.stat-item.unconfirmed { } .stat-item.unconfirmed .stat-value { color: #9e9e9e; } /* 3. Filters & Notice */ @@ -185,7 +185,6 @@ .detail-container { padding: 24px; - border-left: 6px solid #1e5149; background: #f9fafb; box-shadow: inset 0 4px 15px rgba(0,0,0,0.08); position: relative; @@ -223,7 +222,7 @@ .detail-label { font-weight: 700; color: #888; margin-right: 8px; } .detail-q-section { background: #f8f9fa; padding: 20px; border-radius: 8px; } -.detail-a-section { background: #f1f8f7; padding: 20px; border-radius: 8px; border-left: 5px solid #1e5149; } +.detail-a-section { background: #f1f8f7; padding: 20px; border-radius: 8px; } /* 6. Image Preview & Foldable Section */ .img-thumbnail { width: 32px; height: 32px; border-radius: 4px; object-fit: cover; border: 1px solid #ddd; cursor: pointer; transition: transform 0.2s; } diff --git a/style/mail.css b/style/mail.css index ff67104..01a3c24 100644 --- a/style/mail.css +++ b/style/mail.css @@ -34,7 +34,7 @@ display: flex; align-items: flex-start; transition: 0.2s; } .mail-item:hover { background: var(--bg-muted); } -.mail-item.active { background: var(--primary-lv-0); border-left: 4px solid var(--primary-color); } +.mail-item.active { background: var(--primary-lv-0); } .mail-item-checkbox { width: 16px; height: 16px; cursor: pointer; margin-right: 12px; margin-top: 2px; } .mail-item-content { flex: 1; min-width: 0; } diff --git a/templates/analysis.html b/templates/analysis.html index 630c45d..beb6c26 100644 --- a/templates/analysis.html +++ b/templates/analysis.html @@ -69,8 +69,8 @@

규모를 감지하여, 대형 프로젝트 정체 시 데이터 가치 하락 속도를 가속(Acceleration)시킵니다.

-
2. 조직적 위험 전염
-

소속 부서의 전반적인 활력이 낮을 경우, 개별 위험 지수를 상향 조정하여 시스템적 붕괴를 예보합니다.

+
2. 활동 시계열 관성 분석
+

최근 활동의 연속성을 분석하여, 단기 정체 시에도 과거의 운영 모멘텀을 반영하여 지수를 보정합니다.

3. 동적 가치 계수
@@ -98,7 +98,7 @@

Project Activity Vitality Leaderboard (AVI Status)

-

운영 표준(AVI 70%) 대비 파일 보존율 및 미래 가치 기여 리더보드

+

운영 표준(AVI 70%) 대비 운영 활력 및 VCI 기여 리더보드

* AVI (Activity Vitality Index)