rescan.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. #!/usr/bin/env python3
  2. """Re-run the temperature rules from the cached hourly long table (no DB scan).
  3. Usage::
  4. python _tmp_temperature/rescan.py --out _tmp_temperature/out_t1 \
  5. --mild-peer 4 --mild-spread 4 --abnormal-peer 6 --abnormal-spread 8 \
  6. --severe-peer 10 --severe-spread 15
  7. """
  8. from __future__ import annotations
  9. import argparse
  10. import sys
  11. from pathlib import Path
  12. import pandas as pd
  13. ROOT = Path(__file__).resolve().parents[1]
  14. if str(ROOT) not in sys.path:
  15. sys.path.insert(0, str(ROOT))
  16. from _tmp_temperature import hf_validation, unified
  17. from _tmp_temperature.config import build_unit_config
  18. LONG = Path(__file__).resolve().parent / "out" / "pks_temperature_hourly.csv"
  19. def load_wide(path: Path) -> dict[str, list[dict]]:
  20. frame = pd.read_csv(path, low_memory=False)
  21. frame["hour_start"] = pd.to_datetime(frame["小时"])
  22. result: dict[str, list[dict]] = {}
  23. for unit, group in frame.groupby("机组"):
  24. keys = ["hour_start", "批次"]
  25. wide = group.pivot_table(index=keys, columns="点位", values="均值", aggfunc="first")
  26. coverage = group.groupby(keys)["运行覆盖率"].first()
  27. std = group.pivot_table(index=keys, columns="点位", values="标准差", aggfunc="first")
  28. minraw = group.pivot_table(index=keys, columns="点位", values="原始最小值", aggfunc="first")
  29. zeros = group.pivot_table(index=keys, columns="点位", values="零值样本数", aggfunc="first")
  30. high = group.pivot_table(index=keys, columns="点位", values="高报警样本数", aggfunc="first")
  31. high_high = group.pivot_table(index=keys, columns="点位", values="高高报警样本数", aggfunc="first")
  32. rows = []
  33. for (hour_start, batch), values in wide.iterrows():
  34. cov = float(coverage.loc[(hour_start, batch)])
  35. row = {
  36. "机组": unit,
  37. "import_batch_id": int(batch),
  38. "hour_start": hour_start.to_pydatetime(),
  39. "coverage": cov,
  40. # The long table is already filtered to valid running hours by
  41. # scan.py, so every row is valid here (coverage may be <0.8 for
  42. # the 4# cumulative-counter path).
  43. "valid": True,
  44. }
  45. for column, value in values.items():
  46. if pd.notna(value):
  47. row[f"{column}__avg"] = float(value)
  48. svalue = std.loc[(hour_start, batch), column] if column in std.columns else float("nan")
  49. if pd.notna(svalue):
  50. row[f"{column}__std"] = float(svalue)
  51. mvalue = minraw.loc[(hour_start, batch), column] if column in minraw.columns else float("nan")
  52. if pd.notna(mvalue):
  53. row[f"{column}__minraw"] = float(mvalue)
  54. zvalue = zeros.loc[(hour_start, batch), column] if column in zeros.columns else float("nan")
  55. if pd.notna(zvalue):
  56. row[f"{column}__zeros"] = int(zvalue)
  57. h = high.loc[(hour_start, batch), column] if column in high.columns else float("nan")
  58. hh = high_high.loc[(hour_start, batch), column] if column in high_high.columns else float("nan")
  59. row[f"{column}__high"] = 0 if pd.isna(h) else int(h)
  60. row[f"{column}__high_high"] = 0 if pd.isna(hh) else int(hh)
  61. rows.append(row)
  62. rows.sort(key=lambda item: (item["import_batch_id"], item["hour_start"]))
  63. result[unit] = rows
  64. return result
  65. def main() -> None:
  66. parser = argparse.ArgumentParser()
  67. parser.add_argument("--long", default=str(LONG))
  68. parser.add_argument("--out", required=True)
  69. parser.add_argument("--min-hours", type=int, default=1)
  70. parser.add_argument("--confirm-hours", type=int, default=3)
  71. for name in ("mild_peer", "mild_spread", "abnormal_peer", "abnormal_spread",
  72. "severe_peer", "severe_spread", "mild_peer_low", "abnormal_peer_low",
  73. "severe_peer_low", "mild_z", "abnormal_z", "severe_z",
  74. "mild_delta", "abnormal_delta", "severe_delta"):
  75. parser.add_argument(f"--{name.replace('_', '-')}", type=float)
  76. args = parser.parse_args()
  77. overrides = {
  78. key: getattr(args, key)
  79. for key in ("mild_peer", "mild_spread", "abnormal_peer", "abnormal_spread",
  80. "severe_peer", "severe_spread", "mild_peer_low", "abnormal_peer_low",
  81. "severe_peer_low", "mild_z", "abnormal_z", "severe_z",
  82. "mild_delta", "abnormal_delta", "severe_delta", "confirm_hours")
  83. if getattr(args, key) is not None
  84. }
  85. by_unit = load_wide(Path(args.long))
  86. all_qc, all_features, all_segments, all_cycles, all_channels, all_observations, all_hf = [], [], [], [], [], [], []
  87. for unit, rows in by_unit.items():
  88. number = int(unit[0])
  89. config = build_unit_config(number)
  90. qc, features, segments, cycles, channels, observations, hf_segments = unified.run(rows, config, overrides, args.min_hours)
  91. print(f"{unit}: 有效小时 {sum(1 for row in rows if row['valid']):,} 段 {len(segments):,}")
  92. all_qc += qc
  93. all_features += features
  94. all_segments += segments
  95. all_cycles += cycles
  96. all_channels += channels
  97. all_observations += observations
  98. all_hf += hf_segments
  99. output = Path(args.out)
  100. confirmed_hf, hf_candidates = hf_validation.filter_hf(all_hf)
  101. unified.write_results(output, all_qc, all_features, all_segments, all_cycles, all_channels, all_observations,
  102. confirmed_hf, hf_candidates)
  103. for level in ("轻微", "异常", "严重异常"):
  104. print(f" {level}: {sum(1 for segment in all_segments if segment['温度阶段'] == level):,}")
  105. print(f" 观察: {len(all_observations):,}")
  106. print(f" 高频候选: {len(hf_candidates):,} 高频确认: {len(confirmed_hf):,}")
  107. print(f" 合并段: {len(all_segments) + len(confirmed_hf):,}")
  108. print(f"written -> {output}")
  109. if __name__ == "__main__":
  110. main()