目录
- 业务场景故事
- 背景与目标
- 核心技术理念
- 整体技术架构
- 分层架构详细设计
- 关键技术实现细节
1. 业务场景故事
1.1 真实场景:煤矿3号工作面的一天
上午9:15 - 异常发现
值班工程师老张正在监控室查看数字孪生系统的三维画面。突然,他注意到17号液压支架的压力数值有些异常,但具体是否需要处理、会影响哪些设备,他需要查阅多个系统、翻看历史数据,至少需要15-20分钟才能做出判断。
传统方式的痛点:
- 需要在多个系统间切换(监控系统、设备台账、历史数据库)
- 依赖老张20年的现场经验
- 无法快速评估关联风险
- 领导问起来很难用一句话解释清楚
上午9:16 - AI增强后的场景
老张对着系统说:"检查17号液压支架的状态,看看有没有风险"
系统在0.5秒内:
- 前端智能裁剪: 自动识别"17号液压支架"在3号工作面,筛选出该区域相关的8个设备实体
- AI语义理解: Claude理解意图是"风险评估",目标是hyd_017,需要检查关联设备
- 本体语义推理: 知识图谱显示17号支架与3号采煤机cm_003存在工况约束关系
- 规则引擎判断: 压力值超过安全阈值15%,触发黄色预警
- WASM高性能计算: 0.1秒完成周边8个设备的影响域仿真
- 可视化呈现:
- 三维场景中17号支架高亮显示为黄色
- 3号采煤机标注为"受影响设备"
- 屏幕显示AI解释:"17号液压支架压力偏高(当前125bar,安全阈值108bar),建议暂停3号采煤机作业并进行压力释放检查。附近烟感smk_009、摄像头cam_012状态正常。"
价值体现:
- 从20分钟降低到0.5秒
- 不依赖个人经验,系统自动关联分析
- 可视化+自然语言,领导也能看懂
- 所有判断可追溯、可审计
1.2 下午场景:新员工小李的第一天
小李是刚入职的大学生,对现场设备不熟悉。下午领导让他"看看3号工作面现在能不能启动采煤作业"。
传统方式: 小李完全不知道从哪里看起,需要老张手把手教。
AI增强后: 小李对系统说:"3号工作面现在可以启动采煤吗?"
系统回复:"当前不建议启动。原因:17号液压支架压力异常(黄色预警),需要先处理该故障。其他设备状态正常。"
三维场景自动定位到3号工作面,高亮显示问题设备。
价值: 降低人员培训成本,新人也能快速上手。
2. 背景与目标
2.1 公司战略背景
- 公司2026年启动全面AI转型
- 传统数字孪生业务面临"重资产"标签风险
- 需要找到AI与现有核心资产的最佳结合点
2.2 现有资产与痛点
现有资产:
- 成熟的三维建模能力(采煤机、液压支架、传感器、摄像头等)
- 完整的数字孪生可视化系统
- IoT数据采集与实时监控能力
- 多年积累的行业项目经验
核心痛点:
- 系统复杂度高: 操作严重依赖专家经验,人员培养周期3-5年
- 风险判断靠经验: 缺乏自动化辅助决策,误判成本高(单次事故可达数百万)
- 价值感知弱: 领导层难以快速理解系统状态,投资回报不直观
- 知识流失风险: 专家离职即损失,经验无法沉淀
2.3 总体目标
核心目标: 构建"数字孪生 + 本体语义 + AI认知层"体系,实现系统从"可监控"到"可理解、可解释、可辅助决策"的升级。
四大原则:
- 不推翻现有系统: AI是增强层,不是替代层
- AI不越界: 仅做认知与解释,不做高风险决策
- 本体是资产: 语义知识图谱是长期护城河
- 可控可审计: 所有决策可追溯,符合工业安全标准
3. 核心技术理念
3.1 三个核心定位
数字孪生 = AI的眼睛和载体
- 三维模型不是负担,是AI理解物理世界的最佳接口
- 孪生对象天然就是本体节点
本体语义 = AI的世界模型
- 设备、空间、关系、状态构成完整语义网络
- 不是为AI准备的数据结构,而是系统对现实的统一建模
LLM = AI的理解与表达能力
- 处理自然语言到结构化意图的转换
- 生成人类可理解的解释
- 但不做确定性决策
3.2 AI的正确位置
| 层级 | AI参与 | 说明 |
|---|---|---|
| 控制执行 | ❌ | 高风险,必须确定性 |
| 决策规则 | ❌ | 可审计,可回溯 |
| 认知理解 | ✅ | AI的主战场 |
| 语义解释 | ✅ | 价值感知的关键 |
| 可视化呈现 | ❌ | AI的输出载体 |
关键判断: 这不是"AI驱动系统",而是"系统驱动AI在正确范围内工作"。
4. 整体技术架构
4.1 架构总览
┌─────────────────────────────────────┐
│ 用户交互层 │
│ - 自然语言对话 │
│ - 三维场景操作 │
│ - 上下文感知 │
└──────────────┬──────────────────────┘
▼
┌─────────────────────────────────────┐
│ 前端智能裁剪层 (关键创新) │
│ - 候选实体筛选 (WASM加速) │
│ - 场景上下文补全 │
│ - 意图白名单约束 │
│ - 轻量语义匹配 │
└──────────────┬──────────────────────┘
▼
┌─────────────────────────────────────┐
│ AI语义理解与解释层 │
│ - 意图识别 (Claude/LLM) │
│ - 目标实体确认 │
│ - 关联实体发现 │
│ - 自然语言解释生成 │
└──────────────┬──────────────────────┘
▼
┌─────────────────────────────────────┐
│ 本体语义层 │
│ - 实体: 设备/传感器/区域 │
│ - 关系: 空间/工况/约束 │
│ - 状态: 运行/异常/风险等级 │
│ - 规则锚点: 触发条件/安全边界 │
└──────────────┬──────────────────────┘
▼
┌─────────────────────────────────────┐
│ 决策与规则层 │
│ - 确定性规则引擎 │
│ - 安全边界校验 │
│ - 状态机编排 │
│ - 审计日志 │
└──────────────┬──────────────────────┘
▼
┌─────────────────────────────────────┐
│ 执行/查询适配层 (WASM) │
│ - 高性能物理仿真 │
│ - 批量规则校验 │
│ - 影响域计算 │
│ - 指令合法性验证 │
└──────────────┬──────────────────────┘
▼
┌─────────────────────────────────────┐
│ 数字孪生 & 可视化系统 │
│ - 三维模型渲染 │
│ - 目标实体高亮 │
│ - 关联设备标注 │
│ - AI解释文本显示 │
│ - 操作历史回放 │
└─────────────────────────────────────┘
4.2 数据流示例
场景: 用户说"液压支架设备故障了,我想看看"
1. 用户输入: "液压支架设备故障了,我想看看"
2. 前端智能裁剪 (50ms):
- 识别设备类型: "液压支架"
- 当前场景: 3号工作面
- 筛选候选: 该区域12个液压支架
- 状态过滤: 找到2个异常状态的支架 [hyd_017, hyd_023]
- 生成JSON:
{
"user_input": "液压支架设备故障了,我想看看",
"candidate_entities": [
{"id": "hyd_017", "type": "液压支架", "status": "压力异常"},
{"id": "hyd_023", "type": "液压支架", "status": "通讯中断"}
],
"scene_context": {
"area": "3号工作面",
"visible_entities": ["cm_003", "smk_009", "cam_012", ...]
},
"allowed_intents": ["查询状态", "风险评估", "查看历史"]
}
3. AI语义理解 (300ms):
Claude输出:
{
"intent": "view_fault_status",
"target_entities": ["hyd_017", "hyd_023"],
"related_entities": ["cm_003"],
"scope": "current_area",
"need_explanation": true,
"explanation": "发现2个液压支架异常:17号压力偏高,23号通讯中断。3号采煤机受影响,建议暂停作业。"
}
4. 规则校验 (10ms):
- 验证intent在白名单内 ✓
- 验证target_entities可操作 ✓
- 触发规则: "液压支架异常 → 采煤机禁止启动"
5. WASM执行 (100ms):
- 计算影响域: 17号支架周边5米范围
- 仿真压力释放场景
- 更新本体状态
6. 可视化呈现 (50ms):
- 镜头自动飞到3号工作面
- hyd_017高亮红色, hyd_023高亮橙色
- cm_003标注"受影响-禁止启动"
- 显示AI解释文本
- 提供"查看详情"和"生成工单"按钮
总耗时: ~510ms,用户体验流畅
5. 分层架构详细设计
5.1 前端智能裁剪层 (核心创新)
为什么需要这一层?
在大型场景中(500-1000+实体),直接把全量数据丢给LLM会导致:
- Token超限(Claude限制200K tokens)
- 推理延迟高(>3s)
- 成本失控($0.01/1K tokens)
核心功能:
-
场景上下文感知
- 当前用户所在区域/楼层
- 当前选中的设备
- 最近操作的对象
- 时间窗口(实时/历史)
-
候选实体筛选 (WASM加速)
// 伪代码示例 function filterCandidates(userInput, sceneContext) { // 1. 提取关键词: "液压支架" const keywords = extractKeywords(userInput); // 2. 类型匹配 let candidates = entities.filter(e => e.type.includes(keywords.deviceType) ); // 3. 区域过滤 candidates = candidates.filter(e => e.area === sceneContext.currentArea ); // 4. 状态过滤(如果提到"故障") if (keywords.includes("故障")) { candidates = candidates.filter(e => e.status !== "正常" ); } // 5. WASM加速: 语义相似度计算 candidates = wasmSemanticMatch(candidates, userInput); return candidates.slice(0, 10); // Top-10 } -
意图白名单约束
{ "allowed_intents": [ "查询状态", "风险评估", "查看历史", "原因分析" ], "forbidden_intents": [ "启动设备", "修改参数", "删除数据" ] } -
轻量语义匹配 (可选:小模型辅助)
- 使用本地小模型(如MiniLM)做embedding
- 计算用户输入与实体描述的相似度
- 在浏览器端运行,无网络延迟
技术选型:
- 前端框架: Vue3 + TypeScript
- WASM: AssemblyScript / Rust编译
- 小模型: ONNX Runtime (可选)
- 三维引擎: ThingJS / Three.js
5.2 AI语义理解与解释层
职责边界:
✅ AI做什么:
- 意图识别: 自然语言 → 结构化意图
- 实体消歧: 在候选集合中确定目标
- 关联发现: 基于本体语义找相关设备
- 解释生成: 输出人类可理解的文本
❌ AI不做什么:
- 不直接下发控制指令
- 不做确定性决策(由规则层负责)
- 不处理全量实体遍历(由前端裁剪)
Prompt工程 (Claude示例):
System Prompt:
你是工业数字孪生AI助手。任务:
1. 将用户自然语言转为结构化JSON意图
2. 仅在候选实体中选择目标
3. 基于本体关系发现关联设备
4. 生成简洁的中文解释
输出JSON格式:
{
"intent": "意图类型",
"target_entities": ["目标实体ID"],
"related_entities": ["关联实体ID"],
"scope": "作用范围",
"need_explanation": true,
"explanation": "自然语言解释",
"confidence": 0.95
}
约束:
- intent必须在allowed_intents内
- target_entities必须在candidate_entities内
- 不执行任何控制操作
- 如果无法确定,confidence<0.7时返回澄清问题
User Prompt:
{
"user_input": "<用户输入>",
"candidate_entities": [<前端筛选的候选>],
"scene_context": {<场景上下文>},
"allowed_intents": [<允许的意图>],
"ontology_relations": [<本体关系,可选>]
}
输出示例:
{
"intent": "risk_assessment",
"target_entities": ["hyd_017"],
"related_entities": ["cm_003", "smk_009"],
"scope": "area_3",
"need_explanation": true,
"explanation": "17号液压支架压力异常(125bar,超阈值15%),影响3号采煤机作业安全。建议立即检查并暂停采煤作业。附近烟感正常。",
"confidence": 0.92,
"suggested_actions": ["生成工单", "通知现场", "查看历史趋势"]
}
技术选型:
- 主模型: Claude 3.5 Sonnet (API)
- 备选: GPT-4 / 本地部署Qwen
- 调用方式: RESTful API / WebSocket
- 超时控制: 5s (超时返回降级方案)
5.3 本体语义层
本体元素:
实体 (Entity)
├─ 设备类
│ ├─ 采煤机 (CoalMiner)
│ ├─ 液压支架 (HydraulicSupport)
│ └─ 传送带 (Conveyor)
├─ 传感器类
│ ├─ 烟感 (SmokeSensor)
│ ├─ 温度传感器 (TempSensor)
│ └─ 压力传感器 (PressureSensor)
├─ 监控类
│ └─ 摄像头 (Camera)
└─ 空间类
├─ 工作面 (WorkFace)
├─ 巷道 (Tunnel)
└─ 控制室 (ControlRoom)
关系 (Relation)
├─ 空间关系
│ ├─ 位于 (locatedIn)
│ ├─ 相邻 (adjacentTo)
│ └─ 距离 (distanceTo)
├─ 工况关系
│ ├─ 影响 (affects)
│ ├─ 依赖 (dependsOn)
│ └─ 约束 (constrains)
└─ 监控关系
├─ 监测 (monitors)
└─ 控制 (controls)
状态 (State)
├─ 运行状态: 运行中/停止/待机/故障
├─ 健康状态: 正常/预警/异常/严重
├─ 风险等级: 低/中/高/紧急
└─ 操作权限: 可操作/只读/禁止
规则锚点 (Rule Anchor)
├─ 安全阈值
├─ 触发条件
├─ 联锁逻辑
└─ 应急预案
知识图谱示例 (Neo4j Cypher):
// 创建实体
CREATE (cm:CoalMiner {id:'cm_003', name:'3号采煤机', status:'运行中'})
CREATE (hy:HydraulicSupport {id:'hyd_017', name:'17号液压支架',
pressure:125, threshold:108, status:'压力异常'})
CREATE (sm:SmokeSensor {id:'smk_009', name:'9号烟感', status:'正常'})
CREATE (wf:WorkFace {id:'area_3', name:'3号工作面'})
// 创建关系
CREATE (cm)-[:LOCATED_IN]->(wf)
CREATE (hy)-[:LOCATED_IN]->(wf)
CREATE (sm)-[:LOCATED_IN]->(wf)
CREATE (hy)-[:AFFECTS {type:'工况约束', rule:'压力异常时禁止采煤'}]->(cm)
CREATE (sm)-[:MONITORS {range:10}]->(hy)
// 查询: 找到hyd_017的关联设备
MATCH (hy:HydraulicSupport {id:'hyd_017'})-[r:AFFECTS|MONITORS]-(related)
RETURN related, type(r), r
管理策略:
- 增量更新: 实时同步IoT数据
- 版本控制: 支持本体schema演进
- 审计追踪: 记录所有状态变更
- 权限控制: 不同角色看到不同视图
技术选型:
- 图数据库: Neo4j / JanusGraph
- 本体建模: OWL / RDF (可选)
- 查询接口: GraphQL / Cypher
- 缓存层: Redis (热数据)
5.4 决策与规则层
设计原则:
- 所有高风险决策必须由规则层完成
- 规则必须确定性、可审计、可回溯
- AI只能建议,规则层决定"能不能"
规则示例 (DSL):
# 规则1: 液压支架压力异常联锁
rule: hydraulic_pressure_interlock
trigger:
entity_type: HydraulicSupport
condition: pressure > threshold * 1.1
actions:
- set_risk_level: HIGH
- lock_related_devices:
type: CoalMiner
relation: AFFECTS
action: PROHIBIT_START
- generate_alert:
level: WARNING
message: "液压支架{entity.name}压力异常,已禁止采煤机启动"
- log_audit:
operator: SYSTEM
reason: "安全联锁触发"
# 规则2: 烟感报警应急
rule: smoke_alarm_emergency
trigger:
entity_type: SmokeSensor
condition: smoke_level > alarm_threshold
actions:
- set_risk_level: CRITICAL
- emergency_stop:
scope: AREA
devices: [CoalMiner, Conveyor]
- activate_ventilation: true
- notify_personnel:
roles: [安全员, 值班长]
method: SMS + APP
- lock_area_access: true
规则引擎实现 (Go示例):
type Rule struct {
ID string
Trigger Condition
Actions []Action
Priority int
}
type RuleEngine struct {
rules []Rule
ontology *OntologyService
auditLog *AuditLogger
}
func (re *RuleEngine) Evaluate(intent Intent) (Decision, error) {
// 1. 获取目标实体状态
entities := re.ontology.GetEntities(intent.TargetEntities)
// 2. 匹配规则
matchedRules := re.matchRules(entities, intent)
// 3. 按优先级执行
sort.Slice(matchedRules, func(i, j int) bool {
return matchedRules[i].Priority > matchedRules[j].Priority
})
decision := Decision{Allowed: true}
for _, rule := range matchedRules {
result := rule.Execute(entities)
if result.Block {
decision.Allowed = false
decision.Reason = result.Reason
break
}
decision.Actions = append(decision.Actions, result.Actions...)
}
// 4. 审计日志
re.auditLog.Record(intent, decision)
return decision, nil
}
技术选型:
- 规则引擎: Drools / 自研DSL
- 实现语言: Go (高性能) / Python (灵活)
- 持久化: PostgreSQL + Redis
- 审计: ELK Stack
5.5 执行/WASM层
为什么需要WASM?
在大型场景中,需要高性能计算:
- 物理仿真(碰撞检测、力学计算)
- 批量规则校验(1000+实体并发判断)
- 影响域分析(图遍历、最短路径)
- 几何计算(距离、可视域)
JavaScript性能不足,后端计算有网络延迟,WASM是最佳选择。
WASM应用场景:
-
影响域计算
// Rust示例 (编译为WASM) #[wasm_bindgen] pub fn calculate_impact_zone( entity_id: &str, radius: f32, entities: JsValue ) -> JsValue { let entities: Vec<Entity> = entities.into_serde().unwrap(); let target = entities.iter() .find(|e| e.id == entity_id) .unwrap(); let affected: Vec<&Entity> = entities.iter() .filter(|e| { let dist = distance(&target.position, &e.position); dist <= radius }) .collect(); JsValue::from_serde(&affected).unwrap() } -
压力仿真
#[wasm_bindgen] pub fn simulate_pressure_release( support_id: &str, release_rate: f32, duration: f32 ) -> SimulationResult { // 简化的物理模型 let initial_pressure = get_current_pressure(support_id); let final_pressure = initial_pressure * (1.0 - release_rate * duration).max(0.0); SimulationResult { final_pressure, is_safe: final_pressure < SAFETY_THRESHOLD, estimated_time: duration, } } -
批量状态检查
#[wasm_bindgen] pub fn batch_health_check(entities: JsValue) -> JsValue { let entities: Vec<Entity> = entities.into_serde().unwrap(); let results: Vec<HealthStatus> = entities .par_iter() // 并行处理 .map(|e| check_entity_health(e)) .collect(); JsValue::from_serde(&results).unwrap() }
性能对比:
| 操作 | JavaScript | WASM | 提升 |
|---|---|---|---|
| 1000实体距离计算 | 45ms | 3ms | 15x |
| 物理仿真(100步) | 120ms | 8ms | 15x |
| 图遍历(500节点) | 80ms | 6ms | 13x |
技术选型:
- 语言: Rust (首选) / AssemblyScript
- 工具链: wasm-pack / wasm-bindgen
- 调用方式: 前端直接调用 / 后端Worker
- 内存管理: 手动管理 / 引用计数
5.6 数字孪生可视化层
核心功能:
-
智能定位与高亮
// 根据AI输出自动定位 function visualizeAIResult(aiOutput) { // 1. 镜头飞行到目标区域 camera.flyTo({ target: getEntityPosition(aiOutput.target_entities[0]), duration: 1000, easing: 'easeInOutCubic' }); // 2. 高亮目标实体 aiOutput.target_entities.forEach(id => { const entity = scene.getEntity(id); entity.setHighlight({ color: getRiskColor(entity.riskLevel), intensity: 1.5, pulse: true }); }); // 3. 标注关联实体 aiOutput.related_entities.forEach(id => { const entity = scene.getEntity(id); entity.addLabel({ text: '受影响设备', color: '#FFA500', offset: [0, 2, 0] }); }); // 4. 显示AI解释 showExplanationPanel({ title: '分析结果', content: aiOutput.explanation, actions: aiOutput.suggested_actions }); } -
风险可视化
- 正常: 绿色
- 预警: 黄色 + 慢速脉冲
- 异常: 橙色 + 中速脉冲
- 严重: 红色 + 快速脉冲 + 警告图标
-
关联关系可视化
// 绘制实体间的影响关系 function drawRelations(targetId, relatedIds) { relatedIds.forEach(relatedId => { scene.addLine({ from: getEntityPosition(targetId), to: getEntityPosition(relatedId), color: '#FF6B6B', width: 2, style: 'dashed', animation: 'flow' // 流动动画 }); }); } -
历史回放
- 时间轴控制
- 状态变化动画
- 事件标记点
- 可导出视频
技术选型:
- 三维引擎: ThingJS (工业场景优化) / Three.js
- UI框架: Vue3 + Element Plus
- 图表: ECharts (趋势分析)
- 视频导出: CCapture.js
6. 关键技术实现细节
6.1 模糊指令处理流程
问题: 用户说"液压支架设备故障了,我想看看",如何找到具体设备?
解决方案:
Step 1: 前端轻量NLP (50ms)
├─ 提取关键词: ["液压支架", "故障", "看"]
├─ 识别意图类型: "查看状态"
└─ 识别设备类型: "HydraulicSupport"
Step 2: 场景上下文补全 (10ms)
├─ 当前区域: "3号工作面"
├─ 可见实体: [所有3号工作面的设备]
└─ 用户权限: ["查询", "查看历史"]
Step 3: 候选实体筛选 (30ms, WASM加速)
├─ 类型过滤: 12个液压支架
├─ 状态过滤: 2个异常状态 [hyd_017, hyd_023]
└─ 相关性排序: 按异常严重程度
Step 4: LLM精确理解 (300ms)
输入:
{
"user_input": "液压支架设备故障了,我想看看",
"candidates": [
{"id": "hyd_017", "status": "压力异常", "severity": "high"},
{"id": "hyd_023", "status": "通讯中断", "severity": "medium"}
]
}
输出:
{
"target_entities": ["hyd_017", "hyd_023"],
"primary_target": "hyd_017",
"related_entities": ["cm_003"],
"explanation": "发现2个故障:17号压力异常(优先级高),23号通讯中断"
}
Step 5: 可视化呈现 (100ms)
├─ 镜头飞到hyd_017
├─ hyd_017红色高亮, hyd_023橙色高亮
├─ cm_003标注"受影响"
└─ 显示AI解释 + 操作建议
总耗时: ~490ms
6.2 大场景性能优化
挑战: 1000+实体场景,如何保证<500ms响应?
优化策略:
-
空间索引 (R-Tree)
// 只查询可视范围内的实体 const visibleEntities = spatialIndex.query({ minX: camera.frustum.minX, minY: camera.frustum.minY, maxX: camera.frustum.maxX, maxY: camera.frustum.maxY }); // 从1000个降到50-100个 -
LOD (Level of Detail)
- 远距离: 简化模型 + 批量渲染
- 中距离: 标准模型
- 近距离: 高精度模型 + 细节纹理
-
增量更新
// 只更新变化的实体 const changedEntities = diffState(prevState, currentState); changedEntities.forEach(entity => { updateEntityVisual(entity); }); -
Web Worker并行
// 主线程: 渲染 // Worker 1: 前端裁剪 + WASM计算 // Worker 2: LLM调用 // Worker 3: 本体查询 Promise.all([ worker1.filterCandidates(input), worker2.callLLM(prompt), worker3.queryOntology(entityIds) ]).then(([candidates, aiResult, ontologyData]) => { // 合并结果 }); -
缓存策略
- 热数据: Redis (实体状态, 1s TTL)
- 本体schema: 内存缓存 (1h TTL)
- LLM结果: 相似问题缓存 (5min TTL)
6.3 安全边界控制
三层防护:
Layer 1: 前端白名单
├─ allowed_intents: 只允许安全操作
├─ forbidden_entities: 禁止操作的关键设备
└─ permission_check: 用户权限验证
Layer 2: LLM输出校验
├─ intent必须在白名单内
├─ target_entities必须在候选集内
├─ confidence < 0.7时要求用户确认
└─ 敏感操作二次确认
Layer 3: 规则引擎强制
├─ 高风险操作必须规则层批准
├─ 联锁逻辑强制执行
├─ 审计日志完整记录
└─ 异常自动熔断
示例:
// 前端校验
if (!allowedIntents.includes(aiOutput.intent)) {
throw new SecurityError('Intent not allowed');
}
// 后端规则校验
const decision = ruleEngine.evaluate(aiOutput);
if (!decision.allowed) {
return {
success: false,
reason: decision.reason,
requiresApproval: true
};
}
// 审计日志
auditLog.record({
timestamp: Date.now(),
user: currentUser,
intent: aiOutput.intent,
entities: aiOutput.target_entities,
decision: decision,
aiConfidence: aiOutput.confidence
});