Files
ewoooc/services/market_intel/seed_writer_cli.py

198 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""市場情報 seed writer CLI skeleton。
本階段只回報 CLI 執行計畫,不建立 DB session、不寫入、不 commit。
"""
import hashlib
import json
APPROVAL_ENV_VAR = "MARKET_INTEL_SEED_WRITE_APPROVAL"
PLATFORM_UPSERT_SQL = """
INSERT INTO market_platforms (
code,
name,
base_url,
enabled,
crawl_policy_json
) VALUES (
:code,
:name,
:base_url,
:enabled,
:crawl_policy_json
)
ON CONFLICT (code) DO UPDATE SET
name = EXCLUDED.name,
base_url = EXCLUDED.base_url,
enabled = EXCLUDED.enabled,
crawl_policy_json = EXCLUDED.crawl_policy_json,
updated_at = NOW()
""".strip()
def _payload_hash(payload):
encoded = json.dumps(payload, ensure_ascii=False, sort_keys=True).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()[:16]
def build_seed_transaction_preview(writer_plan, migration_blueprint):
"""建立 seed writer transaction preview不建立 DB session。"""
statements = []
for index, operation in enumerate(writer_plan.get("operations", []), start=1):
values = operation.get("values", {})
lookup_code = operation.get("lookup", {}).get("code") or values.get("code")
statements.append(
{
"index": index,
"operation": operation.get("operation", "upsert"),
"table": operation.get("table", "market_platforms"),
"lookup": {"code": lookup_code},
"sql_template": PLATFORM_UPSERT_SQL,
"parameter_keys": sorted(values),
"parameter_payload_hash": _payload_hash(values),
"idempotency_key": f"market_platforms:{lookup_code}",
"diff_status": "not_loaded_no_db_session",
"write_status": "blocked_transaction_preview_only",
}
)
migration_ready = bool(
migration_blueprint.get("file_created")
and migration_blueprint.get("file_matches_blueprint")
and not migration_blueprint.get("migration_executed")
)
return {
"mode": "seed_transaction_preview_no_session",
"target_table": "market_platforms",
"statement_count": len(statements),
"statements": statements,
"migration_draft_ready": migration_ready,
"database_snapshot_loaded": False,
"existing_rows_seen": 0,
"database_session_created": False,
"transaction_opened": False,
"writes_executed": False,
"would_write_database": False,
"database_commit_executed": False,
"database_rollback_executed": False,
"external_network_executed": False,
"scheduler_attached": False,
"required_runtime_order": [
"backup_verified",
"migration_applied_by_operator",
"schema_smoke_passed",
"feature_flags_reviewed",
"one_time_approval_token_verified",
"real_write_implementation_enabled",
],
"safety_contract": {
"idempotent_upsert_preview_only": True,
"does_not_load_existing_rows": True,
"does_not_open_transaction": True,
"does_not_commit": True,
},
}
def build_seed_writer_cli_plan(
*,
platform_code,
execute_requested,
approval_token,
seed_plan,
write_guard,
writer_plan,
migration_blueprint,
):
"""建立 seed writer CLI blocked plan。"""
approval_token_present = bool(approval_token)
migration_ready = bool(
migration_blueprint.get("file_created")
and migration_blueprint.get("file_matches_blueprint")
and not migration_blueprint.get("migration_executed")
)
gates = [
{
"key": "script_created",
"label": "scripts/market_intel_seed_writer.py exists",
"passed": True,
},
{
"key": "migration_file_matches_blueprint",
"label": "migration draft exists and matches the reviewed blueprint",
"passed": migration_ready,
},
{
"key": "execute_requested",
"label": "--execute flag was explicitly provided",
"passed": bool(execute_requested),
},
{
"key": "approval_token_present",
"label": f"{APPROVAL_ENV_VAR} or --approval-token was provided",
"passed": approval_token_present,
},
{
"key": "database_write_allowed",
"label": "runtime database_write_allowed gate is true",
"passed": bool(write_guard.get("database_write_allowed")),
},
{
"key": "manual_operator_approval",
"label": "operator approval has been verified out-of-band",
"passed": False,
},
{
"key": "real_write_implementation_enabled",
"label": "CLI real write implementation has been enabled",
"passed": False,
},
]
blocked_reasons = [gate["key"] for gate in gates if not gate["passed"]]
if execute_requested:
blocked_reasons.insert(0, "execute_request_blocked_by_skeleton")
transaction_preview = build_seed_transaction_preview(
writer_plan=writer_plan,
migration_blueprint=migration_blueprint,
)
return {
"mode": "seed_writer_cli_blocked_skeleton",
"platform_code": platform_code or "all",
"execute_requested": bool(execute_requested),
"approval_token_present": approval_token_present,
"approval_env_var": APPROVAL_ENV_VAR,
"ready_for_real_write": False,
"writes_executed": False,
"would_write_database": False,
"database_session_created": False,
"database_commit_executed": False,
"external_network_executed": False,
"scheduler_attached": False,
"exit_code": 2 if execute_requested else 0,
"blocked_reasons": blocked_reasons,
"approval_gates": gates,
"seed_count": int(seed_plan.get("seed_count") or 0),
"writer_operation_count": int(writer_plan.get("operation_count") or 0),
"transaction_preview": transaction_preview,
"write_guard_summary": {
"ready_to_write": bool(write_guard.get("ready_to_write")),
"would_write_database": bool(write_guard.get("would_write_database")),
"database_write_allowed": bool(write_guard.get("database_write_allowed")),
"blocked_reasons": write_guard.get("blocked_reasons", []),
},
"migration_file_summary": {
"suggested_filename": migration_blueprint.get("suggested_filename"),
"file_created": bool(migration_blueprint.get("file_created")),
"file_matches_blueprint": bool(migration_blueprint.get("file_matches_blueprint")),
"migration_executed": bool(migration_blueprint.get("migration_executed")),
},
"safety_contract": {
"refuses_execute_in_this_phase": True,
"requires_independent_approval_token": True,
"keeps_crawler_disabled_for_seed_write": True,
"no_db_session_in_skeleton": True,
},
}