顯示 PChome controlled apply closeout 狀態
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

This commit is contained in:
ogt
2026-07-02 10:51:13 +08:00
parent 2f53e56460
commit bd6c083a5c
3 changed files with 88 additions and 7 deletions

View File

@@ -2522,11 +2522,15 @@ def api_pchome_growth_ai_automation_readiness():
try:
from config import DATABASE_PATH
from services.pchome_revenue_growth_service import build_pchome_growth_opportunities
from services.pchome_mapping_backlog_service import build_pchome_growth_ai_automation_readiness
from services.pchome_mapping_backlog_service import (
build_pchome_direct_mapping_retry_candidate_exception_controlled_apply_receipt_replay_package,
build_pchome_growth_ai_automation_readiness,
)
force_refresh = str(request.args.get('refresh') or '').strip().lower() in {'1', 'true', 'yes'}
execute_search = str(request.args.get('execute_search') or '').strip().lower() in {'1', 'true', 'yes'}
execute_fetch = str(request.args.get('execute_fetch') or '').strip().lower() in {'1', 'true', 'yes'}
include_receipt_replay = str(request.args.get('include_receipt_replay', 'true') or '').strip().lower() in {'1', 'true', 'yes'}
limit = request.args.get('limit', 20, type=int)
batch_size = request.args.get('batch_size', 8, type=int)
limit = max(5, min(limit, 50))
@@ -2544,11 +2548,23 @@ def api_pchome_growth_ai_automation_readiness():
payload["cache_state"] = "fresh"
_set_pchome_growth_cache(payload)
receipt_replay = None
if include_receipt_replay:
replay_engine = _create_icaim_dashboard_engine(DATABASE_PATH)
try:
receipt_replay = build_pchome_direct_mapping_retry_candidate_exception_controlled_apply_receipt_replay_package(
materialize_artifacts=False,
engine=replay_engine,
)
finally:
replay_engine.dispose()
readiness = build_pchome_growth_ai_automation_readiness(
payload,
batch_size=batch_size,
execute_search=execute_search,
execute_fetch=execute_fetch,
controlled_apply_receipt_replay=receipt_replay,
)
readiness["source_endpoint"] = "/api/ai/pchome-growth/opportunities"
return jsonify(readiness)

View File

@@ -5100,6 +5100,7 @@ def build_pchome_growth_ai_automation_readiness(
execute_search: bool = False,
execute_fetch: bool = False,
search_func: Any = None,
controlled_apply_receipt_replay: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Build a single read-only product-facing AI automation readiness view."""
mapping_summary = summarize_pchome_mapping_backlog(payload)
@@ -5149,6 +5150,16 @@ def build_pchome_growth_ai_automation_readiness(
waiting_candidate_count = selected_search_targets if not candidate_decision_count else 0
receipt_count = int(receipt_summary.get("receipt_count") or 0)
ready_receipt_count = int(receipt_summary.get("ready_for_auto_persistence_count") or 0)
receipt_replay_summary = (controlled_apply_receipt_replay or {}).get("summary") or {}
receipt_replay_selector_count = int(receipt_replay_summary.get("target_selector_count") or 0)
receipt_replay_readback_pass_count = int(receipt_replay_summary.get("post_apply_readback_pass_count") or 0)
receipt_replay_materialized_count = int(receipt_replay_summary.get("executor_receipt_materialized_count") or 0)
receipt_replay_hash_match_count = int(receipt_replay_summary.get("executor_receipt_hash_match_count") or 0)
controlled_apply_closeout_verified = (
bool(receipt_replay_selector_count)
and receipt_replay_readback_pass_count == receipt_replay_selector_count
and int(receipt_replay_summary.get("executor_receipt_ready_count") or 0) > 0
)
exception_count = _summary_exception_count(receipt_summary) + int(
decision_summary.get("machine_review_decision_count") or 0
)
@@ -5175,7 +5186,9 @@ def build_pchome_growth_ai_automation_readiness(
"writes_database": False,
}
if not direct_mapping_count and ready_receipt_count:
if controlled_apply_closeout_verified:
result = "AI_AUTOMATION_CONTROLLED_APPLY_CLOSEOUT_VERIFIED"
elif not direct_mapping_count and ready_receipt_count:
result = "AI_AUTOMATION_READY_FOR_CONTROLLED_APPLY"
elif exception_resolution_closeout_receipt_count:
result = "AI_AUTOMATION_EXCEPTION_RESOLUTION_CLOSEOUT_READY"
@@ -5242,10 +5255,21 @@ def build_pchome_growth_ai_automation_readiness(
_automation_lane(
"controlled_apply",
"受控落地",
"blocked_until_verifier",
0,
"等待 verifier、rollback、readback",
"P1-P4 穩定後才進 P5/P6",
"completed" if controlled_apply_closeout_verified else "blocked_until_verifier",
receipt_replay_selector_count,
(
f"readback {receipt_replay_readback_pass_count}/{receipt_replay_selector_count}"
f" · receipt {receipt_replay_materialized_count}"
) if controlled_apply_closeout_verified else "等待 verifier、rollback、readback",
"維持 receipt replay / drift verifier" if controlled_apply_closeout_verified else "P1-P4 穩定後才進 P5/P6",
),
_automation_lane(
"controlled_apply_receipt_replay",
"落地收據重放",
"completed" if controlled_apply_closeout_verified else "waiting",
receipt_replay_materialized_count,
f"hash match {receipt_replay_hash_match_count}",
"從 artifact + DB readback 自動證明 apply 已收斂",
),
]
@@ -5284,6 +5308,11 @@ def build_pchome_growth_ai_automation_readiness(
),
"receipt_count": receipt_count,
"ready_receipt_count": ready_receipt_count,
"controlled_apply_replay_selector_count": receipt_replay_selector_count,
"controlled_apply_replay_readback_pass_count": receipt_replay_readback_pass_count,
"controlled_apply_receipt_materialized_count": receipt_replay_materialized_count,
"controlled_apply_receipt_hash_match_count": receipt_replay_hash_match_count,
"controlled_apply_closeout_verified_count": 1 if controlled_apply_closeout_verified else 0,
"exception_count": exception_count,
"ai_exception_count": exception_count,
AI_EXCEPTION_REQUIRED_COUNT_KEY: exception_count,
@@ -5299,6 +5328,7 @@ def build_pchome_growth_ai_automation_readiness(
PRIMARY_HUMAN_GATE_COUNT_KEY: 0,
"exception_resolution": "ai_machine_verifiable",
"machine_verifiable_decision_required": True,
"controlled_apply_closeout": "receipt_replay_machine_verified" if controlled_apply_closeout_verified else "waiting_for_verifier",
},
"ai_exception_auto_resolution": ai_exception_auto_resolution,
"manual_policy": {
@@ -5321,6 +5351,7 @@ def build_pchome_growth_ai_automation_readiness(
"dispatches_telegram": False,
"llm_calls_in_preview": False,
"gemini_allowed": False,
"reads_database_for_receipt_replay": bool(controlled_apply_receipt_replay),
},
}

View File

@@ -1434,6 +1434,40 @@ def test_ai_automation_readiness_makes_automation_visible_without_manual_primary
assert readiness["safety"]["llm_calls_in_preview"] is False
def test_ai_automation_readiness_surfaces_controlled_apply_receipt_replay_closeout():
readiness = build_pchome_growth_ai_automation_readiness(
_payload(),
batch_size=1,
controlled_apply_receipt_replay={
"result": "DIRECT_MAPPING_RETRY_EXCEPTION_CONTROLLED_APPLY_RECEIPT_REPLAYED",
"summary": {
"target_selector_count": 4,
"post_apply_readback_pass_count": 4,
"executor_receipt_ready_count": 1,
"executor_receipt_materialized_count": 1,
"executor_receipt_hash_match_count": 1,
},
"safety": {
"writes_database": False,
},
},
)
lanes = {lane["key"]: lane for lane in readiness["automation_lanes"]}
assert readiness["result"] == "AI_AUTOMATION_CONTROLLED_APPLY_CLOSEOUT_VERIFIED"
assert readiness["summary"]["controlled_apply_replay_selector_count"] == 4
assert readiness["summary"]["controlled_apply_replay_readback_pass_count"] == 4
assert readiness["summary"]["controlled_apply_receipt_materialized_count"] == 1
assert readiness["summary"]["controlled_apply_closeout_verified_count"] == 1
assert readiness["automation_policy"]["controlled_apply_closeout"] == "receipt_replay_machine_verified"
assert lanes["controlled_apply"]["status"] == "completed"
assert lanes["controlled_apply"]["value"] == 4
assert lanes["controlled_apply_receipt_replay"]["status"] == "completed"
assert lanes["controlled_apply_receipt_replay"]["value"] == 1
assert readiness["safety"]["reads_database_for_receipt_replay"] is True
assert readiness["safety"]["writes_database"] is False
def test_ai_automation_readiness_reports_candidate_decisions_after_controlled_search():
call_count = {"search": 0}
@@ -16014,7 +16048,7 @@ def test_ai_automation_readiness_route_defaults_to_no_search_and_uses_cached_pay
monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine)
app = Flask(__name__)
with app.test_request_context("/api/ai/pchome-growth/ai-automation-readiness?batch_size=1"):
with app.test_request_context("/api/ai/pchome-growth/ai-automation-readiness?batch_size=1&include_receipt_replay=false"):
response = routes.api_pchome_growth_ai_automation_readiness.__wrapped__()
payload = response.get_json()