feat(market-intel): add scheduler attach plan preview
Some checks failed
CD Pipeline / deploy (push) Failing after 45s

This commit is contained in:
OoO
2026-05-18 15:48:59 +08:00
parent d2d8dbab65
commit a3fb476eed
8 changed files with 335 additions and 11 deletions

View File

@@ -0,0 +1,115 @@
"""市場情報排程掛載 preview。
只描述未來 scheduler job 的節奏、gate 與備援;不註冊 job、不啟動 crawler、
不寫 DB、不連外。
"""
SCHEDULER_JOBS = (
{
"key": "campaign_discovery_daily",
"label": "跨平台活動入口探索",
"cadence": "daily 09:20 Asia/Taipei",
"platforms": ["momo", "pchome", "coupang", "shopee"],
"entrypoint": "services.market_intel.jobs.discover_campaigns",
"max_runtime_minutes": 20,
"requires_external_network": True,
"requires_database_write": False,
"write_target": "market_crawler_runs preview only",
},
{
"key": "campaign_product_probe",
"label": "已核准活動頁商品探測",
"cadence": "manual-first, then every 4 hours for active campaigns",
"platforms": ["momo", "pchome", "coupang", "shopee"],
"entrypoint": "services.market_intel.jobs.probe_campaign_products",
"max_runtime_minutes": 30,
"requires_external_network": True,
"requires_database_write": True,
"write_target": "market_campaign_products / market_product_price_history",
},
{
"key": "product_match_review_seed",
"label": "商品比對審核候選生成",
"cadence": "daily 11:10 Asia/Taipei after discovery",
"platforms": ["momo", "pchome", "coupang", "shopee"],
"entrypoint": "services.market_intel.jobs.seed_product_matches",
"max_runtime_minutes": 15,
"requires_external_network": False,
"requires_database_write": True,
"write_target": "market_product_matches",
},
)
def build_scheduler_attach_plan(
*,
runtime_status,
mcp_fetch_gate,
schema_smoke,
):
"""建立排程掛載計畫;所有輸出都是 preview不會建立 scheduler job。"""
schema_passed = bool(
schema_smoke.get("passed")
or (schema_smoke.get("schema_smoke") or {}).get("passed")
)
gate_checks = {
"market_intel_enabled": bool(runtime_status.enabled),
"market_intel_crawler_enabled": bool(runtime_status.crawler_enabled),
"market_intel_write_enabled": bool(runtime_status.write_enabled),
"database_write_allowed": bool(runtime_status.database_write_allowed),
"schema_smoke_passed": schema_passed,
"mcp_fetch_gate_open": bool(mcp_fetch_gate.get("manual_fetch_gate_open")),
"scheduler_currently_detached": not bool(runtime_status.scheduler_attached),
"manual_operator_approval": False,
}
blocked_reasons = [
key for key, passed in gate_checks.items()
if not passed
]
return {
"mode": "scheduler_attach_plan_preview",
"ready_to_attach_scheduler": False,
"scheduler_attached": False,
"scheduler_registration_executed": False,
"crawler_job_started": False,
"external_network_executed": False,
"database_session_created": False,
"database_write_executed": False,
"database_commit_executed": False,
"writes_executed": False,
"would_write_database": False,
"job_count": len(SCHEDULER_JOBS),
"jobs": list(SCHEDULER_JOBS),
"gate_checks": gate_checks,
"blocked_reasons": blocked_reasons,
"attach_sequence": [
"先完成 market_* migration 與 platform seed 寫入驗證",
"MCP deploy preflight、activation runbook、fetch gate 必須全過",
"先以 manual discovery fetch 產生小樣本,確認 parser diagnostics 正常",
"只掛 campaign_discovery_daily觀察 24 小時後才評估 product probe",
"任何異常立即關閉 MARKET_INTEL_CRAWLER_ENABLED不影響既有 MOMO 排程",
],
"fallback_plan": [
{
"key": "feature_flag_pause",
"label": "關閉 MARKET_INTEL_CRAWLER_ENABLED 即可阻止新排程執行",
},
{
"key": "detach_market_jobs_only",
"label": "只移除 market_intel job不動既有 scheduler 與 momo-db",
},
{
"key": "manual_fetch_only_mode",
"label": "回到人工 fetch gate 模式,保留 UI/API preview",
},
],
"safe_boundaries": [
"do_not_attach_scheduler_from_api",
"do_not_run_external_crawler_from_preview",
"do_not_write_market_tables_before_migration",
"do_not_touch_existing_momo_scheduler_jobs",
"do_not_touch_momo_db_lifecycle",
],
}

View File

@@ -28,6 +28,7 @@ from services.market_intel.mcp_readiness import build_mcp_readiness_plan
from services.market_intel.migration_blueprint import build_migration_blueprint
from services.market_intel.platform_seed import build_platform_seed_rows
from services.market_intel.platform_seed_db_diff import build_platform_seed_db_diff_plan
from services.market_intel.scheduler_plan import build_scheduler_attach_plan
from services.market_intel.platform_seed_writer import (
build_platform_seed_writer_plan,
build_schema_smoke,
@@ -68,7 +69,7 @@ class MarketIntelRuntimeStatus:
class MarketIntelService:
"""市場情報入口服務,先集中 feature gate 與安全狀態。"""
phase = "phase_32_mcp_fetch_gate_preview"
phase = "phase_33_scheduler_plan_preview"
def get_runtime_status(self) -> MarketIntelRuntimeStatus:
return MarketIntelRuntimeStatus(
@@ -366,6 +367,16 @@ class MarketIntelService:
gate["phase"] = self.phase
return gate
def build_scheduler_plan(self):
"""回報市場情報排程掛載計畫;不註冊 job、不啟動 crawler。"""
plan = build_scheduler_attach_plan(
runtime_status=self.get_runtime_status(),
mcp_fetch_gate=self.build_mcp_fetch_gate(),
schema_smoke=build_schema_smoke(MARKET_INTEL_TABLES),
)
plan["phase"] = self.phase
return plan
def build_platform_seed_writer_plan(self, platform_code="all"):
"""建立 platform seed writer dry-run plan不建立 DB session。"""
seed_plan = self.build_platform_seed_plan(platform_code=platform_code)
@@ -445,6 +456,7 @@ class MarketIntelService:
mcp_deploy_preflight = self.build_mcp_deploy_preflight()
mcp_activation_runbook = self.build_mcp_activation_runbook()
mcp_fetch_gate = self.build_mcp_fetch_gate()
scheduler_plan = self.build_scheduler_plan()
checks = {
"schema_smoke_passed": bool(schema_smoke["passed"]),
"feature_flags_default_safe": bool(
@@ -489,6 +501,12 @@ class MarketIntelService:
and not mcp_fetch_gate["network_request_allowed"]
and not mcp_fetch_gate["external_network_executed"]
),
"scheduler_plan_preview_safe": bool(
scheduler_plan["mode"] == "scheduler_attach_plan_preview"
and not scheduler_plan["scheduler_registration_executed"]
and not scheduler_plan["crawler_job_started"]
and not scheduler_plan["database_write_executed"]
),
}
ready_for_production_deploy = all(checks.values())
blocked_reasons = [
@@ -617,6 +635,7 @@ class MarketIntelService:
"/api/market_intel/mcp_deploy_preflight",
"/api/market_intel/mcp_activation_runbook",
"/api/market_intel/mcp_fetch_gate",
"/api/market_intel/scheduler_plan",
],
"status": status.to_dict(),
"schema_smoke": schema_smoke,
@@ -636,4 +655,5 @@ class MarketIntelService:
"mcp_deploy_preflight": mcp_deploy_preflight,
"mcp_activation_runbook": mcp_activation_runbook,
"mcp_fetch_gate": mcp_fetch_gate,
"scheduler_plan": scheduler_plan,
}