feat(ai): add automation surface summary
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

This commit is contained in:
ogt
2026-07-02 18:47:00 +08:00
parent b69d173f18
commit 5b8e4b3568
10 changed files with 479 additions and 20 deletions

View File

@@ -25,7 +25,8 @@ def test_ai_intelligence_first_viewport_has_benchmark_golden_signals():
assert "異動狀態" in strip
assert "下一步" in strip
assert "loadAutomationBenchmarkStrip()" in template
assert "/api/ai/pchome-growth/ai-automation-readiness" in template
assert "/api/ai/pchome-growth/ai-automation-surface-summary" in template
assert "golden_signals" in template
def test_observability_overview_first_viewport_has_benchmark_golden_signals():

View File

@@ -165,3 +165,39 @@ def test_candidate_decision_lane_closeout_route_defaults_to_cached_no_db_no_sear
assert payload["product_readiness"]["primary_human_gate_count"] == 0
assert payload["safety"]["executes_search"] is False
assert payload["safety"]["writes_database"] is False
def test_ai_automation_surface_summary_route_defaults_to_cached_no_db(monkeypatch):
from routes import ai_routes as routes
monkeypatch.setattr(routes, "_get_cached_pchome_growth_payload", lambda: _payload())
def fail_engine(database_path):
raise AssertionError("cached AI automation surface summary should not open a DB engine")
monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", fail_engine)
app = Flask(__name__)
with app.test_request_context(
"/api/ai/pchome-growth/ai-automation-surface-summary"
"?batch_size=1&include_receipt_replay=false&include_drift_verifier=false"
):
response = routes.api_pchome_growth_ai_automation_surface_summary.__wrapped__()
payload = response.get_json()
signals = {signal["key"]: signal for signal in payload["golden_signals"]}
assert payload["success"] is True
assert payload["policy"] == "read_only_pchome_growth_ai_automation_surface_summary"
assert payload["source_endpoint"] == "/api/ai/pchome-growth/ai-automation-readiness"
assert payload["summary"]["safe_lane_count"] >= 4
assert payload["summary"]["primary_human_gate_count"] == 0
assert payload["summary"]["writes_database_count"] == 0
assert set(signals) == {
"automated-landing",
"verified",
"change-state",
"next-machine-action",
}
assert signals["next-machine-action"]["next_machine_action"]
assert payload["safety"]["writes_database"] is False
assert payload["safety"]["llm_calls_in_preview"] is False

View File

@@ -89,6 +89,7 @@ from services.pchome_mapping_backlog_service import (
build_pchome_direct_mapping_retry_candidate_exception_closeout_verifier_input_package,
build_pchome_direct_mapping_retry_candidate_exception_resolution_closeout_package,
build_pchome_growth_ai_automation_readiness,
build_pchome_growth_ai_automation_surface_summary,
build_pchome_mapping_operator_preview,
parse_pchome_product_page_evidence_html,
parse_unit_package_basis,
@@ -1684,6 +1685,97 @@ def test_ai_automation_readiness_surfaces_controlled_apply_drift_detected():
assert readiness["safety"]["writes_database"] is False
def test_ai_automation_surface_summary_turns_readiness_into_golden_signals():
readiness = build_pchome_growth_ai_automation_readiness(
_payload(),
batch_size=1,
controlled_apply_receipt_replay={
"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,
},
},
controlled_apply_drift_verifier={
"summary": {
"target_selector_count": 4,
"post_apply_readback_pass_count": 4,
"drift_count": 0,
"drift_verified_count": 1,
"drift_verifier_artifact_materialized_count": 1,
"drift_verifier_artifact_hash_match_count": 1,
},
},
)
surface = build_pchome_growth_ai_automation_surface_summary(readiness)
signals = {signal["key"]: signal for signal in surface["golden_signals"]}
assert surface["policy"] == "read_only_pchome_growth_ai_automation_surface_summary"
assert surface["summary"]["safe_lane_count"] >= 4
assert surface["summary"]["ready_lane_count"] >= 1
assert surface["summary"]["completed_lane_count"] >= 1
assert surface["summary"]["controlled_apply_replay_readback_pass_count"] == 4
assert surface["summary"]["controlled_apply_drift_count"] == 0
assert surface["summary"]["primary_human_gate_count"] == 0
assert surface["summary"]["manual_required_as_primary_flow_count"] == 0
assert surface["summary"]["writes_database_count"] == 0
assert set(signals) == {
"automated-landing",
"verified",
"change-state",
"next-machine-action",
}
assert signals["automated-landing"]["label"] == "已自動落地"
assert signals["verified"]["label"] == "已驗證"
assert signals["change-state"]["label"] == "異動狀態"
assert signals["next-machine-action"]["label"] == "下一步"
assert signals["automated-landing"]["value"] == "已完成回讀"
assert "4/4" in signals["automated-landing"]["detail"]
assert signals["verified"]["value"] == "4 筆已回讀"
assert signals["change-state"]["value"] == "無漂移"
assert surface["surface_contract"]["first_viewport_required"] is True
assert surface["surface_contract"]["raw_evidence_hidden_from_first_viewport"] is True
assert surface["safety"]["writes_database"] is False
assert surface["safety"]["llm_calls_in_preview"] is False
def test_ai_automation_surface_summary_prioritizes_drift_recovery_action():
readiness = build_pchome_growth_ai_automation_readiness(
_payload(),
batch_size=1,
controlled_apply_receipt_replay={
"summary": {
"target_selector_count": 4,
"post_apply_readback_pass_count": 3,
"executor_receipt_ready_count": 0,
"executor_receipt_materialized_count": 1,
"executor_receipt_hash_match_count": 0,
},
},
controlled_apply_drift_verifier={
"summary": {
"target_selector_count": 4,
"post_apply_readback_pass_count": 3,
"drift_count": 1,
"drift_verified_count": 0,
},
},
)
surface = build_pchome_growth_ai_automation_surface_summary(readiness)
signals = {signal["key"]: signal for signal in surface["golden_signals"]}
assert surface["summary"]["controlled_apply_drift_count"] == 1
assert surface["summary"]["next_machine_action"] == "執行漂移恢復、重新回讀與 rollback path 比對"
assert signals["change-state"]["value"] == "1 筆漂移"
assert signals["change-state"]["status"] == "bad"
assert signals["next-machine-action"]["value"] == surface["summary"]["next_machine_action"]
assert surface["safety"]["writes_database"] is False
def test_ai_automation_readiness_reports_candidate_decisions_after_controlled_search():
call_count = {"search": 0}