83 lines
2.5 KiB
Python
83 lines
2.5 KiB
Python
"""市場情報 Phase 1 骨架服務。
|
|
|
|
本階段只回報 feature flag 與 rollout 狀態,不啟動爬蟲、不寫資料庫。
|
|
"""
|
|
|
|
from dataclasses import asdict, dataclass
|
|
from datetime import datetime, timedelta, timezone
|
|
from uuid import uuid4
|
|
|
|
from config import (
|
|
MARKET_INTEL_CRAWLER_ENABLED,
|
|
MARKET_INTEL_ENABLED,
|
|
MARKET_INTEL_WRITE_ENABLED,
|
|
)
|
|
|
|
|
|
TAIPEI_TZ = timezone(timedelta(hours=8))
|
|
MARKET_INTEL_TABLES = (
|
|
"market_platforms",
|
|
"market_campaigns",
|
|
"market_campaign_snapshots",
|
|
"market_campaign_products",
|
|
"market_product_price_history",
|
|
"market_product_matches",
|
|
"market_crawler_runs",
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MarketIntelRuntimeStatus:
|
|
"""市場情報模組目前的啟用狀態。"""
|
|
|
|
phase: str
|
|
enabled: bool
|
|
crawler_enabled: bool
|
|
write_enabled: bool
|
|
dry_run_only: bool
|
|
scheduler_attached: bool
|
|
database_write_allowed: bool
|
|
|
|
def to_dict(self):
|
|
return asdict(self)
|
|
|
|
|
|
class MarketIntelService:
|
|
"""市場情報入口服務,先集中 feature gate 與安全狀態。"""
|
|
|
|
phase = "phase_2_schema_ready_disabled"
|
|
|
|
def get_runtime_status(self) -> MarketIntelRuntimeStatus:
|
|
return MarketIntelRuntimeStatus(
|
|
phase=self.phase,
|
|
enabled=MARKET_INTEL_ENABLED,
|
|
crawler_enabled=MARKET_INTEL_CRAWLER_ENABLED,
|
|
write_enabled=MARKET_INTEL_WRITE_ENABLED,
|
|
dry_run_only=not MARKET_INTEL_WRITE_ENABLED,
|
|
scheduler_attached=False,
|
|
database_write_allowed=(
|
|
MARKET_INTEL_ENABLED
|
|
and MARKET_INTEL_CRAWLER_ENABLED
|
|
and MARKET_INTEL_WRITE_ENABLED
|
|
),
|
|
)
|
|
|
|
def get_schema_tables(self):
|
|
"""回傳 ADR-035 定義的 market_* schema 名稱。"""
|
|
return list(MARKET_INTEL_TABLES)
|
|
|
|
def build_dry_run_plan(self, platform_code="all"):
|
|
"""建立 dry-run 計畫,不執行爬蟲、不寫 DB。"""
|
|
status = self.get_runtime_status()
|
|
return {
|
|
"batch_id": f"market-dry-run-{uuid4().hex[:12]}",
|
|
"platform_code": platform_code,
|
|
"created_at": datetime.now(TAIPEI_TZ).replace(tzinfo=None).isoformat(),
|
|
"phase": self.phase,
|
|
"would_discover_campaigns": bool(status.enabled and status.crawler_enabled),
|
|
"would_write_database": bool(status.database_write_allowed),
|
|
"scheduler_attached": status.scheduler_attached,
|
|
"schema_tables": self.get_schema_tables(),
|
|
"status": status.to_dict(),
|
|
}
|