|
|
@@ -63,6 +63,52 @@ def system_matrix(cfg=None):
|
|
|
"""38台 × 7系统 状态 + 依据. 轴: 分系统模块(变桨/偏航/蓄能) + 温度通道归系统 + 润滑柱塞×fleet + 测风/通讯."""
|
|
|
cfg = cfg or farm()
|
|
|
TC = _load_tc(cfg)
|
|
|
+
|
|
|
+ # ★ 2026-09-17 缺件容错: 交付包不随产物 ⇒ 这批"无生成端"的件(pitch_daily/yaw_daily/system_aux/
|
|
|
+ # thermal_chain…)在目标机上必然缺。这里逐个兜住: 缺件 = 该面无数据 (记 GAPS, 页面显示缺件),
|
|
|
+ # 绝不让 KeyError/FileNotFoundError 把本体层整步打断 (实测清产物后重算就死在这里)。
|
|
|
+ GAPS: list[str] = []
|
|
|
+
|
|
|
+ def _missing(what: str, why: str = ''):
|
|
|
+ tag = f'{what} 缺件 ({why})' if why else f'{what} 缺件'
|
|
|
+ if tag not in GAPS:
|
|
|
+ GAPS.append(tag)
|
|
|
+
|
|
|
+ def _load_reg(fn, what, idx_col):
|
|
|
+ try:
|
|
|
+ reg = fn()
|
|
|
+ reg = reg[0] if isinstance(reg, tuple) else reg
|
|
|
+ return reg.set_index(idx_col)
|
|
|
+ except FileNotFoundError as e:
|
|
|
+ _missing(what, str(e).split("'")[-2] if "'" in str(e) else str(e)[:60])
|
|
|
+ except Exception as e:
|
|
|
+ _missing(what, f'{type(e).__name__}')
|
|
|
+ return pd.DataFrame()
|
|
|
+
|
|
|
+ def _load_parquet(name):
|
|
|
+ f = pathlib.Path(cfg['store']) / name
|
|
|
+ if not f.exists():
|
|
|
+ _missing(name)
|
|
|
+ return pd.DataFrame()
|
|
|
+ try:
|
|
|
+ return pd.read_parquet(f).set_index('turbine')
|
|
|
+ except Exception as e:
|
|
|
+ _missing(name, f'{type(e).__name__}')
|
|
|
+ return pd.DataFrame()
|
|
|
+
|
|
|
+ def _cell(i, t_, col, default='不可判'):
|
|
|
+ """按台取一格 —— 表空/行缺/列缺都返回 default (缺件时该轴不可判, 不是崩溃)。"""
|
|
|
+ try:
|
|
|
+ v = i.loc[t_, col]
|
|
|
+ except Exception:
|
|
|
+ return default
|
|
|
+ try:
|
|
|
+ if v != v: # NaN
|
|
|
+ return default
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+ return v
|
|
|
+
|
|
|
try:
|
|
|
from .subsys import structure as stxmod
|
|
|
_stx, STXD = stxmod.registry(cfg)
|
|
|
@@ -70,14 +116,15 @@ def system_matrix(cfg=None):
|
|
|
except Exception:
|
|
|
STX, STXD = None, {}
|
|
|
from .subsys import pitch as pitchmod, yaw as yawmod, hydraulic, temp_nbm
|
|
|
- preg, _ = pitchmod.registry()
|
|
|
- yreg, ydet = yawmod.registry()
|
|
|
- hreg, _ = hydraulic.registry()
|
|
|
+ p_i = _load_reg(lambda: pitchmod.registry(), '变桨面 pitch_daily.parquet', '机组')
|
|
|
+ y_i = _load_reg(lambda: yawmod.registry(), '偏航面 yaw_daily.parquet', '机组')
|
|
|
+ h_i = _load_reg(lambda: hydraulic.registry(), '蓄能面 (液压深挖件)', 'turbine')
|
|
|
treg = temp_nbm.registry()
|
|
|
tdf = treg[0] if isinstance(treg, tuple) else treg
|
|
|
- aux = pd.read_parquet(pathlib.Path(cfg['store']) / 'system_aux.parquet').set_index('turbine')
|
|
|
- ydf = pd.read_parquet(pathlib.Path(cfg['store']) / 'yaw_daily.parquet').set_index('turbine')
|
|
|
- p_i = preg.set_index('机组'); y_i = yreg.set_index('机组'); h_i = hreg.set_index('turbine')
|
|
|
+ if tdf is None or not len(tdf):
|
|
|
+ tdf = pd.DataFrame(columns=['turbine', 'channel', 'dev_K', '判'])
|
|
|
+ aux = _load_parquet('system_aux.parquet')
|
|
|
+ ydf = _load_parquet('yaw_daily.parquet')
|
|
|
pairs = temp_nbm.pairs_table()
|
|
|
try:
|
|
|
pcd = pd.read_parquet(pathlib.Path(cfg['store']) / 'powercurve_dev.parquet')
|
|
|
@@ -93,14 +140,22 @@ def system_matrix(cfg=None):
|
|
|
# 每系统: (states, 依据 list)
|
|
|
acc = {s: ([], []) for s in SYSTEMS}
|
|
|
# 变桨 = 分系统四维 + 蓄能深挖
|
|
|
- acc['变桨'][0].append(p_i.loc[t, '系统级'])
|
|
|
- acc['变桨'][1].append('四维:' + '/'.join(f"{d}{p_i.loc[t, d][0]}" for d in pitchmod.DIMS))
|
|
|
- hv = h_i.loc[t, '判']
|
|
|
+ acc['变桨'][0].append(_cell(p_i, t, '系统级'))
|
|
|
+ _pd_ = [_cell(p_i, t, d, '') for d in pitchmod.DIMS]
|
|
|
+ if any(_pd_):
|
|
|
+ acc['变桨'][1].append('四维:' + '/'.join(f'{d}{v}' for d, v in zip(pitchmod.DIMS, _pd_) if v))
|
|
|
+ else:
|
|
|
+ acc['变桨'][1].append('四维: 缺件, 不可判')
|
|
|
+ hv = _cell(h_i, t, '判', '—')
|
|
|
acc['变桨'][0].append('报警' if '候选' in hv else ('良好' if '记基线' in hv else '优秀'))
|
|
|
if hv != '—': acc['变桨'][1].append(f"蓄能: {h_i.loc[t, '依据']}")
|
|
|
# 偏航
|
|
|
- acc['偏航'][0].append(y_i.loc[t, '系统级'])
|
|
|
- acc['偏航'][1].append('四维:' + '/'.join(f"{d[:2]}{y_i.loc[t, d][0]}" for d in yawmod.DIMS))
|
|
|
+ acc['偏航'][0].append(_cell(y_i, t, '系统级'))
|
|
|
+ _yd_ = [_cell(y_i, t, d, '') for d in yawmod.DIMS]
|
|
|
+ if any(_yd_):
|
|
|
+ acc['偏航'][1].append('四维:' + '/'.join(f'{d[:2]}{v}' for d, v in zip(yawmod.DIMS, _yd_) if v))
|
|
|
+ else:
|
|
|
+ acc['偏航'][1].append('四维: 缺件, 不可判')
|
|
|
# 温度通道归系统
|
|
|
for _, r in tdf[tdf['turbine'] == t].iterrows():
|
|
|
ch = r['channel'].replace('tmp_wtc_', '').replace('_mean', '').replace('_max', '')
|
|
|
@@ -118,7 +173,7 @@ def system_matrix(cfg=None):
|
|
|
acc[sysname][1].append(f"配对差{pr['pair']} {pr['pair_diff']:+.1f}K(偏{pr['pair_dev']:+.1f})")
|
|
|
# 润滑柱塞 ×fleet (筛查级; 单窗相对门弱, 只报不定级除非极端)
|
|
|
for s in ('主轴承', '齿轮箱', '发电机'):
|
|
|
- v = aux.loc[t, f'lub_{s}']
|
|
|
+ v = _cell(aux, t, f'lub_{s}', float('nan'))
|
|
|
x = v / max(lub_med[s], 1e-9) if v == v else np.nan
|
|
|
if x == x and (x >= 2.5 or x <= 0.25):
|
|
|
acc[s][0].append('良好')
|
|
|
@@ -158,11 +213,11 @@ def system_matrix(cfg=None):
|
|
|
if pc_a.get(t):
|
|
|
acc['主控与传感网'][0].append('报警')
|
|
|
acc['主控与传感网'][1].append(f"曲线判风速计偏置A类 ({pc_a[t]})")
|
|
|
- wf = float((ydf.loc[t, 'windfau_s'] or 0) + (ydf.loc[t, 'windfau_t'] or 0))
|
|
|
- yfa = int(ydf.loc[t, 'D4风传感'])
|
|
|
+ wf = float((_cell(ydf, t, 'windfau_s', 0) or 0) + (_cell(ydf, t, 'windfau_t', 0) or 0))
|
|
|
+ yfa = int(_cell(ydf, t, 'D4风传感', 0) or 0)
|
|
|
if wf >= 100 or yfa >= 30: acc['主控与传感网'][0].append('报警'); acc['主控与传感网'][1].append(f'测风故障计数{wf:.0f}+报警{yfa}')
|
|
|
elif wf >= 30 or yfa >= 5: acc['主控与传感网'][0].append('良好'); acc['主控与传感网'][1].append(f'测风计数{wf:.0f}/报警{yfa}')
|
|
|
- sc = aux.loc[t, 'scomerr']
|
|
|
+ sc = _cell(aux, t, 'scomerr', float('nan'))
|
|
|
scx = sc / max(sc_med, 1e-9) if sc == sc else np.nan
|
|
|
if scx == scx and scx >= 3 and sc >= 50:
|
|
|
acc['主控与传感网'][0].append('良好'); acc['主控与传感网'][1].append(f'通讯错误增量{sc:.0f} ({scx:.1f}×fleet)')
|
|
|
@@ -172,6 +227,13 @@ def system_matrix(cfg=None):
|
|
|
sysd[s] = dict(状态=base, 依据='; '.join(why) if why else '各轴无非常态')
|
|
|
result[t] = sysd
|
|
|
|
|
|
+ if GAPS:
|
|
|
+ # 整表挂一条缺件说明 (页面/维护页据此显示"哪些面因缺件不可判"), 不静默造数
|
|
|
+ result['_缺件说明'] = dict(GAPS=list(GAPS),
|
|
|
+ 注='这些是"包内没有生成端"的产物 (docs §7); 交付包按用户令不随产物, '
|
|
|
+ '目标机上要么从旧交付包补齐 (products_restore_missing.py --stash <包.zip>), '
|
|
|
+ '要么由研发补生成端。')
|
|
|
+
|
|
|
# 振动映射进分系统状态 (2026-08-28 用户令): handoff 判级按 component 归系统, 取严合并+依据可溯.
|
|
|
# 定论/报警/预警级 → 该系统升'报警'; 候选/监视/观察级 → 状态不动只追加依据 (筛查级展示态,
|
|
|
# 振动判级本身已过双模审, 非本层产判级 — 转录取严)
|