| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 |
- # -*- coding: utf-8 -*-
- """P3-b 验收树状态机: TCM-16 三分支决策树的机器化 (29# 修后即首例).
- 状态: 待执行 → 泵已修(前置判定: 柱塞回带?) → 分支判定(A尖峰消/B尖峰不消/C不回带) → closed.
- 输入槽 (多源, 各归其主): plunger = 我方柱塞仓自动判 / vibration = 振动线判定输入(尖峰消判据在它)
- / oil = 油样输入(29# 主轴后 Fe/PQ vs 2025-03 基线 15/12, 本轮华标缺采待补).
- 纪律: 修泵事件必须人工登记 (repair_done date), 禁从数据猜"已修"; 数据不足降待验禁默认按A结案."""
- from src import paths as P
- import json, pathlib
- import pandas as pd
- from .store import Store
- ONT = P.ont()
- ST = P.store()
- SID = 'acceptance/WTG29/state'
- def plunger_verdict(repair_month):
- """前置判定: 修后柱塞回带? (自身正常带 = 2025-11~2026-05 单次时长中位的 0.7~1.4×)."""
- lub = pd.read_parquet(ST / 'mblub_monthly.parquet')
- l29 = lub[lub.turbine == 'WTG29'].set_index('month')
- per = (l29.pis / l29.cnt.replace(0, pd.NA)).dropna()
- base = float(per.loc['2025-11':'2026-05'].median())
- post = per[per.index > repair_month]
- if not len(post):
- return dict(verdict='待数据', note=f'修后({repair_month}后)无柱塞月度数据 (SCADA 需更新)')
- first = float(post.iloc[0])
- ratio = first / base
- if 0.7 <= ratio <= 1.4:
- return dict(verdict='回带', note=f'修后首月单次 {first:.0f}s = 自身基线({base:.0f}s)的 {ratio:.2f}× ∈ [0.7,1.4] — 突变型预期的立即回带')
- if ratio > 1.4:
- return dict(verdict='未回带', note=f'修后首月 {first:.0f}s = {ratio:.2f}× 仍拖长 → 分支C: 泵没修好或管路残留')
- return dict(verdict='回带过短', note=f'修后首月 {first:.0f}s = {ratio:.2f}× 低于带 → 疑打脂欠量复发, 按分支C排查')
- def evaluate(repair_done=None, vibration_input=None, oil_input=None, *, persist=True, store_path=None):
- """状态机推进 (幂等; 每次月度滚动调一遍).
- repair_done: 'YYYY-MM' 人工登记的修泵完成月 (None=未修)
- vibration_input: dict(verdict='尖峰消|尖峰持续|待数据', note=..., by='振动线', when=...) — 判据在振动线
- oil_input: dict(verdict='支持内窥|不构成无磨损|记基线', note=...) — TCM-16 分支B三分判读
- persist: False = 只算不落盘 (**测试必须传 False**; 否则每跑一次 pytest 就改一次生产本体库,
- 靠"末尾恢复真实态"回滚是脆的 —— 恢复不保证字节一致, 且中途失败就留脏)
- store_path: 指定库文件 (默认生产库)"""
- s = Store(store_path or (ONT / 'objects.json'))
- if not repair_done:
- state, branch, note = '待执行', None, '修泵未发生 — 排期已裁 (用户2026-08-28批准情景A: 2026-09首个CTV窗), 待现场执行与完成登记'
- pv = None
- else:
- pv = plunger_verdict(repair_done)
- if pv['verdict'] in ('未回带', '回带过短'):
- state, branch = '分支C', 'C'
- note = f"柱塞{pv['verdict']} → 不进轴承议题, 重开泵/管路排查。{pv['note']}"
- elif pv['verdict'] == '待数据':
- state, branch, note = '泵已修·待柱塞数据', None, pv['note']
- else: # 回带 → 看振动槽
- vv = (vibration_input or {}).get('verdict', '待数据')
- if vv == '尖峰消':
- state, branch = '分支A·闭环', 'A'
- note = '柱塞回带 + 尖峰消(振动线判) → 纯润滑问题, 轴承逃过; 温度不降不否证A(热滞后)'
- elif vv == '尖峰持续':
- state, branch = '分支B', 'B'
- note = ('柱塞回带但尖峰不消 → 升级轴承内窥; 前置低成本中继=主轴后油样 vs 2025-03基线Fe15/PQ12 '
- f"(油样槽: {(oil_input or {}).get('verdict', '待采 — 华标2026-07轮29#缺采')})")
- else:
- state, branch = '泵已修·待振动判定', None
- note = '柱塞已回带; 尖峰消判据(≥14天/≥20条/max<8/扣台风)在振动线, 待其判定输入; 数据不足禁默认按A结案'
- data_end = str(pd.read_parquet(ST / 'mblub_monthly.parquet').month.max())
- # F8: 状态机演进走 force 通道, 逻辑时钟=柱塞数据窗末 (禁 datetime.now 假时间);
- # 状态实质变化时旧版本自动进 props.history (状态迁移史留痕), 幂等空转不触发
- s.put(dict(id=SID, type='Decision',
- props=dict(name='29# 修泵验收状态机 (TCM-16 三分支的机器化)',
- state=state, branch=branch, note=note,
- repair_done=repair_done or '未登记',
- slots=dict(plunger=pv, vibration=vibration_input, oil=oil_input),
- claim_window=f'柱塞数据窗至 {data_end}'),
- links=dict(basedOn=['decision/WTG29/acceptance_tree', 'verdict/WTG29/TCM-16'])),
- force=True, when=data_end)
- if persist: s.save()
- return state, branch, note
- if __name__ == '__main__':
- st, br, nt = evaluate() # 当前真实状态: 修泵未发生
- print(f'状态={st} 分支={br}\n{nt}')
|