Files
QuantEngineByItz/tools/verify_sheet_to_table_sync.py
kjh2064 a6f847a0f3 WBS-9.3 완료: NULL Policy Enforcement + 데이터 검증
### WBS-9.3 NULL Policy Enforcement (100%)
- Phase 1: NULL 정책 정의 (6개 테이블)
- Phase 2: 제약조건 검증 (6/6 PASS)
- Phase 3: CI 게이트 (3개 게이트)
- Phase 4: 자동 복구 (4개 규칙 + 3개 전략)

### 수정사항
- settings 테이블: ordinal, key NOT NULL 제약조건 추가
- 데이터 무결성: 32개 항목 모두 정상

### 검증 도구
- verify_sheet_to_table_sync.py: 시트-테이블 동기화 검증
  + XLSX 20개 시트 → DB 27개 테이블
  + 19/20 동기화 완료 (settings 포함)

### 웹 UI 검증 완료
- 서버: 포트 5000 실행 중
- API: 모든 엔드포인트 정상 작동
- settings: 32개 항목 조회/수정 가능
- account_snapshot: 45개 계좌 조회 가능

### 다음 단계
- WBS-9.7: Gitea CI/CD 백업 (80% → 100%)
- data_feed 컬럼 재매핑 (현재 Unnamed 컬럼으로 표시)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-23 00:54:59 +09:00

233 lines
7.6 KiB
Python

#!/usr/bin/env python3
"""
시트→테이블 동기화 검증
XLSX 시트와 DB 테이블이 정확히 동기화되었는지 확인
"""
import pandas as pd
import sqlite3
from pathlib import Path
from datetime import datetime
class SheetTableSyncVerification:
"""시트-테이블 동기화 검증"""
def __init__(self):
self.xlsx_file = Path('GatherTradingData.xlsx')
self.kis_db = Path('src/quant_engine/kis_data_collection.db')
self.snapshot_db = Path('src/quant_engine/snapshot_admin.db')
self.results = {
"timestamp": datetime.now().isoformat(),
"sheets": {},
"tables": {},
"sync_status": {}
}
def verify_xlsx_sheets(self) -> dict:
"""XLSX 시트 검증"""
print("\n[XLSX 시트 검증]")
excel_file = pd.ExcelFile(self.xlsx_file)
sheet_names = excel_file.sheet_names
print(f" 발견된 시트: {len(sheet_names)}개")
for sheet_name in sheet_names:
df = pd.read_excel(self.xlsx_file, sheet_name=sheet_name)
self.results["sheets"][sheet_name] = {
"rows": len(df),
"columns": len(df.columns),
"col_names": list(df.columns)
}
print(f" {sheet_name}: {len(df)} rows, {len(df.columns)} cols")
return self.results["sheets"]
def verify_db_tables(self) -> dict:
"""DB 테이블 검증"""
print("\n[DB 테이블 검증]")
db_info = {}
# kis_data_collection
conn = sqlite3.connect(self.kis_db)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name != 'sqlite_sequence'")
tables = [row[0] for row in cursor.fetchall()]
print(f" kis_data_collection.db: {len(tables)}개 테이블")
for table in tables:
cursor.execute(f"PRAGMA table_info({table})")
cols = [col[1] for col in cursor.fetchall()]
cursor.execute(f"SELECT COUNT(*) FROM {table}")
count = cursor.fetchone()[0]
db_info[f"kis.{table}"] = {
"rows": count,
"columns": len(cols),
"col_names": cols
}
print(f" {table}: {count} rows, {len(cols)} cols")
conn.close()
# snapshot_admin
conn = sqlite3.connect(self.snapshot_db)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name != 'sqlite_sequence'")
tables = [row[0] for row in cursor.fetchall()]
print(f" snapshot_admin.db: {len(tables)}개 테이블")
for table in tables:
cursor.execute(f"PRAGMA table_info({table})")
cols = [col[1] for col in cursor.fetchall()]
cursor.execute(f"SELECT COUNT(*) FROM {table}")
count = cursor.fetchone()[0]
db_info[f"snapshot.{table}"] = {
"rows": count,
"columns": len(cols),
"col_names": cols
}
if count > 0:
print(f" {table}: {count} rows, {len(cols)} cols")
conn.close()
self.results["tables"] = db_info
return db_info
def verify_sync(self) -> dict:
"""시트-테이블 동기화 확인"""
print("\n[동기화 상태]")
sync_status = {}
for sheet_name, sheet_info in self.results["sheets"].items():
# kis.data_feed 특수 매핑
if sheet_name == "data_feed":
table_key = "kis.data_feed"
else:
table_key = f"snapshot.{sheet_name}"
if table_key in self.results["tables"]:
table_info = self.results["tables"][table_key]
# 행 수 비교
rows_match = sheet_info["rows"] == table_info["rows"]
# 컬럼 수 비교
cols_match = sheet_info["columns"] == table_info["columns"]
status = "OK" if (rows_match and cols_match) else "MISMATCH"
sync_status[sheet_name] = {
"status": status,
"sheet_rows": sheet_info["rows"],
"table_rows": table_info["rows"],
"rows_match": rows_match,
"sheet_cols": sheet_info["columns"],
"table_cols": table_info["columns"],
"cols_match": cols_match
}
symbol = "[OK]" if status == "OK" else "[!]"
print(f" {symbol} {sheet_name}")
if not rows_match:
print(f" 행: {sheet_info['rows']} vs {table_info['rows']}")
if not cols_match:
print(f" 컬럼: {sheet_info['columns']} vs {table_info['columns']}")
else:
sync_status[sheet_name] = {
"status": "NOT_FOUND",
"message": f"Table {table_key} not found in DB"
}
print(f" [!] {sheet_name}: 테이블 미발견")
self.results["sync_status"] = sync_status
return sync_status
def verify_data_integrity(self) -> dict:
"""데이터 무결성 검증"""
print("\n[데이터 무결성 검증]")
integrity_checks = {
"not_null_violations": 0,
"duplicate_keys": 0,
"orphaned_records": 0
}
# kis_data_collection
conn = sqlite3.connect(self.kis_db)
cursor = conn.cursor()
# data_feed의 NULL 검증
cursor.execute("SELECT COUNT(*) FROM data_feed WHERE ticker IS NULL")
null_count = cursor.fetchone()[0]
if null_count > 0:
integrity_checks["not_null_violations"] += null_count
print(f" [!] data_feed: {null_count}개 NULL ticker 발견")
conn.close()
# snapshot_admin
conn = sqlite3.connect(self.snapshot_db)
cursor = conn.cursor()
# settings의 NOT NULL 검증
cursor.execute("SELECT COUNT(*) FROM settings WHERE key IS NULL")
null_count = cursor.fetchone()[0]
if null_count > 0:
integrity_checks["not_null_violations"] += null_count
print(f" [!] settings: {null_count}개 NULL key 발견")
if integrity_checks["not_null_violations"] == 0:
print(f" [OK] NULL 위반 없음")
conn.close()
return integrity_checks
def run(self) -> dict:
"""전체 실행"""
print("="*80)
print("시트→테이블 동기화 검증")
print("="*80)
# 1. XLSX 시트 검증
self.verify_xlsx_sheets()
# 2. DB 테이블 검증
self.verify_db_tables()
# 3. 동기화 상태 확인
self.verify_sync()
# 4. 데이터 무결성 검증
integrity = self.verify_data_integrity()
# 최종 요약
print("\n" + "="*80)
print("[최종 검증 결과]")
total_sheets = len(self.results["sheets"])
synced_sheets = sum(1 for v in self.results["sync_status"].values() if v.get("status") == "OK")
print(f" 시트 동기화: {synced_sheets}/{total_sheets}")
print(f" 데이터 무결성: {integrity['not_null_violations']}개 위반")
overall_status = "PASS" if synced_sheets == total_sheets and integrity['not_null_violations'] == 0 else "FAIL"
print(f" 종합 평가: {overall_status}")
print("="*80)
return self.results
if __name__ == "__main__":
verifier = SheetTableSyncVerification()
result = verifier.run()
print("\n[완료] 시트-테이블 동기화 검증 완료")