JavaProjectRepo/business-css/情景模拟分析结果v3.md
2026-03-19 11:18:15 +08:00

7.1 KiB
Raw Permalink Blame History

情景模拟分析结果 v3针对“几个疑问”

对应问题来源:几个疑问.md

1. 初始化顺序与优先级:静态值、事件值、影响计算如何排布?

1.1 三类数据的“角色定义”

  • 静态值(设备表/物料表/拓扑 static:系统的“默认底色/初值基线”。没有事件、没有影响计算时,也必须能给出一个自洽的初始状态。
  • 事件值(始发事件/attr_changes:系统的“外部输入”。既可能是一次性的设定(某一步将某属性改成某值),也可能代表持续的干预(在一段时间内强制维持某值)。
  • 影响计算influence 公式):系统的“内部传播/派生规则”。它根据来源属性计算得到目标属性,是一个“生成值”的过程。

1.2 当前代码实现的顺序(实际行为)

ProjectServiceImpl.initSimulation 里,单帧生成顺序是:

  1. 注入尺寸Device.size
  2. 写入设备静态值
  3. 执行设备 influence 计算并写入 state
  4. 写入物料静态值DB + topology
  5. 执行物料 influence 计算并写入 state
  6. 最后对 device/material 应用事件覆盖overrideWithEvents

这相当于:Static → Influence → Event(最终覆盖)。代码位置见:ProjectServiceImpl.initSimulationoverrideWithEvents/readValue

另外,readValue() 在计算 influence 时会“优先读事件、再读静态”,但最终仍会被 overrideWithEvents() 再覆盖一次,这意味着事件在该实现里拥有“最终裁决权”。见:readValue

1.3 推荐的合理顺序(建议落地的“标准管线”)

建议将初始化/每步演进统一成一个明确的“管线”,并对事件类型做区分:

推荐管线:

  1. Static 基线:加载设备/物料静态值(含 DB 补全)作为默认状态。
  2. Event输入/初始条件):应用在当前时刻生效的事件(尤其是 t=0 或 step=1 的“初始条件事件”)。
  3. Influence派生计算:基于当前状态执行影响关系计算,得到派生值。
  4. Event强制覆盖/锁定):仅对“强制锁定类事件”再次覆盖(可选,但强烈建议引入),确保“外部强制干预”不会被公式计算反覆盖。

这样做的好处:

  • 既能让事件作为“输入”影响传播(步骤 2 发生在计算前),又能让“强制事件”在输出阶段生效(步骤 4
  • 能解释并覆盖两类业务直觉:
    • “我设了初始浓度,后续按公式变化”(步骤 2 + 3
    • “我强制把某槽位液位锁死,不管公式怎么算”(步骤 4

1.4 关键补充:时间轴/采样点也属于优先级的一部分

当前 initSimulation 的帧生成时间点来自事件时间集合:collectTimePoints(valueProviders),若没有事件时间点会直接返回“无法生成帧”。见:collectTimePointsinitSimulation timePoints 为空处理

建议:

  • 当没有事件时,允许通过参数指定 start/end/stepInterval,生成基础时间网格(否则“纯静态模拟”永远跑不起来)。
  • 对于 ramp/linear 事件,当前只把 start/end 加入时间点(中间不采样),但 readValue 支持任意 t 的线性插值。若希望输出更“连续”的曲线,必须补齐采样点(例如每 1s/每步都采样)。

2. 合理性分析:为什么推荐上述顺序?

2.1 从“因果关系”角度

  • 静态值是先验事实(设备尺寸、物料基础参数),应先进入状态。
  • 事件是外部驱动/输入,会改变系统边界条件,应在影响传播前进入状态,否则传播用的仍是旧值。
  • influence 是系统内部响应,应在输入就绪后计算。
  • 强制覆盖事件是高优先级干预,必须在计算后仍能维持,否则“强制”二字失去意义。

2.2 从“可维护性/可解释性”角度

把事件分成两类最关键:

  • Event-Input输入型:参与计算的输入(初值设定、控制变量变化)。
  • Event-Override强制型:输出阶段的锁定/强制(故障注入、人工接管)。

如果不区分,只能在“事件最高”与“公式最高”之间二选一,最终会在不同业务场景下反复打补丁。

3. SimController 及相关接口分析(并给出建议)

3.1 现状SimController 是“半成品/原型”,当前不可用

SimController.java 体现了一套更清晰的分层意图:Controller -> Repo 装配 -> Builder -> SimService -> 推理输入,但存在几处硬性问题:

  • 依赖的 ProjectRepository / EventRepository / InfluenceRepository / InferenceConverter 在项目代码中找不到定义,无法编译/运行(当前仓库中仅此文件引用这些类型)。
  • SimBuilder 中对应的 buildUnits/buildEvents/buildInfluenceNodes 方法是注释掉的,占位未实现。见:SimBuilder.java
  • SimService 当前实现仅是 KV 级别的简化引擎,且采用 事件先写、后计算覆盖 的顺序(会导致“事件设定失效”)。见:SimService.runSimulation

3.2 与当前线上接口的关系

当前“初始化/运行模拟”入口实际在 ProjectController/simulation/init、/simulation/run并调用 ProjectServiceImpl。例如 runSimulation:见 ProjectController.java:L289-L300

3.3 建议:保留 SimController 的分层方向,但必须补齐三件事

  1. 补齐依赖与 Builder:明确 Repo 层要读什么(拓扑、事件、影响关系)并落地成可编译的类/接口;实现 SimBuilder 的构建方法或直接复用 ProjectServiceImpl 的解析器。
  2. 统一优先级管线:让 SimService 采用“Static → Event(Input) → Influence → Event(Override)”(见第 1 章建议),避免出现“事件被公式反覆盖”。
  3. 统一对外 API:建议最终以 /sim/* 作为唯一入口。