面向:现场交来的
F:\temp\如东风场数据这一类目录 → 观澜的data/raw/<场站>/。 工具:scripts/extract_site_data.py(纯标准库 + 系统 ODBC,不新增任何 pip 依赖)。
| 文件 | 体量 | 内容 | 用途 |
|---|---|---|---|
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)。
# ① 对账:现场 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 # 或门户「数据重算」
.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 驱动是系统级前提,装了包也解决不了;改成"探针 + 明确补救"更诚实。)
--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,追加式写入(同一机组多月叠加)。| 观澜要的类 | 现状 | 缺什么 |
|---|---|---|
故障报警 |
16 件 2025Q1.xls …(已有) |
— |
风机故障记录 |
10 件(*.rar / *.xlsx) |
— |
油样报告 |
0 件 | 现场 PDF 报告 |
windcms / m5_cms_tcm |
0 件 | 振动侧 CMS 导出件(取料单第 7、8 项) |
<安装目录>/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 输入要不要保留, 故先只做"放进去"这一步。
25年.zip/26年.zip 的形态)—— 探路结果<年>年.zip → <年>年/<月>月/<年>-<月>-<类>.zip → <年>-<月>-<类>.mdb
现场每月 12 类(cnt din dot flg grd int prs scd std sum tmp tur),每类一个 MDB、库内一张表
(如 tblGrid,用 Station 区分机组)。本器同构产出,类名沿用现场叫法:10min / 1min。
| 限制 | 实测冲突 | 处置 |
|---|---|---|
| 单表 ≤ 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 |
按月分库(不可按年一个库) |
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;…] 也不支持:同样报该错。INSERT INTO [;DATABASE=<目标.mdb>;].[表名] SELECT * FROM [<分片>.csv]
(实测语法已被接受:报的是「INSERT 语句语法错误」而非「不支持该操作」;当时两点没对上 ——
方括号里要带结尾分号 [;DATABASE=…;],且目标表列数/列序要与 CSV 完全一致)。mdbtools 无写库能力 ⇒ MDB 生成请在 Windows 上做(或让现场直接给 MDB)。schema.ini(CharacterSet=65001,UTF-8),
否则中文列名/值会按 ANSI 代码页解错。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 一致;--month 2025-01 --turbines WTG01(1 台 1 月)跑通并逐值对拍;用户令: 「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']
并在首次使用时打一行 —— 不静默换源。两种形态都取不到 ⇒ 响亮报错(不返回空表,空表会被下游当成"这台没数据")。
rid,而 1min 源件没有任何机组标识列
(source_file 全行同值)⇒ 那份 1min 库按台取不到数。10min 老库还能救:源件自带 WTG 列,读侧兜底。build_chunks 表头写 names[lo:hi+1]、行写 vals[lo:hi]
⇒ 第 250 列之后的列名整体错位一格,且相邻分片表共用同一列名(实测 t1 末列 == t2 首列)。
这种库读出来是"名字对不上数"的数据 —— 比缺数据危险 ⇒ 读侧响亮拒绝并给出重建命令。. ! [ ],建库时表头被消毒(非法字符→_、
截断 60、撞名加 _1)。规则单一实现在 src/windscada/mdb_names.py:读侧把契约列名翻成库内列名、
取回后改回契约名 —— 否则从 MDB 取数会静默少列。rid 数值排(rid 在库里是文本,直接合并会得到 1,10,100… 字典序 —— 首轮对拍
"对不上"就是这个原因,数据其实没错)。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)。
修好的建库脚本重跑全部月份(10min 19 个月 + 1min 18 个月,约 5 h,后台跑;日志
F:\temp\csv2mdb_rebuild.log)。1min 目前包内没有消费者,所以"1min 的 MDB 形态"属数据层完整性补齐;
10min 才是摄入要用的那一类。