run_wp_sd8_compare_reruns.py 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. #!/usr/bin/env python3
  2. """Compare two WP-SD8 API rerun evidence files, ignoring IDs and timestamps."""
  3. from __future__ import annotations
  4. import json
  5. from pathlib import Path
  6. EVIDENCE = Path(__file__).resolve().parents[4] / "doc" / "plan" / "UAT留证" / "2026-08-17-智慧诊断正式落地"
  7. IGNORE_KEYS = {
  8. "generated_at",
  9. "planId",
  10. "planNo",
  11. "actionId",
  12. "taskId",
  13. "firstFact",
  14. }
  15. def public_view(value):
  16. if isinstance(value, dict):
  17. return {k: public_view(v) for k, v in value.items() if k not in IGNORE_KEYS}
  18. if isinstance(value, list):
  19. return [public_view(v) for v in value]
  20. return value
  21. def module_sig(row: dict) -> dict:
  22. return {
  23. "module": row.get("module"),
  24. "selectionMode": (row.get("global") or {}).get("selectionMode"),
  25. "metricCode": (row.get("global") or {}).get("metricCode"),
  26. "evidenceScope": (row.get("global") or {}).get("evidenceScope"),
  27. "specified_same_as_current": row.get("specified_same_as_current"),
  28. "kanban_root_present": row.get("kanban_root_present"),
  29. "loopClosed": row.get("loopClosed"),
  30. "auto": (row.get("verify") or {}).get("auto") if row.get("verify") else None,
  31. "createSkipped": (row.get("create") or {}).get("skipped"),
  32. "createReason": (row.get("create") or {}).get("reason"),
  33. "actionHttps": [
  34. step.get("http") if isinstance(step, dict) and "http" in step else step
  35. for item in (row.get("actionsCompleted") or [])
  36. for step in ([item] if "http" in (item or {}) else (item or {}).get("steps") or [])
  37. ],
  38. }
  39. def load(name: str) -> dict:
  40. return json.loads((EVIDENCE / name).read_text(encoding="utf-8"))
  41. def compare_tenant(left_name: str, right_name: str) -> dict:
  42. left = load(left_name)
  43. right = load(right_name)
  44. left_mods = {x["module"]: module_sig(x) for x in left.get("modules") or []}
  45. right_mods = {x["module"]: module_sig(x) for x in right.get("modules") or []}
  46. keys = sorted(set(left_mods) | set(right_mods))
  47. diffs = []
  48. for key in keys:
  49. if left_mods.get(key) != right_mods.get(key):
  50. diffs.append({"module": key, "left": left_mods.get(key), "right": right_mods.get(key)})
  51. return {
  52. "left": left_name,
  53. "right": right_name,
  54. "tenant": left.get("tenant"),
  55. "same_closed": left.get("closed_count") == right.get("closed_count"),
  56. "same_blocked": left.get("blocked_count") == right.get("blocked_count"),
  57. "module_diffs": diffs,
  58. "consistent": not diffs
  59. and left.get("closed_count") == right.get("closed_count")
  60. and left.get("blocked_count") == right.get("blocked_count"),
  61. }
  62. def main() -> None:
  63. report = {
  64. "work_package": "WP-SD8-COMPARE",
  65. "pairs": [
  66. compare_tenant("08-rerun1-A.json", "08-rerun2-A.json"),
  67. compare_tenant("08-rerun1-B.json", "08-rerun2-B.json"),
  68. compare_tenant("08-rerun1-DEMO.json", "08-rerun2-DEMO.json"),
  69. ],
  70. }
  71. report["all_consistent"] = all(x["consistent"] for x in report["pairs"])
  72. out = EVIDENCE / "08-rerun-compare.json"
  73. out.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
  74. print(out)
  75. print(json.dumps({"all_consistent": report["all_consistent"], "tenants": [
  76. f"{x['tenant']}:consistent={x['consistent']}:diffs={len(x['module_diffs'])}"
  77. for x in report["pairs"]
  78. ]}, ensure_ascii=False))
  79. if __name__ == "__main__":
  80. main()