197 lines
8.5 KiB
Python
197 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
水力瓶颈管道综合分析
|
|
数据源:管道属性 + 实时水力 + 节点压力 → 复合评分 → 改造建议
|
|
注:realtime links 的 setting 字段为无效值,已移除所有基于 setting 的判定。
|
|
"""
|
|
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['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['id']; prop = pipe_map.get(pid)
|
|
if prop:
|
|
merged.append({**prop, **r, '_prop_id': prop['id'], '_rt_id': r['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()
|