run_wp5_pollution_cleanup.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. #!/usr/bin/env python3
  2. """Remove reconstructible invalid-tenant DWD/KPI rows and add hard guards."""
  3. from __future__ import annotations
  4. import json
  5. import re
  6. from datetime import datetime
  7. from pathlib import Path
  8. import pymysql
  9. ROOT = Path(__file__).resolve().parents[4]
  10. CONFIG = ROOT / "server" / "Admin.NET.Application" / "Configuration" / "Database.json"
  11. TABLES = (
  12. "dwd_material_readiness",
  13. "dwd_material_shortage",
  14. "dwd_order_schedule_trans",
  15. "dwd_supplier_delivery",
  16. "dwd_supplier_risk",
  17. "dwd_supply_demand",
  18. "ado_s9_kpi_value_l1_day",
  19. "ado_s9_kpi_value_l2_day",
  20. "ado_s9_kpi_value_l3_day",
  21. "ado_s9_kpi_value_l4_day",
  22. )
  23. def connect() -> pymysql.Connection:
  24. raw = CONFIG.read_text(encoding="utf-8-sig")
  25. value = next(
  26. item
  27. for item in re.findall(
  28. r'(?m)^\s*"ConnectionString"\s*:\s*"([^"]+)"', raw
  29. )
  30. if "Database=aidopdev" in item
  31. )
  32. parts = {
  33. item.split("=", 1)[0].strip().lower(): item.split("=", 1)[1].strip()
  34. for item in value.split(";")
  35. if "=" in item
  36. }
  37. return pymysql.connect(
  38. host=parts["server"],
  39. port=int(parts["port"]),
  40. user=parts["uid"],
  41. password=parts["pwd"],
  42. database=parts["database"],
  43. charset="utf8mb4",
  44. autocommit=True,
  45. )
  46. def main() -> None:
  47. conn = connect()
  48. results: list[dict[str, object]] = []
  49. try:
  50. with conn.cursor() as cur:
  51. cur.execute(
  52. """
  53. CREATE TABLE IF NOT EXISTS uat_data_cleanup_audit (
  54. id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  55. batch_code VARCHAR(100) NOT NULL,
  56. table_name VARCHAR(128) NOT NULL,
  57. invalid_rows_before BIGINT NOT NULL,
  58. deleted_rows BIGINT NOT NULL,
  59. invalid_rows_after BIGINT NOT NULL,
  60. executed_at DATETIME(3) NOT NULL
  61. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  62. """
  63. )
  64. for index, table in enumerate(TABLES, 1):
  65. constraint = f"ck_wp5_valid_tenant_{index:02d}"
  66. invalid = "tenant_id IS NULL OR tenant_id IN (0,1,1300000000001)"
  67. cur.execute(f"SELECT COUNT(*) FROM `{table}` WHERE {invalid}")
  68. before = int(cur.fetchone()[0])
  69. cur.execute(f"DELETE FROM `{table}` WHERE {invalid}")
  70. deleted = int(cur.rowcount)
  71. cur.execute(f"SELECT COUNT(*) FROM `{table}` WHERE {invalid}")
  72. after = int(cur.fetchone()[0])
  73. cur.execute(
  74. """
  75. INSERT INTO uat_data_cleanup_audit
  76. (batch_code,table_name,invalid_rows_before,deleted_rows,
  77. invalid_rows_after,executed_at)
  78. VALUES('WP5_INVALID_TENANT_20260817',%s,%s,%s,%s,NOW(3))
  79. """,
  80. (table, before, deleted, after),
  81. )
  82. cur.execute(
  83. """
  84. SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS
  85. WHERE CONSTRAINT_SCHEMA=DATABASE()
  86. AND TABLE_NAME=%s AND CONSTRAINT_NAME=%s
  87. """,
  88. (table, constraint),
  89. )
  90. if int(cur.fetchone()[0]) == 0:
  91. cur.execute(
  92. f"""
  93. ALTER TABLE `{table}`
  94. ADD CONSTRAINT `{constraint}`
  95. CHECK (tenant_id IS NOT NULL
  96. AND tenant_id NOT IN (0,1,1300000000001))
  97. """
  98. )
  99. results.append(
  100. {
  101. "table": table,
  102. "before": before,
  103. "deleted": deleted,
  104. "after": after,
  105. "constraint": constraint,
  106. }
  107. )
  108. finally:
  109. conn.close()
  110. payload = {
  111. "executed_at": datetime.now().isoformat(timespec="seconds"),
  112. "results": results,
  113. }
  114. Path(__file__).with_name("WP5-pollution-cleanup-evidence.json").write_text(
  115. json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8"
  116. )
  117. print(json.dumps(payload, ensure_ascii=False, indent=2))
  118. if __name__ == "__main__":
  119. main()