更新v2版本适配skill
This commit is contained in:
1 parent
7611f13208
commit
2f3f7d00ec
58 files changed
+180419
-526
No files matched your search
@@ -1,40 +1,38 @@
|
||||
---
|
||||
name: system-simulation
|
||||
description: 读取、校验并简要解释 SystemSimulationApp 工程 JSON v1 或 System XML v3,安全规范化文件文本,并在用户选定结果曲线后启动、监视或取消仿真及导出 CSV。适用于检查模型文件、修复编码或换行、运行仿真和获取结果;不用于旧格式迁移、任意语义修复、自动迭代或网页自动预装。
|
||||
description: 根据描述在 SystemSimulationApp 中搭建系统、配置参数,生成和校验工程 JSON v2,并通过网页导入或界面操作验收;也支持检查已有 JSON v1/v2、System XML v3,运行和监视仿真、导出曲线与 CSV。
|
||||
---
|
||||
|
||||
# 系统仿真
|
||||
# 系统仿真建模与文件助手
|
||||
|
||||
使用本 Skill 随附的确定性脚本检查模型、调用现有后端并保存结果;不要让语言模型自行重写模型或猜测求解数据。
|
||||
把用户描述转成可编辑、可校验的工程。组件、参数、版本和物理合同取自当前仓库/服务;用随附脚本执行确定性生成和校验。自然语言解释由智能体完成,脚本接收明确的建模清单,不自行推测系统。
|
||||
|
||||
## 基本边界
|
||||
## 选择路径
|
||||
|
||||
- 仅处理 ReactFlow 工程 JSON v1 和 System XML v3。版本缺失、不受支持或模型版本不匹配时,说明问题并停止,不进行迁移猜测。
|
||||
- 组件参数是仿真前设定的固定输入;结果变量才是可随时间绘制的量。不要把“参数”当成结果曲线。
|
||||
- 文件通过格式校验不等于物理系统一定可求解。不要隐瞒编译或运行阶段的诊断。
|
||||
- 不直接覆盖源文件,不自行修改参数、连接、组件类型、模型版本或求解设置。
|
||||
- 本版不支持把模型自动注入网页、生成可直接打开的预装页面、任意损坏文件修复、模型迁移或自动调参迭代。明确告知用户这些能力尚未实现,不要用手工网页操作冒充支持。
|
||||
- **描述生成工程、修改拓扑或参数**:读 [建模与验收](references/modeling.md) 和 [文件合同](references/file-contracts.md)。默认生成 JSON v2,再导入网页检查、导出 XML、进行短时仿真和导出回读核对。用户只要求文件时交付文件及实际完成的校验,不擅自扩成完整时长仿真。
|
||||
- **明确要求在界面模拟操作搭建**:另读 [网页操作](references/browser-workflow.md),通过可用浏览器工具完成添加、连接、参数设置和导出;先读工具操作说明。不要将写文件或调用 API 描述成界面操作。
|
||||
- **检查已有文件、运行和导出结果**:读 [文件合同](references/file-contracts.md) 和 [运行工作流](references/workflows.md)。
|
||||
- **仅修复编码、BOM、换行**:读 [文本规范化](references/repair-policy.md)。其限制只适用于 repair-format,不限制用户已授权的建模/参数修改。
|
||||
|
||||
处理文件、解释格式或选择结果变量时,读取 [references/file-contracts.md](references/file-contracts.md)。请求文件修复时,再读取 [references/repair-policy.md](references/repair-policy.md)。需要运行、监视、取消仿真或交付结果时,读取 [references/workflows.md](references/workflows.md)。
|
||||
## 核心约束
|
||||
|
||||
## 工作原则
|
||||
1. 新工程写 projectSchemaVersion: 2;兼容读取 v1,XML 执行格式为 v3。禁止只改 v1 版本号来迁移。v2 数值和表达式均按所选单位解释,由平台转为 SI。
|
||||
2. 从当前目录选型号,明确实例 ID、真实端口、参数值/单位、介质和边界条件。对影响物理意义的歧义或缺失输入提问;布局可自行决定。使用目录默认值时披露,不能声称为用户给定值。
|
||||
3. 用户要求创建、配置或修改工程即授权相应工作,无须为每个组件、参数或可逆步骤重复确认。保留已有源文件,默认另存;修改只限所请求范围。不得为了“跑通”擅改物理系统、放宽求解条件或删除失败组件。
|
||||
4. 新节点使用目录当前版本。已有工程版本不同/缺失时,现行适配层会警告并按当前模型处理,XML/数值内核仍精确检查版本。报告警告,不擅自声称物理语义等价。
|
||||
5. 区分编辑器结构、活动端口连通、XML 语义、网络编译、浏览器实际导入、数值运行及工程回读。某一层通过不能代替后续层;网络编译不代表 C 编译或积分通过。
|
||||
6. 参数是固定输入;结果曲线使用真实结果变量 key。尊重用户已给定的曲线与展示方式,只有必要歧义才追问。
|
||||
7. 依据诊断纠正生成/操作错误后重跑受影响检查。遇到关键输入不足、缺失模型或不可用工具时说明具体缺口,交付已完成结果;不要无限改参数重试或伪造验收通过。
|
||||
|
||||
1. 先用 `inspect` 确认输入格式、版本、结构和诊断,再基于检查结果简要解释组件、连接与仿真设置。
|
||||
2. 如果用户要求修复,只能执行文本规范化。先展示预览和源文件 SHA-256,获得针对该预览的明确确认后,才可写入另一个输出路径;随后重新 `inspect`。
|
||||
3. 仿真前必须让用户选择直接曲线查看方式,并解析具体结果变量:
|
||||
- 分别查看所选变量;
|
||||
- 将多个同单位、可比较的变量叠加;
|
||||
- 将不同物理量或单位的变量上下排列。
|
||||
4. 用户用显示名称描述组件或变量时,利用检查结果中的稳定 ID、结果 `key`、物理量和单位消歧。存在重名、多个候选或“参数/结果变量”含义不清时,先询问,不能替用户猜。
|
||||
5. 使用 `simulate` 的事件流持续判断 queued、validating、compiling、integrating 和结束状态。仿真时间暂时不变但内部活动仍增长时,只说明正在处理慢步,不能宣称卡死。
|
||||
6. 成功运行后交付用户选择的 SVG 曲线和完整 `results.csv`,并简要说明完成状态、实际仿真终点和重要诊断。失败或取消时交付能够安全生成的部分结果;若运行前即失败而没有 CSV,要明确说明原因。
|
||||
## 执行入口
|
||||
|
||||
脚本命令统一从仓库根目录运行:
|
||||
在仓库根目录运行。Windows 优先 .venv-win\Scripts\python.exe -X utf8,否则 py -3.12 -X utf8;Linux 优先 .venv/bin/python,否则 python3.12。
|
||||
|
||||
```powershell
|
||||
py -3.12 skills/system-simulation/scripts/simulation_skill.py --help
|
||||
.\.venv-win\Scripts\python.exe -X utf8 skills/system-simulation/scripts/project_builder.py --help
|
||||
.\.venv-win\Scripts\python.exe -X utf8 skills/system-simulation/scripts/simulation_skill.py --help
|
||||
```
|
||||
|
||||
Windows 优先使用仓库 `.venv-win\Scripts\python.exe`(若存在),否则使用 `py -3.12`;Linux 优先使用 `.venv/bin/python`,否则使用 `python3.12`。不要调用未经版本确认的 `python`,本项目要求 Python 3.12。
|
||||
project_builder.py 复用本仓库 Python 依赖、注册表、参数适配及编译器,无需启动服务。simulation_skill.py 调用 loopback 后端,默认 http://127.0.0.1:8000。开发网页通常为 http://127.0.0.1:5173,以实际状态为准。按需参考仓库 README.md 与 docs/standard/platform-dependencies.md;后台启动使用隐藏窗口,不重启用户正在使用的服务来掩盖版本不一致。
|
||||
|
||||
优先依赖脚本返回的结构化 JSON/JSONL、稳定错误码和退出码做判断,不解析中文提示文本来驱动下一步。
|
||||
交付工程文件链接、采用的默认值/待确认输入、各层检查结果和未完成原因。运行过仿真时附实际终点、曲线及完整 CSV;部分结果明确标注。浏览器或原生工具链不可用时,不能把静态校验称为完整验收。
|
||||
@@ -1,7 +1,7 @@
|
||||
interface:
|
||||
display_name: "系统仿真文件助手"
|
||||
short_description: "读取与校验模型文件,监视仿真并导出结果曲线和 CSV 文件"
|
||||
default_prompt: "使用 $system-simulation 检查我的模型文件,并在我选定结果曲线后运行和监视仿真。"
|
||||
display_name: "系统仿真建模助手"
|
||||
short_description: "根据描述搭建与配置仿真系统,生成工程 JSON,校验并运行仿真"
|
||||
default_prompt: "使用 $system-simulation 根据我的系统描述生成工程 JSON,并完成网页导入检查、短时仿真和导出核对。"
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "closed-tank",
|
||||
"simulation": {"t_start": 0, "t_stop": 0.02, "step": 0.01, "max_step": 0.001, "method": "BDF"},
|
||||
"components": [
|
||||
{"id": "air", "modelType": "amesim_ideal_air_medium", "parameters": {"gi": 1}, "position": {"x": 0, "y": 220}},
|
||||
{"id": "tank", "modelType": "amesim_pnch023", "parameters": {"gi": 1, "p0": 2.5, "T0": 20}, "parameterUnits": {"p0": "bar", "T0": "degC"}, "position": {"x": 300, "y": 0}},
|
||||
{"id": "left_cap", "modelType": "amesim_pnpl01", "position": {"x": 0, "y": 0}, "rotation": 180},
|
||||
{"id": "right_cap", "modelType": "amesim_pnpl01", "position": {"x": 600, "y": 0}}
|
||||
],
|
||||
"connections": [
|
||||
{"id": "left", "source": {"component": "left_cap", "port": "port_1"}, "target": {"component": "tank", "port": "port_1"}},
|
||||
{"id": "right", "source": {"component": "tank", "port": "port_2"}, "target": {"component": "right_cap", "port": "port_1"}}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# 网页搭建、导入与验收
|
||||
|
||||
使用会话实际提供的浏览器工具,先读其操作文档;需要原生 Windows 交互时才使用已提供的 Computer Use 技能。不要假设原生桌面或某个浏览器工具必定可用。无界面工具仍可生成/检查文件,但网页验收标记未执行。
|
||||
|
||||
## 准备
|
||||
|
||||
- 按用户指定浏览器/标签页选择目标。开发前端通常为 127.0.0.1:5173,后端为 127.0.0.1:8000,以实际启动状态为准。
|
||||
- 观察当前工程、组件库状态和未保存修改。使用隔离浏览器上下文,或先导出当前工程备份再新建/导入;同源新标签页共享 localStorage,不等于隔离。
|
||||
- 以下控件名称来自当前代码,只作定位线索。操作以当下可访问树/截图为准,页面变化后重新观察,不能凭旧坐标点击。
|
||||
|
||||
## 文件导入
|
||||
|
||||
1. 用“导入工程 JSON”及工具支持的文件选择能力选中生成文件。不写 localStorage、React 内部状态或隐藏接口冒充 UI 导入。
|
||||
2. 检查工程名、画布和控制台;确认没有丢弃连线、未知组件、版本/参数错误。文件选择器关闭不代表成功。
|
||||
3. 按实例核对关键参数及单位。单位切换可能联动换算数值,以最终显示与导出 SI 值为准;多实例通过标签或稳定 ID 消歧。
|
||||
4. 点击“检查模型”,展开控制台看诊断,再“生成系统 XML”核对输入。需要运行时点击“运行仿真”并观察阶段和终态;可用后端状态辅助监视,但 API 成功不能代替 UI 步骤记录。
|
||||
5. 点击“导出工程 JSON”,取得实际下载文件后运行 project_builder.py compare。
|
||||
|
||||
## 界面模拟操作搭建
|
||||
|
||||
按建模清单操作,不把直接生成 JSON 描述成拖拽搭建:
|
||||
|
||||
1. 从组件库拖入目标型号,确认新增节点;必要时用“组件名称”设置易区分标签。
|
||||
2. 先配介质定义与引用、影响端口数量的参数,再确认活动端口。LMECHN1 先设 N,再连接 port_1..N 和 port_(N+1) 参考口。
|
||||
3. 参数表按真实标签填写,并设置对应单位;每次改变单位后复核最终数值。离散项用选项控件。参数隐藏时先核对控制条件,不盲输不存在的控件。
|
||||
4. 用观察到的端口完成拖拽/连接。当前端口可访问标签形如“组件标签 端口 port_1”;连线后检查端点实际存在,不能把交叉线或贴近布局当成连接。
|
||||
5. 调整布局和仿真配置,检查模型、导出、短时仿真及回读核对。
|
||||
|
||||
拖拽、缩放、滚动后重新观察。记录完成的组件和连接,失败后从实际状态恢复,避免重复添加。
|
||||
|
||||
## 验收
|
||||
|
||||
- 组件类型/ID/数量、连接端点与清单相符,无静默丢线。
|
||||
- 关键参数、SI 值、介质引用和边界条件符合描述;目录默认项已披露。
|
||||
- 网页检查无阻断错误,警告如实报告。
|
||||
- 要求仿真时取得真实终态和终点;“已开始”不是成功。
|
||||
- 实际下载 JSON 通过 check,compare 证明执行输入等价;布局另行确认。
|
||||
|
||||
此流程通过实际导入或界面操作完成,不承诺仅凭 URL 自动预装模型。若当前工具无法文件选择、下载或操作控件,交付文件并说明具体未验收步骤。
|
||||
@@ -1,95 +1,67 @@
|
||||
# 文件合同与解释规则
|
||||
# 文件合同与当前代码依据
|
||||
|
||||
## 支持范围
|
||||
## 格式与单位
|
||||
|
||||
本 Skill 只接受以下两种当前格式:
|
||||
| 格式 | 当前合同 |
|
||||
| --- | --- |
|
||||
| 工程 JSON v2 | 新建/导出的编辑工程;数值、十进制数字字符串、表达式全部按 parameterUnits[name] 解释,省略单位使用目录 SI 单位 |
|
||||
| 工程 JSON v1 | 兼容读取;普通数值/十进制数字字符串为 SI,表达式按显示单位解释 |
|
||||
| System XML v3 | 根元素 schemaVersion="3" unitSystem="SI",只保存执行模型 |
|
||||
|
||||
| 格式 | 版本标志 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| ReactFlow 工程 JSON | 顶层 `projectSchemaVersion: 1` | 保存组件、画布、端口显示快照、连线和仿真设置,适合继续编辑 |
|
||||
| System XML | 根元素 `System/@schemaVersion="3"` 且 `unitSystem="SI"` | 保存可执行模型,适合校验、编译和求解 |
|
||||
v2 的 p0: 2.5、"2.5"、"=2.5" 配 bar 都是 250000 Pa;20 degC 为 293.15 K。压力为绝压,不能自动加减大气压。缺失参数使用目录 SI 默认值,不随显示单位二次换算。
|
||||
|
||||
默认让 `inspect --format auto` 根据内容和扩展名识别格式。若内容与扩展名不一致、无法唯一识别或用户明确指定格式,则报告实际证据,不悄悄按另一种格式解释。
|
||||
v1→v2 由前端 encodeProjectParameters() 等现有转换逻辑完成,不得只改版本号。内部归一化的 v1/SI 对象不是 v2 导出文件。未知型号、参数、单位不能靠改版本掩盖。
|
||||
|
||||
System XML v1/v2、缺少 `projectSchemaVersion` 的旧工程、字符串端口和不匹配的组件 `modelVersion` 均不属于本 Skill 的迁移范围。不能只改版本号使其看似当前格式。
|
||||
|
||||
## 工程 JSON v1
|
||||
|
||||
顶层合同为:
|
||||
## 编辑工程字段
|
||||
|
||||
```text
|
||||
projectSchemaVersion = 1
|
||||
name
|
||||
nodes[]
|
||||
edges[]
|
||||
simulation { t_start, t_stop, step, max_step, method }
|
||||
projectSchemaVersion: 2
|
||||
name: string
|
||||
nodes[]:
|
||||
id: 唯一稳定 ID
|
||||
type: "simulationComponent"
|
||||
position: { x: 有限数值, y: 有限数值 }
|
||||
data:
|
||||
label, componentType, modelType, modelVersion
|
||||
ports[]: { name, kind, domain, nominalRole, side, positiveFlowDirection? }
|
||||
parameters: { 参数名: 数值或表达式字符串 }
|
||||
parameterUnits: { 参数名: 单位 }
|
||||
parameterScientificNotation: {}
|
||||
rotation: 0 | 90 | 180 | 270
|
||||
mirrored: boolean
|
||||
edges[]:
|
||||
id, source, target, sourceHandle, targetHandle
|
||||
data: { isContactEdge: boolean, routePoints?: [{x,y}] }
|
||||
simulation: { t_start, t_stop, step, max_step, method }
|
||||
```
|
||||
|
||||
重要规则:
|
||||
- 顶层禁止未知字段,假设与验收记录放独立报告。componentType/modelType 均用真实注册类型,显示标签不是模型标识。
|
||||
- Handle 用真实端口名,不按画面编号猜测。普通连线 isContactEdge=false,接触连线由网页几何决定。
|
||||
- 目录端口不能原样复制:信号端口 positiveFlowDirection:null 必须省略。生成器只复制必要快照字段;物理合同始终来自注册表。
|
||||
- LMECHN1 保存完整 21 个注册端口快照;v1=N 决定 N 个右端口和 port_(N+1) 左参考端口。连通性只统计注册模型 active_ports() 返回的活动端口。
|
||||
- 网页要求每个活动显示端口恰好连接一次,不能用后端对某些未接信号口的容忍替代网页检查,不能删快照端口绕过。
|
||||
- 介质节点属于工程但不一定进入动态求解。gi=0 为内置空气;1..99 引用唯一介质定义。多介质不能重复套用默认 gi=1。
|
||||
- 离散参数按目录 editor/options 数值编码,部分开关为 1/2,不能推定为 0/1。
|
||||
|
||||
- 节点的 `id` 是实例稳定标识;显示标签不能替代它。
|
||||
- `data.modelType` 标识注册模型,`data.modelVersion` 必须与当前组件目录精确匹配,执行前不得自动补成当前版本。
|
||||
- `data.parameters` 保存输入值;`parameterUnits`、科学计数法偏好、坐标、旋转和镜像属于编辑显示信息。
|
||||
- 连接必须保留两端组件及 Handle。不能根据节点位置猜测缺失端口。
|
||||
- `simulation.step` 是结果采样间隔;`max_step` 是求解器内部步长上限,两者不能混用。
|
||||
## 执行与结果
|
||||
|
||||
工程 JSON 可以导出为 System XML v3,但转换后不会保留全部画布显示信息的对等逆转换合同。
|
||||
step 是结果采样间隔,max_step 是内部最大步长。当前执行方法为 RK45/BDF;四个时间字段支持平台表达式。
|
||||
|
||||
## System XML v3
|
||||
JSON 适配层对不同/缺失组件版本警告并选择当前模型;类型、端口、未知参数和数值错误仍拒绝。LMECHN1/FORC 的显式兼容处理由平台完成,不能推广为任意迁移。新工程始终取当前版本。
|
||||
|
||||
XML v3 只描述“求解什么”:
|
||||
XML 的组件需要精确 modelVersion 和完整 SI 参数,连接为两个 Endpoint(component,port)。JSON 导出器补齐默认参数,XML 解析器本身不猜默认值。XML parse 返回执行模型,不能直接作为可无损恢复布局的工程。
|
||||
|
||||
- 每个 `Component` 必须有唯一 `id`、注册 `type`、精确 `modelVersion` 和完整 SI 参数;
|
||||
- 每条连接由两个 `Endpoint(component, port)` 组成;端口类型、方向和物理合同由后端注册表恢复;
|
||||
- `Simulation/@sampleStep` 对应工程 JSON 的 `simulation.step`;
|
||||
- 不保存组件位置、旋转、镜像、显示单位或端口显示快照;
|
||||
- 当前后端固定按 v3 校验,不会根据文件内容选择旧解析器。
|
||||
simulation_skill.py inspect 返回源哈希、版本、组件/连接/变量分页、诊断与未接端口;不是网页导入验收。默认页大小分别为 50/25/20,用 --component ID、--variable-query QUERY 和 offset/limit 定位。compiledForSimulation=false 的配置节点仍属于工程。
|
||||
|
||||
XML 校验依次覆盖安全/语法、XSD 和语义层。通过这些检查后,编译和求解仍可能发现未连接端口、缺少储能锚点、方程结构或数值问题。
|
||||
结果曲线按 key + componentId + quantity + unit 选择。separate 分图;overlay 仅叠加同单位且可比较的量;stacked 用于不同量。不能把固定参数当时间序列。
|
||||
|
||||
## 简要解释模型
|
||||
## 权威来源(相对仓库根目录)
|
||||
|
||||
解释必须依据 `inspect` 的结构化输出以及组件目录,而不是仅凭组件名称推测。优先说明:
|
||||
- app/main.py:ReactFlowProjectPayload、JSON→XML、编译及 HTTP 接口。
|
||||
- app/project_parameters.py、frontend/src/projectCompatibility.ts:版本、单位、表达式与兼容转换。
|
||||
- app/simulation/registry.py、GET /api/components/catalog:型号、参数、端口、版本。
|
||||
- frontend/src/App.tsx:parseProjectPayload、displayedPortsForNodeData、导入导出与网页检查。
|
||||
- schemas/parameter-units.json、schemas/system-simulation-v3.xsd。
|
||||
- docs/standard/backend-interface-version-spec-v1.md、component-library-spec-v1.md、port-computation-contract.md、system-xml-v3.md。
|
||||
|
||||
1. 文件格式、版本和项目名;
|
||||
2. 仿真起止时间、采样间隔、最大内部步长和算法;
|
||||
3. 组件数量、稳定 ID、模型类型和主要输入参数;
|
||||
4. 连接数量、连接端点及能够确定的物理域;
|
||||
5. 错误、警告,以及它们属于格式、语义、编译还是运行阶段。
|
||||
|
||||
保持“文件合同正确”和“物理模型合理”两个结论分开。没有组件文档或注册元数据支持时,不声称某个参数具有推测出的物理效果。
|
||||
|
||||
## 参数与结果变量
|
||||
|
||||
必须明确区分:
|
||||
|
||||
- **参数**:仿真开始前设定的固定输入,例如质量、初始压力、摩擦选项;通常没有时间序列。
|
||||
- **结果变量**:仿真返回的时间序列,例如位移、速度、压力或流量;只有这类量可以选作曲线。
|
||||
|
||||
选择曲线时以结果元数据为准,至少核对:
|
||||
|
||||
```text
|
||||
key + componentId + componentType + label/quantity + unit
|
||||
```
|
||||
|
||||
稳定 `key` 是传给 `simulate --variables` 的最终标识。用户只说“质量块的速度”而存在多个质量块,或一个组件存在多个符合描述的速度结果时,列出候选的组件 ID、结果名称和单位,请用户消歧。
|
||||
|
||||
`inspect` 默认对组件摘要、连接和结果变量分页。先读取 `componentTypes` 了解完整模型的组件类型分布,再根据 `componentPage`、`connectionPage` 或 `resultVariablePage` 的 `nextOffset` 翻页。优先使用 `--variable-query` 按组件 ID、标签、物理量或单位缩小范围;只有用户点名组件时才使用 `--component` 读取该组件的完整源数据和可用的编译合同。
|
||||
|
||||
组件摘要中的 `compiledForSimulation` 表示该节点是否进入动态求解网络。介质/物性配置节点仍属于工程,因此会保留在组件总数和列表中,但通常标记为 `false`;这不表示组件丢失或编译失败。
|
||||
|
||||
曲线模式约束:
|
||||
|
||||
- `separate`:每个所选结果变量分别成图;
|
||||
- `overlay`:只叠加单位相同且含义可比较的结果变量;
|
||||
- `stacked`:不同物理量或不同单位上下排列,避免共用一个纵轴造成误读。
|
||||
|
||||
本版运行 `simulate` 时必须指定至少一个 `--variables` 稳定键,避免在大型模型上无意生成成百上千张曲线。完整 CSV 仍包含全部可用结果变量。
|
||||
|
||||
## 权威来源
|
||||
|
||||
- 工程 JSON 请求合同:`app/main.py` 中的 `ReactFlowProjectPayload`
|
||||
- 组件目录:`GET /api/components/catalog`
|
||||
- XML v3:`schemas/system-simulation-v3.xsd`、`docs/standard/system-xml-v3.md`
|
||||
- 接口边界:`docs/standard/backend-interface-version-spec-v1.md`
|
||||
- 结果变量:组件注册合同中的 `RESULT_VARIABLES` 及仿真结果元数据
|
||||
文档文件名 v1 不是工程版本。代码变更与说明不一致时核对解析器、注册表与测试。
|
||||
@@ -0,0 +1,65 @@
|
||||
# 描述到工程与分层验收
|
||||
|
||||
## 建模清单
|
||||
|
||||
从描述提取系统目的、组件型号/实例 ID、真实端口拓扑、介质和边界条件、初始状态、参数值与单位、仿真配置、期望结果变量。关键型号或压力口径不明确时询问;布局可自行决定。不要要求用户先手写 JSON。
|
||||
|
||||
下文 PYTHON 代表入口说明中的 Python 3.12 命令。
|
||||
|
||||
```text
|
||||
PYTHON skills/system-simulation/scripts/project_builder.py catalog --query 气室
|
||||
PYTHON skills/system-simulation/scripts/project_builder.py catalog --model amesim_pnch023
|
||||
```
|
||||
|
||||
默认返回分页摘要,--model 可重复并返回完整模型目录;--offset/--limit 翻页。目录来自本地 checkout,过滤网页隐藏的 experimental 库。与服务配合前核对 GET /api/components/catalog 中所用型号、版本、参数和端口是否一致;不能混用不同代码版本。
|
||||
|
||||
智能体将需求写为任务输出目录中的 plan.json(中间格式,不是平台工程)。完整接线的示例见 [closed-tank.plan.json](../assets/closed-tank.plan.json),只代表一个封闭气罐:
|
||||
|
||||
```text
|
||||
name: 工程名
|
||||
simulation: { t_start, t_stop, step, max_step, method }
|
||||
components[]:
|
||||
id, modelType
|
||||
parameters?: { 参数键: 数值或表达式字符串 }
|
||||
parameterUnits?: { 参数键: 单位 }
|
||||
label?, position?: {x,y}, rotation?, mirrored?
|
||||
connections[]:
|
||||
id
|
||||
source: { component: 实例ID, port: 真实端口名 }
|
||||
target: { component: 实例ID, port: 真实端口名 }
|
||||
```
|
||||
|
||||
参数用目录真实键名;省略项用目录 SI 默认值,报告 defaultsUsed 列出。设置 parameterUnits 时必须同时提供该参数值,防止将默认 SI 数值当所选单位。仿真五字段必须明确提供。布局省略时采用四列网格。
|
||||
|
||||
## 生成与本地检查
|
||||
|
||||
```text
|
||||
PYTHON skills/system-simulation/scripts/project_builder.py build OUTPUT/plan.json --output OUTPUT/project.json
|
||||
PYTHON skills/system-simulation/scripts/project_builder.py check OUTPUT/project.json --xml-output OUTPUT/project.xml
|
||||
```
|
||||
|
||||
build 从目录构造节点及当前版本、精简端口快照、完整参数/单位,完成 check 相同的校验后才写文件。它不猜拓扑、不自动添加堵头/介质来掩盖缺失需求。未知字段/参数/单位/端口、重复 ID、未接或多接活动端口、语义错误都会失败;输出只写新路径。
|
||||
|
||||
check 支持 v1/v2,复用平台代码执行编辑器结构预检、单位/版本适配、XML v3 语义、活动端口连通和网络编译,不运行积分器。报告 browserImport:notRun、simulation:notRun 不能冒充通过。networkCompile 也不代表 C 编译成功。
|
||||
|
||||
修改已有工程时保留未涉及的 ID、布局、参数、拓扑与元数据,另存后 check;不要为使用 build 而将整个工程重建。旧版迁移优先网页导入再导出。
|
||||
|
||||
## 网页与运行验收
|
||||
|
||||
1. 按 [网页操作](browser-workflow.md) 导入文件,检查组件/连接、数值/单位、控制台诊断,记录实际导入结果。
|
||||
2. 网页“生成系统 XML”或 check --xml-output 核对执行输入;服务目录与生成目录应一致。
|
||||
3. 完整建模验收包含短时冒烟仿真;用户只要求文件时可交付文件及未运行说明。原时段很长时复制为独立 smoke.json,只在副本里设置明确记录的短终点,覆盖关心的事件并包含至少两个采样点。保留原工程,不能为跑通而改变参数、拓扑、容差或算法。
|
||||
4. inspect smoke.json 获取结果键,再 simulate 或网页运行。检查终态、实际终点、有限结果与关键物理预期。短时通过不代表完整工况/精度通过。曲线方式未指定可选 separate 并告知,不必阻断验收。
|
||||
5. 网页导出原工程为 roundtrip.json,比较规范化 SI 输入:
|
||||
|
||||
```text
|
||||
PYTHON skills/system-simulation/scripts/project_builder.py compare OUTPUT/project.json OUTPUT/roundtrip.json
|
||||
```
|
||||
|
||||
compare 重新检查两个文件,比较组件 ID/类型/当前版本/完整 SI 参数、连接 ID/端点及仿真配置,忽略布局、标签和连线两端书写顺序。它不比较仿真结果、不证明布局相同;布局另行目视核对。不要把 smoke 副本与原工程直接比较。
|
||||
|
||||
## 交付与边界
|
||||
|
||||
交付需求摘要、建模清单、工程 JSON、默认值清单与分层检查结果;不在工程顶层加验收字段。CLI 成功只证明报告中 passed 的层。网页或数值工具缺失时明确 notRun,保留已完成成果。
|
||||
|
||||
用户已授权范围内的生成/操作错误可直接纠正。需要改变物理需求、猜关键输入或新增模型时询问。相同错误在针对性修正后仍出现,应分析注册表/合同,不做无依据的参数搜索。
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 目的
|
||||
|
||||
`repair-format` 只解决可解析 JSON v1 或 XML v3 的文本层问题,使文件采用稳定的 UTF-8 和跨平台文本格式。它不是模型迁移器,也不是语义修复器。
|
||||
`repair-format` 只解决可解析 JSON v1/v2 或 XML v3 的文本层问题,使文件采用稳定的 UTF-8 和跨平台文本格式。它不是模型迁移器,也不是语义修复器。以下限制只用于此命令;用户请求的拓扑/参数修改按 [建模流程](modeling.md) 处理。
|
||||
|
||||
## 允许的修改
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
## 禁止的修改
|
||||
|
||||
本版不得自动执行下列动作:
|
||||
`repair-format` 不得执行下列动作:
|
||||
|
||||
- 新增、删除、更换或重命名组件和连接;
|
||||
- 修改组件 ID、类型、端口、`modelVersion` 或 Schema 版本;
|
||||
|
||||
@@ -75,11 +75,11 @@ token 同时绑定源文件哈希、规范化输出哈希和目标绝对路径
|
||||
运行前必须完成以下判断:
|
||||
|
||||
1. `inspect` 通过,并取得可用结果变量清单。
|
||||
2. 用户选择 `separate`、`overlay` 或 `stacked`。
|
||||
2. 使用用户已选择的 `separate`、`overlay` 或 `stacked`;未指定展示方式时可采用 `separate` 并告知。仅当选择影响用户意图时询问。
|
||||
3. 把自然语言对象解析为稳定结果 `key`;重名、缺单位或把输入参数误称为曲线时先澄清。
|
||||
4. 向用户复述将运行的文件、仿真时段、算法、所选结果变量和曲线方式。
|
||||
|
||||
本版没有网页自动预装能力。用户要求“网页查看”时,说明当前只能直接交付 SVG 曲线与 CSV;不要启动浏览器、生成临时 URL,或声称现有页面会自动载入文件。
|
||||
用户要求网页查看或搭建时,按 [网页操作](browser-workflow.md) 通过实际导入/操作完成。没有浏览器工具则交付文件与曲线并明确网页步骤未执行;不声称仅凭 URL 可自动载入。
|
||||
|
||||
## 启动并监视仿真
|
||||
|
||||
@@ -139,4 +139,4 @@ py -3.12 skills/system-simulation/scripts/simulation_skill.py cancel SIMULATION_
|
||||
5. 若没有产生可用时间序列,明确说明没有 CSV,不能创建空文件冒充结果;
|
||||
6. 保留 `result.json` 和 `progress.jsonl` 作为诊断依据,但通常无需把完整事件日志逐行展示给用户。
|
||||
|
||||
本版不会根据结果自动改变模型并重试。诊断后若要改参数、拓扑或算法,先把建议交给用户,等待后续迭代能力或单独授权的人工修改流程。
|
||||
可在用户已授权的建模/配置范围内纠正生成与操作错误,重跑受影响检查。不能为了成功擅改物理拓扑、边界或算法;需要改变需求时说明并询问。短时验收使用独立副本,详见 [建模与验收](modeling.md)。
|
||||
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build editor projects from explicit plans using this checkout's real contracts.
|
||||
|
||||
Natural-language interpretation belongs to the skill. This helper never infers
|
||||
components, connections or physical inputs. No running HTTP service is required.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections import Counter
|
||||
from copy import deepcopy
|
||||
import math
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[3]
|
||||
sys.path.insert(0, str(REPOSITORY_ROOT))
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
from simulation_skill import ( # noqa: E402
|
||||
InputError, SkillCliError, _canonical_json, _ensure_distinct_new_output,
|
||||
_sha256, _write_new_file, configure_standard_streams, emit_json,
|
||||
load_source, nonnegative_integer, page_limit, stable_error_payload,
|
||||
)
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise InputError("PROJECT_CONTRACT_INVALID", message)
|
||||
|
||||
|
||||
def record(value, allowed, required, path):
|
||||
require(isinstance(value, dict), f"{path} must be an object.")
|
||||
require(not set(value) - set(allowed), f"{path} has unknown fields: {sorted(set(value) - set(allowed))}.")
|
||||
require(set(required) <= set(value), f"{path} is missing fields: {sorted(set(required) - set(value))}.")
|
||||
return value
|
||||
|
||||
|
||||
def text_value(value):
|
||||
return isinstance(value, str) and bool(value.strip())
|
||||
|
||||
|
||||
def finite_number(value):
|
||||
try:
|
||||
return type(value) in (int, float) and math.isfinite(value)
|
||||
except OverflowError:
|
||||
return False
|
||||
|
||||
|
||||
def parameter_value(value):
|
||||
return isinstance(value, str) or finite_number(value)
|
||||
|
||||
|
||||
def catalog_models():
|
||||
from app.main import get_component_catalog
|
||||
catalog = get_component_catalog()
|
||||
require(catalog.get("schemaVersion") == 1, "Unsupported component catalog schema.")
|
||||
# The actual editor excludes the experimental test library.
|
||||
return [component for library in catalog["libraries"] if library["id"] != "experimental"
|
||||
for component in library["components"]]
|
||||
|
||||
|
||||
def build_project(plan):
|
||||
plan = deepcopy(plan)
|
||||
record(plan, {"name", "components", "connections", "simulation"},
|
||||
{"name", "components", "connections", "simulation"}, "plan")
|
||||
require(text_value(plan["name"]), "plan.name must be nonempty.")
|
||||
require(isinstance(plan["components"], list) and plan["components"], "plan.components must be a nonempty array.")
|
||||
require(isinstance(plan["connections"], list), "plan.connections must be an array.")
|
||||
catalog = {model["modelType"]: model for model in catalog_models()}
|
||||
nodes, defaults = [], {}
|
||||
for index, item in enumerate(plan["components"]):
|
||||
location = f"components[{index}]"
|
||||
record(item, {"id", "modelType", "label", "parameters", "parameterUnits", "position", "rotation", "mirrored"},
|
||||
{"id", "modelType"}, location)
|
||||
require(text_value(item["id"]), f"{location}.id must be nonempty.")
|
||||
require(isinstance(item["modelType"], str) and item["modelType"] in catalog,
|
||||
f"{location}.modelType is not in the editor catalog: {item['modelType']}.")
|
||||
model = catalog[item["modelType"]]
|
||||
definitions = {p["name"]: p for p in model["parameters"]}
|
||||
overrides = record(item.get("parameters", {}), definitions, (), f"{location}.parameters")
|
||||
units = record(item.get("parameterUnits", {}), definitions, (), f"{location}.parameterUnits")
|
||||
require(set(units) <= set(overrides), f"{location}: a selected unit requires an explicit parameter value.")
|
||||
require(all(parameter_value(v) for v in overrides.values()), f"{location}: parameters must be finite numbers or strings.")
|
||||
require(all(isinstance(v, str) for v in units.values()), f"{location}: units must be strings.")
|
||||
ports = []
|
||||
for port in model["ports"]:
|
||||
snapshot = {key: port[key] for key in ("name", "kind", "domain", "nominalRole", "side")}
|
||||
if port.get("positiveFlowDirection") is not None:
|
||||
snapshot["positiveFlowDirection"] = port["positiveFlowDirection"]
|
||||
ports.append(snapshot)
|
||||
nodes.append({
|
||||
"id": item["id"], "type": "simulationComponent",
|
||||
"position": item.get("position", {"x": (index % 4) * 300, "y": (index // 4) * 220}),
|
||||
"data": {
|
||||
"label": item.get("label", item["id"]),
|
||||
"componentType": model["type"], "modelType": model["modelType"],
|
||||
"modelVersion": model["modelVersion"], "ports": ports,
|
||||
"parameters": {**{key: p["default"] for key, p in definitions.items()}, **overrides},
|
||||
"parameterUnits": {**{key: p["unit"] for key, p in definitions.items()}, **units},
|
||||
"parameterScientificNotation": {}, "rotation": item.get("rotation", 0),
|
||||
"mirrored": item.get("mirrored", False),
|
||||
},
|
||||
})
|
||||
defaults[item["id"]] = sorted(set(definitions) - set(overrides))
|
||||
edges = []
|
||||
for index, item in enumerate(plan["connections"]):
|
||||
location = f"connections[{index}]"
|
||||
record(item, {"id", "source", "target"}, {"id", "source", "target"}, location)
|
||||
for side in ("source", "target"):
|
||||
record(item[side], {"component", "port"}, {"component", "port"}, f"{location}.{side}")
|
||||
edges.append({"id": item["id"], "source": item["source"]["component"],
|
||||
"sourceHandle": item["source"]["port"], "target": item["target"]["component"],
|
||||
"targetHandle": item["target"]["port"], "data": {"isContactEdge": False}})
|
||||
project = {"projectSchemaVersion": 2, "name": plan["name"], "nodes": nodes,
|
||||
"edges": edges, "simulation": plan["simulation"]}
|
||||
validate_editor_shape(project)
|
||||
return project, defaults
|
||||
|
||||
|
||||
def validate_editor_shape(project):
|
||||
"""Preflight the stricter editor shape; actual browser acceptance is separate."""
|
||||
record(project, {"projectSchemaVersion", "name", "nodes", "edges", "simulation"},
|
||||
{"projectSchemaVersion", "name", "nodes", "edges", "simulation"}, "project")
|
||||
require(type(project["projectSchemaVersion"]) is int and project["projectSchemaVersion"] in (1, 2),
|
||||
"Expected projectSchemaVersion 1 or 2.")
|
||||
require(isinstance(project["name"], str), "Project name must be a string.")
|
||||
require(isinstance(project["nodes"], list) and project["nodes"], "Project nodes must be nonempty.")
|
||||
require(isinstance(project["edges"], list), "Project edges must be an array.")
|
||||
simulation = record(project["simulation"], {"t_start", "t_stop", "step", "max_step", "method"},
|
||||
{"t_start", "t_stop", "step", "max_step", "method"}, "simulation")
|
||||
require(isinstance(simulation["method"], str) and simulation["method"] in ("RK45", "BDF"),
|
||||
"Only RK45 and BDF are executable.")
|
||||
require(all(parameter_value(simulation[k]) for k in ("t_start", "t_stop", "step", "max_step")),
|
||||
"Simulation inputs must be finite numbers or expressions.")
|
||||
node_ids, edge_ids = set(), set()
|
||||
for node in project["nodes"]:
|
||||
require(isinstance(node, dict), "Each node must be an object.")
|
||||
ident = node.get("id")
|
||||
require(text_value(ident) and ident not in node_ids, "Node IDs must be nonempty and unique.")
|
||||
node_ids.add(ident)
|
||||
require(node.get("type") == "simulationComponent", f"{ident}: invalid editor node type.")
|
||||
position = node.get("position")
|
||||
require(isinstance(position, dict) and all(finite_number(position.get(k)) for k in ("x", "y")),
|
||||
f"{ident}: position requires finite x/y.")
|
||||
data = node.get("data")
|
||||
require(isinstance(data, dict), f"{ident}: data must be an object.")
|
||||
require(isinstance(data.get("label"), str) and all(text_value(data.get(k)) for k in ("componentType", "modelType")),
|
||||
f"{ident}: label and component/model type are required.")
|
||||
require("modelVersion" not in data or isinstance(data["modelVersion"], str), f"{ident}: invalid modelVersion.")
|
||||
require(type(data.get("rotation")) in (int, float) and data["rotation"] in (0, 90, 180, 270)
|
||||
and type(data.get("mirrored")) is bool, f"{ident}: invalid rotation/mirrored.")
|
||||
parameters = data.get("parameters")
|
||||
require(isinstance(parameters, dict) and all(parameter_value(v) for v in parameters.values()),
|
||||
f"{ident}: parameters must be finite numbers or strings.")
|
||||
units = data.get("parameterUnits", {})
|
||||
require(isinstance(units, dict) and all(isinstance(v, str) for v in units.values()), f"{ident}: invalid parameterUnits.")
|
||||
require(isinstance(data.get("parameterScientificNotation", {}), dict), f"{ident}: invalid scientific notation metadata.")
|
||||
ports = data.get("ports")
|
||||
require(isinstance(ports, list), f"{ident}: ports must be objects, not strings.")
|
||||
names = set()
|
||||
for port in ports:
|
||||
require(isinstance(port, dict), f"{ident}: invalid port snapshot.")
|
||||
name = port.get("name")
|
||||
require(text_value(name) and name not in names, f"{ident}: port names must be nonempty and unique.")
|
||||
names.add(name)
|
||||
require(port.get("kind") in ("physical", "signal") and text_value(port.get("domain"))
|
||||
and port.get("nominalRole") in ("inlet", "outlet", "bidirectional", "input", "output")
|
||||
and port.get("side") in ("left", "right"), f"{ident}.{name}: invalid port snapshot.")
|
||||
require("positiveFlowDirection" not in port or port["positiveFlowDirection"] == "intoComponent",
|
||||
f"{ident}.{name}: omit null positiveFlowDirection for browser import.")
|
||||
for edge in project["edges"]:
|
||||
require(isinstance(edge, dict), "Each edge must be an object.")
|
||||
ident = edge.get("id")
|
||||
require(text_value(ident) and ident not in edge_ids, "Edge IDs must be nonempty and unique.")
|
||||
edge_ids.add(ident)
|
||||
require(all(text_value(edge.get(k)) for k in ("source", "target", "sourceHandle", "targetHandle")),
|
||||
f"{ident}: explicit endpoints and handles are required.")
|
||||
require(edge["source"] in node_ids and edge["target"] in node_ids, f"{ident}: missing endpoint component.")
|
||||
data = edge.get("data")
|
||||
require(isinstance(data, dict) and type(data.get("isContactEdge")) is bool,
|
||||
f"{ident}: data.isContactEdge must be boolean.")
|
||||
if "routePoints" in data:
|
||||
require(isinstance(data["routePoints"], list) and all(
|
||||
isinstance(p, dict) and all(finite_number(p.get(k)) for k in ("x", "y"))
|
||||
for p in data["routePoints"]), f"{ident}: invalid routePoints.")
|
||||
|
||||
|
||||
def validate_project(project):
|
||||
from app.main import (ReactFlowProjectPayload, build_reactflow_system_xml,
|
||||
compile_reactflow_network, validate_reactflow_component_contract)
|
||||
from app.project_parameters import prepare_project, version_warning
|
||||
from app.simulation.registry import get_component_model_spec
|
||||
validate_editor_shape(project)
|
||||
available = {model["modelType"] for model in catalog_models()}
|
||||
require(all(node["data"]["modelType"] in available for node in project["nodes"]),
|
||||
"Project contains a model that is not available in the editor catalog.")
|
||||
try:
|
||||
normalized, notices = prepare_project(ReactFlowProjectPayload.model_validate(project))
|
||||
xml = build_reactflow_system_xml(normalized)
|
||||
connected = Counter(endpoint for edge in normalized.edges for endpoint in (
|
||||
(edge.source, edge.sourceHandle), (edge.target, edge.targetHandle)))
|
||||
# Store the full registered snapshot, but check only currently active ports.
|
||||
for node in normalized.nodes:
|
||||
spec = get_component_model_spec(node.data.modelType)
|
||||
parameters = validate_reactflow_component_contract(node, spec)
|
||||
for port in spec.active_ports(parameters):
|
||||
count = connected[(node.id, port.name)]
|
||||
require(count == 1, f"{node.id}.{port.name}: expected one connection, found {count}.")
|
||||
compiled = compile_reactflow_network(normalized).as_interface_dict()
|
||||
except (ValueError, KeyError) as exc:
|
||||
raise InputError("PROJECT_EXECUTION_INVALID", str(exc)) from exc
|
||||
warnings = [version_warning(notices)] if notices else []
|
||||
report = {
|
||||
"ok": True, "projectSchemaVersion": project["projectSchemaVersion"],
|
||||
"componentCount": len(project["nodes"]), "connectionCount": len(project["edges"]),
|
||||
"warnings": warnings,
|
||||
"checks": {"editorShape": "passed", "activePorts": "passed", "xmlV3": "passed",
|
||||
"networkCompile": "passed", "browserImport": "notRun", "simulation": "notRun"},
|
||||
"compiledComponentCount": len(compiled.get("components", [])),
|
||||
}
|
||||
return report, xml
|
||||
|
||||
|
||||
def semantic_signature(xml):
|
||||
"""Compare SI execution inputs, ignoring layout and reversible edge direction."""
|
||||
root = ET.fromstring(xml)
|
||||
simulation = root.find("Simulation")
|
||||
return {
|
||||
"simulation": {k: v if k == "method" else float(v) for k, v in simulation.attrib.items()},
|
||||
"components": {c.attrib["id"]: {
|
||||
"type": c.attrib["type"], "modelVersion": c.attrib["modelVersion"],
|
||||
"parameters": {p.attrib["name"]: float(p.attrib["value"]) for p in c.findall("Parameter")},
|
||||
} for c in root.findall("Components/Component")},
|
||||
"connections": {c.attrib["id"]: sorted((e.attrib["component"], e.attrib["port"]) for e in c)
|
||||
for c in root.findall("Connections/Connection")},
|
||||
}
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
configure_standard_streams()
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
catalog = sub.add_parser("catalog", help="Read current local catalog; filter before requesting full model details.")
|
||||
catalog.add_argument("--query", default="")
|
||||
catalog.add_argument("--model", action="append", default=[])
|
||||
catalog.add_argument("--offset", type=nonnegative_integer, default=0)
|
||||
catalog.add_argument("--limit", type=page_limit, default=20)
|
||||
build = sub.add_parser("build", help="Build and validate JSON v2 from an explicit plan.")
|
||||
build.add_argument("input")
|
||||
build.add_argument("--output", required=True)
|
||||
check = sub.add_parser("check", help="Check editor shape, active connections, XML and network compilation.")
|
||||
check.add_argument("input")
|
||||
check.add_argument("--xml-output")
|
||||
compare = sub.add_parser("compare", help="Check two projects and compare their normalized execution inputs.")
|
||||
compare.add_argument("input")
|
||||
compare.add_argument("roundtrip")
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
if args.command == "catalog":
|
||||
models = catalog_models()
|
||||
available = {m["modelType"] for m in models}
|
||||
require(set(args.model) <= available, f"Unknown models: {sorted(set(args.model) - available)}.")
|
||||
models = [m for m in models if (not args.model or m["modelType"] in args.model)
|
||||
and args.query.casefold() in (m["modelType"] + " " + m["label"]).casefold()]
|
||||
page = models[args.offset:args.offset + args.limit]
|
||||
if not args.model:
|
||||
page = [{k: m[k] for k in ("modelType", "label", "modelVersion")} for m in page]
|
||||
emit_json({"ok": True, "command": "catalog", "source": "localCheckout", "models": page,
|
||||
"total": len(models), "nextOffset": args.offset + len(page),
|
||||
"hasMore": args.offset + len(page) < len(models)})
|
||||
return 0
|
||||
source = load_source(args.input, "json")
|
||||
if args.command == "build":
|
||||
output = _ensure_distinct_new_output(source.path, args.output)
|
||||
project, defaults = build_project(source.parsed)
|
||||
report, _ = validate_project(project)
|
||||
data = _canonical_json(project)
|
||||
_write_new_file(output, data)
|
||||
report.update(output=str(output), sha256=_sha256(data), defaultsUsed=defaults)
|
||||
else:
|
||||
report, xml = validate_project(source.parsed)
|
||||
if args.command == "check" and args.xml_output:
|
||||
output = _ensure_distinct_new_output(source.path, args.xml_output)
|
||||
_write_new_file(output, xml)
|
||||
report["xmlOutput"] = str(output)
|
||||
if args.command == "compare":
|
||||
roundtrip = load_source(args.roundtrip, "json")
|
||||
other_report, other_xml = validate_project(roundtrip.parsed)
|
||||
before, after = semantic_signature(xml), semantic_signature(other_xml)
|
||||
changed = [key for key in before if before[key] != after[key]]
|
||||
require(not changed, f"Round-trip changed execution inputs: {', '.join(changed)}.")
|
||||
report.update(executionEquivalent=True, roundtripSha256=roundtrip.sha256,
|
||||
roundtripWarnings=other_report["warnings"])
|
||||
report.update(command=args.command, sourceSha256=source.sha256)
|
||||
emit_json(report)
|
||||
return 0
|
||||
except SkillCliError as exc:
|
||||
emit_json(stable_error_payload(exc), stream=sys.stderr)
|
||||
return exc.exit_code
|
||||
except (ValueError, KeyError) as exc:
|
||||
emit_json(stable_error_payload(InputError("PROJECT_CONTRACT_INVALID", str(exc))), stream=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -862,11 +862,19 @@ def inspect_source(
|
||||
)
|
||||
backend_validation = compiled.get("validation")
|
||||
if isinstance(backend_validation, dict):
|
||||
if backend_validation.get("valid") is False:
|
||||
raise InputError("MODEL_VALIDATION_FAILED", "Backend model validation failed.", backend_validation)
|
||||
backend_issues = backend_validation.get("issues")
|
||||
if isinstance(backend_issues, list):
|
||||
source_issues.extend(
|
||||
dict(issue) for issue in backend_issues if isinstance(issue, dict)
|
||||
)
|
||||
backend_warnings = compiled.get("warnings")
|
||||
if isinstance(backend_warnings, list):
|
||||
source_issues.extend(
|
||||
{**warning, "severity": "warning"}
|
||||
for warning in backend_warnings if isinstance(warning, dict)
|
||||
)
|
||||
all_variables = _result_variables(compiled)
|
||||
result_variables, variable_page = _variable_page(
|
||||
all_variables,
|
||||
|
||||
Reference in new issue
Block a user