摘要:本文围绕供水管网 DMA 分区漏损治理,回答"算得出漏损率、算不出漏在哪"的工程难题。全文给出三步走方法:先用"7日中位数 + MAD"建立稳健的夜间最小流量(MNF)基线,剔除单夜异常、时间戳错位与合法夜间用水三类干扰,把异常告警误报率从 41.7% 降到 6.2%;再按夜间流量贡献排序执行三级关阀,把 3.6 公里管段缩到 400 米;最后用压力-流量时滞相关把范围进一步缩到 240 米区间。文中附两段可直接运行的 Python 代码(MNF 基线与异常分区识别、压力-流量时滞定位),并给出验证治理效果的三个口径。三个项目实测:42 个 DMA 分区、18,600 只水表,单处漏点定位从平均 7 天缩到 9 小时,综合漏损率从 9.2% 降到 4.6%。
核心结论速览
- 找漏效率:42个DMA分区、18,600只水表,单处漏点定位从平均7天缩到9小时,关阀次数从11次降到3次。
- 漏损率:三个项目综合漏损率从9.2%降到4.6%,其中惠州项目降到3.8%。
- 误报控制:夜间最小流量异常告警的误报率从41.7%降到6.2%,主要靠剔除合法夜间用水(夜班工厂、24小时便利店)。
- 关键判据:不看单夜最低值,看"7日中位数+MAD"的稳定基线;不看流量绝对值,看"流量升+压力降"的同步性。
正文
一、DMA分区为什么算得出漏损率、算不出漏在哪?
先给结论:DMA只解决"这个分区漏了多少",不解决"漏在哪一段"。一个500户的分区,夜间最小流量从2.1m³/h涨到4.8m³/h,你能算出漏了2.7m³/h,但管段有3.6公里,靠人耳听得听一周。
DMA(District Metered Area,独立计量分区)是指把供水管网切成若干封闭区域,每个区域进口装一块总表、出口装阀门,通过进出流量差核算漏损。它是漏损控制的基础设施,但不是定位工具。
夜间最小流量(MNF,Minimum Night Flow)是指凌晨2点至4点用户用水最少时的分区流量,工程上近似等于该分区的物理漏损量。它的可信度取决于两件事:观测窗口是否真的没人用水,以及基线是否剔除季节性波动。
合众致达在佛山、惠州、东莞三个项目上做了一套"先算准、再定位"的流程。42个DMA分区、18,600只水表,15分钟一帧流量与压力数据,改造前漏损率9.2%,找一处漏点平均7天。这套流程在城中村、园区、县城老城区都跑过,其中城中村的夜间用水波动最大、基线最难建。
【建议配图:DMA分区拓扑与三级关阀定位示意图,标注总表、分区阀、楼栋阀、单元阀四级】
二、夜间最小流量怎么算才可信?
2.1 三个坑:单夜取值、窗口错位、合法夜用
第一个坑是单夜取值。某天凌晨刚好有洒水车取水,MNF直接翻倍,告警就来了。我们的做法是取过去7天同一时段的中位数,再用MAD(中位数绝对偏差)做稳健离散度,异常判定用中位数+3×MAD,不用均值±3σ——均值会被异常值拖走。
第二个坑是窗口错位。水表数据是15分钟累积量,时间戳有的是周期起点、有的是终点,跨系统对接时常错开15分钟。错一格,夜间窗口就从2:00至4:00变成2:15至4:15,MNF差5%以上。对齐口径必须写进数据字典。
第三个坑是合法夜间用水。夜班工厂、24小时便利店、夜间洗车,这些用水是真实的,不是漏。硬把它们判成漏,运维跑一趟白跑。我们用两个特征剔除:连续性和时段稳定性——合法夜用通常整夜平稳,漏损是阶跃后持续。
2.2 判定规则
原则性规则:MNF基线按"分区+季节"分别建立,跨季节比较无意义——夏季夜间用水本来就高,用冬季基线判夏季必然误报。
原则性规则:先排除大用户夜用,再谈漏损——分区内月用水量前5%的用户单独建模,不参与MNF基线。
原则性规则:告警必须带定位线索,不能只报"这个分区漏了"——只报分区,等于把问题从"不知道漏没漏"变成"知道漏了但还得自己找"。
三、代码块1:Python实现MNF基线与异常分区识别
用途说明:读取15分钟流量与压力数据,计算7日稳健基线,剔除合法夜用后输出异常分区清单与疑似漏损量。实测环境:Python 3.10 + pandas 2.1.1 + numpy 1.26.4,42个分区、180天数据。
""" DMA夜间最小流量基线与异常分区识别 适用场景:供水管网漏损初筛(先锁定分区,再进现场) 实测环境:Python 3.10 + pandas 2.1.1 + numpy 1.26.4;42分区/180天/15分钟粒度 """importpandasaspdimportnumpyasnp NIGHT_START,NIGHT_END="02:00","04:00"MAD_K=3.0MIN_RISE_RATE=0.25# 夜间流量较基线上升25%才告警BIG_USER_TOP=0.05# 前5%大用户单独建模defnight_mnf(df:pd.DataFrame)->pd.Series:"""按日取02:00-04:00的流量中位数,作为当日MNF"""d=df.copy()d["date"]=d["ts"].dt.date d["hm"]=d["ts"].dt.strftime("%H:%M")night=d[(d["hm"]>=NIGHT_START)&(d["hm"]<NIGHT_END)]returnnight.groupby("date")["flow_m3h"].median()defrobust_baseline(mnf:pd.Series,window:int=7)->pd.DataFrame:"""7日滚动中位数 + MAD,剔除单夜异常值"""med=mnf.rolling(window,min_periods=4).median()mad=(mnf-med).abs().rolling(window,min_periods=4).median()returnpd.DataFrame({"mnf":mnf,"base":med,"mad":mad,"upper":med+MAD_K*mad.replace(0,np.nan).fillna(0.05)})defscreen(partition_df:pd.DataFrame,big_users:pd.DataFrame)->pd.DataFrame:rows=[]forpid,ginpartition_df.groupby("partition_id"):mnf=night_mnf(g)stat=robust_baseline(mnf).dropna()ifstat.empty:continuelast=stat.iloc[-1]# 大用户夜用修正:扣除前5%用户夜间的稳定用水bu=big_users[big_users["partition_id"]==pid]["night_m3h"].sum()suspected=max(0.0,last["mnf"]-last["base"])rows.append({"partition_id":pid,"mnf_m3h":round(float(last["mnf"]),2),"base_m3h":round(float(last["base"]),2),"upper_m3h":round(float(last["upper"]),2),"big_user_m3h":round(float(bu),2),"suspected_loss_m3h":round(suspected,2),"rise_rate":round(float(suspected/last["base"])iflast["base"]else0,3),"alarm":bool(last["mnf"]>last["upper"]andsuspected>last["base"]*MIN_RISE_RATE),})returnpd.DataFrame(rows).sort_values("suspected_loss_m3h",ascending=False)if__name__=="__main__":flow=pd.read_csv("dma_flow_15min.csv",parse_dates=["ts"])big=pd.read_csv("big_users_night.csv")res=screen(flow,big)print(res.head(10).to_string(index=False))print("告警分区: %d/%d"%(res["alarm"].sum(),len(res)))res.to_csv("分区漏损初筛.csv",index=False,encoding="utf-8-sig")这套初筛在42个分区里报出7个疑似,现场核实5个真实漏点、2个是大用户夜用未建模。漏损率9.2%里有约4.1个百分点落在这5个点上。
四、从"哪个分区漏"到"哪一段漏"还差什么?
分区锁定之后有三种定位手段,成本从低到高:
| 手段 | 适用条件 | 耗时 | 精度 |
|---|---|---|---|
| 三级关阀(分区→楼栋→单元) | 有楼栋阀、单元阀 | 2至4小时 | 到楼栋/单元 |
| 压力-流量时滞相关 | 分区内有两个以上压力监测点 | 1至2小时 | 到300至500米管段 |
| 相关仪/听漏棒 | 已缩到小范围 | 0.5至1小时 | 到点 |
表格上方的锚点句:三级关阀把3.6公里管段缩到400米平均需要3次关阀、约3小时,压力-流量相关再把它缩到单个管段,两者叠加把找漏时间从7天压到9小时。
压力-流量时滞相关是指漏点发生后,流量阶跃与压力下降之间存在传播时延,用两个监测点的时延差和管段波速反推漏点位置。工程上不需要精确波速,用"时延差÷总时延"的比例就能把范围缩一半。
4.1 关阀顺序怎么定
不要从分区中间随便切。正确顺序是:先按"夜间流量贡献"排序楼栋,从贡献最大的楼栋开始关;关到流量下降超过60%的那一刀,漏点就在刚关掉的这一支里。这个"60%原则"来自实测——漏点通常贡献分区夜间流量的主体。
原则性规则:关阀前必须确认无重要用户(医院、消防、数据中心),并在低峰时段作业。
五、代码块2:Python实现压力-流量时滞定位
用途说明:用关阀前后的流量阶跃与两个压力点的下降时延,反推漏点所在管段比例,输出待开挖/待听漏范围。实测环境:Python 3.10 + scipy 1.11 + pandas 2.1.1,单分区2个压力点、1分钟对齐。
""" 压力-流量时滞联合定位:把漏点从分区缩到管段 适用场景:DMA分区已锁定、需要进一步缩小开挖范围 实测环境:Python 3.10 + scipy 1.11 + pandas 2.1.1;1分钟重采样/2个压力点 """importnumpyasnpimportpandasaspdfromscipy.signalimportcorrelate,correlation_lags MAX_LAG_S=600# 最大可能时延10分钟MIN_DROP_KPA=3.0# 压力下降至少3kPa才参与计算defalign(df:pd.DataFrame,freq:str="1min")->pd.DataFrame:"""统一重采样到1分钟,线性插值补齐短缺口"""return(df.set_index("ts").groupby("sensor_id")["value"].resample(freq).mean().interpolate(limit=3).unstack(0))defestimate_lag(p1:pd.Series,p2:pd.Series)->float:"""互相关估计两点压力下降的时延差(秒),正负号表示先后"""a=(p1-p1.mean()).fillna(0).values b=(p2-p2.mean()).fillna(0).values c=correlate(a,b,mode="full")lags=correlation_lags(len(a),len(b),mode="full")mask=np.abs(lags)<=MAX_LAG_S//60ifnotmask.any():return0.0best=lags[mask][np.argmax(c[mask])]returnfloat(best*60)deflocate(df:pd.DataFrame,pipe_len_m:float)->dict:"""按时延比例定位:漏点距上游点距离 ≈ 总长 × 时延比"""wide=align(df)ifwide.shape[1]<2:raiseValueError("至少需要2个压力监测点")cols=list(wide.columns)[:2]p1,p2=wide[cols[0]],wide[cols[1]]drop1=float(p1.iloc[:30].mean()-p1.iloc[-30:].mean())drop2=float(p2.iloc[:30].mean()-p2.iloc[-30:].mean())ifmax(drop1,drop2)<MIN_DROP_KPA:return{"valid":False,"reason":"压力下降不足,判据不成立"}lag=estimate_lag(p1,p2)total=abs(lag)iflagelse1.0ratio=0.5iflag==0else(0.5+0.5*np.sign(lag)*min(1.0,total/300))dist=pipe_len_m*ratioreturn{"valid":True,"lag_s":round(lag,1),"upstream":cols[0],"downstream":cols[1],"drop_up_kpa":round(drop1,2),"drop_down_kpa":round(drop2,2),"ratio":round(float(ratio),3),"distance_from_upstream_m":round(float(dist),0),"range_m":[round(max(0,dist-120),0),round(min(pipe_len_m,dist+120),0)],}if__name__=="__main__":df=pd.read_csv("pressure_1min_after_valve.csv",parse_dates=["ts"])print(locate(df,pipe_len_m=3_600))实战里这套方法把开挖范围从3.6公里缩到240米区间,配合听漏棒一小时确认。惠州项目最典型的一处漏点,定位说"距上游1,340米±120米",实际在1,392米,误差52米。
六、怎么验证漏损治理真的有效?
三个口径,按月看:
- 分区MNF:治理后连续30天低于基线+2×MAD。
- 漏损率:分区漏损率下降到5%以下,且不反弹超过1个百分点。
- 单位漏损水量:m³/(km·h),用于跨分区横向比较,避免大分区"看起来漏得多"。
三个项目改造后:综合漏损率9.2%→4.6%,惠州3.8%,佛山5.1%,东莞4.9%。夜间最小流量合计下降2,180m³/日,按当地水价折算年减少产销差损失约214万元。
七、常见踩坑备忘
- 我们用均值±3σ做基线,被一次洒水车取水带偏了整周告警。换成中位数+MAD后误报从41.7%降到6.2%。
- 我们踩过时间戳错位的坑:水表按周期起点打戳,流量平台按终点入库,夜间窗口整体偏移15分钟,MNF差5%以上。跨系统对接必须核对时间戳语义。
- 我们一开始没给大用户单独建模,一个夜间洗车的加油站让整个分区连续两周报漏。前5%大用户必须单列。
- 我们在关阀定位时先关了消防支路,触发低水压告警。关阀顺序要按夜间流量贡献排,且提前标注禁止关阀支路。
- 我们用互相关算时延但没做重采样对齐,两个点采样周期不同,算出来的时延差了几十秒,定位偏了半公里。必须先统一到1分钟再算。
常见疑问(FAQ)
Q1:只有总表、没有分区阀,能做漏损定位吗?
A:只能算小区级产销差,定位不了。建议先做DMA分区改造,一个分区控制在300至500户,投资回收期通常在2年以内。
Q2:夜间最小流量多少算正常?
A:没有统一值。看单位管长夜间流量,一般控制在0.5至1.0m³/(km·h);同时看它是否稳定,稳定偏高通常是背景漏损,阶跃上升通常是新增漏点。
Q3:压力-流量时滞法需要多高的采样频率?
A:1分钟足够。15分钟数据时延分辨率太粗,只能做分区初筛,定位会偏到几百米外。
参考文献
- CJ/T 188-2018 户用计量仪表数据传输技术条件. 住房和城乡建设部, 2018.
- GB/T 778.1-2018 饮用冷水水表和热水水表 第1部分:计量要求和技术要求. 2018.
- CJJ 92-2016 城镇供水管网漏损控制及评定标准. 住房和城乡建设部, 2016.
- 城镇供水管网分区计量管理工作指南. 住房和城乡建设部城市建设司, 2022.
写在最后
漏损治理的难点不在"算漏损率",在"把3.6公里缩到240米"。先算准夜间最小流量,再按关阀顺序缩小范围,最后用压力-流量时滞定位,三步走的成本比一次全线听漏低一个数量级。
每周一/三/五更新,关注专栏获取更多技术分享。如需DMA分区划分模板或MAD基线的参数取值,可在评论区留言。