自适应案例管理(ACM)是一种协调知识密集型工作的方式,其达成结果的路径由案例工作者在运行时决定,而非预先固定。它也被称为动态案例管理,较早的资料将其归入高级案例管理。本指南提供一套持久的心智模型、一个可改编的可运行案例状态实现、一套验证流程,以及决定 ACM 何时优于结构化工作流引擎的权衡因素。本文是一份可供反复查阅的参考,而非产品推荐清单。
自适应案例管理在实践中的含义
自适应案例管理(ACM)通过将案例视为目标、数据和可用操作的容器,而非固定的任务序列,来协调服务请求——例如理赔、调查、入职、公民或患者事务。案例工作者根据上下文和判断选择下一步操作;系统记录发生了什么、执行权限控制,并保持案例可审计。同一概念在较早的资料中被称为动态案例管理或高级案例管理,而 Wikipedia 风格的表述通常强调跨财务、医疗、法律、公民和人力资源事务的协调。
实际区别在于所提出的问题。结构化流程问的是“这件事应该怎么做?”,并将答案编码为一条路径。ACM 问的是“鉴于这个案例,下一步应该做什么?”,并将答案编码为一组允许的操作以及用于在其中做出选择所需的状态。这一转变正是 ACM 适合不可预测、知识密集型工作的原因,也是它不适合高容量、可重复事务的原因。
本文是一份技术参考:它定义模型、展示一个最小实现,并描述如何验证该实现确实表现出自适应行为。它刻意不推荐特定供应商;如果你需要为此类系统背后的基础设施做出具体的托管或端点决策,请将该问题交给 RPC Assistant,而不是将本页视为供应商列表。
- ACM = 目标 + 案例数据 + 允许的操作 + 审计追踪,并由人在运行时做出选择。
- 你会遇到的同义词:动态案例管理、高级案例管理。
- 最适合:异常频繁、依赖判断、低至中等容量的工作。
- 不适合:确定性、高容量、完全明确的事务。
构建前的假设与前提条件
下面的实现假设你可以运行 Python 3.10 或更高版本,有一个地方可以持久化案例状态(数据库、文档存储,甚至原型阶段的一个 JSON 文件),并且有一个身份层可以将每个操作归属到具名用户。没有归属的 ACM 只是一个非结构化收件箱;审计追踪才是核心特性。
你还应事先确定工作的哪些部分确实是结构化的。大多数实际部署都是混合式的:少数强制步骤(身份验证、监管检查)被强制执行,而调查或解决工作则是自适应的。将整个案例视为非结构化会丢弃有用的控制;将整个案例视为固定流程则违背了初衷。
最后,就案例模式达成一致。至少需要案例标识符、目标或结果定义、状态、一组工件(文档、笔记、消息)、一组可用操作,以及一个仅追加的事件日志。如果你的团队无法就这六项达成一致,那么工具选择还为时过早。
心智模型:目标、状态、操作与事件
将案例视为一个小型状态机,其状态转换由人提出并由系统验证。目标是关于案例数据的谓词——例如“理赔已裁定且付款已授权”。状态是当前的事实和工件集合。操作是工作者可以尝试的操作。事件是对尝试了什么以及结果如何的不可变记录。
该模型可以清晰地映射到 CMMN(案例管理模型与符号),后者将案例描述为一组任务、里程碑和哨兵,这些元素根据条件而非固定序列变为可用。你不需要 CMMN 引擎就能从该模型中受益;你需要的是将允许的操作与必需的操作分开的纪律。
一个有用的测试可以判断你的设计是否真正自适应:一个有能力的工作者能否在不遵循与上一个案例相同操作顺序的情况下完成一个有效案例?如果答案是否定的,那么你构建的是一个带有额外步骤的工作流,而不是 ACM。
- 目标:定义完成的谓词,而非任务列表。
- 状态:工作者和系统可以读取的事实与工件。
- 操作:允许的操作,受角色和状态约束。
- 事件:用于审计、重放和指标的仅追加日志。
可运行实现:一个最小自适应案例引擎
以下 Python 模块实现了核心循环:定义案例、列出当前状态的可用操作、应用操作并记录事件。它刻意保持小巧,以便你可以从头到尾阅读,并且仅使用标准库。在任何真实场景使用之前,请将内存存储替换为你选择的数据库。
注意使其具有自适应而非混乱的两个护栏:操作声明对案例数据的前置条件,并且每个应用的操作都会追加一个包含操作者和时间戳的事件。引擎从不决定顺序;它只拒绝前置条件未满足的操作。
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Callable
@dataclass
class Case:
case_id: str
goal: str
data: dict = field(default_factory=dict)
events: list = field(default_factory=list)
closed: bool = False
@dataclass
class Action:
name: str
precondition: Callable[[Case], bool]
effect: Callable[[Case], None]
class CaseEngine:
def __init__(self, actions: list[Action]):
self.actions = {a.name: a for a in actions}
def available(self, case: Case) -> list[str]:
if case.closed:
return []
return [a.name for a in self.actions.values() if a.precondition(case)]
def apply(self, case: Case, action_name: str, actor: str) -> None:
action = self.actions.get(action_name)
if action is None:
raise ValueError(f"unknown action: {action_name}")
if not action.precondition(case):
raise PermissionError(f"precondition failed for {action_name}")
action.effect(case)
case.events.append({
"action": action_name,
"actor": actor,
"at": datetime.now(timezone.utc).isoformat(),
"data_snapshot": dict(case.data),
})
# Example wiring for an insurance-style case
verify = Action(
name="verify_identity",
precondition=lambda c: "identity_verified" not in c.data,
effect=lambda c: c.data.update(identity_verified=True),
)
request_docs = Action(
name="request_documents",
precondition=lambda c: c.data.get("identity_verified") is True,
effect=lambda c: c.data.update(documents_requested=True),
)
adjudicate = Action(
name="adjudicate",
precondition=lambda c: c.data.get("documents_requested") is True,
effect=lambda c: c.data.update(adjudicated=True),
)
close = Action(
name="close_case",
precondition=lambda c: c.data.get("adjudicated") is True,
effect=lambda c: setattr(c, "closed", True),
)
engine = CaseEngine([verify, request_docs, adjudicate, close])
case = Case(case_id="C-1001", goal="claim adjudicated and closed")
print(engine.available(case)) # ['verify_identity']
engine.apply(case, "verify_identity", "worker@example.com")
print(engine.available(case)) # ['request_documents']通过 HTTP 暴露案例引擎以便集成
大多数团队不会直接调用引擎;他们会通过 API 暴露它,以便案例工作者 UI、自动化服务和审计消费者都能与同一状态交互。下面的示例使用 FastAPI 并保持相同的护栏。它是一个草图,而非生产服务:在依赖它之前,请添加身份验证、持久化和幂等键。
重要的设计选择是 API 将“可用操作”作为一等端点暴露。这使 UI 只能渲染当前允许的内容,这正是 ACM 系统在案例复杂性增长时保持可用的方式。
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
CASES: dict[str, Case] = {}
class CreateCase(BaseModel):
case_id: str
goal: str
class ApplyAction(BaseModel):
action: str
actor: str
@app.post("/cases")
def create_case(body: CreateCase):
CASES[body.case_id] = Case(case_id=body.case_id, goal=body.goal)
return {"case_id": body.case_id}
@app.get("/cases/{case_id}/actions")
def list_actions(case_id: str):
case = CASES.get(case_id)
if not case:
raise HTTPException(status_code=404)
return {"available": engine.available(case)}
@app.post("/cases/{case_id}/actions")
def apply_action(case_id: str, body: ApplyAction):
case = CASES.get(case_id)
if not case:
raise HTTPException(status_code=404)
try:
engine.apply(case, body.action, body.actor)
except PermissionError as exc:
raise HTTPException(status_code=409, detail=str(exc))
return {"events": len(case.events), "closed": case.closed}验证流程与结果表
ACM 系统的验证不是单一的通过/失败测试;它是一组针对已记录案例检查的属性。对真实或合成案例样本运行以下流程,并将结果记录在表中。不要填写你未测量的数字——该表是你自己运行的模板。
流程:(1)导出至少 30 个案例的事件日志;(2)对于每个案例,确认每个应用的操作在应用时其前置条件已满足;(3)确认没有案例在其目标谓词不为真时达到关闭状态;(4)统计跨案例出现了多少种不同的操作顺序——一个健康的自适应系统会表现出多样性;(5)统计前置条件拒绝次数,并检查它们是否集中在某个令人困惑的操作上。下表是填写格式;其中的值是占位符,请替换为你的测量结果。
- 属性 1——前置条件完整性:每个应用的操作在应用时都是允许的。
- 属性 2——目标完整性:没有已关闭案例违反其目标谓词。
- 属性 3——顺序多样性:样本中不同的操作序列数量。
- 属性 4——拒绝聚集:按操作名称分组的拒绝。
- 属性 5——审计完整性:每个事件都有操作者和时间戳。
| Property | Sample size | Pass count | Fail count | Notes |
|---|---|---|---|---|
| Precondition integrity | <N cases> | <measured> | <measured> | <where failures occurred> |
| Goal integrity | <N cases> | <measured> | <measured> | <goal predicate used> |
| Ordering variety | <N cases> | <distinct sequences> | n/a | <most common sequence> |
| Rejection clustering | <N events> | n/a | <measured> | <top rejected action> |
| Audit completeness | <N events> | <measured> | <measured> | <missing fields> |预期验证输出及其解读
一次健康的运行应显示零前置条件完整性失败和零目标完整性失败;任何非零值都表明引擎或操作被带外应用的方式存在缺陷。对于真正自适应的工作,顺序多样性应大于一——如果每个案例都遵循相同的序列,那么你实际上构建了一个工作流,应考虑改用 BPM 引擎。
拒绝聚集是最有用的诊断。某个操作的高拒绝计数通常意味着前置条件比工作要求的更严格,或者 UI 提供了很少有效的操作。两者都是设计信号,不一定是缺陷。
审计完整性应为 100%。如果事件缺少操作者或时间戳,请停止并修复写入路径,然后再添加功能;没有可信日志的 ACM 系统无法审计或改进。
常见 ACM 实现问题的故障排查
以下故障涵盖了大多数早期 ACM 问题。每个故障都有一个具体的首要检查项。
如果工作者报告系统“不让我做任何事”,请检查代表性案例的可用操作端点,并将其与代码中的前置条件进行比较。常见原因是前置条件依赖于工作者跳过的步骤所写入的数据,这是建模错误而非缺陷。
如果案例无限期停滞,你可能没有升级或老化机制。ACM 假设人类判断,但它仍然需要基于时间的触发器——一个 N 天未处理的案例应提交给主管。将其添加为事件驱动作业,而不是硬编码步骤。
如果审计日志与工作者记得做过的事情不一致,请检查带外写入:直接数据库编辑、管理工具或绕过引擎的批量导入。将每个变更都通过操作 API 路由,否则你将失去使 ACM 可审计的特性。
- 没有可用操作:前置条件依赖于被跳过的数据——审查案例模式。
- 案例停滞:在操作集之外添加老化和升级触发器。
- 审计不匹配:查找并关闭带外写入路径。
- 拒绝过多:放宽或澄清前置条件,或在 UI 中隐藏该操作。
ACM 与 BPM:选择合适的控制模型
业务流程管理(BPM)编码已知序列,并优化一致性和吞吐量。ACM 编码允许的操作,并优化判断和异常处理。两者互补:一种常见模式是 BPM 管理的受理创建案例、ACM 管理的解决阶段,以及 BPM 管理的关闭和付款步骤。
当路径已知、容量高且偏离即为错误时,选择 BPM。当路径取决于工作中才出现的事实、容量中等且偏离是预期之内时,选择 ACM。如果你无法将目标表述为谓词,那么你还没有准备好使用 ACM——你仍在发现流程。
对于运行任一模型背后基础设施的团队,运维关注点有重叠:可靠的状态存储、经过身份验证的 API 和可观测的事件流。如果你的案例系统依赖链上数据或链上身份,端点可靠性问题应交给 RPC Assistant;从多链 RPC 端点指南和RPC 端点指南了解通用模型,然后在需要规划容量时使用 RPC 定价和专用节点。
局限性、权衡与治理
ACM 以可预测性换取契合度。这种权衡有代价:测试更难、容量规划更难,并且更依赖工作者技能。你无法像测试固定工作流那样对自适应系统进行回归测试,因此你需要投资于属性检查和抽样审查。
治理是另一项成本。由于工作者可以选择操作,你需要清晰的角色定义、前置条件变更的审查流程以及事件日志的保留规则。在受监管领域——医疗、法律、金融服务——审计追踪通常是主要的合规工件,因此将其模式视为契约。
最后,对容量要诚实。ACM 工具和审查开销在非常高容量、低方差的工作上无法很好地摊销。如果你的大多数案例遵循相同路径,那么自适应层就是只有成本没有收益。
- 测试更难:依赖属性检查和抽样审查。
- 容量规划更难:按案例到达和处理时间建模,而非步骤数。
- 需要治理:角色、变更审查、保留策略。
- 容量上限:如果大多数案例共享一条路径,请重新考虑。
后续步骤:从原型到生产
从内存原型迁移到具有仅追加事件表的持久化存储,然后添加身份验证,使每个操作都携带经过验证的操作者。接下来,对可用操作端点和拒绝计数进行埋点;这两个信号告诉你模型是否与工作匹配。
当案例系统触及区块链基础设施时——例如锚定审计事件或验证链上身份——将端点选择视为一个独立的、可衡量的决策。API 服务概览和支持的网络列表是正确的起点,区块链节点托管指南涵盖了运维方面。对于具体的端点和供应商选择,请询问 RPC Assistant,而不是从列表中猜测。
最后,在首批一百个案例后安排一次审查。将顺序多样性和拒绝聚集与你的基线进行比较;如果多样性崩溃,你已经漂向 BPM,应要么简化模型,要么有意采用工作流引擎。