现场收资接入_v0.1.md 12 KB

现场收资接入(v0.1,2026-09-17)

面向:现场交来的 F:\temp\如东风场数据 这一类目录 → 观澜的 data/raw/<场站>/。 工具:scripts/extract_site_data.py(纯标准库 + 系统 ODBC,不新增任何 pip 依赖)。


1. 现场交来的东西长什么样(2026-09-17 实测)

文件 体量 内容 用途
10分钟数据.zip 3,760 MB 38 件 WTG01.csv … WTG38.csv,表头 TimeStamp,WTG,StationId,cnt_wtc_… 10 分钟 SCADA(观澜 scada_10min 的正源)
1分钟数据.zip 1,548 MB 38 件 01E.csv … 38B.csv(机组代号命名),表头 time_stamp,3#桨电机温度,…(中文列) 1 分钟 SCADA(观澜 scada_1min 的正源)
25年.zip 1,071 MB 25年/<月>/2025-<月>-<类>.zip → .mdb(Access) 分类深度源(每月 12 类)
26年.zip 961 MB 26年/<月>/2026-<月>-<类>.zip → .mdb 同上

年度 zip 内的类目(每月一套):cnt din dot flg grd int prs scd std sum tmp tur。 实测 std.mdb 只有一张表 tblGrid:

TimeStamp | Station | WPSStatus | TimestampStation | CurrentL1..L3 | VoltageL1..L3 |
ActivePower | ReActivePower | ActivePowerExport | ReActivePowerExport | …(23 列)

TimeStamp 为 10 分钟节拍,Station 为机组号(如 91/92)。

2. 接入观澜(命令)

# ① 对账:现场 zip ↔ 现有 data/raw(逐件比大小,不解压十几 GB 就能验,幂等)
python scripts/extract_site_data.py --src "F:\temp\如东风场数据" --verify-raw
#    实测: 10分钟数据.zip → data/raw/如东/scada_10min  38/38 同大小
#          1分钟数据.zip → data/raw/如东/scada_1min    38/38 同大小  ⇒ 现有 raw 输入可追溯

# ② 提取(幂等;已有同大小文件即跳过;--only-missing 只补缺件)
python scripts/extract_site_data.py --src "F:\temp\如东风场数据" --extract-csv --only-missing

# ③ 年度 MDB(深度源)—— 只看结构 / 解出 / 探针
python scripts/extract_site_data.py --src "F:\temp\如东风场数据" --list
python scripts/extract_site_data.py --src "F:\temp\如东风场数据" --extract --year 26 --month 1
python scripts/extract_site_data.py --src "F:\temp\如东风场数据" --probe --class std

# ④ 接上观澜既有链路(体检 → 重算)
python scripts/raw_data_check.py            # 结构/增量/时间空洞体检(R1–R8)
python scripts/rebuild_all.py               # 或门户「数据重算」

3. 读 Access .mdb 的路线(离线、零 pip 依赖)

平台 用什么 前提
Windows .NET System.Data.Odbc + 系统 Microsoft Access Driver (*.mdb, *.accdb) 装了 Office 或 Access Runtime 的机器自带;本机实测可用
Linux mdbtools 的 mdb-export / mdb-tables apt/yum install mdbtools

两条路本器都会先探针:--probe 缺能力时给补救办法并返回 rc=4,不静默、不猜。 (刻意不把 pyodbc 之类的包塞进交付包:ODBC 驱动是系统级前提,装了包也解决不了;改成"探针 + 明确补救"更诚实。)

4. 从 MDB 转 CSV(--to-csv)

python scripts/extract_site_data.py --src <…> --to-csv --class std [--year 26 --month 1] [--turbines WTG01,WTG02]

  • 落点按类目映射:CLASS_TO_RAW(std → scada_10min;其余类目待现场口径确认后才接,未确认前不硬接);
  • 机组号映射:Station 起点 90 ⇒ WTG01(实测 std.mdb 里 Station=91/92);
  • 时间列统一改名 Time,逐机组落 data/raw/如东/<类>/WTG<NN>.csv,追加式写入(同一机组多月叠加)。

5. 还没有的输入(如实列)

观澜要的类 现状 缺什么
故障报警 16 件 2025Q1.xls …(已有) —
风机故障记录 10 件(*.rar / *.xlsx) —
油样报告 0 件 现场 PDF 报告
windcms / m5_cms_tcm 0 件 振动侧 CMS 导出件(取料单第 7、8 项)

6. 落位约定(用户令 2026-09-17:用年度 MDB 归档取代此前的两个 CSV zip)

<安装目录>/data/raw/如东/
├─ scada_mdb/            ← ★ 现场原始收资(Access 按月分类),就地归档
│   ├─ 25年.zip          (1,071,376,695 B, sha256 E451FF68…;内层 25年/1-12月/2025-<月>-<类>.zip)
│   └─ 26年.zip          (1,007,354,282 B, sha256 0B641309…;内层 26年/1-9月/2026-<月>-<类>.zip)
├─ scada_10min/          38 件 WTG01..WTG38.csv(源: 10分钟数据.zip,已对账 38/38 同大小)
├─ scada_1min/           38 件 01E..38B.csv(源: 1分钟数据.zip,已对账 38/38 同大小)
├─ 故障报警/ 风机故障记录/ 油样报告/ windcms/ m5_cms_tcm/
  • 为什么要单开 scada_mdb/:年度归档里是每月 12 类的 Access 库(cnt din dot flg grd int prs scd std sum tmp tur),不是"一台一个 CSV",硬塞进 scada_10min/ 会破坏该目录"平铺 <机组>.csv"的约定。
  • 目录已登记进两份真源:src/windscada/config.py::STATION_SUBDIRS 与 scripts/raw_data_check.py::EXPECT(允许 .zip 原样归档与 .mdb 解出件)⇒ 现场收资体检认它, 不再报"未约定的子目录"。
  • 工具直接吃这个目录(不必再指向 F:\temp): python scripts/extract_site_data.py --src data/raw/如东/scada_mdb --list ⇒ 实测 25年.zip:12 个月 · 12 类;26年.zip:9 个月 · 12 类。

⚠️ 尚未做的一步(等你定):把年度 zip 解出(每月 12 个 .mdb,约十几 GB), 以及是否把 MDB 转成 scada_10min/、scada_1min/ 的 CSV(即"用 MDB 取代现有 CSV")。 }这个决定会影响现有 27 GB 的 CSV 输入要不要保留, 故先只做"放进去"这一步。


7. CSV → MDB(用户令 2026-09-17:仿照 25年.zip/26年.zip 的形态)—— 探路结果

7.1 目标形态(照现场归档抄)

<年>年.zip → <年>年/<月>月/<年>-<月>-<类>.zip → <年>-<月>-<类>.mdb

现场每月 12 类(cnt din dot flg grd int prs scd std sum tmp tur),每类一个 MDB、库内一张表 (如 tblGrid,用 Station 区分机组)。本器同构产出,类名沿用现场叫法:10min / 1min。

7.2 两条必须偏离现场形态的硬限制(Access 引擎,绕不过)

限制 实测冲突 处置
单表 ≤ 255 列 scada_10min/WTG01.csv = 598 列(scada_1min/01E.csv = 79 列) 每库按列拆表 t1/t2/t3(建议每片 250 列),各表首列放 rid 行号便于对齐
单 MDB ≤ 2 GB scada_10min 14.4 GB + scada_1min 12.8 GB 按月分库(不可按年一个库)

7.3 写 MDB 的能力与路线(本机实测,2026-09-17)

  • ✅ 建库可用:ADOX.Catalog COM + Microsoft.ACE.OLEDB.12.0/16.0 提供程序(64 位 PowerShell 下实测 可实例化、$cat.Create("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=x.mdb;") 成功建出 180 KB 库)。
  • ❌ SELECT … INTO [文本源] 不支持:实测报「这种对象类型不支持该操作」。
  • ❌ 用目标库连接去 INSERT INTO … SELECT * FROM [text;…] 也不支持:同样报该错。
  • ✅ 可行方向(下一步要落的就是它):反过来的写法 —— 连接文本源, 用 Jet 的链接语法指向目标库:INSERT INTO [;DATABASE=<目标.mdb>;].[表名] SELECT * FROM [<分片>.csv] (实测语法已被接受:报的是「INSERT 语句语法错误」而非「不支持该操作」;当时两点没对上 —— 方括号里要带结尾分号 [;DATABASE=…;],且目标表列数/列序要与 CSV 完全一致)。
  • ⚠ Linux 侧只能读不能写:mdbtools 无写库能力 ⇒ MDB 生成请在 Windows 上做(或让现场直接给 MDB)。
  • ⚠ 中文字段名(1 分钟数据是中文列名):文本 ISAM 需配 schema.ini(CharacterSet=65001,UTF-8), 否则中文列名/值会按 ANSI 代码页解错。

7.4 尚未做的(下次接手的最短路径)

  1. 在 scripts/extract_site_data.py 增 --csv-to-mdb --month <YYYY-MM> --class {10min,1min}: 按月过滤 → 按 250 列切片写临时分片 CSV(首列 rid)→ 建库 → 按列生成 DDL(CREATE TABLE)→ 按 §7.3 的链接语法 INSERT … SELECT → 打成同名 zip → 读回校验行数/首末行与源 CSV 一致;
  2. 试点:--month 2025-01 --turbines WTG01(1 台 1 月)跑通并逐值对拍;
  3. 全量:38 台 × 21 个月(10min 3 张表/月、1min 1 张表/月),按 §7.2 按月分库。

8. SCADA 接入兼容 CSV 与 MDB(用户令 2026-09-19)

用户令: 「scada 数据接入兼容支持 CSV、MDB」。此前 src/windscada/data.py::load_10min() 把路径写死成 <src_10min>/<台>.csv ⇒ 只有"已导出 CSV"形态能被摄入, 现场交付的年度归档库只能看不能算。 现在取数统一走 src/windscada/scada_source.py:

形态 位置 取数方式
CSV(优先) data/raw/<场站>/scada_10min/WTG01.csv… pandas.read_csv(engine='pyarrow'),按契约列取交集(既有行为不变)
MDB(回落) data/raw/<场站>/scada_mdb/<年>年/<月>月/<年>-<月>-<类>.mdb ACE(Microsoft.ACE.OLEDB.12.0) + PowerShell,逐分片表 SELECT … WHERE turbine=?,按 (rid, turbine) 拼列

规则: CSV 在位就用 CSV;该台没有 CSV 才回落 MDB;用的哪种记在返回表的 attrs['scada_source'] 并在首次使用时打一行 —— 不静默换源。两种形态都取不到 ⇒ 响亮报错(不返回空表,空表会被下游当成"这台没数据")。

8.1 库这一侧的四个坑(2026-09-19 实测逮到)

  1. 老库没有机组列:2026-09-19 之前建的库每行只有 rid,而 1min 源件没有任何机组标识列 (source_file 全行同值)⇒ 那份 1min 库按台取不到数。10min 老库还能救:源件自带 WTG 列,读侧兜底。
  2. 老库分片表表头与数据行差一列:旧 build_chunks 表头写 names[lo:hi+1]、行写 vals[lo:hi] ⇒ 第 250 列之后的列名整体错位一格,且相邻分片表共用同一列名(实测 t1 末列 == t2 首列)。 这种库读出来是"名字对不上数"的数据 —— 比缺数据危险 ⇒ 读侧响亮拒绝并给出重建命令。
  3. 列名消毒两边必须同一套:Access 列名不能含 . ! [ ],建库时表头被消毒(非法字符→_、 截断 60、撞名加 _1)。规则单一实现在 src/windscada/mdb_names.py:读侧把契约列名翻成库内列名、 取回后改回契约名 —— 否则从 MDB 取数会静默少列。
  4. 行序必须按 rid 数值排(rid 在库里是文本,直接合并会得到 1,10,100… 字典序 —— 首轮对拍 "对不上"就是这个原因,数据其实没错)。

8.2 验收(可重复)

python scripts/csv_to_mdb.py --month 2026-07 --class 10min    # 用修好的脚本重建一个月
# 逐值对拍: 同一台同一月, CSV 取数 vs MDB 取数 → 全列逐值一致(含 t2/t3 分片里的列)

2026-09-19 实测:2026-07 库 866 行 × 4 列(含跨分片列)逐值一致;2025-03 老库被如实拒绝。 data.load_10min() 从 CSV 取与从 MDB 取,返回同一张表(attrs['scada_source'] 分别是 csv / mdb)。

8.3 重建进度

修好的建库脚本重跑全部月份(10min 19 个月 + 1min 18 个月,约 5 h,后台跑;日志 F:\temp\csv2mdb_rebuild.log)。1min 目前包内没有消费者,所以"1min 的 MDB 形态"属数据层完整性补齐; 10min 才是摄入要用的那一类。