版本: v0.2.0 现场版 · 2026-09-11 适用:
F:\temp\guanlan-rudong-v2_0.2.0(安装目录, 下称<安装目录>) 本文回答三件事: ①离线数据往哪放、系统怎么认;②每个目录干什么用;③放/不放数据时系统分别表现什么。
<安装目录>\
├─ data\raw\ ★离线数据唯一入口 (现场数据只放这里)
│ └─ <场站名称>\ ← 扫描辨识的抓手: 下一级目录就是场站名
│ ├─ scada_10min\ SCADA 10min 导出 (逐台 WTG01.csv…WTG38.csv)
│ ├─ scada_1min\ (可选) 1min 导出
│ ├─ 故障报警\ 报警事件导出 (SpreadsheetML *.xls)
│ ├─ 风机故障记录\ 检修工单台账 (*.xls/xlsx, 内按 {年}年故障记录\ 分年)
│ └─ 油样报告\ 油液化验报告 (*.pdf)
├─ outputs\rudong\ ★系统取数用的**产物仓** (页面 99% 读这里, 不直接读 raw)
│ ├─ windscada\ L0 标准仓: 37 个 parquet + 索引/日志
│ ├─ ontology\ 本体对象库 objects.json + 检索索引 + release_r1/r2
│ ├─ windcms\ CMS 振动诊断产物 (报告/缓存/单机页)
│ ├─ m5_cms_tcm\ 振动线 handoff 与 TCM 兼容件
│ ├─ guanlan\facts_contract_v0.json 事实契约 (问答引文的依据)
│ └─ sop\ SOP 中间件/评审/台账 (含少量历史装配脚本)
├─ configs\ 端口/模型/场配置/机型契约
│ ├─ serve.json 端口、发布目录、Python 路径 (install 脚本写)
│ ├─ models.json Ollama 档位与 pull 命令
│ ├─ farms\<场名>.json 外场配置 (内置 rudong 可不配); _模板.json.example 是模板
│ ├─ contracts\ 机型契约 (变桨/偏航/发电机…判据参数)
│ ├─ canonical\ terms\ 术语与词典 (canonical 决策/字典/手册; terms 显示映射)
├─ src\ 库源码: windscada(分析) / windcms(振动) / ontology(本体) / sop(方法)
├─ scripts\ 服务与工具: 网关/工作台/CMS + 摄入链 + 扫描/落位
├─ release\ 交付层: portal.html(门户) viewer(三维) sim_sys_server.py
│ └─ 如东\ 客户交付件 (治理清单/取数单), 按约定不入 git
├─ resources\oem_envision_sc1_rudong2014\ 仿真回放资产
├─ reference\rudong\ 契约与语言资产 (windscada_contract.yaml / 英文语言库 / 报警码表 …)
├─ wheels\win_amd64\ 离线轮子 (随包分发, 不入 git)
├─ vendor\ 便携 Python / Ollama 离线包 (不入 git)
├─ docs\ 说明书与本文
├─ logs\ run\ 运行日志 / pids.json (不入 git)
└─ _修复记录_20260911\ 现场修复记录与可重放脚本
data\raw\ 落位约定与扫描辨识一句话: data\raw\ 的下一级目录就是场站名称, 系统在取数时扫描辨识, 不写死目录名。
辨识规则(逐条命中即止, 依据会打印出来, 见 scripts\scan_stations.py 与维护页那一行「场站数据目录 (扫描辨识)」):
| # | 规则 | 例 |
|---|---|---|
| ① | 目录名与场配置 raw_station 完全相同 |
配置 "raw_station": "如东" ↔ 目录 data\raw\如东\ |
| ② | 目录名与场名写法 src_farm_names 互为子串 |
配置 ["如海","如东"] ↔ 目录 如东海上风电场\ 也能认 |
| ③ | data\raw\ 下只有这一个场站目录(单站部署) |
采用它, 但报告里标"凭单站唯一性", 不假装精确匹配 |
| ④ | 多目录且都不匹配 | 不猜: 报"未识别", 页面显示无数据并列出扫到的目录 |
约定子目录(这五个名字是摄入接口, 改名等于换接口):
| 子目录 | 放什么 | 文件形态要求 |
|---|---|---|
scada_10min\ |
SCADA 10min 导出 | 逐台平铺 WTG01.csv…WTG38.csv(放文件本身, 不是压缩包) |
scada_1min\ |
1min 导出(可选) | 同上, 台号须在场配置的 38 台内 |
故障报警\ |
报警事件导出 | .xls 实为 SpreadsheetML(XML), <row> 需带 TimeOn + Alarmcode;「全年/年至今」累计快照件会自动跳过 |
风机故障记录\ |
检修工单台账 | 模板表(表头含 机组编号+故障名称+故障代码);表里 风场名称 必须属本场(集团导出件混着十来个场);按年分组不限层级 |
油样报告\ |
油液化验报告 | .pdf, 文件名须含 日期_台号_部件(如 17072025_10303681_…_1#_主轴后.pdf) |
机理层(厂商资料)按 A2 仍放 data\raw\西门子4.0技术资料\, 不在场站目录下。
怎么把现场包落成这个样子: scripts\place_raw_data.py 把"包里的哪个目录 → 落到哪"写成一张表,
可复核、可重跑(--dry-run 先看计划; 同尺寸文件自动跳过, 重跑不会把十几 GB 重抄一遍)。
.venv\Scripts\python.exe scripts\place_raw_data.py --src <现场包目录> --scope a2 --dry-run
.venv\Scripts\python.exe scripts\place_raw_data.py --src <现场包目录> --scope full
| 范围 | 落什么 | 依据 |
|---|---|---|
--scope a2(默认) |
上表四类(scada_10min/故障报警/风机故障记录/油样报告) |
A2 约定的四项数据层 |
--scope mech |
data\raw\西门子4.0技术资料\(317 件 2.7 GB,含那份对译表)+ <场站>\scada_1min\(38 件 12.8 GB) |
本体层构建器 kb_ingest.py 的 TECH 与 rd() 四个文件名;config.STATION_SUBDIRS 声明的 scada_1min |
--scope full |
两组一起 | 从零重算要用的全集 |
为什么
scada_1min也属"该落的": 它写在config.STATION_SUBDIRS里,scan_stations.py会把它报成 "缺"。但包内没有消费者 —— 落它是补数据层完整性, 不改任何页面数值。同理, 现场包里的scada数据(如东)\(19 个月原始通道导出)、fastlog数据\、(8)…\振动分析报告\三种故意不落: 前者是scada_10min的上游且无人读, 中间那种全库只有 2 处注释提到, 后一种是振动线的成品牌报告 而非可再加工的测量数据 —— 理由都逐条写在脚本的SKIPPED表里。
四类源的消费方式不同 —— 这决定了"放进去"和"用上"之间差哪一步:
| 源 | 运行期是否实时读 | 要跑什么才进系统 | 影响的页面/面板 |
|---|---|---|---|
scada_10min\ |
是(windscada_serve.py 的 adhoc_query 请求时现读 WTGnn.csv) |
派生产物需 rebuild_from_raw.py --scada |
工作台「台内即时查询」等原始窗立即生效;损失/可用率/温度/偏航等派生面需重建 |
故障报警\ |
否(只有摄入脚本读) | scripts\windscada_alarms_ingest.py |
数据层「报警事件」、故障分析、停机事件、限电绑定 |
风机故障记录\ |
否 | scripts\windscada_workorder_ingest.py |
数据层「检修工单台账」、检修面、闭环验证 |
油样报告\ |
否 | scripts\windscada_watch_channels_build.py |
数据层「油液化验」、油液时效胶囊、融合面油样轴 |
命令(在 <安装目录> 下跑):
.venv\Scripts\python.exe scripts\scan_stations.py # 看扫到了什么、辨识依据
.venv\Scripts\python.exe scripts\rebuild_from_raw.py # 跑三类台账摄入 (秒级~分钟级)
.venv\Scripts\python.exe scripts\rebuild_from_raw.py --scada # 再加 SCADA 侧 10 个构建器 (慢)
.venv\Scripts\python.exe scripts\rebuild_from_raw.py --verify# 与随包基线逐值等价验收
.venv\Scripts\python.exe scripts\place_raw_data.py # 现场压缩包 → 约定子目录 (映射表)
data\raw 能重建什么、不能重建什么(实测, 2026-09-11)outputs\rudong\windscada\ 是页面取数的产物仓。其中哪些能由 data\raw 重算出来, 是"数据驱动程度"的边界:
✅ 能从 data\raw 重算(构建器都在包内, 已实测等价)
| 产物 | 来源 | 入口 |
|---|---|---|
alarms.parquet |
故障报警 | windscada_alarms_ingest.py(39211/39211 行逐值一致) |
workorders.parquet |
风机故障记录 | windscada_workorder_ingest.py(1574 种内容复现 1566 种, 8 种差异已逐条定位) |
oil_samples_index.parquet |
油样报告 | windscada_watch_channels_build.py——部分可重建: 404/506 行(SGS 批, 文件名可解析)逐值一致; 见下表缺口 |
powercurve_dev/bins · loss_monthly · curve_lenses/liveness · control_profile/schedule · stop_events · temp_bins · yaw_daily · hydraulic_accum · thermal_chain · system_aux |
scada_10min + alarms | rebuild_from_raw.py --scada(实测: loss_monthly 3729/3729、powercurve_dev 38/38、powercurve_bins 912/912 与随包基线逐值完全一致) |
objects.json(本体 2340 个对象)· retrieval_index.json |
厂商技术资料 data\raw\西门子4.0技术资料(不在场站目录下) |
python -m src.ontology.kb_ingest;检索索引 python -c "from src.ontology import retrieval as R; R.build(use_vec=False)"(BM25 词法索引; 向量那半要 Ollama 的 bge-m3, 本机没装模型时会跳过并保持纯词法可用) |
turbine_params.parquet(1706 条整定值) |
同上, Turbine+Parameters.*.xlsx |
python -c "from src.ontology.maintenance import refresh_params as f; f()" |
本体层这三件的前提是技术资料在盘上(
place_raw_data.py --scope mech会落, 见 §2);技术资料不在时kb_ingest会打印⚠ 源缺失并降级 —— 不静默。
⛔ 包内没有生成端 / 源件不在数据包内(只读不写, 因此 data\raw 换数据不会更新它们)
| 产物 | 谁在读它 | 说明 |
|---|---|---|
oil_samples_index 里的 102 行华标 2026-07 批 |
数据层「油液化验」、油液时效胶囊 | 源件是一份合并报告 BG-2026-07-YP013 中广核新能源如东海上风电场.pdf(一份覆盖多台, 台号/部件只在 PDF 表格里), 且该文件不在现场数据包里 → 清空重建会丢这 102 行。摄入脚本因此采取合并语义: 认不出的老行原样保留、绝不删(场景② 实测就出现了 506→404 的落差, 需现场补那份 PDF 才能从 raw 全量重建) |
pc_monthly_bins · duty_monthly · thermal_monthly · sector_power |
趋势件/热链/扇区 | 组级产物, v0.2.0 未附构建脚本(全库只有读取方, 0 处写入方) |
yaw_dynamic_monthly · yaw_press_monthly · yaw1min_liveness · yaw_err_clean · genbearing_monthly · mblub_monthly |
偏航/温度/润滑面 | 同上 |
structure.parquet · watch_channels_monthly.parquet |
结构面/温度联动/融合 | 后者连包内注释都写着"须重建 scripts\windscada_watch_channels_build.py" —— 本版该脚本只产油样索引, 未含 watch_channels 月表 |
windcms\* · m5_cms_tcm\* · tcm_compatible_replay\* |
CMS/融合/TCM 兼容链 | 来源是振动线 handoff(CMS 测点索引); 现场包里只有成品牌报告 PDF, 没有测点数据 → 仍 ⛔(/cms/ 因此 503) |
本体层的可重算性取决于技术资料在不在:
kb_ingest.py的TECH = P.RAW_ROOT/'西门子4.0技术资料', 且rd()要读故障处理/故障处理手册.xlsx、广核如海风电场西门子风机故障代码中英文对译表.xlsx、如海故障代码表.xlsx、维护相关/维护作业指导书.xlsx四个文件 —— 缺任一个都会打印⚠ 源缺失并降级, 所以place_raw_data.py --scope mech连那份不在技术资料目录里的对译表都单独补了一条规则。 本体层的其它入口 (populate/chain_ingest/trend_ingest/scenario_29) 要从 L1 产物转录 (判级矩阵system_matrix/fusion_table、振动 handoff、链盘/api/fleet), 而 L1 那几件本身 ⛔, 所以从零重算时它们要么报错退役、要么无变化 ——populate实测卡在pitch\pitch_daily.parquet。
从零重算实测(2026-09-11,只用 data\raw,不吃随包产物)
| 环节 | 结果 |
|---|---|
| 三门台账 | 报警 0→39211 行 · 工单 0→5876 行 · 油样 0→404 行(唯一源件数 80, 其中 2 张非台账表跳过) |
| SCADA 侧 10 个构建器 | 全跑通(loss_monthly 3729 · temp_bins 24624 · yaw_daily/hydraulic_accum/thermal_chain/system_aux 各 38 台 · curve_lenses · control_profile/schedule · stop_events · powercurve_dev/bins)→ L0 仓 16 件 1.78 MB(随包是 118 件:差额就是 §4 第二张表那些 ⛔ 件) |
| 本体层 | objects.json 1.43 MB + retrieval_index.json 0.62 MB + turbine_params.parquet |
等价验收 --verify |
alarms 39211/39211 逐值完全一致; workorders 8 行差异全部可归类(旧链时间解析缺陷)+ 69 行本链新增覆盖; 1 处人工项 = 油样 506→404(那 102 行的源件不在包内) |
场景① 清空 data\raw + 挪开产物 → 系统无数据呈现 ✅ 通过
Move-Item .\data\raw\如东 F:\temp\_raw_off # 数据挪走
# 把「这四类源产出的产物」也挪走 (只清源不动产物, 页面照旧显示旧数 —— 设计如此, 见 §3)
Move-Item .\outputs\rudong\windscada\{alarms,workorders,oil_samples_index,loss_monthly,powercurve_bins,powercurve_dev}.parquet F:\temp\_prod_off\
.venv\Scripts\python.exe guanlan.py stop; .venv\Scripts\python.exe guanlan.py serve
实测结果:
| 检查项 | 结果 |
|---|---|
scan_stations.py |
扫描到 0 个场站目录;辨识结论 how=none;依据: “data\raw 下没有任何场站目录 → 系统无数据可呈现” |
| 维护页「数据层」 | 「场站数据目录 (扫描辨识)」存在=False / 覆盖=—;四项 条数=0 / 覆盖=— / 存在=False |
工作台实时原始窗 /api/query |
首次暴露 HTTP 500(异常直接抛给前端,人分不清"没放数据"还是"程序坏了")→ 已修: 改为 HTTP 200 + 结构化无数据 err=no_source,并给出"本机应在 <…>\scada_10min\WTG01.csv、放入后重跑 rebuild_from_raw.py --scada" |
场景② 按约定结构放回 data\raw → 系统有数据呈现 ✅ 通过
Move-Item F:\temp\_raw_off .\data\raw\如东
.venv\Scripts\python.exe scripts\rebuild_from_raw.py # 三类台账 (实测: 报警 0→39211, 工单 0→5876, 油样 0→404)
# loss_monthly 另跑 SCADA 两步 (powercurve 31s + availability 88s)
.venv\Scripts\python.exe guanlan.py serve
实测结果:
| 检查项 | 结果 |
|---|---|
scan_stations.py |
扫描到 1 个场站目录: 如东 (592 件);how=raw_station(依据: 目录名与场配置 raw_station 完全相同);四个子目录件数 38 / 16 / 134 / 404 |
| 维护页「数据层」 | 扫描行 覆盖=如东 / 存在=True;10min 2025-01 ~ 2026-07 / 3729;报警 39211;工单 5876;油样 506(恢复完整件后) |
| 工作台实时原始窗 | err=None, months=7, series 7 点 —— 数据回来了 |
| 产物等价 | loss_monthly 3729/3729、powercurve_dev 38/38、powercurve_bins 912/912、alarms 39211/39211 逐值完全一致;workorders 5876 行仅 1 条记录有纳秒尾差(同一时刻 …16:23:00.0000015 vs …0000010);oil_samples_index 需恢复那 102 行(见 §4) |
注意: 清
data\raw不会自动清产物 —— 这是刻意的(产物是"上一次算好的结果", 现场拔盘不该让页面变空)。要验证"无数据呈现", 必须同时挪开产物, 如上。
产物统一在 outputs\<场名>\ 下(当前只有 rudong),按"线"分仓;门户与交付件在 release\:
| 仓 | 件数/体积 | 功用 | 生成端 | 能否由 data\raw 重算 |
谁在读 |
|---|---|---|---|---|---|
outputs\<场>\windscada\ |
118 件 5.6 MB | L0 标准仓(34 个 parquet + index.html + build 日志 + turbines\ review\) |
scripts\rebuild_from_raw.py(三类摄入 + --scada 十个构建器) |
✅ 大部分(见 §4) | 工作台、分台页、网关 /release/、所有分析面 |
outputs\<场>\ontology\ |
24 件 100 MB | 本体对象库 objects.json + 检索索引 retrieval_vec.npy + release_r1/r2 |
python -m src.ontology.kb_ingest / retrieval / chain_ingest …(读 data\raw\西门子4.0技术资料 与仓) |
⛔(源是厂商资料,不在这四类里) | 图谱检索、问答、台账对象 |
outputs\<场>\m5_cms_tcm\ |
90 件 53.5 MB | 振动线 handoff、TCM 兼容件、窗/谱/模型产物 | 振动线(windcms 会话)出件 |
⛔ | 融合面、台账判级、验收件 |
outputs\<场>\windcms\ |
53 件 27.7 MB | CMS 报告与单机页(HTML×41) | windcms analyze |
⛔ | 数据层「CMS 振动评估报告」、温度/振动互证 |
outputs\<场>\tcm_compatible_replay\ |
59 件 32.7 MB | TCM 兼容链模型表(mask_thresholds.tsv 等) |
振动线 | ⛔ | windcms 配置引用 |
outputs\<场>\sop\ |
210 件 15.8 MB | SOP 中间件/评审/台账 + 22 个历史装配脚本 | src\sop\farm_pipeline.py 等 |
⛔(历史脚本含 mac 死路径,仅留档) | 判据链、事实契约的输入 |
outputs\<场>\paradigm_r1\ |
29 件 0.5 MB | 范式实验底稿(E3/E5/E8…) | 实验脚本 | ⛔ | 事实契约的 source_refs |
outputs\<场>\guanlan\ |
5 件 0.5 MB | 事实契约 facts_contract_v0.json + derived\ + cloud\(可上云面孔) |
scripts\guanlan_facts_contract.py 等 |
⛔ | 问答引文、门户契约段 |
outputs\<场>\pitch\ |
2 件 0.3 MB | 变桨零位月表 | 变桨线 | ⛔ | 变桨面 |
release\ |
— | 门户 portal.html(20.2 MB 装配产物)、三维 viewer\、sim_sys_server.py、交付件 如东\、受管外壳 portal_src\ |
scripts\portal_build.py(拆 / 装) |
⛔ | 网关首页、三维页、交付 |
logs\ run\ |
— | 运行日志、pids.json |
启动器/服务 | — | 排障 |
"能重算"这一列才是关键:
data\raw换数据只会更新 ✅ 那几项;其余要么来自别的源(厂商资料/振动线),要么包内没有生成端(见 §4 的 ⛔ 表)。
门户的"壳/件分离"(2026-09-11):release\portal.html 20.23 MB 里 99.3% 是内嵌的交付文档正文
(28 个 <template>:治理清单分册、单机与整机报告、仿真台面板),只有 138 KB 是门户自己的壳。因此:
release\portal_src\shell.html(138 KB,含 <!--@TEMPLATE:id--> 与 <!--@GOVERNANCE_SOURCES--> 标记)、
manifest.json(各件 sha256 + 期望门户 sha256)、README.md —— 入库;release\portal_src\templates\(20.04 MB 交付件正文)、governance_sources.json、release\portal.html ——
按产物处理(.gitignore,磁盘文件随包分发、服务照读);scripts\portal_build.py --extract / (默认装) / --verify / --check;--verify 实测"拆→装"回到
逐字节相同的文件(sha256 9b6aabeb6ca18d15…,20,226,052 B)。行尾必须是 LF —— 三个相关脚本
都用字节级写文件(newline=""),装配后另有 CRLF 自检。<section id="contract-claims"> 是产物(由 guanlan_portal_inject_claims.py 在装配最后注入,
内容来自 outputs\<场>\guanlan\derived\portal_claims.json)—— 产物挪走时装出的门户会缺这一段,属预期。三条硬约定(用户令 2026-09-11;由 scripts\check_portability.py 自动门禁):
/Users/…、/Volumes/…、C:\……)。
整包拷到别的电脑/别的盘、或换 Linux,都不需要改任何路径。src\paths.py 解析;Path('outputs/…') 这种 cwd 相对写法
会让"从别处调用同一个脚本"指到别的地方(本仓实测曾有 20+ 处,已全部归到 paths)。outputs/rudong/…,见 P.rel());电脑间传输不受
分隔符影响。显示给人看时才用本机分隔符(P.disp() / P.disp_dir())。唯一真源:src\paths.py
from src import paths as P
P.ROOT # 安装根 (env WINDSCADA_ROOT → 否则按 src/paths.py 位置回溯)
P.RAW_ROOT # data/raw (离线数据入口)
P.store() # outputs/<场>/windscada P.ont() / P.cms() / P.m5() / P.sop() / P.guanlan() / P.pitch()
P.guanlan()/… P.tcm_replay() P.RELEASE / P.PORTAL / P.VIEWER / P.SIM_DIR / P.LOGS / P.RUN
P.rel(p) # → 'outputs/rudong/windscada/…' (POSIX 相对, 用于清单/契约)
P.disp(p) # → '<安装目录>/…' (显示)
P.venv_python() # 跨平台探测 .venv/Scripts/python.exe 或 .venv/bin/python
configs\serve.json 的 python 现在写相对路径(.venv/Scripts/python.exe;Linux 为 .venv/bin/python)——
换机后由启动器按安装根解析;也支持绝对路径与"留空自动探测"。
换机部署清单
# 目标机: 拷整个安装目录 → 跑安装 → 自检 → 回归比对
.venv\Scripts\python.exe scripts\check_portability.py # 1) 静态门禁: 无机器路径/无 cwd 相对
.venv\Scripts\python.exe guanlan.py check # 2) 依赖/产物/端口自检
.venv\Scripts\python.exe scripts\scan_stations.py # 3) 场站目录扫描辨识 (数据认到了没)
.venv\Scripts\python.exe scripts\page_fingerprint.py --diff <原机导出的指纹.json> # 4) 与基准机逐端点比对
第 4 步是换机验收的硬证据:指纹对每组页面/接口取 status + 归一化 sha256(自动剔掉
checked/ms/repo_head 这类设计上会变的字段,并把安装根前缀归一成 <ROOT>),因此"换了机器所以前缀不同"
不会误报,而"内容真的不对"一定报。基准机导指纹:page_fingerprint.py --save <文件名>。
已知例外(check_portability.py 的 ALLOW 里逐项写了理由):src\sop\farm_paths.py(它就是盘符映射表本体)、
scripts\sim_hub\*(开发期生成器)、outputs\rudong\sop\*.py(历史装配脚本留档)、若干文档字符串/证据文本
里的路径引用,以及交付侧离线工具(scripts\guanlan_cloud_* 等,ROOT 相对但尚未收敛为 paths,属 WARN 级)。
| 日期 | 变更 |
|---|---|
| 2026-09-11 | A2: 四项数据源改为 data\raw\<场站名称>\… |
| 2026-09-11 | 场站目录改为扫描辨识(config.scan_stations() / station_scan(),不再写死目录名),维护页新增「场站数据目录 (扫描辨识)」一行 |
| 2026-09-11 | 补三类摄入(报警/工单/油样)+ 总入口 rebuild_from_raw.py + 等价验收 --verify;新增 scan_stations.py、place_raw_data.py |
| 2026-09-11 | 无数据时实时接口不再抛 500,改为结构化"无数据"回答(场景① 逮到) |
| 2026-09-11 | 两个验收场景实测通过(见 §5);记录油样 102 行华标批的源件缺口(见 §4) |
| 2026-09-11 | 路径跨平台化:新增 src\paths.py 唯一真源;清理机器绝对路径(mac 检出/TCM 主仓/写死解释器)与 20+ 处 cwd 相对路径;serve.json 的 python 改相对;新增静态门禁 check_portability.py 与回归尺子 page_fingerprint.py;改造前后 10 个端点指纹逐字节一致;补本文件 §6 产物地图与 §7 跨平台约定 |
| 2026-09-11 | 门户拆包:release\portal.html 拆成受管外壳 release\portal_src\shell.html(138 KB) + 不入库内嵌件 templates\(28 件 20.04 MB)与 governance_sources.json;新增 scripts\portal_build.py(--extract/--verify/--check),实测拆→装逐字节一致(见 §6);配套 git rm --cached release\portal.html |
| 2026-09-11 | 验证闸门退出码修复:控制台 GBK 编不出 ✔ 时降级为 ?(新增 src\console.py),修 page_fingerprint / check_portability / rebuild_from_raw / scan_stations / portal_build —— 此前"全部一致"会因 print 抛异常而退出码 1(通过被报成失败) |
| 2026-09-11 | 从零重算实测 + 落位扩范围:place_raw_data.py 新增 --scope a2\|mech\|full(机理层技术资料 + 对译表 + scada_1min)、修"单文件规则恒不落件"与缺 import os(自 d90da05 起脚本跑不通)、同尺寸文件跳过;src\ontology\ 全层 10 处文本 I/O 补 encoding='utf-8'(中文 Windows 上本体层原本根本不可用:默认 cp936 写 objects.json 崩在 \u200b);check_portability.py 新增 WARN 项 6「文本读写缺 encoding=」;实测数字见 §4 末 |
| 2026-09-11 | 收尾这 18 处同类文本 I/O(scripts\ontology_p2_verify.py、scripts\windscada_serve.py、src\ontology\scenario_29.py、src\windcms\{knowledge,pipeline,plugins}.py、src\windscada\subsys\fusion.py、src\windscada\taxonomy.py),门禁 WARN 项 6 扫描面同时扩到 open('w') 与 open(p)(无 mode = 文本读);WARN 归零 |