فهرست منبع

A2: 数据层四项数据源目录改为 data/raw/<场站名称>/{scada_10min,故障报警,风机故障记录,油样报告}

- src/windscada/config.py: 新增 raw_station(场站目录名, 如东) 与 src_oil; 四项 src_* 改为
  data/raw/如东/... (src_1min 一并对齐为 scada_1min); 新增 raw_station_dir() 供页面取场站目录
- src/ontology/maintenance.py: 维护页「数据层」四项 位置 改为 disp(data/raw/如东/…) + 尾部分隔符,
  场站目录从 config.raw_station_dir() 取 (不另抄一份); 文件形态提示移入 说明
  (WTG01..38.csv 平铺 / {年}年故障记录分年 / 按台号部件的化验报告)
- data_tpl_en.py: 四项 位置 的英文整串映射原键是开发机路径(/Users/yuanying/...), 早已查不中,
  改为新路径 (Windows 反斜杠与 POSIX 斜杠两套都列)
- configs/farms/_模板.json.example: 外场模板同步为按场分目录约定 + raw_station
- data/raw/README_把原始数据放这里.txt: 写明四个目录约定
- 机理层 (技术资料等) 不动: A2 只点名数据层四项

实测 /api/maint_survey 四项位置:
  <安装目录>\\data\\raw\\如东\\scada_10min\\ 等四项 (详见提交说明)
zhouyang.xie 1 ماه پیش
والد
کامیت
54f7e34e1a

+ 5 - 3
configs/farms/_模板.json.example

@@ -1,11 +1,13 @@
 {
   "name": "示例风电场",
+  "raw_station": "示例场",
   "turbines": "WTG{:02d}:1-25",
   "rated_kw": 3000,
-  "src_10min": "/Volumes/WINDDATA/DATA2/示例场/10分钟数据",
-  "src_1min": "/Volumes/WINDDATA/DATA2/示例场/1分钟数据",
-  "src_alarm": "/Volumes/WINDDATA/DATA2/示例场/报警数据",
+  "src_10min": "/Volumes/WINDDATA/DATA2/示例场/scada_10min",
+  "src_1min": "/Volumes/WINDDATA/DATA2/示例场/scada_1min",
+  "src_alarm": "/Volumes/WINDDATA/DATA2/示例场/故障报警",
   "src_workorder": "/Volumes/WINDDATA/DATA2/示例场/风机故障记录",
+  "src_oil": "/Volumes/WINDDATA/DATA2/示例场/油样报告",
   "store": "outputs/示例场/windscada",
   "contract": "reference/示例场/windscada_contract.yaml"
 }

+ 10 - 7
data/raw/README_把原始数据放这里.txt

@@ -1,8 +1,11 @@
 原始 SCADA 与技术资料放这里 (不随包分发)。
-约定子目录 (见系统维护页):
-  (按场) 10min 导出/
-  风机故障记录/*年至今.xls
-  风机故障记录/<年>年故障记录/
-  油样报告/
-  西门子4.0技术资料/
-位置可在 configs/serve.json 的 raw_dir 改 (支持绝对路径, 如 D:\guanlan\data\raw)。
+约定 (2026-09-11 A2): 原始件按**场站**分目录, 系统维护页「数据层」显示的四个目录就是它:
+  <场站名称>\scada_10min\     SCADA 10min 导出; 逐台平铺 WTG01.csv…WTG38.csv (放文件本身, 不是压缩包)
+  <场站名称>\故障报警\         报警事件台账; 年度/季度 .xls (实为 XML 导出)
+  <场站名称>\风机故障记录\     检修工单台账; 目录内按 {年}年故障记录\ 分年
+  <场站名称>\油样报告\         油液化验报告 (按台号/部件分目录)
+如东本场的 <场站名称> = 如东  →  data\raw\如东\scada_10min\ 等四个目录。
+改场站目录名: src\windscada\config.py 的 raw_station (一处生效, 维护页跟着变);
+外场配置(configs\farms\<场名>.json)可自带 raw_station, 或只写四个 src_* 绝对路径。
+机理层技术资料仍在 data\raw\西门子4.0技术资料\ (不在场站目录下, A2 未点名)。
+位置根可在 configs\serve.json 的 raw_dir 改 (支持绝对路径, 如 D:\guanlan\data\raw)。

+ 25 - 8
src/ontology/maintenance.py

@@ -26,6 +26,15 @@ try:
 except Exception:
     _RAW_STR = os.environ.get('WINDSCADA_RUDONG_SRC') or str(ROOT / 'data' / 'raw')
 RAW = pathlib.Path(_RAW_STR)
+# ★2026-09-11 用户令 A2: 四项数据层源改为 data/raw/<场站名称>/…。场站目录名只在场配置里写
+#   (src/windscada/config.py 的 raw_station), 这里取用不另抄 —— 抄一份就会出现"页面指着一个
+#   不存在的目录让人补数据"。
+try:
+    from src.windscada.config import raw_station_dir as _station_dir
+    STATION = _station_dir()
+except Exception:
+    STATION = RAW / '如东'
+# 机理层: 技术资料仍在原始件根下 (A2 只点名数据层那四项, 机理层不动)
 TECH = RAW / '西门子4.0技术资料'
 
 
@@ -65,19 +74,27 @@ SOURCES = [
          摄入='python -m src.ontology.trend_ingest',
          频率='振动线每轮出件后', 责任='振动线 → 图谱',
          说明='在升(末三窗/早三窗≥1.3)与换件闭环两类实证入图谱; 补前这类信息只在链盘里, 图谱检索不到'),
-    # ── 数据层 ──
+    # ── 数据层 (2026-09-11 A2: 四项数据源目录 = data/raw/<场站名称>/{scada_10min,故障报警,风机故障记录,油样报告}) ──
     dict(层='数据', 名='SCADA 10min 月度衍生', 产物=ST / 'loss_monthly.parquet', 时间列='month',
-         位置=disp(RAW / '(按场) 10min 导出'), 摄入='python scripts/windscada_report.py  (内含 availability.summary 产 loss_monthly)',
-         频率='月', 责任='数据线', 说明='五态/损失/可用率的底座; 换月必重跑下游判级'),
+         位置=disp(STATION / 'scada_10min') + os.sep,
+         摄入='python scripts/windscada_report.py  (内含 availability.summary 产 loss_monthly)',
+         频率='月', 责任='数据线',
+         说明='逐台平铺 WTG01.csv…WTG38.csv (放目录本身, 不是压缩包); '
+              '五态/损失/可用率的底座; 换月必重跑下游判级'),
     dict(层='数据', 名='报警事件', 产物=ST / 'alarms.parquet', 时间列='t_on',
-         位置=disp(RAW / '风机故障记录/*年至今.xls (XML 导出)'), 摄入='python scripts/windscada_alarms_ingest.py',
+         位置=disp(STATION / '故障报警') + os.sep,
+         摄入='python scripts/windscada_alarms_ingest.py',
          频率='月', 责任='数据线', 说明='码级事件; 注意年度总表 .xls 实为 XML 报警导出非工单'),
     dict(层='数据', 名='检修工单台账', 产物=ST / 'workorders.parquet', 时间列='t_report',
-         位置=disp(RAW / '风机故障记录/{年}年故障记录/'), 摄入='scripts/windscada_workorder_ingest.py',
-         频率='季/年', 责任='现场提供', 说明='★当前止 2024-11-21; 2025-26 六类源已 data-first 核过确无 — 缺口需现场补'),
+         位置=disp(STATION / '风机故障记录') + os.sep,
+         摄入='scripts/windscada_workorder_ingest.py',
+         频率='季/年', 责任='现场提供',
+         说明='目录内按 {年}年故障记录/ 分年; ★当前止 2024-11-21; 2025-26 六类源已 data-first 核过确无 — 缺口需现场补'),
     dict(层='数据', 名='油液化验', 产物=ST / 'oil_samples_index.parquet', 时间列='date',
-         位置=disp(RAW / '油样报告/'), 摄入='python scripts/windscada_watch_channels_build.py  (产 oil_samples_index)',
-         频率='半年', 责任='化验机构', 说明='SGS + 华标两家; 新一轮到货后重跑, 时效胶囊自动翻绿'),
+         位置=disp(STATION / '油样报告') + os.sep,
+         摄入='python scripts/windscada_watch_channels_build.py  (产 oil_samples_index)',
+         频率='半年', 责任='化验机构',
+         说明='SGS + 华标两家, 报告按台号/部件分目录; 新一轮到货后重跑, 时效胶囊自动翻绿'),
     dict(层='数据', 名='CMS 振动评估报告', 产物=CMS / '报告_CMS振动状态评估报告_*.md', 时间列=None,
          位置=disp(CMS), 摄入='windcms analyze (振动线会话)',
          频率='月/事件驱动', 责任='振动线', 说明='设备状态五级来源; 观澜自动取最新版不写死日期'),

+ 38 - 4
src/windscada/config.py

@@ -26,14 +26,33 @@ REQUIRED = ('name', 'n_turbines', 'turbines', 'src_10min', 'src_alarm', 'store',
 # 原始件不随包分发, 目录不存在时页面照常显示"约定位置", 只是摄入命令跑不了。
 RUDONG_SRC = os.environ.get('WINDSCADA_RUDONG_SRC') or str(ROOT / 'data' / 'raw')
 
+# 如东在原始件根下的**场站目录名** (2026-09-11 用户令 A2)。原始件不再平铺在 data/raw 下,
+# 而是按场分目录 —— 同一台机器上多场/多份现场数据不会互相混:
+#   data/raw/<场站名称>/scada_10min/     SCADA 10min 导出, 逐台平铺 WTG01.csv…WTG38.csv
+#                                        (读法见 src/windscada/data.py: src_10min/<turbine>.csv)
+#   data/raw/<场站名称>/故障报警/         报警事件台账, 年度/季度 .xls (实为 XML 导出)
+#   data/raw/<场站名称>/风机故障记录/     检修工单台账, 内按 {年}年故障记录/ 分年
+#   data/raw/<场站名称>/油样报告/         油液化验报告
+# 目录名只在这一处写死; 系统维护页「数据层」显示的就是这四个目录, 路径经 raw_station_dir() 取,
+# 不各自拼字符串 (拼歪了页面就会指着一个不存在的目录让人补数据)。
+_RUDONG_STATION = '如东'
+
+
+def _src(*parts):
+    """原始件子目录 → data/raw/<场站名称>/… (A2 约定)。"""
+    return '/'.join([RUDONG_SRC, _RUDONG_STATION, *parts])
+
+
 _BUILTIN = {
     'rudong': {
         'name': '如东海上风电场', 'n_turbines': 38,
         'turbines': [f'WTG{i:02d}' for i in range(1, 39)],
-        'src_10min': f'{RUDONG_SRC}/10分钟数据',
-        'src_1min': f'{RUDONG_SRC}/1分钟数据',
-        'src_alarm': f'{RUDONG_SRC}/报警数据',
-        'src_workorder': f'{RUDONG_SRC}/风机故障记录',
+        'raw_station': _RUDONG_STATION,                      # data/raw/<场站名称>
+        'src_10min': _src('scada_10min'),
+        'src_1min': _src('scada_1min'),
+        'src_alarm': _src('故障报警'),
+        'src_workorder': _src('风机故障记录'),
+        'src_oil': _src('油样报告'),
         'store': ROOT / 'outputs/rudong/windscada',          # L0 标准仓
         'contract': ROOT / 'reference/rudong/windscada_contract.yaml',   # M1 产出
         'rated_kw': 4000,
@@ -94,3 +113,18 @@ def farm(name=None):
                          f'(新场请在 {FARM_DIR.relative_to(ROOT)}/ 放 <场名>.json)')
     _CACHE[name] = _expand(json.loads(f.read_text(encoding='utf-8')), name)
     return _CACHE[name]
+
+
+def raw_station_dir(name=None):
+    """本场原始件根目录 = data/raw/<场站名称> (2026-09-11 用户令 A2)。
+
+    A2 之后四项数据源 (scada_10min / 故障报警 / 风机故障记录 / 油样报告) 都在它下面;
+    维护页与摄入侧统一从这里取, 不再各自拼字符串。场配置给了 raw_station 就用它
+    (相对 RUDONG_SRC); 给了绝对路径则原样用; 没给就退到 src_10min 的上一级
+    (外场配置常只写四个 src_*)。"""
+    c = farm(name)
+    st = c.get('raw_station')
+    if st:
+        p = Path(st)
+        return p if p.is_absolute() else Path(RUDONG_SRC) / p
+    return Path(str(c['src_10min'])).parent

+ 18 - 8
src/windscada/data_tpl_en.py

@@ -1050,14 +1050,24 @@ NOTES = {
         'windscada decision chain (computed at run time, read through /api/fleet)',
     'outputs/rudong/m5_cms_tcm/component_history.json (振动线出件)':
         'outputs/<site>/m5_cms_tcm/component_history.json (published by the vibration line)',
-    '/Users/yuanying/rudong/如东风场数据/(按场) 10min 导出':
-        '<data root>/(per site) 10-minute exports',
-    '/Users/yuanying/rudong/如东风场数据/风机故障记录/*年至今.xls (XML 导出)':
-        '<data root>/turbine fault records/*year-to-date.xls (XML export)',
-    '/Users/yuanying/rudong/如东风场数据/风机故障记录/{年}年故障记录':
-        '<data root>/turbine fault records/{year} fault records',
-    '/Users/yuanying/rudong/如东风场数据/油样报告':
-        '<data root>/oil sample reports',
+    # 维护页「数据层」四项数据源目录 (2026-09-11 A2 改为 data/raw/<场站名称>/…)。
+    # ★两套分隔符都列: disp() 的路径随本机 (Windows 反斜杠 / POSIX 斜杠), 只写一套在另一平台就查不中。
+    '<安装目录>\\data\\raw\\如东\\scada_10min\\':
+        '<install dir>/data/raw/<site>/scada_10min/',
+    '<安装目录>/data/raw/如东/scada_10min/':
+        '<install dir>/data/raw/<site>/scada_10min/',
+    '<安装目录>\\data\\raw\\如东\\故障报警\\':
+        '<install dir>/data/raw/<site>/alarm events/',
+    '<安装目录>/data/raw/如东/故障报警/':
+        '<install dir>/data/raw/<site>/alarm events/',
+    '<安装目录>\\data\\raw\\如东\\风机故障记录\\':
+        '<install dir>/data/raw/<site>/turbine fault records/',
+    '<安装目录>/data/raw/如东/风机故障记录/':
+        '<install dir>/data/raw/<site>/turbine fault records/',
+    '<安装目录>\\data\\raw\\如东\\油样报告\\':
+        '<install dir>/data/raw/<site>/oil sample reports/',
+    '<安装目录>/data/raw/如东/油样报告/':
+        '<install dir>/data/raw/<site>/oil sample reports/',
     '/Users/yuanying/rudong/如东风场数据/西门子4.0技术资料/故障处理/故障处理手册.xlsx':
         '<data root>/OEM 4.0 technical documents/fault handling/fault handling manual.xlsx',
     '/Users/yuanying/rudong/如东风场数据/西门子4.0技术资料/维护相关/维护作业指导书.xlsx':