☰
PHAST-SAFETI二次开发教程(16):数据对接——Phast API / Phast Web Services 与 PyPWS
2026/10/8 19:32:18 网站建设 项目流程

PHAST-SAFETI二次开发教程(16):数据对接——Phast API / Phast Web Services 与 PyPWS

版本声明块

  • 工具/软件:Phast™ / Safeti™(版本主线 9.x,具体以官方发布说明为准);Phast API 官方托管服务 Build20260820.1(以官方门户当前显示为准)
  • 语言/环境:Python ≥ 3.11(PyPWS 官方要求);官方客户端形态 Excel / Python / .NET / TypeScript 四选一即可
  • 本文目标:搞清楚官方 API 的形态、端点、令牌与 PyPWS 的包结构,并写出第一个能判定成功与失败的最小计算

一句话结论:Phast™ 的官方二次开发入口不是本地 SDK,而是托管的 Phast Web Services(REST,端点https://phastwebservices.dnv.com/api/analytics/v1/)+ 四种官方客户端(Excel / Python / .NET / TypeScript);Python 侧的官方库是PyPWS(PyPI 包名小写pypws),它的公开结构是pypws.calculations/pypws.entities/pypws.enums/pypws.utilities/pypws.constants/pypws.materials六个模块,而判断一次计算是否成功,靠的是calculation.run()返回的ResultCode是否为ResultCode.SUCCESS。

本篇铁律适用(三级标注,取自系列登记的 10 条)

  • [硬约束] 铁律 2 闭源事实纪律:本篇出现的每个类名/方法名/端点都必须能回溯官方来源;无官方证据的一律标注「未确证 / 以官方文档为准」,不臆造。
  • [硬约束] 铁律 3 只用官方接口:二次开发仅走 DNV 官方 Phast API / Phast Web Services / PyPWS,不引入来源不明的第三方封装。
  • [硬约束] 铁律 5 令牌与许可不硬编码:access token 只从环境变量或密钥管理读取,绝不写进脚本与仓库。
  • [强约束] 铁律 4 单位与坐标系纪律:Phast Web Services 全程SI 单位,换算点必须集中在入参边界与出参边界。
  • [提示] 铁律 10 能力边界不越界:官方明列的可编程形态是 Web Services + 四种客户端库,COM / SDK 形态未确证,正文不作主张。

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

  1. Phast API / Phast Web Services 的官方形态到底是什么,与「本地装一个 SDK」有什么本质区别?
  2. 官方明列的四种客户端(Excel / Python / .NET / TypeScript)分别适合谁,能不能共享同一套计算语义?
  3. 端点https://phastwebservices.dnv.com/api/analytics/v1/与 access token 是什么关系,token 应该放在哪里?
  4. PyPWS 的内部结构是怎么组织的,六个模块各装什么,动手时先看哪一个?
  5. 一次计算怎么判成功?ResultCode与print_messages()在诊断链条里各扮演什么角色?
  6. 为什么 Phast Web Services 全程 SI 单位,对接实践里最容易在什么地方把单位丢掉?

以下「认知问题回显(FAQ)」与上列问题一一对应、同序作答。


一、机制解析

1.1 官方形态:托管 REST 服务 + 四种官方客户端

必须先纠正一个常见误解:Phast™ 的官方二次开发能力不是一个装在本地、可以随意import的 COM 组件或原生 SDK。官方门户phastwebservices.dnv.com明确给出的形态是一组托管的 Web API(门户标题即用 “APIs”),并同时明列四种官方客户端形态:

官方客户端形态载体典型使用者计算语义是否同源
Excel官方 Excel 客户端工艺安全工程师、报表型作业同源(同一托管计算)
Python官方 Python 库PyPWS自动化脚本、数据工程师同源
.NET官方 .NET 客户端企业内部工具、桌面系统集成同源
TypeScript官方 TypeScript 客户端Web 应用、数字孪生前端同源

四种客户端不是四套模型,而是同一套托管计算内核的四个翻译层。这一点有很强的工程含义:用 PyPWS 跑出的结果,与用 .NET 客户端跑同一组输入应当一致;如果出现差异,问题通常在输入装配或单位换算,而不在「模型不一样」。

按未确证项 B 的登记:COM / Scripting 独立接口未确证。因此在本系列中,任何「注册一个 COM 对象然后调用 Phast」的写法都不予采用。

1.2 一次调用的生命周期

① 客户端(Excel / Python / .NET / TypeScript) │ 计算请求:物料 + 源项 + 气象 + 结果选项(全部 SI) ▼ ② Phast Web Services(托管) 端点 https://phastwebservices.dnv.com/api/analytics/v1/ │ 鉴权:access token(须向 DNV 获取) ▼ ③ Phast 后果计算内核(与桌面 Phast™ 同源的模型链) │ 结果:状态 / 泄漏 / 火灾 / 辐射断线 等结果对象 ▼ ④ 结果对象回传客户端 ├── ResultCode ──▶ 成功/失败硬判定 └── print_messages() ──▶ 诊断域消息(失败时的唯一线索)

这张图里最值得记住的一句是:鉴权在网络层,判定在结果层。token 错了会在第②步就失败(通常是连接/授权类错误,脚本里看不到任何ResultCode);而 token 正确但输入不合法时,请求能到达第③步,最终以一个非SUCCESS的ResultCode回到脚本。两种失败的排查路径完全不同,这也是第三节报错排查的分叉点。

1.3 PyPWS 的包结构

PyPWS 是官方 Python 库,文档体例写PyPWS,PyPI 包名小写pypws,官方要求 Python ≥ 3.11。它的公开模块按下表组织:

模块装什么什么时候看
pypws.calculations计算对象,如VesselStateCalculation/VesselLeakCalculation/JetFireCalculation/RadiationTransectCalculation每个场景的主入口
pypws.entities输入实体,如Material/MaterialComponent/State/Vessel/Leak/Weather/Substrate装配输入
pypws.enums枚举,如ResultCode/FluidSpec/VesselShape/VesselConditions/TimeVaryingOption/FlashAtOrifice/AtmosphericStabilityClass/SurfaceType/PoolSurfaceType/PoolFireType/RadiationType/ContourType/Resolution约束取值、避免拼写漂移
pypws.utilities工具函数,如getAnalyticsApiTarget()环境自检
pypws.constants常量(物理常量、默认值等)避免自造常数(铁律 8)
pypws.materials物料库函数,如getAllCasIds()/getMaterialByCasId(casId)/getDNVComponents()/getDIPPRComponents()/getUserComponents()/getComponentById(id)/getComponentByName(name)/getComponentByCasId(casId)/storeMaterialComponent(...)/storeMaterialComponentAndCreateMaterial(...)选物料、建自定义物料

工程习惯:先utilities自检,再materials选料,再entities装配,最后calculations跑。四步顺序反过来写(先跑再查物料)会在批量作业里浪费大量网络往返。

关于pypws-cli:依未确证项 I 的登记,卸载输出中出现过pypws-cli,但其完整命令范围未见官方文档,正文仅承认其存在,细节以官方文档为准。

1.4 结果对象与 ResultCode

PyPWS 的运行接口是一条极简契约:calculation.run()返回ResultCode,成功判据是与ResultCode.SUCCESS比较;另有print_messages()用于打印诊断消息。已登记的结果属性包括outputState、material、vesselConditions、dischargeRecords等——这些是下游取数的接口面:

VesselStateCalculation.run() ──▶ ResultCode + outputState │(下游取数) ▼ VesselLeakCalculation ──▶ ResultCode + dischargeRecords(泄漏记录) │(下游取数) ▼ JetFireCalculation / RadiationTransectCalculation ──▶ ResultCode + 后果结果

注意「下游取数」这四个字:链路里每一环的结果属性同时是下一环的输入。因此判成败不能只看最后一环——如果上一环run()返回了非SUCCESS,下游即使返回SUCCESS也可能是基于残缺输入算出的无意义值。这是批量作业里最隐蔽的一类错误,第三节会给出对应的门禁写法。

1.5 单位与数据交换纪律

Phast Web Services全程 SI 单位(铁律 4)。工程上把换算集中到两个边界:

边界动作反例(应避免)
入参边界把工程单位/现场单位一次性换算为 SI在装配函数里散落乘除系数
出参边界把 SI 结果换算为报告单位,并同时保留 SI 原值只留报告单位,回归时无法与官方 SI 对账

数据集层面,跨客户端交换建议统一用**扁平表(CSV/JSON)**并显式带上units列;不要依赖列名暗示单位。原因很实际:Excel 客户端与 Python 客户端之间交换的往往就是这种扁平表,一旦单位靠列名暗示,第一次被别的同事改列名就崩了。


二、完整操作与脚本逐段剖析

构造参数与属性名声明:以下代码用到pypws的类名/函数名/枚举成员均取自官方公开来源;但各实体的构造参数签名与部分结果属性名会随版本演进,一律以官方 PyPWS 参考文档(官方 PDF:PyPws.pdf)为准。读者照抄前应先核对当前版本文档。

2.1 第一段:环境自检与端点确认

# -*- coding: utf-8 -*-# 演示:PyPWS 环境自检 —— Python 版本、库版本与当前 API 目标端点importsys# 标准库:解释器信息importimportlib.metadataasmd# 标准库:读取已安装包版本frompypws.utilitiesimportgetAnalyticsApiTarget# 官方工具:返回当前 API 目标REQUIRED_PYTHON=(3,11)# PyPWS 官方要求 Python >= 3.11assertsys.version_info[:2]>=REQUIRED_PYTHON,\"PyPWS 要求 Python >= 3.11,当前为 %s"%".".join(map(str,sys.version_info[:3]))try:pypws_version=md.version("pypws")# 注意:包名是小写 pypwsexceptmd.PackageNotFoundError:# 未安装分支pypws_version="未安装(修复:pip install pypws)"# 给读者可执行的修复提示target=getAnalyticsApiTarget()# 打印当前 API 目标端点print("Python :",sys.version.split()[0])# 解释器版本print("pypws :",pypws_version)# 官方库版本(检索期为 4.2.25)print("API目标 :",target)# 应指向 phastwebservices 域# —— 硬门禁:端点必须落在官方托管域,防止被环境变量指到测试/第三方地址 ——assert"phastwebservices"instr(target),"API 目标异常,请检查环境配置(铁律 3)"

这段是整篇最便宜的一次自检:三行输出就能回答「我的库对不对、我的端点对不对」。把assert写成硬门禁而不是print提示,是因为在 CI 里print只会留下日志,而assert会直接阻断流水线。

2.2 第二段:最小可用计算(容器状态 → 容器泄漏)

# -*- coding: utf-8 -*-# 演示:最小可用链路 VesselStateCalculation -> VesselLeakCalculationimportos# 标准库:读取环境变量frompypws.calculationsimportVesselStateCalculation,VesselLeakCalculation# 计算类frompypws.entitiesimportMaterial,MaterialComponent,State,Vessel,Leak# 输入实体frompypws.enumsimportResultCode,FluidSpec,VesselShape,VesselConditions# 枚举# —— 令牌治理:只从环境变量读取,绝不硬编码(铁律 5)——TOKEN=os.environ.get("DNV_PWS_TOKEN")# 由密钥管理注入的环境变量assertTOKEN,"缺少环境变量 DNV_PWS_TOKEN,access token 不得写入脚本或仓库"# —— ① 物料装配:单组元丙烷(构造参数签名以官方 PyPWS 参考文档为准)——component=MaterialComponent(name="PROPANE",mole_fraction=1.0)# 单个组元及其摩尔分数material=Material(name="PROPANE-100",components=[component])# 组物料# —— ② 滞止/容器状态(SI:K、Pa)——stagnation_state=State(temperature=298.15,# 298.15 K = 25 ℃(SI)pressure=1.0e6)# 1.0e6 Pa = 10 bar(SI)# —— ③ 容器与泄漏几何(SI:m)——vessel=Vessel(vessel_shape=VesselShape.HORIZONTAL_CYLINDER,# 容器型式枚举fluid_spec=FluidSpec.GAS,# 相态说明枚举vessel_conditions=VesselConditions.INSULATED)# 容器条件枚举leak=Leak(hole_diameter=0.02)# 泄漏孔径 0.02 m(SI)# —— ④ 上游:容器状态计算 ——state_calc=VesselStateCalculation(material=material,# 输入物料state=stagnation_state,# 输入状态vessel=vessel)# 输入容器state_code=state_calc.run()# 运行,返回 ResultCodeprint("VesselStateCalculation:",state_code)# 打印结果码ifstate_code!=ResultCode.SUCCESS:# 成功判定state_calc.print_messages()# 失败时打印诊断消息raiseSystemExit("上游状态计算失败,下游不再执行(避免基于残缺输入)")# —— ⑤ 下游:容器泄漏计算,取上游 outputState 作为输入(下游取数)——leak_calc=VesselLeakCalculation(material=material,# 同一物料state=state_calc.outputState,# 关键:接入上游结果属性leak=leak)# 泄漏几何leak_code=leak_calc.run()# 运行print("VesselLeakCalculation:",leak_code)# 打印结果码ifleak_code!=ResultCode.SUCCESS:# 同样硬判定leak_calc.print_messages()# 打印诊断raiseSystemExit("泄漏计算失败")print("泄漏记录条数:",len(leak_calc.dischargeRecords))# 结果属性取数(以官方文档为准)

这二十来行里有三个必须内化的动作。第一,上游失败即中止:state_code != ResultCode.SUCCESS时直接raise SystemExit,不让下游拿着残缺的outputState继续跑。第二,诊断只在失败时打印:print_messages()是失败时的唯一线索,成功时打印只会污染日志。第三,outputState显式接链:state=state_calc.outputState这一行就是「模型链路」在代码里的样子,它与第 05 篇讲的 Discharge→Dispersion→Effects 链路是同一件事在 API 层的投影。

2.3 第三段:结果读取与结构化落盘

# -*- coding: utf-8 -*-# 演示:把单场景结果抽成以 case_id 为键的长表,便于与工况表 JOIN(铁律 6)importcsv# 标准库importhashlib# 标准库:稳定唯一键defmake_case_id(payload:dict)->str:"""由输入维度生成稳定唯一键:同工况同号,是幂等与断点续跑的前提。"""raw="|".join([# 竖线拼接,避免歧义payload["material"],# 物料名format(payload["pressure"],".3f"),# 压力(Pa,定长格式化)format(payload["hole_diameter"],".6f"),# 孔径(m,定长格式化)])returnhashlib.sha1(raw.encode("utf-8")).hexdigest()[:12]# 取前 12 位,够用且可读defharvest_single(case_id:str,leak_calc:object)->list:"""把一次泄漏计算的记录抽成长表行:case_id / metric / value / unit。"""rows=[]# 长表容器forindex,recordinenumerate(leak_calc.dischargeRecords):# 逐条泄漏记录rows.append({"case_id":case_id,# 幂等键(可 JOIN 工况表)"metric":"discharge_record_%d"%index,# 记录序号作为指标名"value":record,# 记录本体(以官方文档结构为准)"unit":"SI",# 单位纪律:原值保持 SI})returnrows PAYLOAD={"material":"PROPANE-100","pressure":1.0e6,"hole_diameter":0.02}# 示例输入CASE_ID=make_case_id(PAYLOAD)# 计算唯一键print("case_id =",CASE_ID)# 打印键,便于对账# —— 装配好 leak_calc 之后(省略构造,见 2.2 段)——# LONG_ROWS = harvest_single(CASE_ID, leak_calc)LONG_ROWS=[]# 占位:无运行时保持脚本可解析withopen("pypws_results_long.csv","w",newline="",encoding="utf-8-sig")asfh:writer=csv.DictWriter(fh,fieldnames=["case_id","metric","value","unit"])writer.writeheader()# 表头forrowinLONG_ROWS:# 逐行写出writer.writerow(row)print("长表行数:",len(LONG_ROWS))# 回收规模确认

case_id的构造与第 08 篇的批量纪律完全一致——这不是重复,而是同一套幂等契约在 API 侧的延续:无论数据来自 GUI 批处理还是 PyPWS,键的构造规则必须统一,否则两批结果无法合并。

2.4 第四段:多客户端协同与令牌脱敏

# -*- coding: utf-8 -*-# 演示:多客户端协同的产物约定 + 令牌脱敏打印importos# 标准库CLIENT_MATRIX=[# 四客户端分工建议表{"client":"Excel","role":"工程师手工核对与交付","artifacts":"xlsx"},{"client":"Python","role":"自动化流水线","artifacts":"csv/json"},{"client":".NET","role":"企业内部工具集成","artifacts":"app"},{"client":"TypeScript","role":"Web/数字孪生前端","artifacts":"json"},]foriteminCLIENT_MATRIX:# 逐行打印分工建议print("%-10s %-24s -> %s"%(item["client"],item["role"],item["artifacts"]))defmasked(token:str)->str:"""令牌脱敏:只保留头 4 位与长度,便于排查「取到没有」而不泄露。"""ifnottoken:# 空值分支return"<未设置>"returntoken[:4]+"*"*max(0,len(token)-4)# 其余全部打码TOKEN=os.environ.get("DNV_PWS_TOKEN")# 从环境变量读取print("token =",masked(TOKEN))# 只打印脱敏值(铁律 5)

保留masked()这种小工具很值:调试期最常见的两类问题——「环境变量没注入」与「token 过期」——都能靠脱敏打印快速区分,而不会把凭据写进日志。


三、常见报错与排查

现象 1:脚本在run()之前就抛连接/授权类异常,完全看不到ResultCode。
根因:鉴权发生在网络层,token 缺失、失效或未注入到运行环境,请求根本没到达计算内核。
解法:先用 2.1 段确认端点,再用 2.4 段的masked()确认 token 是否被取到;token 一律通过环境变量或密钥管理注入(铁律 5),不在代码里传参。

现象 2:run()返回非SUCCESS,但脚本没有输出任何有用信息。
根因:只判断了结果码,没有调用print_messages();诊断消息域是失败时的唯一线索。
解法:在每次run()后紧跟「非SUCCESS即print_messages()」的两行模式(见 2.2 段),把诊断固化进代码模板。

现象 3:下游计算返回SUCCESS,但结果数量级明显不合理。
根因:上游run()实际失败,脚本仍把outputState传给了下游,下游基于残缺输入算出了「成功但无意义」的结果。
解法:链路每一环都做成功判定,上游失败即中止(见 2.2 段的raise SystemExit),并把「上游结果码」一并记入结果表。

现象 4:报「参数名未知 / 未知关键字参数」。
根因:实体构造参数签名随版本演进,照抄了旧版本示例。
解法:以官方 PyPWS 参考文档(PyPws.pdf)为准核对当前版本的构造签名;把逐篇示例视为「结构参考」而非「签名权威」。

现象 5:结果数值与桌面 GUI 差一个众所周知的比例(如 14.5 / 1000 / 3600)。
根因:单位换算点散落,某个输入仍带着工程单位(bar、mm、℃、t/h)进入了 SI 的世界。
解法:把换算集中在入参边界与出参边界两处(1.5 节的表);在结果表里同时保留 SI 原值与报告单位,便于对账(铁律 4)。

现象 6:物料在库中找不到,或名称拼写与库中不一致。
根因:pypws.materials的检索函数需要按 CAS 号或组件 ID/名称查找,名称字符串不匹配时查不到。
解法:优先用getComponentByCasId(casId)或getMaterialByCasId(casId)按 CAS 号定位(唯一性强于名称);需要建自定义物料时用storeMaterialComponent(...)/storeMaterialComponentAndCreateMaterial(...),并把物料来源登记入库(铁律 9 的可追溯思路)。


四、动手练习

  1. 把 2.1 段的自检脚本改成「无assert版」,人为把端点指向一个错误域名,对比两种写法在 CI 中的行为差异。
  2. 用pypws.materials的getAllCasIds()列出可用物料编号,再用getMaterialByCasId(casId)取回其中一个,打印其组件信息。
  3. 把 2.2 段的链路扩展一环:在VesselLeakCalculation之后接入JetFireCalculation,并要求「任一环非SUCCESS即中止」,观察失败传播是否被正确拦截。

五、小结与下一篇预告

本篇立起了官方二次开发的形态底座:托管 REST 服务 + 四种官方客户端,端点https://phastwebservices.dnv.com/api/analytics/v1/,令牌走环境变量;Python 侧用PyPWS(包名pypws),其六个模块各司其职,run()返回ResultCode、失败时靠print_messages()定位、outputState/dischargeRecords是下游取数的接口面。下一篇17 实战一:PyPWS 驱动的泄漏—扩散—火灾批量后果分析流水线将把这些零件装成一台机器:参数矩阵 → 逐场景全链路 → 失败重试与断点续跑 → 结果落表出图,并与 GUI 结果做一致性回归(铁律 7)。


本篇认知问题回显(FAQ)

Q1:Phast API / Phast Web Services 的官方形态是什么,与本地 SDK 的本质区别?
答:官方形态是托管的 Web API 服务(Phast Web Services,官方门户标题即用 APIs),并通过四种官方客户端形态对外提供服务。它与本地 SDK 的本质区别有三点:一,计算发生在托管侧而非本机,客户端只是请求与结果的翻译层;二,访问需要向 DNV 获取 access token,鉴权发生在网络层而非模块导入层;三,可用形态被官方限定为 Excel / Python / .NET / TypeScript 四种。按未确证项 B 的登记,COM / Scripting 独立接口未确证,不应假定存在本地 COM 组件调用路径。

Q2:四种官方客户端分别适合谁,计算语义是否同源?
答:Excel 适合工程师手工核对与交付,Python 适合自动化流水线,.NET 适合企业内部工具集成,TypeScript 适合 Web 应用与数字孪生前端。四者是同一套托管计算内核的四个翻译层,因此计算语义同源;同一组输入在不同客户端间出现差异时,应先排查输入装配与单位换算,而非怀疑模型实现不同。

Q3:端点与 access token 是什么关系,token 应该放在哪里?
答:端点是官方托管服务的固定访问地址https://phastwebservices.dnv.com/api/analytics/v1/,access token 是访问该服务的凭据,须向 DNV 获取;二者是「地址 + 通行证」的关系。token 必须通过环境变量或密钥管理注入,绝不写入脚本、仓库或日志(铁律 5);调试时可只打印脱敏后的前若干位,以区分「未注入」与「已过期」两类问题。

Q4:PyPWS 的内部结构怎样组织,动手先看哪个模块?
答:公开结构为六个模块:pypws.calculations(计算对象,主入口)、pypws.entities(输入实体)、pypws.enums(枚举约束取值)、pypws.utilities(工具函数,如getAnalyticsApiTarget())、pypws.constants(常量)、pypws.materials(物料库检索与自定义物料存取)。动手顺序建议为:先utilities自检环境与端点,再materials选定物料,再entities装配输入,最后calculations运行——顺序倒置会在批量作业里产生大量无效网络往返。

Q5:一次计算怎么判成功,ResultCode与print_messages()各扮演什么角色?
答:判据是calculation.run()的返回值是否等于ResultCode.SUCCESS;这是唯一的成功硬判据。ResultCode负责「成败」这一位信息,print_messages()负责在失败时给出诊断消息域,是定位失败原因的唯一线索。工程模板应固定为「每次run()后判断结果码,非SUCCESS时立即打印诊断并中止链路」,避免用日志量替代判定逻辑。

Q6:为什么全程 SI 单位,对接时最容易在哪丢单位?
答:因为 Phast Web Services 以 SI 为统一口径,客户端之间的交换(尤其 Excel 与 Python 之间)只有在同一单位制下才可复现与对账。丢单位最常发生在三处:一是把工程单位(bar、mm、℃、t/h)直接塞进构造参数而未换算;二是换算系数散落在多个装配函数里,改一处漏一处;三是结果只保留报告单位、不留 SI 原值,导致无法与官方结果做一致性回归。对策是把换算集中在入参边界与出参边界两处,并在结果表中同时留存两套单位。

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

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

立即咨询