kcg-monitoring/prediction/time_bucket.py
htlee 2534c9dbca fix: time_bucket 수집 안전 윈도우 도입 — incremental fetch 데이터 누락 방지
snpdb 5분 버킷 데이터가 적재 완료까지 ~12분 소요되는데,
기존 fetch_incremental이 상한 없이 미완성 버킷을 수집하여
_last_bucket이 조기 전진 → 뒤늦게 완성된 행 영구 누락.

- time_bucket.py 신규: safe_bucket(12분 지연) + backfill(3 bucket)
- snpdb.py: fetch_all_tracks/fetch_incremental에 safe 상한 + 백필 하한
- vessel_store.py: merge_incremental sort+keep='last', evict_stale time_bucket 우선
- config.py: SNPDB_SAFE_DELAY_MIN=12, SNPDB_BACKFILL_BUCKETS=3

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 15:11:20 +09:00

43 lines
1.5 KiB
Python

from __future__ import annotations
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
from config import settings
_KST = ZoneInfo('Asia/Seoul')
_BUCKET_MINUTES = 5
def normalize_bucket_kst(bucket: datetime) -> datetime:
if bucket.tzinfo is None:
return bucket
return bucket.astimezone(_KST).replace(tzinfo=None)
def floor_bucket_kst(value: datetime, bucket_minutes: int = _BUCKET_MINUTES) -> datetime:
if value.tzinfo is None:
localized = value.replace(tzinfo=_KST)
else:
localized = value.astimezone(_KST)
floored_minute = (localized.minute // bucket_minutes) * bucket_minutes
return localized.replace(minute=floored_minute, second=0, microsecond=0)
def compute_safe_bucket(now: datetime | None = None) -> datetime:
current = now or datetime.now(timezone.utc)
if current.tzinfo is None:
current = current.replace(tzinfo=timezone.utc)
safe_point = current.astimezone(_KST) - timedelta(minutes=settings.SNPDB_SAFE_DELAY_MIN)
return floor_bucket_kst(safe_point).replace(tzinfo=None)
def compute_initial_window_start(hours: int, safe_bucket: datetime | None = None) -> datetime:
anchor = normalize_bucket_kst(safe_bucket or compute_safe_bucket())
return anchor - timedelta(hours=hours)
def compute_incremental_window_start(last_bucket: datetime) -> datetime:
normalized = normalize_bucket_kst(last_bucket)
return normalized - timedelta(minutes=settings.SNPDB_BACKFILL_BUCKETS * _BUCKET_MINUTES)