from dataclasses import dataclass, field from datetime import datetime, timezone from typing import Optional @dataclass class AnalysisResult: """vessel_analysis_results 테이블 28컬럼 매핑.""" mmsi: str timestamp: datetime # 분류 결과 vessel_type: str = 'UNKNOWN' confidence: float = 0.0 fishing_pct: float = 0.0 cluster_id: int = -1 season: str = 'UNKNOWN' # ALGO 01: 위치 zone: str = 'EEZ_OR_BEYOND' dist_to_baseline_nm: float = 999.0 # ALGO 02: 활동 상태 activity_state: str = 'UNKNOWN' ucaf_score: float = 0.0 ucft_score: float = 0.0 # ALGO 03: 다크 베셀 is_dark: bool = False gap_duration_min: int = 0 # ALGO 04: GPS 스푸핑 spoofing_score: float = 0.0 bd09_offset_m: float = 0.0 speed_jump_count: int = 0 # ALGO 05+06: 선단 cluster_size: int = 0 is_leader: bool = False fleet_role: str = 'NOISE' # ALGO 07: 위험도 risk_score: int = 0 risk_level: str = 'LOW' # ALGO 08: 환적 의심 is_transship_suspect: bool = False transship_pair_mmsi: str = '' transship_duration_min: int = 0 # 특징 벡터 features: dict = field(default_factory=dict) # 메타 analyzed_at: Optional[datetime] = None def __post_init__(self): if self.analyzed_at is None: self.analyzed_at = datetime.now(timezone.utc) def to_db_tuple(self) -> tuple: import json def _f(v: object) -> float: """numpy float → Python float 변환.""" return float(v) if v is not None else 0.0 def _i(v: object) -> int: """numpy int → Python int 변환.""" return int(v) if v is not None else 0 # features dict 내부 numpy 값도 변환 safe_features = {k: float(v) for k, v in self.features.items()} if self.features else {} return ( str(self.mmsi), self.analyzed_at, # analyzed_at (PK 파티션키) str(self.vessel_type), _f(self.confidence), _f(self.fishing_pct), _i(self.cluster_id), str(self.season), str(self.zone), # → zone_code _f(self.dist_to_baseline_nm), str(self.activity_state), _f(self.ucaf_score), _f(self.ucft_score), bool(self.is_dark), _i(self.gap_duration_min), _f(self.spoofing_score), _f(self.bd09_offset_m), _i(self.speed_jump_count), _i(self.cluster_id), # → fleet_cluster_id bool(self.is_leader), # → fleet_is_leader str(self.fleet_role), _i(self.risk_score), str(self.risk_level), bool(self.is_transship_suspect), # → transship_suspect str(self.transship_pair_mmsi), _i(self.transship_duration_min), json.dumps(safe_features), )