WBS-9 진행: API 에러 수정 및 DB 스키마 정규화
- snapshot_admin_store_v1.py: summarize_workspace에서 account_snapshot의 captured_at 사용 (updated_at 대신) - account_snapshot 테이블: 올바른 스키마 재정의 (ordinal PK, row_json, 핵심필드, updated_at) - settings 테이블: 올바른 스키마 재정의 (ordinal PK, key, value_json, note, updated_at) - initialize_snapshot_admin_db.py: XLSX에서 settings, account_snapshot을 올바른 스키마로 로드 - load_from_xlsx_correct.py: account_snapshot을 특별 처리해서 스키마 보존 - /api/settings/save: 정상 작동 (200 응답) - build_ui_state: load_collection_dashboard_state 예외 처리 추가 (진행 중) 데이터 현황: - kis_data_collection.db: 1 테이블 (data_feed), 25행 - snapshot_admin.db: 27 테이블, 7,501행 * settings: 32행 (올바른 스키마) * account_snapshot: 44행 (올바른 스키마) 남은 작업: - /api/state 크래시 원인 진단 및 수정 - /api/export 데이터 검증 - 웹 UI 개선 (백오피스 수준) - T+20 모니터링 활성화 - CI/CD 백업 기능 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,7 @@ class CorrectXLSXLoader:
|
||||
return data.get('metadata', {})
|
||||
|
||||
def load_excel_sheets(self, metadata: dict) -> dict:
|
||||
"""Excel에서 올바른 header를 사용해서 모든 시트 로드"""
|
||||
"""Excel에서 올바른 header를 사용해서 모든 시트 로드 (account_snapshot 제외)"""
|
||||
print("[로드 중] Excel 파일 읽기...")
|
||||
|
||||
sheet_headers = metadata.get('sheet_headers', {})
|
||||
@@ -43,6 +43,11 @@ class CorrectXLSXLoader:
|
||||
|
||||
sheets_data = {}
|
||||
for sheet_name in sheet_names:
|
||||
# account_snapshot은 건너뛴다 (별도 처리)
|
||||
if sheet_name == 'account_snapshot':
|
||||
print(f" [SKIP] {sheet_name} (수동 처리)")
|
||||
continue
|
||||
|
||||
# metadata에서 header_row_1based 읽기
|
||||
header_info = sheet_headers.get(sheet_name, {})
|
||||
header_row_1based = header_info.get('header_row_1based', 1)
|
||||
@@ -86,7 +91,13 @@ class CorrectXLSXLoader:
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
df.to_sql(sheet_name, conn, if_exists='replace', index=False)
|
||||
|
||||
# account_snapshot은 특별하게 처리: 스키마를 보존하면서 데이터만 추가
|
||||
if sheet_name == 'account_snapshot':
|
||||
self._load_account_snapshot(conn, df)
|
||||
else:
|
||||
df.to_sql(sheet_name, conn, if_exists='replace', index=False)
|
||||
|
||||
conn.close()
|
||||
|
||||
print(f" [OK] {sheet_name}: {len(df)} rows → {db_path.name}")
|
||||
@@ -100,6 +111,45 @@ class CorrectXLSXLoader:
|
||||
print(f" [FAIL] {sheet_name}: {str(e)[:80]}")
|
||||
self.results["errors"].append(sheet_name)
|
||||
|
||||
def _load_account_snapshot(self, conn: sqlite3.Connection, df: pd.DataFrame) -> None:
|
||||
"""account_snapshot 데이터를 올바른 스키마로 로드"""
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
cursor = conn.cursor()
|
||||
timestamp = datetime.now().isoformat()
|
||||
|
||||
# 기존 데이터 삭제 (옵션: DELETE 또는 유지)
|
||||
cursor.execute("DELETE FROM account_snapshot")
|
||||
|
||||
for ordinal, row in enumerate(df.iterrows(), start=1):
|
||||
idx, series = row
|
||||
row_dict = series.to_dict()
|
||||
|
||||
# row_json으로 저장
|
||||
row_json = json.dumps(row_dict, default=str, ensure_ascii=False)
|
||||
|
||||
# 핵심 필드 추출
|
||||
captured_at = str(row_dict.get('captured_at', ''))
|
||||
account = str(row_dict.get('account', ''))
|
||||
account_type = str(row_dict.get('account_type', ''))
|
||||
ticker = str(row_dict.get('ticker', ''))
|
||||
name = str(row_dict.get('name', ''))
|
||||
parse_status = str(row_dict.get('parse_status', ''))
|
||||
user_confirmed = str(row_dict.get('user_confirmed', ''))
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO account_snapshot (
|
||||
ordinal, row_json, captured_at, account, account_type, ticker, name,
|
||||
parse_status, user_confirmed, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
ordinal, row_json, captured_at, account, account_type, ticker, name,
|
||||
parse_status, user_confirmed, timestamp
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
|
||||
def verify(self) -> None:
|
||||
"""로드 검증"""
|
||||
print("\n[검증 중...]")
|
||||
|
||||
Reference in New Issue
Block a user