AI_MMI_Analyser/app/core/reference_data.py

256 lines
8.1 KiB
Python

"""
통합 레퍼런스 데이터 관리자
SQLite 로컬 캐시 + Supabase 동기화 준비
"""
import os
import sqlite3
from typing import Dict, Optional, List
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from app.core.reference_data_defaults import (
DEFAULT_STATIONS,
DEFAULT_ERROR_CODES,
REFERENCE_DATA_VERSION
)
class DoorDirection(Enum):
DW = "DW" # Door West (Left)
DE = "DE" # Door East (Right)
@dataclass
class StationMeta:
code: int
name: str
pos: float
door_dir: DoorDirection
station_no: int
track_line: str # 'TC1' or 'TC2'
class ReferenceDataManager:
"""
통합 레퍼런스 데이터 관리자
- SQLite 로컬 캐싱
- 메모리 캐시로 빠른 조회
- Supabase 동기화 준비
"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(ReferenceDataManager, cls).__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self._initialized = True
# DB 경로 설정
self.db_path = Path(__file__).parent / "ref_cache.db"
# 메모리 캐시
self.tc1_stations: Dict[int, StationMeta] = {}
self.tc2_stations: Dict[int, StationMeta] = {}
self.station_name_map: Dict[str, int] = {}
self.error_code_map: Dict[str, str] = {}
self._is_loaded = False
# 초기화
self._init_db()
self._load_to_memory()
def _init_db(self):
"""DB 초기화 (첫 실행 시 기본 데이터로 생성)"""
db_exists = self.db_path.exists()
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 테이블 생성
cursor.execute("""
CREATE TABLE IF NOT EXISTS stations (
code INTEGER PRIMARY KEY,
name TEXT NOT NULL,
pos REAL NOT NULL,
door_dir TEXT NOT NULL,
station_no INTEGER NOT NULL,
track_line TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS metadata (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS error_codes (
code TEXT PRIMARY KEY,
description TEXT NOT NULL
)
""")
# 인덱스 생성
cursor.execute("CREATE INDEX IF NOT EXISTS idx_station_name ON stations(name)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_track_line ON stations(track_line)")
# 첫 실행 시 기본 데이터 삽입
if not db_exists:
print("[RefData] Initializing database with default data...")
# 역 정보 삽입
for station in DEFAULT_STATIONS:
cursor.execute("""
INSERT INTO stations (code, name, pos, door_dir, station_no, track_line)
VALUES (?, ?, ?, ?, ?, ?)
""", (
station["code"],
station["name"],
station["pos"],
station["door_dir"],
station["station_no"],
station["track_line"]
))
# 에러 코드 삽입
for code, desc in DEFAULT_ERROR_CODES.items():
cursor.execute("""
INSERT INTO error_codes (code, description)
VALUES (?, ?)
""", (code, desc))
# 버전 정보 삽입
cursor.execute("""
INSERT INTO metadata (key, value)
VALUES ('version', ?)
""", (REFERENCE_DATA_VERSION,))
conn.commit()
print(f"[RefData] Database initialized with version {REFERENCE_DATA_VERSION}")
conn.close()
def _load_to_memory(self):
"""SQLite에서 메모리로 로드 (빠른 조회를 위한 캐싱)"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 역 정보 로드
cursor.execute("SELECT code, name, pos, door_dir, station_no, track_line FROM stations")
rows = cursor.fetchall()
for row in rows:
code, name, pos, door_dir, station_no, track_line = row
station = StationMeta(
code=code,
name=name,
pos=pos,
door_dir=DoorDirection(door_dir),
station_no=station_no,
track_line=track_line
)
if track_line == "TC1":
self.tc1_stations[code] = station
elif track_line == "TC2":
self.tc2_stations[code] = station
# 이름 매핑 (TC1 우선)
if track_line == "TC1":
self.station_name_map[name] = code
# TC2 이름 매핑 (TC1에 없는 경우만)
for code, station in self.tc2_stations.items():
if station.name not in self.station_name_map:
self.station_name_map[station.name] = code
# 에러 코드 로드
cursor.execute("SELECT code, description FROM error_codes")
for code, desc in cursor.fetchall():
self.error_code_map[code] = desc
conn.close()
self._is_loaded = True
print(f"[RefData] Loaded from cache. TC1: {len(self.tc1_stations)}, TC2: {len(self.tc2_stations)}")
# ========== 조회 메서드 (기존 API 호환) ==========
def get_station_name(self, code: int) -> str:
"""역 코드로 역 이름 조회"""
if code in self.tc1_stations:
return self.tc1_stations[code].name
if code in self.tc2_stations:
return self.tc2_stations[code].name
return ""
def get_station_info(self, code: int) -> Optional[StationMeta]:
"""역 코드로 역 정보 조회"""
if code in self.tc1_stations:
return self.tc1_stations[code]
if code in self.tc2_stations:
return self.tc2_stations[code]
return None
def get_station_code(self, name: str) -> int:
"""역 이름으로 역 코드 조회"""
return self.station_name_map.get(name, 0)
def get_station_distance(self, station_name: str, is_tc1: bool) -> float:
"""역 이름으로 거리 조회"""
target_list = self.tc1_stations if is_tc1 else self.tc2_stations
for station in target_list.values():
if station.name == station_name:
return station.pos
return 0.0
def get_door_direction(self, station_name_or_code, is_tc1: bool) -> DoorDirection:
"""역 이름 또는 코드로 도어 방향 조회"""
target_list = self.tc1_stations if is_tc1 else self.tc2_stations
for station in target_list.values():
if isinstance(station_name_or_code, str):
if station.name == station_name_or_code:
return station.door_dir
else:
if station.code == station_name_or_code:
return station.door_dir
return DoorDirection.DW
# ========== Supabase 동기화 (추후 구현) ==========
async def sync_from_supabase(self):
"""
Supabase에서 최신 데이터 동기화
TODO: 추후 구현
"""
pass
def get_version(self) -> str:
"""현재 데이터 버전 조회"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT value FROM metadata WHERE key = 'version'")
result = cursor.fetchone()
conn.close()
return result[0] if result else "unknown"
# 전역 인스턴스
ref_data = ReferenceDataManager()