“待检设备验收通过后进入在役,退役后不能恢复”是一条业务约定。把它画成状态图,再画成带英文名称的类图,最后贴一段代码,仍然缺少一个可检查的环节:哪些输入经过什么规则,产生了哪些输出,又有哪些原信息没有进入实现?

本篇只转换设备生命周期的一个小片段。输入是人工整理的中文业务条款,输出经过有限状态模型、Python平台模型和实际可执行代码。预留、金额、设备身份都不在转换范围,聚合一致性边界 中的并发保证也不会因为生成了状态机就自动出现。

三个视角需要同一条转换线

本文采用 OMG 于2003年发布的《MDA Guide》1.0版术语。该版将 CIM 的关注点放在环境与需求,PIM 隐藏指定平台的实现细节,PSM 再加入使用具体平台的方式;平台无关具有相对范围。原文,2.2.9—2.2.15节

业务人员关心验收后能否继续使用设备;有限状态模型关心状态、操作和允许迁移;Python目标还要决定状态用什么值表示、如何查找迁移,以及操作被拒绝时返回还是抛异常。三者围绕同一组约定,却各自回答不同问题。

flowchart LR
  C[CIM 中文条款与业务环境] -->|显式词汇映射| P[PIM 三状态四条边]
  P -->|Python平台决策| S[PSM 字符串与字典分派]
  S -->|校验后生成| G[Python源码]
  G --> R[执行轨迹与拒绝结果]
  C --> L[损失清单]
  P --> M[转换映射记录]
  S --> M

本例使用自定义 JSON 和 Python标准库,并未实现 MOF、XMI 或标准转换语言。因此它是展示这些视角如何落到转换规则的教学链,不是符合 OMG 全部要求的 MDA工具。文章不会用一个文件后缀或三层目录替代标准符合性证明。

CIM 从已经整理的条款开始

models/31/requirements.json 保存四条规则:待检验收通过进入在役,在役送检回到待检,待检或在役均可退役。每条记录含条款编号、前状态、动作、后状态和说明文字;初始状态为待检,退役被列为终态。

文件还保存业务目的、参与人员、授权约定和响应目标。例如授权约定要求操作人员取得相应权限。它们属于业务环境,但当前有限状态语言没有人员、权限或时间性能的概念,不能由四条迁移边顺便实现。

这些字段是人工整理后的受限需求表达。转换器不会阅读一句任意中文并推导出领域模型;before、action、after 才是经整理的可转换部分。statement 中的自然语言只进入清单,若它与结构化字段矛盾,需要人工修正,程序没有宣称能够消化这个冲突。

选择条款编号也有具体用途。R1发生变化时,可以找到它对应的目标迁移,而不必只靠数组位置猜测来源。编号重复被拒绝,防止两个不同约定在记录中被当成同一来源;未知字段也会失败,避免新增需求被静默丢弃。

PIM 的生成规则足够小才能核对

转换器显式把“待检、在役、退役”映射为三个状态符号,把“验收通过、送检、退役”映射为三个操作符号。每条规则产生一个迁移三元组,初态与终态分别进入对应字段。这里没有根据类名猜测语义,也没有让生成器自行决定退役是否可逆。

得到的数据交给第30篇的 parse_model 校验,再用 validate_equipment_contract 检查与第24篇设备生命周期子集的一致性。正式输出的 PIM 与冻结的 models/30/equipment.json 相等;转换没有悄悄换成另一套设备状态。

未知动作“自动出租”会在中文词汇映射处被拒绝。已知词汇形成的错误迁移还可能在可达性或设备合同检查中失败。不同错误发生在不同环节,因此日志保留错误类型与来源路径,不统一写成无法追踪的“生成失败”。

PIM没有指定 Python 类、字典或异常,但它已经选择离散状态和确定性迁移表达。这是相对于 Python目标的平台无关,并不是完全没有任何计算假设。时间连续变化、并行子状态、操作参数及授权条件都无法塞进这个有限格式。

PSM 必须影响生成结果

pim_to_psm 把有限模型与平台决策一起写入 psm.json:运行目标为 Python,状态表示为字符串,迁移表使用状态与操作组成的二元组作字典键,非法操作抛出 ValueError,状态保存由调用者负责。

psm_to_python 实际读取这个文件,检查目标和全部决策后才调用第30篇生成器。把持久化选择改成 SQLite 会得到明确拒绝,因为当前模板没有数据库能力。若只是打印一个“支持SQLite”的标签,却仍生成同样的内存查表代码,PSM就没有兑现自己的含义。

生成文件只含初态、迁移表和 step(state, operation)。它在运行时不导入转换器,也不重新读取 JSON。调用者传入旧状态,取得新状态,再自行决定是否存储;一次纯函数返回不等于设备实体、数据库记录或外部系统已经更新。

flowchart TB
  R[R1 待检验收后在役] --> E[PIM transitions 第零条]
  E --> S[PSM model 中同一迁移]
  S --> K[Python TRANSITIONS 二元组键]
  K --> V[结果 ACTIVE]
  A[授权人员要求] --> N[loss ledger 未实现]
  N -. 无对应运行时检查 .-> K

映射文件把 R1 指向 PIM中的目标路径,再将每条 PIM边指向 PSM对应边及生成迁移表的键。指南也单独讨论转换记录,用它关联源模型与目标模型中的元素。原文,3.8节 本例记录的是自己的字段和模板规则,没有把这些路径说成标准交换格式。

运行结果说明保留了什么

实验读取生成的 Python文件,编译并执行其中函数。完整轨迹为待检、在役、待检、在役、退役,对应验收、送检、再次验收、退役四步操作。随后枚举三个状态与三个操作的九种组合,比较解释器与生成代码的成功结果或拒绝结果。

九对输入中四对有合法迁移,五对被拒绝。退役后的三个操作全部失败;检查不只观察一条顺利完成的路径。解释器抛出带代码与路径的 ModelError,生成代码抛出普通 ValueError,对照的是业务允许关系,不是假装异常对象也完全相同。

生成函数仍要求调用者提供当前状态。两次都把初态传进去,会得到两次基于初态的结果,无法代表同一设备已经走过两步。实验因此把每一步返回值传给下一步,并保存完整轨迹。这一点让“源码能够导入”和“状态行为已经运行”成为不同的验收项。

脚本还将相同输入转换到另一临时目录,逐文件比较五个产物的字节,结果一致。这个结论限定于当前输入表示、映射规则和模板;不同数组顺序或未来模板版本可能改变输出。它没有证明所有语义相同的模型必然得到同一个文件哈希。

丢失的信息要能被指出

loss-ledger.json 包含九项记录。四条说明文字只作为文字保存,程序不理解其额外含义;目的、人员、授权和响应目标四项没有运行时实现。第九项记录平台层损失:状态、终态与操作声明列表没有进入生成模块,运行时异常也不保留结构化模型路径。每项都带源路径、原值、处理方式与理由,便于判断哪些要求仍待处理。

实验把授权约定改为“双人批准”,再次转换后 PIM保持相同,损失清单发生变化。这是一个实际的信息损失反例:两组不同业务要求得到相同状态机。即使状态机测试全部通过,也不能据此宣布双人授权已经满足。

清单记录“未实现”不等于批准删除要求。若授权是上线必备条件,需要增加能表达权限的模型及相应实现,或明确交给手写边界。当前转换器只保留证据,不替需求负责人决定这个缺口是否可接受。

映射记录与损失清单也有不同用途。前者回答目标元素从哪里来,后者回答哪些来源没有获得相应行为。只有映射而没有损失说明,容易把未出现的要求当作不存在;只有损失清单而没有目标路径,又难以定位已经实现的那部分规则。两份产物分别保留这两种检查入口。

反向转换同样受限。仅凭生成代码中的迁移表,无法恢复原中文措辞、参与人员和响应目标。原始CIM、映射记录与损失清单仍应单独保存。重新生成会覆盖同名文件,多文件写入也没有原子提交保证,实验使用专属目录避免混入手写业务代码。

独立复跑转换链

从仓库根目录运行 examples/software-modeling/labs/31/run.sh。本次在 Python 3.14.4 上执行26项检查并退出零,产物位于 examples/software-modeling/evidence/31/generated/。附加 unsupported-term 会构造未知动作,得到映射异常并退出一。

也可以直接执行 python3 examples/software-modeling/labs/31/pipeline.py --cim examples/software-modeling/models/31/requirements.json --output /tmp/modeling31-output。该入口会实际读取CIM并生成五个文件,不依靠文章里的图运行。API与范围记录在 examples/software-modeling/models/31/contracts.md。

第30篇原始回归另在临时副本中运行,23项检查全部通过,日志保存在本章证据目录;原工具和他人证据没有被改写。这条转换链已经提供模型到行为的局部证据,身份、并发、持久化和完整租赁用例仍由各自实现负责。

下载转换器、模型、生成物与原始日志