run_uat_kpi_target_baseline_seed.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #!/usr/bin/env python3
  2. """Create effective UAT target configurations for every enabled KPI."""
  3. from __future__ import annotations
  4. import argparse
  5. import json
  6. from datetime import datetime
  7. from decimal import Decimal
  8. from typing import Any
  9. from apply_sql_file import connect
  10. DEFAULT_TENANT = 838257186181189
  11. DEFAULT_FACTORY = 1
  12. BATCH_ID = "UAT-KPI-TARGET-20260821"
  13. def semantic_target(metric: dict[str, Any]) -> Decimal:
  14. name = str(metric.get("MetricName") or "")
  15. unit = str(metric.get("Unit") or "").strip().upper()
  16. module = str(metric.get("ModuleCode") or "")
  17. direction = str(metric.get("Direction") or "")
  18. if unit == "PPM":
  19. return Decimal("5000")
  20. if unit == "%" or "率" in name or "OEE" in name.upper() or "效率" in name:
  21. return Decimal("95")
  22. if unit in {"H", "小时"}:
  23. return Decimal("24")
  24. if unit in {"天", "DAY", "DAYS"}:
  25. if "周转" in name:
  26. return Decimal("30")
  27. return {
  28. "S1": Decimal("3"),
  29. "S3": Decimal("30"),
  30. "S5": Decimal("14"),
  31. "S6": Decimal("20"),
  32. "S7": Decimal("8"),
  33. "S9": Decimal("3"),
  34. }.get(module, Decimal("7"))
  35. if unit in {"个/人", "行/人", "单/人", "批次/人"} or "人效" in name:
  36. return {
  37. "S1": Decimal("100"),
  38. "S3": Decimal("231.1"),
  39. "S5": Decimal("7"),
  40. "S6": Decimal("2.1"),
  41. "S7": Decimal("10"),
  42. }.get(module, Decimal("10"))
  43. if unit in {"个", "人", "单", "批", "次"}:
  44. return Decimal("0") if direction == "lower_is_better" else Decimal("1")
  45. return Decimal("1")
  46. def seed(tenant_id: int, factory_id: int, dry_run: bool) -> dict[str, Any]:
  47. result: dict[str, Any] = {
  48. "tenantId": tenant_id,
  49. "factoryId": factory_id,
  50. "dryRun": dry_run,
  51. "inserted": 0,
  52. "existing": 0,
  53. "fromHistory": 0,
  54. "fromAncestor": 0,
  55. "fromSemanticBaseline": 0,
  56. }
  57. conn = connect()
  58. try:
  59. conn.begin()
  60. with conn.cursor() as cur:
  61. cur.execute(
  62. """
  63. SELECT Id,MetricCode,ModuleCode,MetricLevel,ParentId,MetricName,Unit,Direction,TenantId
  64. FROM ado_smart_ops_kpi_master
  65. WHERE IsEnabled=1 AND TenantId IN (0,%s)
  66. ORDER BY MetricCode,TenantId DESC
  67. """,
  68. (tenant_id,),
  69. )
  70. metrics_by_code: dict[str, dict[str, Any]] = {}
  71. metrics_by_id: dict[int, dict[str, Any]] = {}
  72. for row in cur.fetchall():
  73. metrics_by_code.setdefault(row["MetricCode"], row)
  74. metrics_by_id[int(row["Id"])] = row
  75. cur.execute(
  76. """
  77. SELECT metric_code,target_value
  78. FROM ado_smart_ops_kpi_target_config
  79. WHERE tenant_id=%s AND factory_id=%s AND status=1
  80. AND effective_from<=NOW() AND (effective_to IS NULL OR effective_to>=NOW())
  81. """,
  82. (tenant_id, factory_id),
  83. )
  84. active_targets = {row["metric_code"]: Decimal(row["target_value"]) for row in cur.fetchall()}
  85. cur.execute(
  86. """
  87. SELECT metric_code,target_value,biz_date FROM ado_s9_kpi_value_l1_day
  88. WHERE tenant_id=%s AND factory_id=%s AND is_deleted=0 AND target_value IS NOT NULL
  89. UNION ALL
  90. SELECT metric_code,target_value,biz_date FROM ado_s9_kpi_value_l2_day
  91. WHERE tenant_id=%s AND factory_id=%s AND is_deleted=0 AND target_value IS NOT NULL
  92. UNION ALL
  93. SELECT metric_code,target_value,biz_date FROM ado_s9_kpi_value_l3_day
  94. WHERE tenant_id=%s AND factory_id=%s AND is_deleted=0 AND target_value IS NOT NULL
  95. UNION ALL
  96. SELECT metric_code,target_value,biz_date FROM ado_s9_kpi_value_l4_day
  97. WHERE tenant_id=%s AND factory_id=%s AND is_deleted=0 AND target_value IS NOT NULL
  98. ORDER BY metric_code,biz_date DESC
  99. """,
  100. (tenant_id, factory_id) * 4,
  101. )
  102. history_targets: dict[str, Decimal] = {}
  103. for row in cur.fetchall():
  104. history_targets.setdefault(row["metric_code"], Decimal(row["target_value"]))
  105. resolved = dict(active_targets)
  106. effective_from = datetime(datetime.now().year, 1, 1)
  107. now = datetime.now().replace(microsecond=0)
  108. for code, metric in metrics_by_code.items():
  109. if code in active_targets:
  110. result["existing"] += 1
  111. continue
  112. target = history_targets.get(code)
  113. source = "history"
  114. if target is None:
  115. parent_id = metric.get("ParentId")
  116. while parent_id:
  117. parent = metrics_by_id.get(int(parent_id))
  118. if not parent:
  119. break
  120. parent_code = parent["MetricCode"]
  121. if str(parent.get("Unit") or "") == str(metric.get("Unit") or ""):
  122. target = resolved.get(parent_code) or history_targets.get(parent_code)
  123. if target is not None:
  124. source = "ancestor"
  125. break
  126. parent_id = parent.get("ParentId")
  127. if target is None:
  128. target = semantic_target(metric)
  129. source = "semantic"
  130. resolved[code] = target
  131. result[{
  132. "history": "fromHistory",
  133. "ancestor": "fromAncestor",
  134. "semantic": "fromSemanticBaseline",
  135. }[source]] += 1
  136. if not dry_run:
  137. cur.execute(
  138. """
  139. INSERT INTO ado_smart_ops_kpi_target_config
  140. (tenant_id,factory_id,metric_code,target_value,effective_from,effective_to,
  141. source_type,source_batch_id,status,remark,create_time,update_time)
  142. VALUES
  143. (%s,%s,%s,%s,%s,NULL,'UAT_BASELINE',%s,1,
  144. 'UAT验收目标基线:优先沿用历史/同单位上级目标,否则按指标单位与方向设置',%s,%s)
  145. """,
  146. (
  147. tenant_id,
  148. factory_id,
  149. code,
  150. target,
  151. effective_from,
  152. BATCH_ID,
  153. now,
  154. now,
  155. ),
  156. )
  157. result["inserted"] += 1
  158. if dry_run:
  159. conn.rollback()
  160. else:
  161. conn.commit()
  162. except Exception:
  163. conn.rollback()
  164. raise
  165. finally:
  166. conn.close()
  167. return result
  168. def main() -> int:
  169. parser = argparse.ArgumentParser(description=__doc__)
  170. parser.add_argument("--tenant", type=int, default=DEFAULT_TENANT)
  171. parser.add_argument("--factory", type=int, default=DEFAULT_FACTORY)
  172. parser.add_argument("--dry-run", action="store_true")
  173. args = parser.parse_args()
  174. print(json.dumps(seed(args.tenant, args.factory, args.dry_run), ensure_ascii=False, indent=2, default=str))
  175. return 0
  176. if __name__ == "__main__":
  177. raise SystemExit(main())