fix(playwright): sudo 권한 없이 브라우저 의존성 해결 및 불필요한 파일 정리
Deploy Web Application / deploy (push) Successful in 6s
Deploy Web Application / deploy (push) Successful in 6s
- 호스트 환경에서 헤드리스 크롬을 실행할 수 있도록 Docker에서 필수 공유 라이브러리(libnss, libasound, libsoftokn 등)를 libs/ 폴더로 복사 - 데이터베이스 커스텀 포트(3307) 지원을 위해 서버 및 크롤러 설정 업데이트 - 요청에 따라 사용하지 않는 데이터베이스, 락파일, 개별 헬퍼 스크립트 및 문서 파일 삭제
This commit is contained in:
@@ -4,3 +4,5 @@ DB_HOST=localhost
|
||||
DB_USER=root
|
||||
DB_PASSWORD=45278434
|
||||
DB_NAME=PM_proto
|
||||
DB_PORT=3307
|
||||
|
||||
|
||||
@@ -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가 낮은 프로젝트**: "활동은 있으나 불규칙합니다. 관리의 지속성을 확보하여 운영 리듬을 찾으십시오."
|
||||
@@ -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**: 다양한 해상도에 대응하는 반응형 레이아웃 구성
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
-45
@@ -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()
|
||||
@@ -21,6 +21,7 @@ def get_db_connection():
|
||||
"""MySQL 데이터베이스 연결을 반환 (환경변수 기반)"""
|
||||
return pymysql.connect(
|
||||
host=os.getenv('DB_HOST', 'localhost'),
|
||||
port=int(os.getenv('DB_PORT', 3306)),
|
||||
user=os.getenv('DB_USER', 'root'),
|
||||
password=os.getenv('DB_PASSWORD', '45278434'),
|
||||
database=os.getenv('DB_NAME', 'PM_proto'),
|
||||
|
||||
@@ -21,6 +21,7 @@ def get_db_connection():
|
||||
"""MySQL 데이터베이스(TEST) 연결을 반환"""
|
||||
return pymysql.connect(
|
||||
host=os.getenv('DB_HOST', 'localhost'),
|
||||
port=int(os.getenv('DB_PORT', 3306)),
|
||||
user=os.getenv('DB_USER', 'root'),
|
||||
password=os.getenv('DB_PASSWORD', '45278434'),
|
||||
database='PM_proto_test', # 테스트용 DB 고정
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
libasound.so.2.0.0
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
-6
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "AICodeTest",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import os
|
||||
import sys
|
||||
import asyncio
|
||||
import pymysql
|
||||
from dotenv import load_dotenv
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import StreamingResponse, FileResponse
|
||||
@@ -16,6 +17,7 @@ from project_service import ProjectService
|
||||
from analysis_service import AnalysisService
|
||||
|
||||
# --- 환경 설정 ---
|
||||
load_dotenv()
|
||||
os.environ["PYTHONIOENCODING"] = "utf-8"
|
||||
TESSDATA_PREFIX = os.getenv("TESSDATA_PREFIX", r"C:\Users\User\AppData\Local\Programs\Tesseract-OCR\tessdata")
|
||||
os.environ["TESSDATA_PREFIX"] = TESSDATA_PREFIX
|
||||
@@ -41,6 +43,7 @@ def get_db_connection():
|
||||
"""MySQL 데이터베이스 연결을 반환"""
|
||||
return pymysql.connect(
|
||||
host=os.getenv('DB_HOST', 'localhost'),
|
||||
port=int(os.getenv('DB_PORT', 3306)),
|
||||
user=os.getenv('DB_USER', 'root'),
|
||||
password=os.getenv('DB_PASSWORD', '45278434'),
|
||||
database=os.getenv('DB_NAME', 'PM_proto'),
|
||||
|
||||
@@ -2,6 +2,7 @@ import os
|
||||
import sys
|
||||
import asyncio
|
||||
import pymysql
|
||||
from dotenv import load_dotenv
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import StreamingResponse, FileResponse
|
||||
@@ -16,6 +17,7 @@ from project_service import ProjectService
|
||||
from analysis_service import AnalysisService
|
||||
|
||||
# --- 환경 설정 ---
|
||||
load_dotenv()
|
||||
os.environ["PYTHONIOENCODING"] = "utf-8"
|
||||
TESSDATA_PREFIX = os.getenv("TESSDATA_PREFIX", r"C:\Users\User\AppData\Local\Programs\Tesseract-OCR\tessdata")
|
||||
os.environ["TESSDATA_PREFIX"] = TESSDATA_PREFIX
|
||||
@@ -41,6 +43,7 @@ def get_db_connection():
|
||||
"""MySQL 데이터베이스(TEST) 연결을 반환"""
|
||||
return pymysql.connect(
|
||||
host=os.getenv('DB_HOST', 'localhost'),
|
||||
port=int(os.getenv('DB_PORT', 3306)),
|
||||
user=os.getenv('DB_USER', 'root'),
|
||||
password=os.getenv('DB_PASSWORD', '45278434'),
|
||||
database='PM_proto_test', # 테스트용 DB로 고정
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user