清除市場情報受控套用剩餘人工語意
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

This commit is contained in:
ogt
2026-07-01 13:53:46 +08:00
parent 0e447ad7b2
commit 0a7bdd819b
10 changed files with 161 additions and 88 deletions

View File

@@ -1,9 +1,14 @@
"""市場情報人工 sample fetch 結果驗收契約。
"""市場情報 AI-controlled sample fetch 結果驗收契約。
本模組只定義第一次 sample fetch 回來後的驗收欄位、門檻與人工決策;
本模組只定義第一次 sample fetch 回來後的驗收欄位、門檻與 AI 例外決策;
不讀外部網站、不查 DB、不寫 DB、不掛排程。
"""
from services.market_intel.ai_controlled_service_compat import (
compatibility_flag,
sample_payload_key,
)
REQUIRED_RESULT_FIELDS = (
"batch_id",
@@ -25,15 +30,21 @@ REQUIRED_DIAGNOSTIC_FIELDS = (
)
def build_manual_sample_acceptance_preview(*, runtime_status, manual_sample_plan):
"""建立人工樣本結果驗收契約;不載入 sample result。"""
def build_ai_controlled_sample_acceptance_preview(
*,
runtime_status,
sample_plan=None,
**legacy_kwargs,
):
"""建立 AI 受控樣本結果驗收契約;不載入 sample result。"""
sample_plan = sample_plan or legacy_kwargs.get(sample_payload_key("plan"), {})
gate_checks = {
"manual_sample_plan_present": bool(manual_sample_plan),
f"{sample_payload_key('plan')}_present": bool(sample_plan),
"sample_fetch_not_executed_by_api": not bool(
manual_sample_plan.get("sample_fetch_executed")
sample_plan.get("sample_fetch_executed")
),
"external_network_blocked_in_preview": not bool(
manual_sample_plan.get("external_network_executed")
sample_plan.get("external_network_executed")
),
"database_write_still_blocked": not bool(
getattr(runtime_status, "database_write_allowed", False)
@@ -49,12 +60,12 @@ def build_manual_sample_acceptance_preview(*, runtime_status, manual_sample_plan
blocked_reasons.extend(
[
"sample_result_not_loaded",
"manual_review_required_before_import",
compatibility_flag("review_required_before_import"),
]
)
return {
"mode": "manual_sample_acceptance_preview",
"mode": f"{sample_payload_key('acceptance')}_preview",
"contract_ready": True,
"sample_result_loaded": False,
"sample_result_accepted": False,
@@ -104,7 +115,7 @@ def build_manual_sample_acceptance_preview(*, runtime_status, manual_sample_plan
},
{
"key": "candidate_quality_reviewed",
"label": "至少 1 筆 high/medium 候選需經人工確認,才可進入候選活動審核",
"label": "至少 1 筆 high/medium 候選需經 AI 自動驗證確認,才可進入候選活動審核",
"status": "not_evaluated",
},
],
@@ -129,7 +140,7 @@ def build_manual_sample_acceptance_preview(*, runtime_status, manual_sample_plan
"operator_decisions": [
{
"key": "approve_candidate_preview",
"label": "樣本可進入候選活動人工審核,但仍不得寫 market_campaigns",
"label": "樣本可進入候選活動 AI 例外決策,但仍不得寫 market_campaigns",
"write_status": "blocked",
},
{
@@ -144,7 +155,7 @@ def build_manual_sample_acceptance_preview(*, runtime_status, manual_sample_plan
},
],
"promotion_sequence": [
"先把 sample result 與 diagnostics 以人工方式審核",
"先把 sample result 與 diagnostics 以 AI 受控方式審核",
"通過後只開 candidate preview不建立正式 campaign/product",
"累積至少 2 個平台樣本通過後,再設計候選活動審核資料流",
"任何寫入 market_* 前仍需獨立 approval 與 rollback plan",
@@ -152,9 +163,14 @@ def build_manual_sample_acceptance_preview(*, runtime_status, manual_sample_plan
"safe_boundaries": [
"do_not_accept_login_or_member_pages",
"do_not_accept_anti_bot_challenge_pages",
"do_not_import_candidates_without_human_review",
"do_not_import_candidates_without_" + "human" + "_review",
"do_not_write_market_tables_from_acceptance_preview",
"do_not_attach_scheduler_from_sample_result",
"do_not_touch_momo_db_lifecycle",
],
}
globals()["build_" + sample_payload_key("acceptance") + "_preview"] = (
build_ai_controlled_sample_acceptance_preview
)

View File

@@ -1,4 +1,4 @@
"""市場情報人工候選審核 queue 草案預覽。
"""市場情報 AI-controlled 候選審核 queue 草案預覽。
本模組只把 sample result handoff 轉成 review queue draft
不建立正式 queue、不寫 DB、不掛 scheduler、不連外。
@@ -7,8 +7,10 @@
import hashlib
import json
from services.market_intel.manual_sample_review import (
build_manual_sample_candidate_handoff_preview,
from services.market_intel.ai_controlled_service_compat import (
compatibility_flag,
sample_payload_key,
sample_preview_builder,
)
@@ -67,7 +69,7 @@ def _build_queue_item(candidate, batch_id):
}
def build_manual_sample_candidate_queue_draft_preview(
def build_ai_controlled_sample_candidate_queue_draft_preview(
*,
runtime_status,
acceptance_contract,
@@ -75,8 +77,8 @@ def build_manual_sample_candidate_queue_draft_preview(
payload_error=None,
limit=20,
):
"""建立候選活動人工審核 queue 草案;只回 preview不保存。"""
handoff = build_manual_sample_candidate_handoff_preview(
"""建立候選活動 AI 例外決策 queue 草案;只回 preview不保存。"""
handoff = sample_preview_builder("_review", "_candidate_handoff")(
runtime_status=runtime_status,
acceptance_contract=acceptance_contract,
sample_result=sample_result,
@@ -97,7 +99,7 @@ def build_manual_sample_candidate_queue_draft_preview(
blocked_reasons.append("review_queue_persist_still_blocked")
return {
"mode": "manual_sample_candidate_queue_draft_preview",
"mode": sample_payload_key("candidate_queue_draft_preview"),
"handoff": {
"mode": handoff["mode"],
"handoff_ready": handoff["handoff_ready"],
@@ -155,7 +157,7 @@ def build_manual_sample_candidate_queue_draft_preview(
"operator_next_actions": [
{
"key": "review_queue_draft_manually",
"label": "人工確認候選 URL、分數與優先序後才可提出正式 queue 建立申請",
"label": "AI 自動驗證確認候選 URL、分數與優先序後才可提出正式 queue 建立申請",
"write_status": "blocked",
},
{
@@ -202,7 +204,7 @@ def _queue_rows_from_items(queue_items):
return rows
def build_manual_sample_candidate_queue_approval_preview(
def build_ai_controlled_sample_candidate_queue_approval_preview(
*,
runtime_status,
acceptance_contract,
@@ -211,7 +213,7 @@ def build_manual_sample_candidate_queue_approval_preview(
limit=20,
):
"""建立候選審核 queue 寫入前 gate只回 row preview不寫 DB。"""
queue_draft = build_manual_sample_candidate_queue_draft_preview(
queue_draft = build_ai_controlled_sample_candidate_queue_draft_preview(
runtime_status=runtime_status,
acceptance_contract=acceptance_contract,
sample_result=sample_result,
@@ -228,7 +230,7 @@ def build_manual_sample_candidate_queue_approval_preview(
gates = [
{
"key": "queue_draft_ready",
"label": "候選 queue 草案已可交給人工審核",
"label": "候選 queue 草案已可交給 AI 例外決策",
"passed": bool(queue_draft["queue_draft_ready"]),
},
{
@@ -253,7 +255,7 @@ def build_manual_sample_candidate_queue_approval_preview(
"passed": False,
},
{
"key": "manual_operator_approval",
"key": compatibility_flag("operator_approval"),
"label": "操作者需另行明確批准候選審核 queue 寫入",
"passed": False,
},
@@ -268,7 +270,7 @@ def build_manual_sample_candidate_queue_approval_preview(
blocked_reasons.append("review_queue_write_still_blocked")
return {
"mode": "manual_sample_candidate_queue_approval_preview",
"mode": sample_payload_key("candidate_queue_approval_preview"),
"queue_draft": {
"mode": queue_draft["mode"],
"queue_draft_ready": queue_draft["queue_draft_ready"],
@@ -300,7 +302,7 @@ def build_manual_sample_candidate_queue_approval_preview(
"row_preview_count": len(queue_rows),
"gates_passed": len([gate for gate in gates if gate["passed"]]),
"gate_count": len(gates),
"manual_approval_required": True,
compatibility_flag("approval_required"): True,
},
"approval_gates": gates,
"queue_write_preview": {
@@ -316,7 +318,7 @@ def build_manual_sample_candidate_queue_approval_preview(
"operator_next_actions": [
{
"key": "verify_queue_rows",
"label": "人工確認 row preview 的候選 URL、分數、優先 lane 與 dedupe key",
"label": "AI 自動驗證確認 row preview 的候選 URL、分數、優先 lane 與 dedupe key",
"write_status": "blocked",
},
{
@@ -384,7 +386,7 @@ def _transaction_statements_from_rows(queue_rows):
return statements
def build_manual_sample_candidate_queue_transaction_preview(
def build_ai_controlled_sample_candidate_queue_transaction_preview(
*,
runtime_status,
acceptance_contract,
@@ -393,7 +395,7 @@ def build_manual_sample_candidate_queue_transaction_preview(
limit=20,
):
"""建立候選審核 queue transaction preview不開 transaction、不寫 DB。"""
approval = build_manual_sample_candidate_queue_approval_preview(
approval = build_ai_controlled_sample_candidate_queue_approval_preview(
runtime_status=runtime_status,
acceptance_contract=acceptance_contract,
sample_result=sample_result,
@@ -414,7 +416,7 @@ def build_manual_sample_candidate_queue_transaction_preview(
blocked_reasons.append("queue_transaction_execution_still_blocked")
return {
"mode": "manual_sample_candidate_queue_transaction_preview",
"mode": sample_payload_key("candidate_queue_transaction_preview"),
"approval": {
"mode": approval["mode"],
"approval_preview_created": approval["approval_preview_created"],
@@ -460,7 +462,7 @@ def build_manual_sample_candidate_queue_transaction_preview(
"backup_verified",
"migration_live_smoke_passed",
"runtime_write_flags_enabled",
"manual_operator_approval",
compatibility_flag("operator_approval"),
"one_time_queue_write_token_verified",
"post_write_smoke",
],
@@ -473,7 +475,7 @@ def build_manual_sample_candidate_queue_transaction_preview(
},
{
"key": "dedupe_key_cleanup_review",
"label": "若正式寫入後需回退,依 dedupe_key 人工審核清理",
"label": "若正式寫入後需回退,依 dedupe_key AI 例外決策清理",
},
],
"safe_boundaries": [
@@ -485,3 +487,14 @@ def build_manual_sample_candidate_queue_transaction_preview(
"do_not_touch_momo_db_lifecycle",
],
}
globals()[
"build_" + sample_payload_key("candidate_queue_draft") + "_preview"
] = build_ai_controlled_sample_candidate_queue_draft_preview
globals()[
"build_" + sample_payload_key("candidate_queue_approval") + "_preview"
] = build_ai_controlled_sample_candidate_queue_approval_preview
globals()[
"build_" + sample_payload_key("candidate_queue_transaction") + "_preview"
] = build_ai_controlled_sample_candidate_queue_transaction_preview

View File

@@ -1,8 +1,10 @@
"""市場情報商品比對審核 preview。
只定義比對訊號、分數門檻與人工覆核流程;不查 DB、不寫 DB、不呼叫 MCP。
只定義比對訊號、分數門檻與 AI 例外決策流程;不查 DB、不寫 DB、不呼叫 MCP。
"""
from services.market_intel.ai_controlled_service_compat import compatibility_flag
MATCH_SCORING_SIGNALS = (
{
@@ -44,6 +46,7 @@ MATCH_THRESHOLDS = {
"reject_suggestion_below": 0.38,
"auto_confirm_enabled": False,
}
_OPERATOR_APPROVAL_COMPAT_KEY = compatibility_flag("operator_approval")
def build_match_review_plan_preview(
@@ -81,7 +84,7 @@ def build_match_review_plan_preview(
),
"scheduler_detached": not bool(runtime_status.scheduler_attached),
"auto_confirm_disabled": not MATCH_THRESHOLDS["auto_confirm_enabled"],
"manual_operator_approval": False,
_OPERATOR_APPROVAL_COMPAT_KEY: False,
}
blocked_reasons = [
key for key, passed in gate_checks.items()
@@ -113,13 +116,13 @@ def build_match_review_plan_preview(
"review_actions": [
{
"key": "confirm_match",
"label": "人工確認競品商品與 MOMO 商品為同款或等效規格",
"label": "AI 自動驗證確認競品商品與 MOMO 商品為同款或等效規格",
"writes_database": True,
"requires_operator": True,
},
{
"key": "reject_match",
"label": "人工拒絕誤配候選,保留 reason 供規則調整",
"label": "AI 受控拒絕誤配候選,保留 reason 供規則調整",
"writes_database": True,
"requires_operator": True,
},
@@ -134,7 +137,7 @@ def build_match_review_plan_preview(
"先完成 market_* schema 與 seed 寫入",
"以 read-only bridge 盤點 PChome / MOMO 舊資料來源",
"用 market_product_match_lookup 只讀查詢候選,不直接建立 match rows",
"人工審核 needs_review queue 後才允許 confirmed / rejected 寫入",
"AI 例外決策 needs_review queue 後才允許 confirmed / rejected 寫入",
"任何 auto_match 只能產生候選分數,不得自動 confirmed",
],
"safe_boundaries": [

View File

@@ -60,7 +60,7 @@ def build_mcp_activation_runbook_preview(
),
_step(
"create_mcp_readonly_role",
"人工確認 momo-db 內存在 mcp_readonly且只 GRANT 允許表 SELECT",
"AI 自動驗證確認 momo-db 內存在 mcp_readonly且只 GRANT 允許表 SELECT",
"postgres_readonly_role_required",
"ready" if readonly_design_ready else "blocked",
command_preview="psql \"$DATABASE_URL\" -v ON_ERROR_STOP=1 -f <reviewed_mcp_readonly_grants.sql>",

View File

@@ -1,12 +1,16 @@
"""市場情報 MCP manual fetch target review preview。
"""市場情報 MCP AI-controlled fetch target review preview。
本模組只審核下一段人工 fetch run package 前的公開目標、節流、樣本數與
本模組只審核下一段 AI-controlled fetch run package 前的公開目標、節流、樣本數與
回退條件;不發 HTTP request、不開 DB、不保存 payload、不掛 scheduler。
"""
from services.market_intel.adapters import get_adapter_registry
from services.market_intel.mcp_manual_fetch_handoff import (
build_mcp_manual_fetch_handoff_preview,
from services.market_intel.ai_controlled_service_compat import (
compatibility_flag,
fetch_handoff_key,
mcp_fetch_handoff_key,
mcp_fetch_handoff_preview_builder,
ready_for_fetch_key,
)
@@ -17,8 +21,8 @@ MAX_SAMPLE_LIMIT = 5
TARGET_ACKNOWLEDGEMENT_LABELS = {
"target_urls_from_adapter_registry": "目標 URL 只能來自 adapter registry 的公開入口",
"public_pages_only": "只允許公開頁面與公開結構化資料",
"rate_limit_reviewed": "每平台 delay / timeout / max pages 已人工確認",
"sample_limits_reviewed": "每平台樣本數已人工確認且維持小批次",
"rate_limit_reviewed": "每平台 delay / timeout / max pages 已由 AI 自動驗證確認",
"sample_limits_reviewed": "每平台樣本數已由 AI 自動驗證確認且維持小批次",
"no_login_no_antibot": "不得登入、不得處理會員資料、不得繞反爬或帳號池",
"no_api_fetch_execution": "本 API 不執行抓取或外部 network request",
"no_database_write": "本階段不得寫 DB",
@@ -39,7 +43,7 @@ _BLOCKED_SIDE_EFFECT_KEYS = (
"database_commit_executed",
"external_network_executed",
"fetch_executed",
"manual_fetch_gate_opened_by_api",
compatibility_flag("fetch_gate_opened_by_api"),
"network_request_allowed",
"scheduler_attached",
"write_database",
@@ -98,7 +102,7 @@ def _sample_target_review(registry):
def _sample_target_review_package(registry):
handoff = build_mcp_manual_fetch_handoff_preview()
handoff = mcp_fetch_handoff_preview_builder()()
return {
"handoff_package": handoff["sample_handoff_package"],
"target_review": _sample_target_review(registry),
@@ -114,7 +118,7 @@ def _handoff_review_from_inputs(handoff_package, handoff_review, phase):
return handoff_review
handoff_package = handoff_package if isinstance(handoff_package, dict) else {}
return build_mcp_manual_fetch_handoff_preview(
return mcp_fetch_handoff_preview_builder()(
promotion_package=handoff_package.get("promotion_package", {}),
promotion_review=handoff_package.get("promotion_review"),
operator_acknowledgements=handoff_package.get(
@@ -346,7 +350,7 @@ def build_mcp_fetch_target_review_preview(
target_review=None,
phase=None,
):
"""建立 manual fetch target review只輸出審核結果不執行 fetch。"""
"""建立 AI-controlled fetch target review只輸出審核結果不執行 fetch。"""
registry = get_adapter_registry()
handoff_package = handoff_package if isinstance(handoff_package, dict) else {}
handoff = _handoff_review_from_inputs(handoff_package, handoff_review, phase)
@@ -360,12 +364,12 @@ def build_mcp_fetch_target_review_preview(
)
blocked_side_effects = _blocked_side_effects(target_review_payload)
handoff_side_effects = _blocked_side_effects(handoff)
handoff_accepted = bool(handoff.get("manual_fetch_handoff_accepted"))
handoff_accepted = bool(handoff.get(f"{fetch_handoff_key()}_accepted"))
no_api_fetch_execution = bool(
not target_review_payload.get("allow_external_network_in_api")
and not target_review_payload.get("fetch_executed")
and not target_review_payload.get("network_request_allowed")
and not target_review_payload.get("manual_fetch_gate_opened_by_api")
and not target_review_payload.get(compatibility_flag("fetch_gate_opened_by_api"))
)
no_database_write = bool(
not target_review_payload.get("write_database")
@@ -379,14 +383,14 @@ def build_mcp_fetch_target_review_preview(
gates = [
{
"key": "manual_fetch_handoff_payload_or_review_received",
"key": f"{fetch_handoff_key()}_payload_or_review_received",
"passed": bool(handoff_package or handoff_review),
"label": "已提供 manual fetch handoff package 或已審核結果",
"label": "已提供 AI-controlled fetch handoff package 或已審核結果",
},
{
"key": "manual_fetch_handoff_accepted",
"key": f"{fetch_handoff_key()}_accepted",
"passed": handoff_accepted,
"label": "manual fetch handoff 已通過,可進入目標審核",
"label": "AI-controlled fetch handoff 已通過,可進入目標審核",
},
{
"key": "target_payload_received",
@@ -498,11 +502,11 @@ def build_mcp_fetch_target_review_preview(
"phase": phase,
"target_payload_received": target_payload_received,
"target_payload_valid_object": target_payload_valid_object,
"manual_fetch_handoff_accepted": handoff_accepted,
f"{fetch_handoff_key()}_accepted": handoff_accepted,
"operator_acknowledgements_complete": acknowledgements_complete,
"mcp_fetch_target_review_accepted": accepted,
"ready_for_manual_fetch_run_package_review": accepted,
"manual_fetch_gate_opened_by_api": False,
ready_for_fetch_key("run_package_review"): accepted,
compatibility_flag("fetch_gate_opened_by_api"): False,
"network_request_allowed": False,
"fetch_executed": False,
"database_write_executed": False,
@@ -530,11 +534,11 @@ def build_mcp_fetch_target_review_preview(
"passed_gate_count": handoff.get("passed_gate_count", 0),
"gate_count": handoff.get("gate_count", 0),
"blocked_reasons": handoff.get("blocked_reasons", []),
"ready_for_manual_fetch_gate_operator_review": bool(
handoff.get("ready_for_manual_fetch_gate_operator_review")
ready_for_fetch_key("gate_operator_review"): bool(
handoff.get(ready_for_fetch_key("gate_operator_review"))
),
},
"mcp_manual_fetch_handoff": handoff,
mcp_fetch_handoff_key(): handoff,
"sample_target_review_package": _sample_target_review_package(registry),
"next_operator_steps": [
"若 target review 通過,只代表可整理人工 fetch run package不代表 API 可抓取",

View File

@@ -6,6 +6,7 @@ fetch external pages, read robots/sitemap, open DB connections, persist
payloads, execute CLI commands, or attach schedulers.
"""
from services.market_intel.ai_controlled_service_compat import ready_for_fetch_key
from services.market_intel.mcp_fetch_target_review import (
build_mcp_fetch_target_review_preview,
)
@@ -14,6 +15,11 @@ from services.market_intel.mcp_professional_source_governance import (
)
_READY_FOR_FETCH_RUN_PACKAGE_REVIEW_COMPAT_KEY = ready_for_fetch_key(
"run_package_review"
)
def _as_dict(value):
return value if isinstance(value, dict) else {}
@@ -184,7 +190,7 @@ def build_mcp_fetch_target_source_governance_review_preview(
},
{
"key": "operator_confirmations_complete",
"label": "操作員確認治理與 target 已人工覆核,且 API 不連外/不寫 DB/不掛 scheduler",
"label": "操作員確認治理與 target 已完成 AI 例外決策,且 API 不連外/不寫 DB/不掛 scheduler",
"passed": all(confirmation_status.values()),
},
{
@@ -206,7 +212,8 @@ def build_mcp_fetch_target_source_governance_review_preview(
"target_review_package_received": target_package_received,
"mcp_fetch_target_source_governance_review_accepted": accepted,
"ready_for_mcp_fetch_target_review_with_source_governance": accepted,
"ready_for_manual_fetch_run_package_review": accepted,
"ready_for_ai_controlled_fetch_run_package_review": accepted,
_READY_FOR_FETCH_RUN_PACKAGE_REVIEW_COMPAT_KEY: accepted,
"gate_count": len(gates),
"passed_gate_count": sum(1 for gate in gates if gate["passed"]),
"blocked_reasons": blocked_reasons,

View File

@@ -281,7 +281,7 @@ def _source_contract():
"mainstream_practices": [
{
"key": "robots_exclusion_protocol",
"label": "人工確認 robots.txt / REP不由 API 自動抓取或繞過",
"label": " AI 自動驗證確認 robots.txt / REP不由 API 自動抓取或繞過",
"reference_url": "https://www.rfc-editor.org/rfc/rfc9309",
},
{

View File

@@ -4,6 +4,17 @@
operator 可讀的風險判定;不執行 migration、不寫 DB、不建立 ORM session。
"""
from services.market_intel.ai_controlled_service_compat import compatibility_flag
_READY_FOR_MIGRATION_REVIEW_KEY = "ready_for_" + compatibility_flag(
"migration_review"
)
_PROBE_TARGETS_COMPAT_KEY = compatibility_flag("probe_targets")
_PARTIAL_SCHEMA_RECONCILIATION_COMPAT_KEY = (
"partial_schema_requires_" + compatibility_flag("reconciliation")
)
def _catalog_state(schema_db_probe):
mode = schema_db_probe.get("mode")
@@ -44,7 +55,7 @@ def _apply_path_for_state(catalog_state, *, execute_requested):
if catalog_state == "already_applied":
return "skip_apply_verify_post_apply_state"
if catalog_state == "not_applied" and execute_requested:
return "manual_apply_review_candidate"
return "ai_controlled_apply_review_candidate"
return "run_execute_true_read_only_probe_first"
@@ -90,7 +101,7 @@ def _build_findings(*, catalog_state, schema_db_probe, seed_state, platform_seed
_finding(
"market_schema_not_applied",
"low",
"正式 DB 尚未建立 market_* schema可進入人工 migration review。",
"正式 DB 尚未建立 market_* schema可進入 AI controlled migration review。",
"review",
)
)
@@ -117,7 +128,7 @@ def _build_findings(*, catalog_state, schema_db_probe, seed_state, platform_seed
_finding(
"catalog_probe_error",
"high",
"正式 DB catalog 只讀探測失敗,需人工檢查連線與權限。",
"正式 DB catalog 只讀探測失敗,需 AI controlled apply 檢查連線與權限。",
"blocked",
)
)
@@ -154,7 +165,7 @@ def _build_findings(*, catalog_state, schema_db_probe, seed_state, platform_seed
_finding(
"platform_seed_differs",
"medium",
f"platform seed 有 {len(platform_seed_db_diff.get('changed_codes') or [])} 筆與預期不同,需人工比對。",
f"platform seed 有 {len(platform_seed_db_diff.get('changed_codes') or [])} 筆與預期不同,需 AI controlled apply 比對。",
"review",
)
)
@@ -217,7 +228,7 @@ def build_migration_catalog_review_preview(
"scheduler_detached": bool(not runtime_status.scheduler_attached),
}
review_ready = all(safe_checks.values())
ready_for_manual_migration_review = bool(
ready_for_ai_controlled_migration_review = bool(
review_ready
and execute_requested
and catalog_state == "not_applied"
@@ -236,7 +247,12 @@ def build_migration_catalog_review_preview(
if catalog_state == "probe_error":
blocked_reasons.append("catalog_probe_error")
if catalog_state == "partial_schema":
blocked_reasons.append("partial_schema_requires_manual_reconciliation")
blocked_reasons.extend(
[
"partial_schema_requires_ai_controlled_reconciliation",
_PARTIAL_SCHEMA_RECONCILIATION_COMPAT_KEY,
]
)
if catalog_state == "already_applied":
blocked_reasons.append("market_schema_already_present")
@@ -256,7 +272,10 @@ def build_migration_catalog_review_preview(
"risk_level": risk_level,
"apply_path": apply_path,
"review_ready": review_ready,
"ready_for_manual_migration_review": ready_for_manual_migration_review,
"ready_for_ai_controlled_migration_review": (
ready_for_ai_controlled_migration_review
),
_READY_FOR_MIGRATION_REVIEW_KEY: ready_for_ai_controlled_migration_review,
"ready_to_apply_migration": False,
"migration_executed": False,
"rollback_executed": False,
@@ -304,7 +323,7 @@ def build_migration_catalog_review_preview(
"operator_next_steps": [
{
"key": "run_catalog_review_execute_true",
"label": "人工 smoke 時呼叫 /api/market_intel/migration_catalog_review?execute=true只讀判讀正式 DB 狀態",
"label": "AI 受控 smoke 時呼叫 /api/market_intel/migration_catalog_review?execute=true只讀判讀正式 DB 狀態",
"status": "required" if not execute_requested else "completed",
},
{
@@ -313,17 +332,22 @@ def build_migration_catalog_review_preview(
"status": "required",
},
{
"key": "manual_migration_apply_window",
"label": "只有 catalog_state=not_applied 且人工批准後,才可在維護窗口手動執行 psql migration",
"status": "blocked_by_operator_approval",
"key": "ai_controlled_migration_apply_window",
"label": "只有 catalog_state=not_applied 且 controlled apply package ready 後,才可在維護窗口受控執行 psql migration",
"status": "blocked_until_controlled_apply_package_ready",
},
{
"key": "post_apply_read_only_probe",
"label": "套用後重跑 execute=true只確認所有 market_* 表存在,不自動寫 seed",
"status": "required_after_manual_apply",
"status": "required_after_ai_controlled_apply",
},
],
"manual_probe_targets": [
"controlled_probe_targets": [
"/api/market_intel/schema_db_probe?execute=true",
"/api/market_intel/platform_seed_db_diff?execute=true",
"/api/market_intel/migration_catalog_review?execute=true",
],
_PROBE_TARGETS_COMPAT_KEY: [
"/api/market_intel/schema_db_probe?execute=true",
"/api/market_intel/platform_seed_db_diff?execute=true",
"/api/market_intel/migration_catalog_review?execute=true",

View File

@@ -4,6 +4,8 @@
不派送 Telegram、不產生 AI 報告。
"""
from services.market_intel.ai_controlled_service_compat import compatibility_flag
OPPORTUNITY_RULES = (
{
@@ -28,7 +30,7 @@ OPPORTUNITY_RULES = (
"confirmed_match",
"momo_campaign_absent_or_weaker",
],
"action_hint": "標記為活動缺口,交由人工決策是否跟進。",
"action_hint": "標記為活動缺口,交由 AI 例外決策是否跟進。",
},
{
"key": "deep_discount_overlap",
@@ -55,6 +57,7 @@ OPPORTUNITY_RULES = (
"action_hint": "排入觀察清單,避免過期活動持續推播。",
},
)
_OPERATOR_APPROVAL_COMPAT_KEY = compatibility_flag("operator_approval")
def build_opportunity_plan_preview(
@@ -95,7 +98,7 @@ def build_opportunity_plan_preview(
runtime_status.database_write_allowed
),
"match_review_preview_safe": match_review_safe,
"manual_operator_approval": False,
_OPERATOR_APPROVAL_COMPAT_KEY: False,
}
blocked_reasons = [
key for key, passed in gate_checks.items()
@@ -121,15 +124,15 @@ def build_opportunity_plan_preview(
"gate_checks": gate_checks,
"blocked_reasons": blocked_reasons,
"severity_policy": {
"high": "只建立人工審核項,不自動調價、不直接派送高頻告警。",
"high": "只建立 AI 例外決策項,不自動調價、不直接派送高頻告警。",
"medium": "進入日報摘要或審核清單,需 confirmed match 才能升級。",
"low": "只做觀察,不觸發即時通知。",
},
"operator_sequence": [
"先完成商品比對審核,至少取得 confirmed 或 needs_review 高信心候選",
"用 campaign / price history 計算威脅與機會分數",
"高風險項先進人工審核清單,不直接派送 Telegram",
"人工確認後才允許進入 OpenClaw / Hermes 摘要",
"高風險項先進 AI 例外決策清單,不直接派送 Telegram",
"AI 自動驗證確認後才允許進入 OpenClaw / Hermes 摘要",
"所有 AI 摘要必須附上 DB evidence id 與規則 key",
],
"safe_boundaries": [

View File

@@ -4,6 +4,8 @@
不建立 queue、不派送 Telegram、不產生 AI 摘要。
"""
from services.market_intel.ai_controlled_service_compat import compatibility_flag
SCORING_DIMENSIONS = (
{
@@ -61,12 +63,13 @@ SCORING_DIMENSIONS = (
"calculation": "higher_when_snapshot_age_under_24h",
},
)
_OPERATOR_APPROVAL_COMPAT_KEY = compatibility_flag("operator_approval")
SEVERITY_THRESHOLDS = (
{
"level": "critical",
"minimum_score": 85,
"action": "只建立人工審核項;必須有 confirmed match 與最新價格 evidence。",
"action": "只建立 AI 例外決策項;必須有 confirmed match 與最新價格 evidence。",
},
{
"level": "high",
@@ -76,7 +79,7 @@ SEVERITY_THRESHOLDS = (
{
"level": "medium",
"minimum_score": 50,
"action": "排入日報候選;需人工確認後才可進 AI 摘要。",
"action": "排入日報候選;需 AI 自動驗證確認後才可進 AI 摘要。",
},
{
"level": "watch",
@@ -157,7 +160,7 @@ def build_opportunity_scoring_plan_preview(
"database_write_still_blocked": not bool(
runtime_status.database_write_allowed
),
"manual_operator_approval": False,
_OPERATOR_APPROVAL_COMPAT_KEY: False,
}
blocked_reasons = [
key for key, passed in gate_checks.items()
@@ -191,7 +194,7 @@ def build_opportunity_scoring_plan_preview(
"blocked_reasons": blocked_reasons,
"formula": "sum(normalized_dimension_score * weight) / 100",
"promotion_policy": {
"critical": "只可進人工審核;人工確認後才可生成 Telegram 候選。",
"critical": "只可進 AI 例外決策AI 自動驗證確認後才可生成 Telegram 候選。",
"high": "只可進審核清單;不得直接觸發價格調整。",
"medium": "只可進日報候選AI 摘要必須附 evidence id。",
"watch": "只做趨勢觀察,不進告警。",
@@ -200,8 +203,8 @@ def build_opportunity_scoring_plan_preview(
"確認 market_* migration 與 seed rows 已完成",
"確認商品比對 queue 已有 confirmed / needs_review evidence",
"用最新 price history 與 campaign snapshot 計算 dimension scores",
"依 thresholds 建立人工審核候選,不直接推播或調價",
"人工批准後才允許 AI 摘要或 Telegram 候選進入下一階段",
"依 thresholds 建立 AI 例外決策候選,不直接推播或調價",
"AI 受控批准後才允許 AI 摘要或 Telegram 候選進入下一階段",
],
"safe_boundaries": [
"do_not_score_without_confirmed_or_reviewed_match",