#!/usr/bin/env python3 """ 水力瓶颈管道综合分析 数据源:管道属性 + 实时水力 + 节点压力 → 复合评分 → 改造建议 注:realtime links 的 setting 字段为无效值,已移除所有基于 setting 的判定。 schema 适配(2026-08 实测):realtime links 主键为 link_id,realtime nodes 主键为 node_id, time 为 UTC 格式,--target-time 需传 UTC 时刻(如北京时间 08:00 对应 00:00+00:00)。 """ import json, sys, math, argparse from collections import defaultdict VELOCITY_THRESHOLDS = {"critical": 3.0, "severe": 2.0, "high": 1.5} def pct(d, v): if not d: return 0 k = (v/100)*(len(d)-1); f=math.floor(k); c=math.ceil(k) return d[f] if f==c else d[f]*(c-k)+d[c]*(k-f) def load_json(path): with open(path, encoding='utf-8') as f: raw = f.read() return json.loads(raw[raw.find('{'):]) def main(): ap = argparse.ArgumentParser() ap.add_argument('--pipe-props', required=True) ap.add_argument('--realtime', required=True) ap.add_argument('--node-pressures', required=True) ap.add_argument('--target-time', required=True) ap.add_argument('--top', type=int, default=100) args = ap.parse_args() TT = args.target_time # 1. Load print("[1/5] Loading data...", file=sys.stderr) props = load_json(args.pipe_props).get('data', []) pipe_map = {p['id']: p for p in props} rt = load_json(args.realtime).get('data', []) rt = [r for r in rt if r.get('time') == TT] np = load_json(args.node_pressures).get('data', []) np = [n for n in np if n.get('time') == TT] node_pressure = {n['node_id']: n.get('pressure', n.get('value',0)) for n in np} print(f" Pipes: {len(props)}, Realtime: {len(rt)}, Node pressures: {len(node_pressure)}", file=sys.stderr) # 2. Merge print("[2/5] Merging...", file=sys.stderr) merged = [] for r in rt: pid = r['link_id']; prop = pipe_map.get(pid) if prop: merged.append({**prop, **r, '_prop_id': prop['id'], '_rt_id': r['link_id']}) print(f" Merged: {len(merged)}", file=sys.stderr) # 3. Score print("[3/5] Scoring...", file=sys.stderr) hl_vals = sorted([abs(m['headloss']) for m in merged]) p80 = pct(hl_vals, 80); p90 = pct(hl_vals, 90); p95 = pct(hl_vals, 95) scored = [] for m in merged: vel = abs(m['velocity']); hl = abs(m['headloss']) diam = m.get('diameter', 0) length = m.get('length', 0); roughness = m.get('roughness', 0) n1, n2 = m['node1'], m['node2'] pid = m['id'] vs = 3 if vel>=3 else (2 if vel>=2 else (1 if vel>=1.5 else 0)) vg = "极危" if vs==3 else ("严重" if vs==2 else ("偏高" if vs==1 else "正常")) hs = 2 if hl>p90 else (1 if hl>p80 else 0) hg = "严重" if hs==2 else ("中度" if hs==1 else "正常") composite = vs + hs is_bn = (vs>=1 and hs>=1) or (vs>=2) # Node pressures p1 = node_pressure.get(n1); p2 = node_pressure.get(n2) min_p = min(p1, p2) if (p1 is not None and p2 is not None) else None # Pipe category if diam <= 50: dcat = "微型(≤50mm)" elif diam <= 100: dcat = "小型(51-100mm)" elif diam <= 200: dcat = "中型(101-200mm)" elif diam <= 400: dcat = "大型(201-400mm)" elif diam <= 800: dcat = "主干(401-800mm)" else: dcat = "干管(>800mm)" scored.append({ 'id': pid, 'node1': n1, 'node2': n2, 'velocity': round(vel, 4), 'flow': round(m.get('flow',0), 4), 'headloss': round(hl, 4), 'length': round(length, 4), 'diameter': int(diam), 'roughness': int(roughness), 'vel_grade': vg, 'hl_grade': hg, 'composite_score': composite, 'is_bottleneck': is_bn, 'n1_pressure': round(p1, 2) if p1 is not None else None, 'n2_pressure': round(p2, 2) if p2 is not None else None, 'min_pressure': round(min_p, 2) if min_p is not None else None, 'diam_cat': dcat, }) # 4. Filter & sort print("[4/5] Filtering bottlenecks...", file=sys.stderr) bn = [s for s in scored if s['is_bottleneck']] bn.sort(key=lambda x: (-x['composite_score'], -x['velocity'])) critical = [b for b in bn if b['vel_grade']=='极危'] severe = [b for b in bn if b['vel_grade']=='严重'] high_vel = [b for b in bn if b['vel_grade']=='偏高'] low_p = sum(1 for b in bn if b['min_pressure'] and b['min_pressure'] < 20) lowish_p = sum(1 for b in bn if b['min_pressure'] and 20 <= b['min_pressure'] < 25) high_rough = sum(1 for b in bn if b['roughness'] > 130) # Diam distribution dd = defaultdict(int) for b in bn: d = b['diameter'] if d <= 50: dd['≤50mm']+=1 elif d <= 100: dd['51-100mm']+=1 elif d <= 200: dd['101-200mm']+=1 elif d <= 400: dd['201-400mm']+=1 elif d <= 800: dd['401-800mm']+=1 else: dd['>800mm']+=1 # 5. Output print("[5/5] Generating report...", file=sys.stderr) # Print text summary to stderr print(f"\n{'='*70}", file=sys.stderr) print(f" 水力瓶颈分析报告 - {TT}", file=sys.stderr) print(f"{'='*70}", file=sys.stderr) print(f" 总管道数: {len(scored)}", file=sys.stderr) print(f" 瓶颈管道: {len(bn)} ({len(bn)/len(scored)*100:.1f}%)", file=sys.stderr) print(f" 极危(>3.0m/s): {len(critical)} 条", file=sys.stderr) print(f" 严重(2.0-3.0): {len(severe)} 条", file=sys.stderr) print(f" 偏高(1.5-2.0): {len(high_vel)} 条", file=sys.stderr) print(f"\n 水头损失阈值: P80={p80:.4f}m P90={p90:.4f}m P95={p95:.4f}m", file=sys.stderr) print(f" 平均={sum(hl_vals)/len(hl_vals):.4f}m 最大={max(hl_vals):.4f}m", file=sys.stderr) print(f"\n 低压节点(<20m): {low_p} 条, 偏低(20-25m): {lowish_p} 条", file=sys.stderr) print(f" 高粗糙度(>130): {high_rough} 条", file=sys.stderr) print(f"\n 瓶颈管径分布:", file=sys.stderr) for cat in ['≤50mm','51-100mm','101-200mm','201-400mm','401-800mm','>800mm']: print(f" {cat}: {dd.get(cat,0)} 条", file=sys.stderr) # Text table (Top 30) to stderr print(f"\n{'='*120}", file=sys.stderr) print(f"{'排名':<5} {'管道ID':<10} {'流速(m/s)':<10} {'水损(m)':<10} {'管径(mm)':<9} {'管长(km)':<10} {'评分':<4} {'压力1':<8} {'压力2':<8} {'管径类别':<18}", file=sys.stderr) print('-'*120, file=sys.stderr) for i, b in enumerate(bn[:30]): p1s = f"{b['n1_pressure']:.1f}" if b['n1_pressure'] is not None else "-" p2s = f"{b['n2_pressure']:.1f}" if b['n2_pressure'] is not None else "-" print(f"{i+1:<5} {b['id']:<10} {b['velocity']:<10.4f} {b['headloss']:<10.2f} {b['diameter']:<9} {b['length']:<10.4f} {b['composite_score']:<4} {p1s:<8} {p2s:<8} {b['diam_cat']:<18}", file=sys.stderr) # Generate suggestions for each bottleneck for b in bn: sug = [] if b['velocity'] > 2.0: sug.append(f"流速{b['velocity']:.2f}m/s过高,需扩容或分流") elif b['velocity'] > 1.5: sug.append(f"流速{b['velocity']:.2f}m/s偏高") hl = b['headloss'] if hl > p95: sug.append(f"水头损失{hl:.2f}m(>P95)严重超标") elif hl > p90: sug.append(f"水头损失{hl:.2f}m(>P90)") if b['diameter'] < 100: sug.append(f"管径{b['diameter']}mm偏小,建议扩径至≥150mm") elif b['diameter'] < 200: sug.append(f"管径{b['diameter']}mm,评估扩容至250-300mm") if b['roughness'] > 130: sug.append(f"粗糙系数{b['roughness']}偏高,建议内衬修复") if b['min_pressure'] is not None and b['min_pressure'] < 20: sug.append(f"端节点压力{b['min_pressure']:.1f}m(<20m),低压区域需增压") elif b['min_pressure'] is not None and b['min_pressure'] < 25: sug.append(f"端节点压力{b['min_pressure']:.1f}m偏低") if b['length'] < 0.01 and b['headloss'] > 0.5: sug.append("短管高水损,检查是否存在模型异常或局部阻塞") b['suggestions'] = sug result = { 'target_time': TT, 'summary': { 'total_pipes': len(scored), 'bottleneck_count': len(bn), 'critical': len(critical), 'severe': len(severe), 'high_vel': len(high_vel), 'low_pressure_nodes': low_p, 'lowish_pressure_nodes': lowish_p, 'high_roughness_pipes': high_rough, 'headloss_p80': round(p80,4), 'headloss_p90': round(p90,4), 'headloss_p95': round(p95,4), 'diameter_distribution': dict(dd), }, 'top_bottlenecks': bn[:args.top], } print(json.dumps(result, ensure_ascii=False, indent=2)) if __name__ == '__main__': main()