☰
AFT二次开发教程(20):收官——企业级批量水力分析平台(完整项目)
2026/10/3 2:33:20 网站建设 项目流程

AFT二次开发教程(20):收官——企业级批量水力分析平台(完整项目)

版本与事实声明

  • 产品与版本:AFT Fathom 15 / AFT Impulse 12(当前,Datacor Flow Simulation);机制细节引用官方Fathom 13 / Impulse 10帮助页,当前版以官方文档为准。
  • 语言/环境:Python 3.x(stdlib + pandas + openpyxl);AFT 桌面端 + Excel。
  • 本文目标:读完能拥有一个多模块、可交付团队的 AFT 自动化平台aftkit,并知道它的五层架构、回归测试与交付清单。
  • 本篇是收官篇,综合第 01~19 篇全部知识;所有数值为示例性建模。

一句话结论:aftkit把 AFT 二次开发收敛为五层架构——环境探测层(安装/授权/可执行文件,来自第 02 篇)、数据契约层(AFT Transfer 列契约、长表契约、对象类型表,来自第 04/05/11 篇)、变更生成层(设计矩阵→变更表,来自第 09/15 篇)、批跑编排层(Batch File/看门狗/三闸门,来自第 06/10/17 篇)、取数报表层(导出契约/幂等落库/包络仪表盘,来自第 06/11/12/19 篇),并以"Python 全自动 + AFT 三 GUI 闸门"为运行宪法。

〇、本篇要解决的认知问题

  • Q1:一个"企业级"AFT 平台和个人脚本的差别,到底在哪几件事上?
  • Q2:为什么是五层?这个分层怎么来的、每层的职责边界是什么?
  • Q3:平台如何把Fathom 稳态与Impulse 瞬态串成一条流水线?
  • Q4:什么叫"回归测试"?在 AFT 这种有 GUI 闸门的系统里怎么做?
  • Q5:一个能交付给整个团队的平台,交付清单该有什么?

一、机制解析

20.1 个人脚本 vs 企业平台:差在三件事

把 1~19 篇的脚本收进一个文件夹,还不是平台。企业平台多出三件事:

维度个人脚本企业平台
环境“在我机器上跑得通”环境探测层:安装/授权/路径全部探测成 JSON,跨机器可复现
契约名字写死在代码里数据契约层:列契约、长表契约、对象类型表集中定义,一处改处处生效
可恢复崩了重头来账本 + 幂等:断点续跑、重复执行无副作用
可追溯只有结果元数据齐备:版本、配置、时间步、Export Guide 全部归档
可回归改了不知道坏没坏回归测试:基准工况 + 自检断言,改动前后对账

一句话:平台 = 脚本 + 契约 + 账本 + 元数据 + 回归。

20.2 五层架构

┌──────────────────────────────────────────────────────────────────┐ │ L5 取数报表层 collect/report │ │ Excel Export 映射(06) · 长表清洗(11) · 幂等落库(11) · 包络(12) · │ │ 仪表盘(19) · 载荷包(14) │ ├──────────────────────────────────────────────────────────────────┤ │ L4 批跑编排层 run │ │ 三闸门顺序(17) · Batch File(10) · 看门狗超时/释放/清理(10) · │ │ 账本状态机(17) · 转换守恒复核(13) │ ├──────────────────────────────────────────────────────────────────┤ │ L3 变更生成层 design/generate │ │ 设计矩阵(09) · 边界/规格模板(15) · Apply 门控(09) · 分批(09) · │ │ 场景树与路径名(08) │ ├──────────────────────────────────────────────────────────────────┤ │ L2 数据契约层 contracts │ │ AFT Transfer 列(05) · 参数/对象类型表(04) · 变更码(05) · │ │ 长表 schema(11) · 错误表(05) · Special Condition(04/15) │ ├──────────────────────────────────────────────────────────────────┤ │ L1 环境探测层 probe │ │ 安装目录/可执行文件(02) · 授权层级与席位(02) · 备份规则(04) · │ │ 扩展名与格式(04/16) │ └──────────────────────────────────────────────────────────────────┘

分层原则(为什么这么分):

  • 下层不含上层的知识:L1 只管"机器上有什么",不知道"要跑什么工况";
  • 契约在 L2 集中:所有"官方规定的东西"(列顺序、参数类型、错误文本)只在 L2 出现一次;
  • 上层可替换:L3 换成"手工写变更表"也能跑;L5 换成"只出 CSV"也能跑。

这就是"低耦合"在 AFT 自动化里的具体样子——因为 AFT 没有 API,唯一的"接口"就是这些文件契约,所以把它们显式分层,收益最大。

20.3 运行宪法:Python 全自动 + 三 GUI 闸门

平台的运行模型(第 17 篇的分工图升级版):

aftkit design/generate ──► [G1] Import Excel Change Data ──┐ ▼ aftkit probe/status ◄── [G2] Start Batch Run ◄── [G3] Excel Export Manager 配置 │ aftkit collect/report ◄────────────────────────────────────┘

宪法三条:

  1. Python 只做文件与数据,从不假装能驱动 AFT(铁律 2/3);
  2. 三个 GUI 闸门是流程的显式节点,平台用gate_checklist()打印该确认什么;
  3. 每过一个闸门,账本状态前进一步,崩溃后可定位到"卡在哪个闸门"。

20.4 回归测试:在"有 GUI 闸门"的系统里怎么做

软件工程里,回归测试靠"跑同样的输入、比同样的输出"。AFT 有 GUI 闸门,怎么破?

三层回归策略:

层测什么怎么测依赖 AFT?
单元回归契约与算法每个模块的--selftest(前 19 篇都写了)不依赖
契约回归契约是否被改坏断言"官方列顺序/参数类型/枚举值"未变不依赖
端到端回归基准工况结果跑基准场景 → 与基准快照比对(导出精度内一致)依赖 GUI 闸门

关键设计:单元回归与契约回归必须不依赖 AFT(这样能进 CI)。端到端回归需要 GUI,按周/按版本手动跑一次,把结果存成"基准快照",之后每次比对。

契约回归的重要性:AFT 在活跃演进(2026-09 刚发 Fathom 15 / Impulse 12)。升级版本后第一件事,就是跑契约回归——如果列顺序或枚举值变了,L2 会立刻报警,而不是等到批量跑到一半才发现。

20.5 交付清单

一个可交付团队的平台,交付物应包含:

aftkit/ # 代码 ├── aftkit/ # 包 │ ├── probe.py (L1) │ ├── contracts.py (L2) │ ├── design.py (L3) │ ├── run.py (L4) │ └── report.py (L5) ├── cli.py # 统一入口 ├── tests/ # 回归测试 ├── README.md # 平台手册(运行宪法、三闸门、五层) ├── ENVIRONMENT.md # 环境与授权要求(L1 产物) └── DELIVERY.md # 交付清单与验收

加上运维手册要点:席位使用纪律(铁律 8)、本地磁盘纪律(铁律 7)、版本升级流程(先契约回归)、示例性建模声明。

二、完整代码与逐行剖析

代码 20-1:aftkit/contracts.py(L2 数据契约层,平台地基)

# -*- coding: utf-8 -*-""" contracts.py —— L2 数据契约层:把"官方规定"集中一处定义 所有契约来自官方 Fathom 13 / Impulse 10 帮助页(见 references)。 """VERSION="AFT Fathom 15 / AFT Impulse 12(机制依据 Fathom 13 / Impulse 10 官方帮助)"TRANSFER_SHEET="AFT Transfer"TRANSFER_COLS=["Apply","Object Type","Object Number","Parameter","Change Code","Value","Scenario Path Name"]CHANGE_CODES={"S":("Set equal to value","Change by value (+/-)","Change by percent (+/-)"),"I":("Set equal to value","Change by value (+/-)"),"C":("Set equal to value","Add string to list","Delete string from list"),}# 官方 Table 1 节选(对象 -> {参数: 类型码})OBJECT_PARAMS={"Pipe":{"Length":"S","Roughness Value (for unspecified friction models)":"S","Design Factor - Pipe Friction":"S","Pipe Name":"C","Special Condition":"I"},"Pump":{"Fixed Speed (%)":"S","Fixed Flow Rate":"S","Control Setpoint":"S","Junction Name":"C","Special Condition":"I"},"Valve":{"Open Percentage":"S","Loss Value":"S","Junction Name":"C","Special Condition":"I"},"Control Valve":{"Setpoint":"S","Loss When Fully Open":"S","Special Condition":"I"},"Reservoir":{"Liquid Surface Elevation":"S","Liquid Surface Pressure":"S","Liquid Temperature":"S","Cross-Sectional Area":"S","Tank Bottom Elevation":"S","Junction Name":"C"},"Assigned Pressure":{"Pressure":"S","Elevation":"S","Temperature":"S"},"Assigned Flow":{"Flow Rate":"S","Elevation":"S","Temperature":"S","Special Condition":"I"},"Spray Discharge":{"Cd (Discharge Coefficient)":"S","Discharge Flow Area":"S","Number of Holes":"I","Special Condition":"I"},}# 官方 Table 3SPECIAL_CONDITION={"Pump":{0:"None",1:"Pump Off No Flow",2:"Pump Off With Flow Through"},"Valve":{0:"None",1:"Closed"},"Control Valve":{0:"None",1:"Closed",2:"Fully Open - No Control"},"Assigned Flow":{0:"None",1:"Closed"},"Spray Discharge":{0:"None",1:"Closed"},"Heat Exchanger":{0:"None",2:"Ignore Heat Transfer"},}# 官方 Table 4 错误文本IMPORT_ERRORS=("Scenario name is not unique in the model","Invalid change type","Invalid parameter ID","Invalid parameter value","Object not found","Parameter conflict","Scenario not found in the model")LONG_SCHEMA=("scenario","object","parameter","unit","value")MODEL_EXT={".fth":"AFT Fathom",".imp":"AFT Impulse",".aro":"AFT Arrow",".xtr":"AFT xStream"}defparam_type(obj_type:str,param:str):"""返回参数类型码 S/I/C;不属于该对象返回 '!',未登记返回 None。"""tbl=OBJECT_PARAMS.get(obj_type)iftblisNone:returnNoneifparamnotintbl:return"!"returntbl[param]

逐行剖析:

  • 整个 L2没有任何算法,只有常量与一个查表函数——这正是它的价值:把"官方规定"变成唯一真相源。前 19 篇散落在各脚本里的列名、枚举、错误文本,在平台里都归到这里。
  • CTR里SPECIAL_CONDITION是从官方 Table 3 逐条抄来的整数映射——这是平台里最不能出错的一张表(写错就是静默物理错误)。
  • IMPORT_ERRORS存官方错误文本:日志分拣(第 05 篇read_change_log)用它做关键词,契约与消费者同源。
  • param_type()用'!'与None区分"明确错误"与"未登记"——调用方可以对前者报错、对后者提示"以官方文档为准"。

代码 20-2:cli.py(统一入口 + 契约自检)

# -*- coding: utf-8 -*-""" cli.py —— aftkit 统一入口 python cli.py probe # L1 环境探测(写 aft_env.json) python cli.py selftest # 契约与算法回归(不依赖 AFT) python cli.py gates # 打印三个 GUI 闸门的验收清单 """importargparseimportjsonimportsysfromaftkitimportcontractsasC GATES={"G1 Import Excel Change Data":["工作表名为 AFT Transfer;数据从第 2 行 B 列起(第 1 行/A 列不被读取)","Apply 仅 Yes 生效;统计 Yes 行数并与预期比对","导入后 Object Change Log 零错误(对照官方 7 条错误文本)",],"G3 Excel Export Manager 配置(必须先于 G2)":["导出项无区域重叠(Ending Cell 与预览网格)","多场景进同一工作簿时只指定一个 sheet","Header/Units/Export Guide 均打开(增强可追溯)",],"G2 Start Batch Run":["Batch Run Type = Scenarios in Current Model(用 Excel 导出时必须)","场景 Analysis Setup 已完整定义(否则被跳过)","勾 Run Batch in Background;批跑期间不打开目标工作簿",],}defselftest()->int:"""契约回归:官方规定未被改坏(不依赖 AFT,可进 CI)。"""problems=[]ifC.TRANSFER_SHEET!="AFT Transfer":problems.append("工作表名契约被改")ifC.TRANSFER_COLS!=["Apply","Object Type","Object Number","Parameter","Change Code","Value","Scenario Path Name"]:problems.append("AFT Transfer 列顺序契约被改")ifC.param_type("Pipe","Length")!="S"orC.param_type("Pipe","Pipe Name")!="C":problems.append("参数类型契约被改")ifC.param_type("Reservoir","Length")!="!":problems.append("参数归属校验失效(Length 不应属于 Reservoir)")ifC.SPECIAL_CONDITION["Pump"][1]!="Pump Off No Flow":problems.append("Pump 停机枚举契约被改")iflen(C.IMPORT_ERRORS)!=7:problems.append("官方导入错误文本条数契约被改(应为 7)")ifproblems:print("契约回归失败:")forpinproblems:print(" -",p)return1print(f"契约回归通过(版本:{C.VERSION})")return0defgates()->int:forg,itemsinGATES.items():print(f"\n[{g}]")fori,itinenumerate(items,1):print(f"{i}.{it}")return0defmain():ap=argparse.ArgumentParser()ap.add_argument("cmd",choices=["probe","selftest","gates"])a=ap.parse_args()ifa.cmd=="selftest":returnselftest()ifa.cmd=="gates":returngates()print("probe:调用 L1 环境探测(实现见第 02 篇 probe_env.py),产出 aft_env.json")return0if__name__=="__main__":sys.exit(main())

逐行剖析:

  • selftest()是契约回归测试:逐条断言"官方规定"(表名、列顺序、参数类型、枚举、错误文本条数)没被改坏。它是升级 AFT 版本后第一个要跑的东西(20.4)。
  • GATES把第 05/06/10/17 篇的散点纪律收成三张清单:G1/G3/G2 各列 3 条判据。平台把"人该确认什么"也做成了产品。
  • 注意GATES的键顺序:G1 → G3 → G2,刻意把 G3 写在 G2 前面——因为"导出项必须先配"是流水线的硬性顺序(第 17 篇)。
  • main()用子命令分发,统一入口,这样团队成员只需要记python cli.py probe/selftest/gates(加上各层子命令)。

代码 20-3:README.md(平台手册要点,交付给团队)

# aftkit —— AFT 批量水力分析平台 ## 运行宪法 1. Python 只做文件与数据,不驱动 AFT(AFT 无公开 CLI/API) 2. 三个 GUI 闸门是显式流程节点:G1 导入 → G3 配置导出 → G2 批跑 3. 每过一闸门,账本 status 前进;崩溃可定位卡点 ## 五层与入口 | 层 | 入口 | 主要产物 | |---|---|---| | L1 环境探测 | `python cli.py probe` | aft_env.json | | L2 数据契约 | `python cli.py selftest` | 契约回归结果 | | L3 变更生成 | `python cli.py design/generate` | AFT Transfer 工作簿 | | L4 批跑编排 | `python cli.py run` | Batch File、账本、日志 | | L5 取数报表 | `python cli.py collect/report` | 长表、sqlite、仪表盘 | ## 运维铁律(摘自全系列) - 席位纪律:批跑三步法(超时+正常关闭+残留清理),崩溃会锁席位最长 2 小时 - 本地磁盘:模型文件禁止放网络盘/云同步目录 - 版本升级:先跑 `selftest` 契约回归,再跑端到端基准回归 - 单位:交付件每条数值带单位;CAESAR II 力文件单位只能 lbf/N

为什么手册也是"代码":把运行宪法与铁律写进仓库,新同事第一天就能看到。平台的可维护性,一半在文档。

三、常见报错与排查

报错 20-1:升级到新版本后批量结果全变了。
现象:同一工况结果与旧版不同。根因:版本行为变化(AFT 正活跃演进)。解法:先跑selftest契约回归确认契约未变;再跑端到端基准回归,比对差异;差异记录进版本升级说明。

报错 20-2:换了一台机器,平台跑不起来。
现象:路径找不到。根因:环境相关常量被写死在代码里。解法:跑 L1probe重新生成aft_env.json;确认所有路径来自清单(铁律 3)。

报错 20-3:多人同时用同一套授权批跑,互相锁席位。
现象:有人打不开软件。根因:席位池 + 崩溃未释放(2 小时 checkout interval)。解法:L4 的看门狗(铁律 8);团队层面约定"批跑排期",避免并发抢席位。

报错 20-4:账本与磁盘对不上(有状态没数据、有数据没状态)。
现象:续跑判断错。根因:手工干预未同步账本。解法:状态只能由脚本推进;任何人工干预后手工修正账本并记录原因。

报错 20-5:交付件被质疑"数从哪来",无法回答。
现象:审计不通过。根因:缺元数据(版本/配置/Export Guide)。解法:L5 强制产出config.json+manifest.json+ Export Guide(第 19 篇)。

四、动手练习

  • 练习 1(契约回归):把代码 20-1、20-2 放进aftkit/包,跑python cli.py selftest。判定:输出"契约回归通过";随后把TRANSFER_COLS里两项对调,重跑必须失败,恢复后通过。
  • 练习 2(闸门清单):跑python cli.py gates。判定:打印 G1/G3/G2 三组清单,且G3 出现在 G2 之前;写出为什么顺序不能反。
  • 练习 3(五层贯通):把第 02(L1)、第 09(L3)、第 17(L4)、第 11+19(L5)篇的脚本按五层归位,跑一次端到端(含三闸门)。判定:账本全部collected;落库总行数 = 工况数 × 对象数 × 参数数;交付件含 config/manifest/Export Guide。
  • 练习 4(平台手册):按代码 20-3 写你自己项目的 README。判定:含运行宪法 3 条、五层入口表、运维铁律 4 条;找一个不看代码的同事验证"照着能跑"。

五、小结(全系列收官)

aftkit把 20 篇的知识收成了一台可交付的机器:五层架构(环境探测 / 数据契约 / 变更生成 / 批跑编排 / 取数报表)、运行宪法(Python 全自动 + AFT 三 GUI 闸门)、三层回归测试(单元 / 契约 / 端到端)、完整交付清单(代码 + 手册 + 环境说明 + 交付验收)。至此全系列闭环:

认识边界(01-02)→ 跑通闭环(03-04)→ 掌握通道(05-06)→ 组织工况(07-08)→ 工程化扫描与批跑(09-10)→ 数据面落库(11-12)→ 稳态接瞬态与载荷交付(13-14)→ 边界与资产面(15-16)→ 端到端与水锤流水线(17-18)→ 报表交付(19)→ 平台收官(20)。

FAQ(与第〇节一一对应)

Q1:企业级 AFT 平台与个人脚本差在哪?
A:差在环境(安装/授权/路径探测成 JSON,跨机器可复现)、契约(列契约与对象类型表集中定义)、可恢复(账本加幂等,支持断点续跑)、可追溯(版本/配置/时间步/Export Guide 齐备)、可回归(基准工况与自检断言)五件事上。

Q2:为什么是五层架构?
A:因为 AFT 无公开 API,平台唯一的"接口"就是文件契约,所以按职责分为环境探测层(机器上有什么)、数据契约层(官方规定集中一处)、变更生成层(设计矩阵转变更表)、批跑编排层(三闸门顺序与账本状态机)、取数报表层(导出映射、幂等落库、包络与仪表盘),下层不含上层知识、契约集中在 L2、上层可替换。

Q3:平台如何把 Fathom 稳态与 Impulse 瞬态串成流水线?
A:按三段式——Fathom 稳态批跑产出每个工况的稳态解,然后在 Impulse 里 Open .fth 完成互转并做对象守恒复核,最后在 Impulse 母模型内用 Scenario Manager 组织瞬态事件(阀关闭/泵停机)批跑并导出 Force File,瞬态工况数是稳态数乘以事件数。

Q4:在有 GUI 闸门的系统里怎么做回归测试?
A:分三层——单元回归(各模块 --selftest,不依赖 AFT)、契约回归(断言官方列顺序/参数类型/枚举值/错误文本未变,不依赖 AFT、可进 CI)、端到端回归(跑基准工况与基准快照在导出精度内比对,需 GUI 闸门按周或按版本手动执行);升级版本后先跑契约回归。

Q5:一个能交付团队的平台该有什么?
A:应含五层代码包与统一 CLI、回归测试目录、平台手册(运行宪法、三闸门、五层入口表、运维铁律)、环境与授权要求说明、交付清单与验收标准;运维铁律包括席位三步法、本地磁盘编辑、版本升级先契约回归、交付件数值带单位等。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询