fix(playwright): resolve browser dependencies without sudo & cleanup unused files
All checks were successful
Deploy Web Application / deploy (push) Successful in 6s

- Copy necessary shared libraries (libnss, libasound, libsoftokn, etc.) from docker to libs/ to support headless chromium execution on host
- Update server and crawler configuration to support custom DB_PORT (3307)
- Remove unused databases, lockfiles, standalone helper scripts, and documentation files as requested
This commit is contained in:
2026-06-23 17:36:18 +09:00
parent 35cd1a20c3
commit 533e4c073d
36 changed files with 11 additions and 279 deletions

2
.env
View File

@@ -4,3 +4,5 @@ DB_HOST=localhost
DB_USER=root DB_USER=root
DB_PASSWORD=45278434 DB_PASSWORD=45278434
DB_NAME=PM_proto DB_NAME=PM_proto
DB_PORT=3307

View File

@@ -1,60 +0,0 @@
# 📊 시스템 운영 자산 가치 분석 보고 (Sabermetrics Report)
본 보고서는 프로젝트 관리 시스템 내에서 수집된 활동 로그 및 자산 데이터를 통계적/AI 기법으로 분석하여, 각 프로젝트의 운영 활력과 조직 기여도를 정량화한 지표를 정의합니다.
---
## 1. 운영 활력 지수 (AVI, Activity Vitality Index)
프로젝트가 현재 얼마나 건강하게 가동되고 있는지를 나타내는 **'디지털 자산 생존 지표'**입니다.
### 1.1 산출 공식
$$AVI = e^{-\lambda \times Stagnant\_Days} \times Quality \times 100$$
### 1.2 3대 핵심 변수 상세 설명
#### ① 지수 감쇄 모델 ($e^{-\lambda \times t}$) : "가치의 시한폭탄"
자산은 관리하지 않으면 시간이 흐를수록 가치가 기하급수적으로 소멸한다는 **'정보 휘발성'** 원리를 반영합니다.
* **Stagnant Days (정체 일수)**: 마지막 유효 활동 로그 기록일로부터 오늘까지 경과된 날짜입니다.
* **특징**: 정체 초기에는 점수가 빠르게 하락하다가, 시간이 지날수록 하락 폭이 둔화되며 0에 수렴합니다. 이는 관리가 중단된 직후의 정보 망실 위험이 가장 크다는 실무적 경험을 반영한 것입니다.
#### ② 위험 가속 계수 ($\lambda$) : "대형 자산의 높은 관리 비용"
모든 프로젝트는 자산 규모에 따라 '늙어가는 속도'가 다릅니다.
* **공식**: $\lambda = 0.04 + \log_{10}(Files + 1) \times 0.008$
* **비즈니스 로직**: 파일이 많은 대형 프로젝트일수록 관리 부재 시 조직에 미치는 타격이 큽니다. 따라서 대형 프로젝트일수록 $\lambda$ 값이 커지며, 소형 프로젝트보다 **훨씬 빠른 속도로 AVI가 하락**하도록 설계되었습니다. (대형 프로젝트는 더 자주 관리해야 점수가 유지됨)
#### ③ 활동 품질 가중치 ($Quality$) : "행정과 실무의 구분"
단순히 접속하거나 로그가 찍혔다고 해서 활력이 100% 회복되지 않습니다. AI가 로그 키워드를 분석하여 활동의 **'진정성'**을 평가합니다.
* **High (1.0)**: **성과물 중심 활동** (파일 업로드, 수정, 등록, 업데이트 등)
* **Medium (0.7)**: **구조적 유지 활동** (폴더 생성, 삭제, 이동 등)
* **Low (0.4)**: **단순 행정 활동** (권한 변경, 메일 확인, 참가자 추가 등)
---
## 2. 자산 가치 기여도 (VCI, Value Contribution Index)
야구의 **WAR(Wins Above Replacement)** 개념을 도입하여, 전체 포트폴리오 평균 대비 개별 프로젝트가 조직 가치에 얼마나 기여하는지 산출합니다.
### 2.1 산출 공식
$$VCI = (Individual\_AVI - Portfolio\_Avg\_AVI) \times Asset\_Weight$$
* **Asset Weight (파일 규모 가중치)**: $max(0.2, \frac{Individual\_Files}{Portfolio\_Avg\_Files})$
### 2.2 지표의 의미: "평균(0.0)을 기준으로 한 상대 평가"
* **0.0 (평균)**: 조직 내 평균적인 관리 수준과 규모를 가진 표준 프로젝트.
* **(+) 점수**: 평균 이상의 활력으로 조직의 디지털 자산 가치를 증대시키는 프로젝트.
* **(-) 점수**: 평균 이하의 방치로 인해 조직에 잠재적 기회비용 손실을 입히는 리스크 프로젝트.
* **상대 가중치**: 조직의 평균 파일 수보다 큰 프로젝트가 방치될 때 마이너스 점수가 더 가파르게 하락하여 **'우선 관리 대상'**을 명확히 식별합니다.
---
## 3. 강력한 예외 처리 (Hard Rules)
데이터의 신뢰도를 확보하기 위해 다음과 같은 **'사망 판정'** 규칙이 적용됩니다.
1. **자동 삭제 패널티**: 최근 로그가 시스템에 의한 '폴더자동삭제'인 경우, AVI는 즉시 **0.1%**로 고정됩니다. (관리 포기 상태)
2. **자산 부재 패널티 (ECV)**: 파일 개수가 0개인 경우 운영 일관성(OCI)은 **0.0점**이며, 파일 10개 미만은 최종 가중치에 **50% 패널티**를 적용하여 '껍데기 프로젝트'를 걸러냅니다.
---
## 4. 발표 및 분석 가이드 (Executive Summary)
* **AVI가 낮은 프로젝트**: "데이터가 낡아가고 있으니 즉시 최신 성과물을 업데이트하십시오."
* **VCI가 음수(-)인 대형 프로젝트**: "조직에서 가장 중요한 자산임에도 불구하고 평균 이하로 방치되고 있습니다. **최우선 관리 대상**입니다."
* **OCI가 낮은 프로젝트**: "활동은 있으나 불규칙합니다. 관리의 지속성을 확보하여 운영 리듬을 찾으십시오."

34
PLAN.md
View File

@@ -1,34 +0,0 @@
# 데이터 분석 관리자 페이지 기획안
## 1. 프로젝트 개요
본 프로젝트는 데이터 분석 프로세스 및 프로젝트 리소스를 통합 관리하기 위한 관리자 대시보드입니다. 사용자 인터랙션 관리부터 시스템 로그, 리소스 현황을 한눈에 파악하는 것을 목표로 합니다.
## 2. 주요 기능 상세
### ① 메일 관리 및 요구사항 시스템 (Mail & Inquiry Management) - [완료]
- **UI/UX 고도화**: 리스트 영역 너비 확장(400px) 및 시각적 가독성 개선
- **검색 및 필터링**: 키워드 및 기간별 메일 검색 기능 구현
- **동적 연동**: 리스트 클릭 시 메일 본문 실시간 업데이트 구현
- **메일 관리**: 개별 삭제 및 체크박스를 활용한 대량 삭제 기능 추가
- **탭 시스템**: 수신/발신/임시/휴지통별 데이터 분류 및 동적 렌더링 적용
### ② 로그 관리 (Log Management)
- **최근 로그**: 실시간으로 발생하는 시스템 및 분석 작업 로그 출력
- **전체 로그**: 날짜별, 프로젝트별 필터링을 통한 로그 기록 조회 및 내보내기
### ③ 파일 관리 (File Management)
- 프로젝트별 데이터셋, 분석 결과물 파일 개수 및 용량 통계
- 파일 확장자별 구성 비율(CSV, JSON, Python 등) 시각화 지표 제공
### ④ 인원 관리 (Personnel Management)
- 프로젝트 참여 인원 현황 조회
- 사용자별 권한(관리자, 분석가, 뷰어) 부여 및 수정 기능
### ③ 공지사항 (Notice & Patch Notes)
- 분석 모델 업데이트, 시스템 점검, 패치 내역 공유
- 사용자 대상 공지사항 작성 및 게시판 관리
## 3. UI/UX 가이드라인
- **Layout**: 좌측 내비게이션 바(Sidebar) + 상단 헤더(Header) + 중앙 컨텐츠 영역
- **Theme**: 신뢰감을 주는 Dark Blue / White 톤의 깨끗한 디자인
- **Responsiveness**: 다양한 해상도에 대응하는 반응형 레이아웃 구성

View File

@@ -1,48 +0,0 @@
import pymysql
import re
from collections import Counter
def get_db_connection():
return pymysql.connect(
host='localhost',
user='root',
password='45278434',
database='pm_proto_test',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
def analyze_logs():
conn = get_db_connection()
try:
with conn.cursor() as cursor:
cursor.execute("SELECT DISTINCT recent_log FROM projects_history WHERE recent_log IS NOT NULL AND recent_log != ''")
rows = cursor.fetchall()
logs = [r['recent_log'] for r in rows]
output = []
output.append("[Raw Log Samples]")
for log in logs[:20]:
output.append(f"- {log}")
patterns = []
for log in logs:
p = re.sub(r'\d{2,4}\.\d{2}\.\d{2}', '[DATE]', log)
p = re.sub(r'\d+', '[NUM]', p)
patterns.append(p)
output.append("\n[Log Patterns Frequency]")
pattern_counts = Counter(patterns).most_common(20)
for p, count in pattern_counts:
output.append(f"({count}) {p}")
with open("log_analysis_result.txt", "w", encoding="utf-8") as f:
f.write("\n".join(output))
print("Analysis complete. Result saved to log_analysis_result.txt")
finally:
conn.close()
if __name__ == "__main__":
analyze_logs()

View File

@@ -1,29 +0,0 @@
import pymysql
import os
from dotenv import load_dotenv
load_dotenv()
def show_tables():
conn = pymysql.connect(
host=os.getenv('DB_HOST', 'localhost'),
user=os.getenv('DB_USER', 'root'),
password=os.getenv('DB_PASSWORD', '45278434'),
database='PM_proto_test',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
try:
with conn.cursor() as cursor:
cursor.execute("SHOW TABLES")
tables = cursor.fetchall()
print("Tables in PM_proto_test:")
for t in tables:
print(f" - {list(t.values())[0]}")
except Exception as e:
print(f"Error occurred: {e}")
finally:
conn.close()
if __name__ == "__main__":
show_tables()

View File

@@ -1,29 +0,0 @@
import pymysql
import os
from dotenv import load_dotenv
load_dotenv()
def clear_project_history():
conn = pymysql.connect(
host=os.getenv('DB_HOST', 'localhost'),
user=os.getenv('DB_USER', 'root'),
password=os.getenv('DB_PASSWORD', '45278434'),
database='PM_proto_test',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
try:
with conn.cursor() as cursor:
# 테이블의 모든 데이터를 삭제
print("Cleaning projects_history table in PM_proto_test...")
cursor.execute("DELETE FROM projects_history")
conn.commit()
print("Successfully cleared all records from projects_history.")
except Exception as e:
print(f"Error occurred: {e}")
finally:
conn.close()
if __name__ == "__main__":
clear_project_history()

View File

@@ -1,45 +0,0 @@
import pymysql
import os
def clone_database():
try:
connection = pymysql.connect(
host=os.getenv('DB_HOST', 'localhost'),
user=os.getenv('DB_USER', 'root'),
password=os.getenv('DB_PASSWORD', '45278434'),
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
with connection.cursor() as cursor:
# 1. Create test database
cursor.execute("CREATE DATABASE IF NOT EXISTS PM_proto_test")
print("Database PM_proto_test created or already exists.")
# 2. Get all tables from source database
cursor.execute("SHOW TABLES FROM PM_proto")
tables = cursor.fetchall()
for table_row in tables:
table_name = list(table_row.values())[0]
# 3. Drop existing table in test DB if exists
cursor.execute(f"DROP TABLE IF EXISTS PM_proto_test.{table_name}")
# 4. Clone schema and data
# Note: CREATE TABLE ... LIKE doesn't copy data, and CREATE TABLE ... AS SELECT doesn't copy indexes.
# So we use LIKE first, then INSERT INTO ... SELECT *
cursor.execute(f"CREATE TABLE PM_proto_test.{table_name} LIKE PM_proto.{table_name}")
cursor.execute(f"INSERT INTO PM_proto_test.{table_name} SELECT * FROM PM_proto.{table_name}")
print(f"Table {table_name} cloned.")
connection.commit()
print("Database cloning completed successfully.")
except Exception as e:
print(f"Error during database cloning: {e}")
finally:
if 'connection' in locals():
connection.close()
if __name__ == "__main__":
clone_database()

View File

@@ -21,6 +21,7 @@ def get_db_connection():
"""MySQL 데이터베이스 연결을 반환 (환경변수 기반)""" """MySQL 데이터베이스 연결을 반환 (환경변수 기반)"""
return pymysql.connect( return pymysql.connect(
host=os.getenv('DB_HOST', 'localhost'), host=os.getenv('DB_HOST', 'localhost'),
port=int(os.getenv('DB_PORT', 3306)),
user=os.getenv('DB_USER', 'root'), user=os.getenv('DB_USER', 'root'),
password=os.getenv('DB_PASSWORD', '45278434'), password=os.getenv('DB_PASSWORD', '45278434'),
database=os.getenv('DB_NAME', 'PM_proto'), database=os.getenv('DB_NAME', 'PM_proto'),

View File

@@ -21,6 +21,7 @@ def get_db_connection():
"""MySQL 데이터베이스(TEST) 연결을 반환""" """MySQL 데이터베이스(TEST) 연결을 반환"""
return pymysql.connect( return pymysql.connect(
host=os.getenv('DB_HOST', 'localhost'), host=os.getenv('DB_HOST', 'localhost'),
port=int(os.getenv('DB_PORT', 3306)),
user=os.getenv('DB_USER', 'root'), user=os.getenv('DB_USER', 'root'),
password=os.getenv('DB_PASSWORD', '45278434'), password=os.getenv('DB_PASSWORD', '45278434'),
database='PM_proto_test', # 테스트용 DB 고정 database='PM_proto_test', # 테스트용 DB 고정

1
libs/libasound.so.2 Symbolic link
View File

@@ -0,0 +1 @@
libasound.so.2.0.0

BIN
libs/libasound.so.2.0.0 Normal file

Binary file not shown.

BIN
libs/libfreebl3.chk Normal file

Binary file not shown.

BIN
libs/libfreebl3.so Normal file

Binary file not shown.

BIN
libs/libfreeblpriv3.chk Normal file

Binary file not shown.

BIN
libs/libfreeblpriv3.so Normal file

Binary file not shown.

BIN
libs/libnspr4.so Normal file

Binary file not shown.

BIN
libs/libnss3.so Normal file

Binary file not shown.

BIN
libs/libnss_compat.so.2 Normal file

Binary file not shown.

BIN
libs/libnss_dns.so.2 Normal file

Binary file not shown.

BIN
libs/libnss_files.so.2 Normal file

Binary file not shown.

BIN
libs/libnss_hesiod.so.2 Normal file

Binary file not shown.

BIN
libs/libnss_systemd.so.2 Normal file

Binary file not shown.

BIN
libs/libnssckbi.so Normal file

Binary file not shown.

BIN
libs/libnssdbm3.chk Normal file

Binary file not shown.

BIN
libs/libnssdbm3.so Normal file

Binary file not shown.

BIN
libs/libnssutil3.so Normal file

Binary file not shown.

BIN
libs/libplc4.so Normal file

Binary file not shown.

BIN
libs/libplds4.so Normal file

Binary file not shown.

BIN
libs/libsmime3.so Normal file

Binary file not shown.

BIN
libs/libsoftokn3.chk Normal file

Binary file not shown.

BIN
libs/libsoftokn3.so Normal file

Binary file not shown.

6
package-lock.json generated
View File

@@ -1,6 +0,0 @@
{
"name": "AICodeTest",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View File

View File

@@ -2,6 +2,7 @@ import os
import sys import sys
import asyncio import asyncio
import pymysql import pymysql
from dotenv import load_dotenv
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, FileResponse from fastapi.responses import StreamingResponse, FileResponse
@@ -16,6 +17,7 @@ from project_service import ProjectService
from analysis_service import AnalysisService from analysis_service import AnalysisService
# --- 환경 설정 --- # --- 환경 설정 ---
load_dotenv()
os.environ["PYTHONIOENCODING"] = "utf-8" os.environ["PYTHONIOENCODING"] = "utf-8"
TESSDATA_PREFIX = os.getenv("TESSDATA_PREFIX", r"C:\Users\User\AppData\Local\Programs\Tesseract-OCR\tessdata") TESSDATA_PREFIX = os.getenv("TESSDATA_PREFIX", r"C:\Users\User\AppData\Local\Programs\Tesseract-OCR\tessdata")
os.environ["TESSDATA_PREFIX"] = TESSDATA_PREFIX os.environ["TESSDATA_PREFIX"] = TESSDATA_PREFIX
@@ -41,6 +43,7 @@ def get_db_connection():
"""MySQL 데이터베이스 연결을 반환""" """MySQL 데이터베이스 연결을 반환"""
return pymysql.connect( return pymysql.connect(
host=os.getenv('DB_HOST', 'localhost'), host=os.getenv('DB_HOST', 'localhost'),
port=int(os.getenv('DB_PORT', 3306)),
user=os.getenv('DB_USER', 'root'), user=os.getenv('DB_USER', 'root'),
password=os.getenv('DB_PASSWORD', '45278434'), password=os.getenv('DB_PASSWORD', '45278434'),
database=os.getenv('DB_NAME', 'PM_proto'), database=os.getenv('DB_NAME', 'PM_proto'),

View File

@@ -2,6 +2,7 @@ import os
import sys import sys
import asyncio import asyncio
import pymysql import pymysql
from dotenv import load_dotenv
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, FileResponse from fastapi.responses import StreamingResponse, FileResponse
@@ -16,6 +17,7 @@ from project_service import ProjectService
from analysis_service import AnalysisService from analysis_service import AnalysisService
# --- 환경 설정 --- # --- 환경 설정 ---
load_dotenv()
os.environ["PYTHONIOENCODING"] = "utf-8" os.environ["PYTHONIOENCODING"] = "utf-8"
TESSDATA_PREFIX = os.getenv("TESSDATA_PREFIX", r"C:\Users\User\AppData\Local\Programs\Tesseract-OCR\tessdata") TESSDATA_PREFIX = os.getenv("TESSDATA_PREFIX", r"C:\Users\User\AppData\Local\Programs\Tesseract-OCR\tessdata")
os.environ["TESSDATA_PREFIX"] = TESSDATA_PREFIX os.environ["TESSDATA_PREFIX"] = TESSDATA_PREFIX
@@ -41,6 +43,7 @@ def get_db_connection():
"""MySQL 데이터베이스(TEST) 연결을 반환""" """MySQL 데이터베이스(TEST) 연결을 반환"""
return pymysql.connect( return pymysql.connect(
host=os.getenv('DB_HOST', 'localhost'), host=os.getenv('DB_HOST', 'localhost'),
port=int(os.getenv('DB_PORT', 3306)),
user=os.getenv('DB_USER', 'root'), user=os.getenv('DB_USER', 'root'),
password=os.getenv('DB_PASSWORD', '45278434'), password=os.getenv('DB_PASSWORD', '45278434'),
database='PM_proto_test', # 테스트용 DB로 고정 database='PM_proto_test', # 테스트용 DB로 고정

View File

@@ -1,28 +0,0 @@
import pymysql
from analysis_service import AnalysisService
def verify_analysis():
conn = pymysql.connect(
host='localhost',
user='root',
password='45278434',
database='pm_proto_test',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
try:
with conn.cursor() as cursor:
results = AnalysisService.get_p_zsr_analysis_logic(cursor)
print(f"Total Projects Analyzed: {len(results)}")
print("\n[Sample Project Analysis Result]")
for res in results[:5]:
print(f"Project: {res['project_nm']}")
print(f" - Log Quality Score (Semantic): {res['log_quality']}")
print(f" - AVI Score (p_war): {res['p_war']}")
print(f" - OCI Score: {res['oci_score']}")
print("-" * 30)
finally:
conn.close()
if __name__ == "__main__":
verify_analysis()