@skaterqiang/protege-js 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/LICENSE +48 -0
- package/README.md +287 -0
- package/docs/API.md +654 -0
- package/docs/design/OWL2-/347/274/272/345/217/243/345/210/206/346/236/220.md +140 -0
- package/docs/owl2-rl/OWL2-RL-/350/247/204/345/210/231/347/273/206/345/210/231.md +168 -0
- package/docs/owl2-rl/owl2-profiles-spec-zh.html +1914 -0
- package/docs/owl2-rl/owl2-profiles-spec.html +1916 -0
- package/docs/owl2-rl/rl_rules.json +392 -0
- package/main.js +46 -0
- package/package.json +76 -0
- package/sample/README.md +74 -0
- package/sample/case1-ecommerce-risk.js +170 -0
- package/sample/case10-ecommerce-customer-service.js +454 -0
- package/sample/case2-family-swrl.js +147 -0
- package/sample/case3-pharma-safety.js +132 -0
- package/sample/case4-knowledge-publishing.js +111 -0
- package/sample/case5-obda-profiles.js +153 -0
- package/sample/case6-medical-ontology.js +152 -0
- package/sample/case7-manufacturing-ppr.js +155 -0
- package/sample/case8-real-medical-bfo-ogms.js +353 -0
- package/sample/case9-real-manufacturing-iao.js +360 -0
- package/sample/ontologies/bfo.owl +1715 -0
- package/sample/ontologies/iao.owl +8532 -0
- package/sample/ontologies/ogms.owl +4320 -0
- package/sample/ontologies/ro-core.owl +1010 -0
- package/scripts/verify-api.js +53 -0
- package/src/index.js +176 -0
- package/src/inference/OWL2ProfileReasoners.js +67 -0
- package/src/inference/OWL2RLReasoner.js +65 -0
- package/src/inference/ReasonerQueries.js +111 -0
- package/src/inference/SWRLReasoner.js +409 -0
- package/src/inference/TripleStore.js +82 -0
- package/src/inference/rdf.js +83 -0
- package/src/inference/rules/owl2el.js +33 -0
- package/src/inference/rules/owl2ql.js +29 -0
- package/src/inference/rules/owl2rl.js +823 -0
- package/src/io/FunctionalSyntaxParser.js +399 -0
- package/src/io/FunctionalSyntaxWriter.js +180 -0
- package/src/io/ManchesterSyntaxParser.js +398 -0
- package/src/io/OWLXMLParser.js +356 -0
- package/src/io/OntologyLoader.js +125 -0
- package/src/io/RDFGraphToOntology.js +702 -0
- package/src/io/RDFXMLParser.js +319 -0
- package/src/io/RDFXMLWriter.js +196 -0
- package/src/io/SWRLParser.js +150 -0
- package/src/io/TurtleParser.js +363 -0
- package/src/io/TurtleWriter.js +393 -0
- package/src/model/IRI.js +62 -0
- package/src/model/OWLAxiom.js +507 -0
- package/src/model/OWLClassExpression.js +242 -0
- package/src/model/OWLEntity.js +142 -0
- package/src/model/OWLLiteral.js +30 -0
- package/src/model/OWLModelManager.js +106 -0
- package/src/model/OWLOntology.js +119 -0
- package/src/model/SWRL.js +152 -0
- package/src/model/event/OWLModelManagerEvent.js +33 -0
- package/src/model/hierarchy/HierarchyProvider.js +214 -0
- package/src/profiles/OWL2Profiles.js +185 -0
- package/src/server/public/app.js +97 -0
- package/src/server/public/index.html +49 -0
- package/src/server/webServer.js +138 -0
- package/src/validation/GlobalRestrictionsValidator.js +172 -0
- package/test/core.test.js +111 -0
- package/test/exports-map.test.js +165 -0
- package/test/owl2/axioms.test.js +160 -0
- package/test/owl2/classExpressions.test.js +99 -0
- package/test/owl2/functional-syntax-writer.test.js +65 -0
- package/test/owl2/functional.test.js +113 -0
- package/test/owl2/global-restrictions.test.js +76 -0
- package/test/owl2/longtail.test.js +74 -0
- package/test/owl2/manchester-syntax.test.js +100 -0
- package/test/owl2/ontology-loader-imports.test.js +62 -0
- package/test/owl2/owlxml-parser.test.js +112 -0
- package/test/owl2/profile-reasoners.test.js +108 -0
- package/test/owl2/profiles.test.js +66 -0
- package/test/owl2/rdf-graph-to-ontology.test.js +177 -0
- package/test/owl2/rdfxml-punning.test.js +25 -0
- package/test/owl2/rdfxml-writer.test.js +55 -0
- package/test/owl2/rdfxml.test.js +91 -0
- package/test/owl2/reasoner-queries.test.js +58 -0
- package/test/owl2/sample-e2e.test.js +195 -0
- package/test/owl2/swrl-parser.test.js +60 -0
- package/test/owl2/swrl.test.js +131 -0
- package/test/owl2/turtle-writer.test.js +84 -0
- package/test/owl2/turtle.test.js +124 -0
- package/test/owl2rl/cax.test.js +57 -0
- package/test/owl2rl/cls.test.js +210 -0
- package/test/owl2rl/dt.test.js +72 -0
- package/test/owl2rl/eq.test.js +95 -0
- package/test/owl2rl/prp.test.js +198 -0
- package/test/owl2rl/scm.test.js +203 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ===========================================================================
|
|
4
|
+
// 业务案例 1:电商订单风控 —— 高风险订单识别(OWL 2 RL)
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// 【业务背景】
|
|
7
|
+
// 电商平台要在下单后实时识别「高风险订单」,交给人工审核,防止欺诈。
|
|
8
|
+
//
|
|
9
|
+
// 【业务规则】
|
|
10
|
+
// 1. 预售订单(PreOrder)、批发订单(WholesaleOrder) 都是 订单(Order) 的子类。
|
|
11
|
+
// 2. 黑名单买家(BlacklistedBuyer) 是 买家(Buyer) 的一种。
|
|
12
|
+
// 3. 规则:凡是被 黑名单买家 提交的订单,自动标记为 高风险订单(HighRiskOrder)。
|
|
13
|
+
// 用 OWL 表达:OrderSubmittedByBlacklisted ≡ submittedBy some BlacklistedBuyer,
|
|
14
|
+
// 且 OrderSubmittedByBlacklisted ⊑ HighRiskOrder。OWL 2 RL 的 cls-svf1 规则
|
|
15
|
+
// 可由 someValuesFrom 限制自动推出实例归类,无需手工挂中间类型。
|
|
16
|
+
// 4. 一致性约束:高风险订单 与 可信订单(TrustedOrder) 互斥(disjointWith)。
|
|
17
|
+
// 若同一订单既是高风险又是可信,则本体不一致,风控系统应报警。
|
|
18
|
+
//
|
|
19
|
+
// 【用到的 OWL 2 能力】
|
|
20
|
+
// rdfs:subClassOf (cax-sco) / rdf:type 实例分类 (cax-eqc) /
|
|
21
|
+
// owl:someValuesFrom + owl:onProperty (cls-svf1) / owl:equivalentClass /
|
|
22
|
+
// owl:disjointWith 冲突检测 (cax-dw) / OWL2RLReasoner.materialize
|
|
23
|
+
//
|
|
24
|
+
// 【预期结果】
|
|
25
|
+
// - ord-001(黑名单买家下单)被自动推导为 OrderSubmittedByBlacklisted → HighRiskOrder。
|
|
26
|
+
// - 把 ord-001 同时声明为 TrustedOrder 后,reasoner.isConsistent() = false。
|
|
27
|
+
// ===========================================================================
|
|
28
|
+
|
|
29
|
+
// 引入推理引擎核心组件
|
|
30
|
+
const { TripleStore } = require('../src/inference/TripleStore'); // 三元组存储:存放 RDF 主谓宾
|
|
31
|
+
const { OWL2RLReasoner } = require('../src/inference/OWL2RLReasoner'); // OWL 2 RL 推理机:执行规则物化
|
|
32
|
+
const { ReasonerQueries } = require('../src/inference/ReasonerQueries'); // 查询接口:读取推理结果
|
|
33
|
+
const { NS } = require('../src/inference/rdf'); // 命名空间常量:RDF/RDFS/OWL/XSD
|
|
34
|
+
|
|
35
|
+
// 简化命名空间引用,后面拼接 IRI 用
|
|
36
|
+
const RDF = NS.RDF, RDFS = NS.RDFS, OWL = NS.OWL;
|
|
37
|
+
// 本案例统一前缀:http://shop.example.com/risk#
|
|
38
|
+
const ex = (s) => 'http://shop.example.com/risk#' + s;
|
|
39
|
+
|
|
40
|
+
// 构建知识图谱(TBox 类层级 + ABox 实例 + OWL 限制)
|
|
41
|
+
function buildStore() {
|
|
42
|
+
const s = new TripleStore();
|
|
43
|
+
|
|
44
|
+
// ========== TBox:类层级与约束 ==========
|
|
45
|
+
// 预售订单是订单的子类
|
|
46
|
+
s.add(ex('PreOrder'), RDFS + 'subClassOf', ex('Order'));
|
|
47
|
+
// 批发订单是订单的子类
|
|
48
|
+
s.add(ex('WholesaleOrder'), RDFS + 'subClassOf', ex('Order'));
|
|
49
|
+
// 黑名单买家是买家的子类
|
|
50
|
+
s.add(ex('BlacklistedBuyer'), RDFS + 'subClassOf', ex('Buyer'));
|
|
51
|
+
// 高风险订单是订单的子类
|
|
52
|
+
s.add(ex('HighRiskOrder'), RDFS + 'subClassOf', ex('Order'));
|
|
53
|
+
// 高风险订单 与 可信订单 互斥(disjointWith)
|
|
54
|
+
s.add(ex('HighRiskOrder'), OWL + 'disjointWith', ex('TrustedOrder'));
|
|
55
|
+
|
|
56
|
+
// ========== ABox:实例数据 ==========
|
|
57
|
+
// 黑名单买家实例
|
|
58
|
+
s.add(ex('buyer-bad-9'), RDF + 'type', ex('BlacklistedBuyer'));
|
|
59
|
+
// ord-001 是一笔预售订单
|
|
60
|
+
s.add(ex('ord-001'), RDF + 'type', ex('PreOrder'));
|
|
61
|
+
// ord-001 由黑名单买家提交
|
|
62
|
+
s.add(ex('ord-001'), ex('submittedBy'), ex('buyer-bad-9'));
|
|
63
|
+
|
|
64
|
+
// ========== OWL 限制:业务规则形式化 ==========
|
|
65
|
+
// 核心规则:凡 submittedBy 指向某 BlacklistedBuyer 的订单 → 高风险
|
|
66
|
+
// 用 OWL someValuesFrom 表达:OrderSubmittedByBlacklisted ≡ submittedBy some BlacklistedBuyer
|
|
67
|
+
s.add(ex('OrderSubmittedByBlacklisted'), OWL + 'equivalentClass', ex('_restr_submittedByBlacklisted'));
|
|
68
|
+
// 限制:存在 submittedBy 指向 BlacklistedBuyer
|
|
69
|
+
s.add(ex('_restr_submittedByBlacklisted'), OWL + 'someValuesFrom', ex('BlacklistedBuyer'));
|
|
70
|
+
// 限制作用在 submittedBy 属性上
|
|
71
|
+
s.add(ex('_restr_submittedByBlacklisted'), OWL + 'onProperty', ex('submittedBy'));
|
|
72
|
+
// 被黑名单提交的订单 是 高风险订单 的子类
|
|
73
|
+
s.add(ex('OrderSubmittedByBlacklisted'), RDFS + 'subClassOf', ex('HighRiskOrder'));
|
|
74
|
+
|
|
75
|
+
// 一笔正常订单对照:批发订单,由普通买家提交
|
|
76
|
+
s.add(ex('ord-002'), RDF + 'type', ex('WholesaleOrder'));
|
|
77
|
+
s.add(ex('ord-002'), ex('submittedBy'), ex('buyer-good-1'));
|
|
78
|
+
s.add(ex('buyer-good-1'), RDF + 'type', ex('Buyer'));
|
|
79
|
+
|
|
80
|
+
return s;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 执行推理并返回业务结论
|
|
84
|
+
function run() {
|
|
85
|
+
// 构建知识图谱
|
|
86
|
+
const store = buildStore();
|
|
87
|
+
|
|
88
|
+
// 保存原始输入三元组快照(推理前),供打印输入区使用
|
|
89
|
+
const inputTriples = store.match(null, null, null).map(t => [t[0], t[1], t[2]]);
|
|
90
|
+
|
|
91
|
+
// 创建 OWL 2 RL 推理机并执行物化(把所有能推出的三元组都算出来)
|
|
92
|
+
const reasoner = new OWL2RLReasoner(store);
|
|
93
|
+
reasoner.materialize();
|
|
94
|
+
|
|
95
|
+
// 创建查询接口,读取推理结果
|
|
96
|
+
const q = new ReasonerQueries(reasoner);
|
|
97
|
+
|
|
98
|
+
// 1) ord-001 应被归类为 HighRiskOrder(经由 someValuesFrom → OrderSubmittedByBlacklisted ⊑ HighRiskOrder)
|
|
99
|
+
const isHighRisk = q.getTypes(ex('ord-001')).includes(ex('HighRiskOrder'));
|
|
100
|
+
|
|
101
|
+
// 2) ord-001 同时是 Order(PreOrder ⊑ Order,且 HighRiskOrder ⊑ Order)
|
|
102
|
+
const isOrder = q.getTypes(ex('ord-001')).includes(ex('Order'));
|
|
103
|
+
|
|
104
|
+
// 3) 正常订单 ord-002 不应是 HighRiskOrder(对照组)
|
|
105
|
+
const ord2HighRisk = q.getTypes(ex('ord-002')).includes(ex('HighRiskOrder'));
|
|
106
|
+
|
|
107
|
+
// 4) 黑名单买家被归类为 Buyer(BlacklistedBuyer ⊑ Buyer)
|
|
108
|
+
const badIsBuyer = q.getTypes(ex('buyer-bad-9')).includes(ex('Buyer'));
|
|
109
|
+
|
|
110
|
+
// 5) 一致性检查:此时本体应当一致(没有把 ord-001 同时声明为 TrustedOrder)
|
|
111
|
+
const consistentBefore = reasoner.isConsistent();
|
|
112
|
+
|
|
113
|
+
// 6) 制造冲突:把 ord-001 错误地标为 TrustedOrder → 应触发 disjoint 冲突
|
|
114
|
+
store.add(ex('ord-001'), RDF + 'type', ex('TrustedOrder'));
|
|
115
|
+
const r2 = new OWL2RLReasoner(store); // 重建推理机(因 store 已改)
|
|
116
|
+
r2.materialize();
|
|
117
|
+
const consistentAfter = r2.isConsistent(); // 现在应不一致
|
|
118
|
+
|
|
119
|
+
// 返回所有业务结论 + 查询接口 + 原始输入快照
|
|
120
|
+
return { isHighRisk, isOrder, ord2HighRisk, badIsBuyer, consistentBefore, consistentAfter, q, store, inputTriples };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 导出供测试与复用
|
|
124
|
+
module.exports = { run, buildStore, ex };
|
|
125
|
+
|
|
126
|
+
// ===========================================================================
|
|
127
|
+
// 直接运行时(node sample/case1-ecommerce-risk.js)打印详细输入/输出
|
|
128
|
+
// ===========================================================================
|
|
129
|
+
if (require.main === module) {
|
|
130
|
+
const r = run();
|
|
131
|
+
|
|
132
|
+
// 本案例命名空间前缀,用于缩短打印
|
|
133
|
+
const EX = 'http://shop.example.com/risk#';
|
|
134
|
+
// 把完整 IRI 缩成 ex:ClassName 形式
|
|
135
|
+
const short = (iri) => (typeof iri === 'string' ? iri.replace(EX, 'ex:') : iri);
|
|
136
|
+
// 只保留业务相关三元组:过滤掉推理机自动生成的 sameAs 噪音
|
|
137
|
+
const bizOnly = ([s, p, o]) =>
|
|
138
|
+
[s, p, o].every(x => typeof x === 'string') && // 三元组都是字符串
|
|
139
|
+
(s.startsWith(EX) || o.startsWith(EX)) && // 主语或宾语在本案例命名空间
|
|
140
|
+
!p.includes('sameAs'); // 排除 sameAs 自反噪音
|
|
141
|
+
|
|
142
|
+
// ========== 打印输入:TBox + ABox 原始三元组 ==========
|
|
143
|
+
console.log('================ 输入(TBox 类层级 + ABox 实例)================');
|
|
144
|
+
for (const [s, p, o] of r.inputTriples) {
|
|
145
|
+
if (bizOnly([s, p, o])) {
|
|
146
|
+
// 把命名空间缩写成常用前缀
|
|
147
|
+
const pp = p.replace('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdf:')
|
|
148
|
+
.replace('http://www.w3.org/2000/01/rdf-schema#', 'rdfs:')
|
|
149
|
+
.replace('http://www.w3.org/2002/07/owl#', 'owl:');
|
|
150
|
+
console.log(' ', short(s), pp, short(o));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ========== 打印推理后:各实例被归类的类型 ==========
|
|
155
|
+
console.log('\n================ 推理后:各实例被归类的类型 ================');
|
|
156
|
+
for (const inst of ['ord-001', 'ord-002', 'buyer-bad-9', 'buyer-good-1']) {
|
|
157
|
+
// 只保留本案例命名空间的类型,过滤掉 owl:Thing 等顶层噪音
|
|
158
|
+
const types = r.q.getTypes(ex(inst)).filter(t => t.startsWith(EX)).map(short);
|
|
159
|
+
console.log(' ', short(ex(inst)), '→', types.join(', '));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ========== 打印业务结论 ==========
|
|
163
|
+
console.log('\n================ 业务结论 ================');
|
|
164
|
+
console.log('ord-001 被识别为高风险订单 :', r.isHighRisk); // true
|
|
165
|
+
console.log('ord-001 仍是合法订单 :', r.isOrder); // true
|
|
166
|
+
console.log('ord-002 被误判为高风险 :', r.ord2HighRisk, '(应为 false)'); // false
|
|
167
|
+
console.log('黑名单买家被归类为 Buyer :', r.badIsBuyer); // true
|
|
168
|
+
console.log('冲突前本体一致 :', r.consistentBefore); // true
|
|
169
|
+
console.log('误标可信后触发不一致告警 :', !r.consistentAfter); // true(取反后)
|
|
170
|
+
}
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ===========================================================================
|
|
4
|
+
// 业务案例 10:电商客服智能工单分派 —— 真实 BFO/IAO 本体 + SWRL 技能路由
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// 【业务背景】
|
|
7
|
+
// 某电商平台客服中心每天收到数千条客户工单(退换货/物流投诉/支付问题),
|
|
8
|
+
// 需要按「问题类型 + 客户等级 + 时效要求」自动分派到合适的客服技能组,
|
|
9
|
+
// 并自动生成处理 SLA 承诺(金牌客户 4 小时响应、普通客户 24 小时响应)。
|
|
10
|
+
// 本案例把客服业务实体挂到真实公开标准本体上:
|
|
11
|
+
// - Customer / ServiceAgent / ServiceOrder(客户/客服/工单)
|
|
12
|
+
// → BFO_0000040 material entity(物质实体)
|
|
13
|
+
// - RoutingPlan(分派方案)→ IAO_0000104 plan specification(计划规范)
|
|
14
|
+
// - SLASpecification(时效承诺)→ IAO_0000033 directive information entity
|
|
15
|
+
// - RoutingLog(分派日志)→ IAO_0000310 document(文档)
|
|
16
|
+
// - IssueCategory / SkillGroup(问题分类/技能组)→ BFO_0000031 GDC
|
|
17
|
+
// SWRL 规则把「金牌客户 + 高优先级工单」自动路由到「金牌客服组」,
|
|
18
|
+
// RL 推理沿 BFO 层级把工单归因为 material entity。
|
|
19
|
+
//
|
|
20
|
+
// 【业务规则】
|
|
21
|
+
// 1. 客户分级:金牌客户(GoldCustomer) / 普通客户(RegularCustomer)。
|
|
22
|
+
// 2. 工单分类:退换货(ReturnExchange) / 物流(Logistics) / 支付(Payment)。
|
|
23
|
+
// 3. 技能组:金牌客服组(GoldTeam) / 通用客服组(GeneralTeam)。
|
|
24
|
+
// 4. SWRL 路由:GoldCustomer(?c) ∧ submitOrder(?c,?o) ∧ Priority(?o,high)
|
|
25
|
+
// ⇒ routeTo(?o, GoldTeam)。
|
|
26
|
+
// 5. RL 归因:工单/客户/客服沿 BFO 层级归为 material entity。
|
|
27
|
+
// 6. 一致性:同一工单不能同时 routeTo GoldTeam 与 GeneralTeam(disjoint)。
|
|
28
|
+
//
|
|
29
|
+
// 【扩展场景:知识图谱在电商客服中的深度应用】
|
|
30
|
+
// 除基础工单分类/路由外,本案例还覆盖以下真实业务场景:
|
|
31
|
+
//
|
|
32
|
+
// 场景A 根因分析(Root Cause Analysis):
|
|
33
|
+
// 工单→订单→SKU→批次→供应商,当某批次商品集中投诉时,沿图谱边
|
|
34
|
+
// 批量圈出所有受影响工单,定位质量根因并触发下架整改。
|
|
35
|
+
// SWRL规则:Ticket(?t) ∧ hasRootCause(?t,?batch) ∧ DefectBatch(?batch)
|
|
36
|
+
// ⇒ BatchAlert(?t)。
|
|
37
|
+
//
|
|
38
|
+
// 场景B 投诉升级预测(Escalation Risk Prediction):
|
|
39
|
+
// 高价值客户+情绪激动+重复进线+敏感问题类型 → 预测升级为舆情/监管投诉,
|
|
40
|
+
// 自动触发危机预案并升级专家组。SWRL规则:GoldCustomer(?c) ∧ Angry(?c)
|
|
41
|
+
// ∧ RepeatTicket(?t) ∧ SensitiveType(?t) ⇒ EscalationRisk(?t)。
|
|
42
|
+
//
|
|
43
|
+
// 场景C 跨渠道问题关联(Cross-Channel Issue Linking):
|
|
44
|
+
// 同一客户在App/电话/微博多渠道反复反馈同一问题,经OneID实体对齐
|
|
45
|
+
// 串成一条问题主线,客服可看到完整时间线,实现「问题只描述一次」。
|
|
46
|
+
// OWL2RL transitive:sameIssueAs传递关联跨渠道同源工单。
|
|
47
|
+
//
|
|
48
|
+
// 【用到的 OWL 2 能力】
|
|
49
|
+
// 真实 BFO/IAO 本体加载(parseRDFXML)/ SWRL 技能路由规则(ClassAtom +
|
|
50
|
+
// ObjectPropertyAtom + BuiltInAtom)/ SWRL 根因分析规则 / SWRL 升级预测规则 /
|
|
51
|
+
// OWL 2 RL disjoint 一致性 (cax-dw) / OWL 2 RL transitive (prp-trp) /
|
|
52
|
+
// writeRDFXML 导出工单分派记录 / RO-core has_participant 关联
|
|
53
|
+
//
|
|
54
|
+
// 【预期结果】
|
|
55
|
+
// - 金牌客户工单被 SWRL 路由到 GoldTeam,普通客户到 GeneralTeam。
|
|
56
|
+
// - 工单沿 BFO 层级归因为 material entity;分派方案归为 IAO plan specification。
|
|
57
|
+
// - 误标 GoldTeam+GeneralTeam 的工单触发 disjoint 不一致告警。
|
|
58
|
+
// - 批次缺陷工单一键圈出(场景A根因分析)。
|
|
59
|
+
// - 高危投诉自动标记升级风险(场景B升级预测)。
|
|
60
|
+
// - 跨渠道同源工单传递关联(场景C问题关联)。
|
|
61
|
+
// ===========================================================================
|
|
62
|
+
|
|
63
|
+
// 引入解析器、推理机与 SWRL 模型组件
|
|
64
|
+
const fs = require('fs'); // 文件系统:读取真实 OWL 文件
|
|
65
|
+
const path = require('path'); // 路径处理
|
|
66
|
+
const { parseRDFXML } = require('../src/io/RDFXMLParser'); // RDF/XML 解析器:加载 BFO/IAO
|
|
67
|
+
const { writeRDFXML } = require('../src/io/RDFXMLWriter'); // RDF/XML 序列化器:导出工单记录
|
|
68
|
+
const { TurtleParser } = require('../src/io/TurtleParser'); // Turtle 解析器:解析客服数据
|
|
69
|
+
const { triplesToOntology } = require('../src/io/RDFGraphToOntology'); // TripleStore → OWLOntology
|
|
70
|
+
const { TripleStore } = require('../src/inference/TripleStore'); // 三元组存储
|
|
71
|
+
const { OWL2RLReasoner } = require('../src/inference/OWL2RLReasoner'); // OWL 2 RL 推理机
|
|
72
|
+
const { SWRLReasoner } = require('../src/inference/SWRLReasoner'); // SWRL 前向链推理机
|
|
73
|
+
const {
|
|
74
|
+
SWRLRule, SWRLClassAtom, SWRLObjectPropertyAtom, SWRLDataPropertyAtom,
|
|
75
|
+
SWRLBuiltInAtom, SWRLVariable
|
|
76
|
+
} = require('../src/model/SWRL'); // SWRL 规则模型
|
|
77
|
+
const { OWLClass, OWLObjectProperty, OWLDataProperty, OWLNamedIndividual } = require('../src/model/OWLEntity'); // OWL 实体
|
|
78
|
+
const { IRI } = require('../src/model/IRI'); // IRI 构造器
|
|
79
|
+
const { NS } = require('../src/inference/rdf'); // 命名空间常量
|
|
80
|
+
|
|
81
|
+
// 简化命名空间引用
|
|
82
|
+
const RDF = NS.RDF, RDFS = NS.RDFS, OWL = NS.OWL, XSD = NS.XSD;
|
|
83
|
+
// 真实 OWL 文件所在目录:sample/ontologies/
|
|
84
|
+
const ONT_DIR = path.join(__dirname, 'ontologies');
|
|
85
|
+
// 本案例统一前缀:http://ecommerce.example.com/cs#
|
|
86
|
+
const ex = (s) => 'http://ecommerce.example.com/cs#' + s;
|
|
87
|
+
// BFO 标准类 IRI(顶层本体)
|
|
88
|
+
const BFO_MATERIAL_ENTITY = 'http://purl.obolibrary.org/obo/BFO_0000040'; // material entity
|
|
89
|
+
const BFO_GDC = 'http://purl.obolibrary.org/obo/BFO_0000031'; // generically dependent continuant
|
|
90
|
+
// IAO 标准类 IRI(信息构件本体)
|
|
91
|
+
const IAO_PLAN_SPEC = 'http://purl.obolibrary.org/obo/IAO_0000104'; // plan specification
|
|
92
|
+
const IAO_DIE = 'http://purl.obolibrary.org/obo/IAO_0000033'; // directive information entity
|
|
93
|
+
const IAO_DOCUMENT = 'http://purl.obolibrary.org/obo/IAO_0000310'; // document
|
|
94
|
+
// RO-core 标准关系 IRI
|
|
95
|
+
const RO_HAS_PARTICIPANT = 'http://purl.obolibrary.org/obo/RO_0000057'; // has participant
|
|
96
|
+
|
|
97
|
+
// 加载真实本体的类层级到 TripleStore
|
|
98
|
+
function loadHierarchy(ontPath) {
|
|
99
|
+
const ont = parseRDFXML(fs.readFileSync(ontPath, 'utf8'));
|
|
100
|
+
const store = new TripleStore();
|
|
101
|
+
for (const ax of ont.getAxioms()) {
|
|
102
|
+
if (ax.constructor.name === 'OWLSubClassOfAxiom') {
|
|
103
|
+
const sub = typeof ax.subClass === 'string' ? ax.subClass : (ax.subClass && ax.subClass.iri && ax.subClass.iri._iri);
|
|
104
|
+
const sup = typeof ax.superClass === 'string' ? ax.superClass : (ax.superClass && ax.superClass.iri && ax.superClass.iri._iri);
|
|
105
|
+
if (sub && sup && sub.startsWith('http') && sup.startsWith('http')) {
|
|
106
|
+
store.add(sub, RDFS + 'subClassOf', sup);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return store;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 电商客服业务数据(挂到真实 BFO/IAO 标准父类)
|
|
114
|
+
function buildCSStore() {
|
|
115
|
+
const s = new TripleStore();
|
|
116
|
+
|
|
117
|
+
// 加载真实 BFO 类层级
|
|
118
|
+
const bfoStore = loadHierarchy(path.join(ONT_DIR, 'bfo.owl'));
|
|
119
|
+
for (const [sub, p, sup] of bfoStore.match(null, RDFS + 'subClassOf', null)) {
|
|
120
|
+
s.add(sub, p, sup);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ========== TBox:客服业务类(含标准本体对齐) ==========
|
|
124
|
+
// 客户 ⊑ BFO material entity
|
|
125
|
+
s.add(ex('Customer'), RDFS + 'subClassOf', BFO_MATERIAL_ENTITY);
|
|
126
|
+
// 金牌客户 ⊑ 客户
|
|
127
|
+
s.add(ex('GoldCustomer'), RDFS + 'subClassOf', ex('Customer'));
|
|
128
|
+
// 普通客户 ⊑ 客户
|
|
129
|
+
s.add(ex('RegularCustomer'), RDFS + 'subClassOf', ex('Customer'));
|
|
130
|
+
// 客服 ⊑ BFO material entity
|
|
131
|
+
s.add(ex('ServiceAgent'), RDFS + 'subClassOf', BFO_MATERIAL_ENTITY);
|
|
132
|
+
// 工单 ⊑ BFO material entity
|
|
133
|
+
s.add(ex('ServiceOrder'), RDFS + 'subClassOf', BFO_MATERIAL_ENTITY);
|
|
134
|
+
// 问题分类 ⊑ BFO GDC(分类是一种信息实体)
|
|
135
|
+
s.add(ex('IssueCategory'), RDFS + 'subClassOf', BFO_GDC);
|
|
136
|
+
// 技能组 ⊑ BFO GDC
|
|
137
|
+
s.add(ex('SkillGroup'), RDFS + 'subClassOf', BFO_GDC);
|
|
138
|
+
// 分派方案 ⊑ IAO plan specification
|
|
139
|
+
s.add(ex('RoutingPlan'), RDFS + 'subClassOf', IAO_PLAN_SPEC);
|
|
140
|
+
// SLA 承诺 ⊑ IAO directive information entity
|
|
141
|
+
s.add(ex('SLASpecification'), RDFS + 'subClassOf', IAO_DIE);
|
|
142
|
+
// 分派日志 ⊑ IAO document
|
|
143
|
+
s.add(ex('RoutingLog'), RDFS + 'subClassOf', IAO_DOCUMENT);
|
|
144
|
+
// 批次告警标记类(场景A根因分析用)
|
|
145
|
+
s.add(ex('BatchAlert'), RDF + 'type', OWL + 'Class');
|
|
146
|
+
s.add(ex('BatchAlert'), RDFS + 'subClassOf', BFO_GDC);
|
|
147
|
+
|
|
148
|
+
// 金牌客服组工单 与 通用客服组工单 互斥(同一工单不能同时属于两类)
|
|
149
|
+
s.add(ex('GoldTeamOrder'), OWL + 'disjointWith', ex('GeneralTeamOrder'));
|
|
150
|
+
s.add(ex('GoldTeamOrder'), RDFS + 'subClassOf', ex('ServiceOrder'));
|
|
151
|
+
s.add(ex('GeneralTeamOrder'), RDFS + 'subClassOf', ex('ServiceOrder'));
|
|
152
|
+
|
|
153
|
+
// 对象属性
|
|
154
|
+
s.add(ex('submitOrder'), RDF + 'type', OWL + 'ObjectProperty');
|
|
155
|
+
s.add(ex('routeTo'), RDF + 'type', OWL + 'ObjectProperty');
|
|
156
|
+
s.add(ex('hasIssue'), RDF + 'type', OWL + 'ObjectProperty');
|
|
157
|
+
s.add(ex('belongsToTeam'), RDF + 'type', OWL + 'ObjectProperty');
|
|
158
|
+
// 场景A/B/C 扩展属性
|
|
159
|
+
s.add(ex('hasRootCause'), RDF + 'type', OWL + 'ObjectProperty'); // 工单→根因批次
|
|
160
|
+
s.add(ex('suppliedBy'), RDF + 'type', OWL + 'ObjectProperty'); // 批次→供应商
|
|
161
|
+
s.add(ex('fromChannel'), RDF + 'type', OWL + 'ObjectProperty'); // 工单→渠道
|
|
162
|
+
s.add(ex('sameIssueAs'), RDF + 'type', OWL + 'ObjectProperty'); // 工单→同源工单
|
|
163
|
+
// 数据属性:优先级(gold=1, normal=2)
|
|
164
|
+
s.add(ex('priority'), RDF + 'type', OWL + 'DatatypeProperty');
|
|
165
|
+
|
|
166
|
+
// ========== ABox:客户/工单/技能组实例 ==========
|
|
167
|
+
// 金牌客户
|
|
168
|
+
s.add(ex('cust-gold-001'), RDF + 'type', ex('GoldCustomer'));
|
|
169
|
+
// 普通客户
|
|
170
|
+
s.add(ex('cust-normal-002'), RDF + 'type', ex('RegularCustomer'));
|
|
171
|
+
// 金牌客户提交的工单(优先级 1=高)
|
|
172
|
+
s.add(ex('order-gold-001'), RDF + 'type', ex('ServiceOrder'));
|
|
173
|
+
s.add(ex('cust-gold-001'), ex('submitOrder'), ex('order-gold-001'));
|
|
174
|
+
s.add(ex('order-gold-001'), ex('priority'), '"1"^^<' + XSD + 'integer>');
|
|
175
|
+
// 普通客户提交的工单(优先级 2=普通)
|
|
176
|
+
s.add(ex('order-normal-002'), RDF + 'type', ex('ServiceOrder'));
|
|
177
|
+
s.add(ex('cust-normal-002'), ex('submitOrder'), ex('order-normal-002'));
|
|
178
|
+
s.add(ex('order-normal-002'), ex('priority'), '"2"^^<' + XSD + 'integer>');
|
|
179
|
+
// 技能组实例
|
|
180
|
+
s.add(ex('team-gold'), RDF + 'type', ex('GoldTeam'));
|
|
181
|
+
s.add(ex('team-general'), RDF + 'type', ex('GeneralTeam'));
|
|
182
|
+
// 客服属于技能组
|
|
183
|
+
s.add(ex('agent-alice'), ex('belongsToTeam'), ex('team-gold'));
|
|
184
|
+
s.add(ex('agent-bob'), ex('belongsToTeam'), ex('team-general'));
|
|
185
|
+
// RO-core 关联:分派过程 has participant 客服
|
|
186
|
+
s.add(ex('routing-process-001'), RO_HAS_PARTICIPANT, ex('agent-alice'));
|
|
187
|
+
|
|
188
|
+
// ========== 场景A:根因分析(批次→供应商→受影响工单) ==========
|
|
189
|
+
// 缺陷批次类
|
|
190
|
+
s.add(ex('DefectBatch'), RDF + 'type', OWL + 'Class');
|
|
191
|
+
s.add(ex('DefectBatch'), RDFS + 'subClassOf', BFO_GDC);
|
|
192
|
+
// 批次实例
|
|
193
|
+
s.add(ex('batch-2024-08'), RDF + 'type', ex('DefectBatch'));
|
|
194
|
+
// 工单根因:ticket → batch
|
|
195
|
+
s.add(ex('ticket-rc-001'), RDF + 'type', ex('ServiceOrder'));
|
|
196
|
+
s.add(ex('ticket-rc-001'), ex('hasRootCause'), ex('batch-2024-08'));
|
|
197
|
+
s.add(ex('ticket-rc-002'), RDF + 'type', ex('ServiceOrder'));
|
|
198
|
+
s.add(ex('ticket-rc-002'), ex('hasRootCause'), ex('batch-2024-08'));
|
|
199
|
+
s.add(ex('ticket-rc-003'), RDF + 'type', ex('ServiceOrder'));
|
|
200
|
+
s.add(ex('ticket-rc-003'), ex('hasRootCause'), ex('batch-normal-01')); // 正常批次
|
|
201
|
+
// 批次→供应商
|
|
202
|
+
s.add(ex('supplier-x'), RDF + 'type', BFO_MATERIAL_ENTITY);
|
|
203
|
+
s.add(ex('batch-2024-08'), ex('suppliedBy'), ex('supplier-x'));
|
|
204
|
+
|
|
205
|
+
// ========== 场景B:投诉升级预测(情绪+重复+敏感+高价值) ==========
|
|
206
|
+
// 情绪标签
|
|
207
|
+
s.add(ex('Angry'), RDF + 'type', OWL + 'Class');
|
|
208
|
+
// 敏感问题类型
|
|
209
|
+
s.add(ex('SensitiveType'), RDF + 'type', OWL + 'Class');
|
|
210
|
+
s.add(ex('SensitiveType'), RDFS + 'subClassOf', ex('IssueCategory'));
|
|
211
|
+
// 升级风险标记
|
|
212
|
+
s.add(ex('EscalationRisk'), RDF + 'type', OWL + 'Class');
|
|
213
|
+
s.add(ex('EscalationRisk'), RDFS + 'subClassOf', BFO_GDC);
|
|
214
|
+
// 高危客户:金牌+情绪激动
|
|
215
|
+
s.add(ex('cust-vip-angry'), RDF + 'type', ex('GoldCustomer'));
|
|
216
|
+
s.add(ex('cust-vip-angry'), RDF + 'type', ex('Angry'));
|
|
217
|
+
// 重复进线工单(7天内第3次)
|
|
218
|
+
s.add(ex('ticket-esc-001'), RDF + 'type', ex('ServiceOrder'));
|
|
219
|
+
s.add(ex('cust-vip-angry'), ex('submitOrder'), ex('ticket-esc-001'));
|
|
220
|
+
s.add(ex('ticket-esc-001'), ex('priority'), '"1"^^<' + XSD + 'integer>');
|
|
221
|
+
s.add(ex('ticket-esc-001'), ex('hasIssue'), ex('sensitive-food'));
|
|
222
|
+
s.add(ex('sensitive-food'), RDF + 'type', ex('SensitiveType'));
|
|
223
|
+
// 重复工单标记(sameIssueAs 传递关联)
|
|
224
|
+
s.add(ex('ticket-esc-001'), ex('sameIssueAs'), ex('ticket-esc-002'));
|
|
225
|
+
s.add(ex('ticket-esc-002'), RDF + 'type', ex('ServiceOrder'));
|
|
226
|
+
s.add(ex('ticket-esc-002'), ex('sameIssueAs'), ex('ticket-esc-003'));
|
|
227
|
+
s.add(ex('ticket-esc-003'), RDF + 'type', ex('ServiceOrder'));
|
|
228
|
+
|
|
229
|
+
// ========== 场景C:跨渠道问题关联(OneID + sameIssueAs传递) ==========
|
|
230
|
+
// 渠道
|
|
231
|
+
s.add(ex('channel-app'), RDF + 'type', BFO_GDC);
|
|
232
|
+
s.add(ex('channel-phone'), RDF + 'type', BFO_GDC);
|
|
233
|
+
s.add(ex('channel-weibo'), RDF + 'type', BFO_GDC);
|
|
234
|
+
// 同一客户多渠道工单
|
|
235
|
+
s.add(ex('ticket-app-001'), RDF + 'type', ex('ServiceOrder'));
|
|
236
|
+
s.add(ex('ticket-app-001'), ex('fromChannel'), ex('channel-app'));
|
|
237
|
+
s.add(ex('ticket-app-001'), ex('sameIssueAs'), ex('ticket-phone-001'));
|
|
238
|
+
s.add(ex('ticket-phone-001'), RDF + 'type', ex('ServiceOrder'));
|
|
239
|
+
s.add(ex('ticket-phone-001'), ex('fromChannel'), ex('channel-phone'));
|
|
240
|
+
s.add(ex('ticket-phone-001'), ex('sameIssueAs'), ex('ticket-weibo-001'));
|
|
241
|
+
s.add(ex('ticket-weibo-001'), RDF + 'type', ex('ServiceOrder'));
|
|
242
|
+
s.add(ex('ticket-weibo-001'), ex('fromChannel'), ex('channel-weibo'));
|
|
243
|
+
// sameIssueAs 声明为传递属性(OWL 2 RL prp-trp)
|
|
244
|
+
s.add(ex('sameIssueAs'), RDF + 'type', OWL + 'TransitiveProperty');
|
|
245
|
+
|
|
246
|
+
return s;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// SWRL 路由规则:金牌客户 + 高优先级工单 → 路由到金牌客服组
|
|
250
|
+
function routingRules() {
|
|
251
|
+
const cls = (s) => new OWLClass(IRI.create(ex(s)));
|
|
252
|
+
const op = (s) => new OWLObjectProperty(IRI.create(ex(s)));
|
|
253
|
+
const dp = (s) => new OWLDataProperty(IRI.create(ex(s)));
|
|
254
|
+
const ind = (s) => new OWLNamedIndividual(IRI.create(ex(s)));
|
|
255
|
+
const v = (n) => new SWRLVariable(IRI.create('urn:swrl:var:' + n));
|
|
256
|
+
const intLit = (n) => ({ lexicalValue: String(n), datatype: { iri: IRI.create(XSD + 'integer') } });
|
|
257
|
+
const SWRLB = 'http://www.w3.org/2003/11/swrlb#';
|
|
258
|
+
|
|
259
|
+
// 规则1:金牌客户 + 优先级=1 → 路由到金牌客服组
|
|
260
|
+
// body: GoldCustomer(?c) ∧ submitOrder(?c,?o) ∧ priority(?o,1)
|
|
261
|
+
// head: routeTo(?o, team-gold)
|
|
262
|
+
const goldRoute = new SWRLRule(
|
|
263
|
+
[
|
|
264
|
+
new SWRLClassAtom(cls('GoldCustomer'), v('c')),
|
|
265
|
+
new SWRLObjectPropertyAtom(op('submitOrder'), v('c'), v('o')),
|
|
266
|
+
new SWRLDataPropertyAtom(dp('priority'), v('o'), intLit(1))
|
|
267
|
+
],
|
|
268
|
+
[new SWRLObjectPropertyAtom(op('routeTo'), v('o'), ind('team-gold'))]
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// 规则2:普通客户 + 优先级>=2 → 路由到通用客服组
|
|
272
|
+
// body: RegularCustomer(?c) ∧ submitOrder(?c,?o) ∧ priority(?o,?p) ∧ greaterThanOrEqual(?p,2)
|
|
273
|
+
// head: routeTo(?o, team-general)
|
|
274
|
+
const normalRoute = new SWRLRule(
|
|
275
|
+
[
|
|
276
|
+
new SWRLClassAtom(cls('RegularCustomer'), v('c')),
|
|
277
|
+
new SWRLObjectPropertyAtom(op('submitOrder'), v('c'), v('o')),
|
|
278
|
+
new SWRLDataPropertyAtom(dp('priority'), v('o'), v('p')),
|
|
279
|
+
new SWRLBuiltInAtom(IRI.create(SWRLB + 'greaterThanOrEqual'), [v('p'), intLit(2)])
|
|
280
|
+
],
|
|
281
|
+
[new SWRLObjectPropertyAtom(op('routeTo'), v('o'), ind('team-general'))]
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
return [goldRoute, normalRoute];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// SWRL 根因分析规则:批次缺陷 → 批量圈出受影响工单
|
|
288
|
+
function rootCauseRules() {
|
|
289
|
+
const cls = (s) => new OWLClass(IRI.create(ex(s)));
|
|
290
|
+
const op = (s) => new OWLObjectProperty(IRI.create(ex(s)));
|
|
291
|
+
const v = (n) => new SWRLVariable(IRI.create('urn:swrl:var:' + n));
|
|
292
|
+
|
|
293
|
+
// 规则3:工单根因是缺陷批次 → 标记为批次告警
|
|
294
|
+
// body: ServiceOrder(?t) ∧ hasRootCause(?t,?b) ∧ DefectBatch(?b)
|
|
295
|
+
// head: BatchAlert(?t)
|
|
296
|
+
const batchAlert = new SWRLRule(
|
|
297
|
+
[
|
|
298
|
+
new SWRLClassAtom(cls('ServiceOrder'), v('t')),
|
|
299
|
+
new SWRLObjectPropertyAtom(op('hasRootCause'), v('t'), v('b')),
|
|
300
|
+
new SWRLClassAtom(cls('DefectBatch'), v('b'))
|
|
301
|
+
],
|
|
302
|
+
[new SWRLClassAtom(cls('BatchAlert'), v('t'))]
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
return [batchAlert];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// SWRL 升级预测规则:高价值+情绪+重复+敏感 → 升级风险
|
|
309
|
+
function escalationRules() {
|
|
310
|
+
const cls = (s) => new OWLClass(IRI.create(ex(s)));
|
|
311
|
+
const op = (s) => new OWLObjectProperty(IRI.create(ex(s)));
|
|
312
|
+
const v = (n) => new SWRLVariable(IRI.create('urn:swrl:var:' + n));
|
|
313
|
+
|
|
314
|
+
// 规则4:金牌客户 ∧ 情绪激动 ∧ 重复工单 ∧ 敏感类型 → 升级风险
|
|
315
|
+
// body: GoldCustomer(?c) ∧ Angry(?c) ∧ submitOrder(?c,?t) ∧
|
|
316
|
+
// sameIssueAs(?t,?t2) ∧ hasIssue(?t,?issue) ∧ SensitiveType(?issue)
|
|
317
|
+
// head: EscalationRisk(?t)
|
|
318
|
+
const escalation = new SWRLRule(
|
|
319
|
+
[
|
|
320
|
+
new SWRLClassAtom(cls('GoldCustomer'), v('c')),
|
|
321
|
+
new SWRLClassAtom(cls('Angry'), v('c')),
|
|
322
|
+
new SWRLObjectPropertyAtom(op('submitOrder'), v('c'), v('t')),
|
|
323
|
+
new SWRLObjectPropertyAtom(op('sameIssueAs'), v('t'), v('t2')),
|
|
324
|
+
new SWRLObjectPropertyAtom(op('hasIssue'), v('t'), v('issue')),
|
|
325
|
+
new SWRLClassAtom(cls('SensitiveType'), v('issue'))
|
|
326
|
+
],
|
|
327
|
+
[new SWRLClassAtom(cls('EscalationRisk'), v('t'))]
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// 规则5:关联工单也传递升级风险(sameIssueAs 对称)
|
|
331
|
+
// body: EscalationRisk(?t1) ∧ sameIssueAs(?t1,?t2)
|
|
332
|
+
// head: EscalationRisk(?t2)
|
|
333
|
+
const escalatePropagate = new SWRLRule(
|
|
334
|
+
[
|
|
335
|
+
new SWRLClassAtom(cls('EscalationRisk'), v('t1')),
|
|
336
|
+
new SWRLObjectPropertyAtom(op('sameIssueAs'), v('t1'), v('t2'))
|
|
337
|
+
],
|
|
338
|
+
[new SWRLClassAtom(cls('EscalationRisk'), v('t2'))]
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
return [escalation, escalatePropagate];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 执行真实本体加载、SWRL 路由、RL 推理与导出
|
|
345
|
+
function run() {
|
|
346
|
+
const store = buildCSStore();
|
|
347
|
+
|
|
348
|
+
// 1) SWRL 路由:金牌/普通客户工单分派到对应技能组
|
|
349
|
+
const swrl = new SWRLReasoner(store);
|
|
350
|
+
const newFacts = swrl.run(routingRules());
|
|
351
|
+
|
|
352
|
+
// 验证路由结果
|
|
353
|
+
const goldRouted = store.has(ex('order-gold-001'), ex('routeTo'), ex('team-gold')); // 金牌工单→金牌组
|
|
354
|
+
const normalRouted = store.has(ex('order-normal-002'), ex('routeTo'), ex('team-general')); // 普通工单→通用组
|
|
355
|
+
|
|
356
|
+
// 2) RL 推理:沿 BFO 层级归因
|
|
357
|
+
const rl = new OWL2RLReasoner(store);
|
|
358
|
+
rl.materialize();
|
|
359
|
+
const orderIsMaterialEntity = store.has(ex('order-gold-001'), RDF + 'type', BFO_MATERIAL_ENTITY); // 工单→material entity
|
|
360
|
+
const custIsMaterialEntity = store.has(ex('cust-gold-001'), RDF + 'type', BFO_MATERIAL_ENTITY); // 客户→material entity
|
|
361
|
+
const planIsPlanSpec = store.has(ex('RoutingPlan'), RDFS + 'subClassOf', IAO_PLAN_SPEC); // 分派方案→plan specification
|
|
362
|
+
const slaIsDIE = store.has(ex('SLASpecification'), RDFS + 'subClassOf', IAO_DIE); // SLA→directive information entity
|
|
363
|
+
const logIsDocument = store.has(ex('RoutingLog'), RDFS + 'subClassOf', IAO_DOCUMENT); // 日志→document
|
|
364
|
+
|
|
365
|
+
// 3) 一致性:初始应一致;同一工单同时标记为金牌组和通用组工单触发 disjoint 冲突
|
|
366
|
+
const consistentBefore = rl.isConsistent();
|
|
367
|
+
// 制造冲突:order-gold-001 已归为 GoldTeamOrder,再误标为 GeneralTeamOrder
|
|
368
|
+
store.add(ex('order-gold-001'), RDF + 'type', ex('GoldTeamOrder'));
|
|
369
|
+
store.add(ex('order-gold-001'), RDF + 'type', ex('GeneralTeamOrder'));
|
|
370
|
+
const r2 = new OWL2RLReasoner(store);
|
|
371
|
+
r2.materialize();
|
|
372
|
+
const consistentAfter = r2.isConsistent();
|
|
373
|
+
const conflictFound = r2.inconsistencies.length > 0;
|
|
374
|
+
|
|
375
|
+
// 4) 场景A:根因分析 —— SWRL 规则圈出缺陷批次工单
|
|
376
|
+
const rcSwrl = new SWRLReasoner(store);
|
|
377
|
+
rcSwrl.run(rootCauseRules());
|
|
378
|
+
const rcAlert1 = store.has(ex('ticket-rc-001'), RDF + 'type', ex('BatchAlert')); // 缺陷批次工单应告警
|
|
379
|
+
const rcAlert2 = store.has(ex('ticket-rc-002'), RDF + 'type', ex('BatchAlert')); // 同批次也应告警
|
|
380
|
+
const rcAlert3 = store.has(ex('ticket-rc-003'), RDF + 'type', ex('BatchAlert')); // 正常批次不应告警
|
|
381
|
+
const rcSupplierLinked = store.has(ex('batch-2024-08'), ex('suppliedBy'), ex('supplier-x')); // 批次→供应商关联
|
|
382
|
+
|
|
383
|
+
// 5) 场景B:升级预测 —— SWRL 规则标记高危工单
|
|
384
|
+
const escSwrl = new SWRLReasoner(store);
|
|
385
|
+
escSwrl.run(escalationRules());
|
|
386
|
+
const escRisk = store.has(ex('ticket-esc-001'), RDF + 'type', ex('EscalationRisk')); // 高危工单应标记升级风险
|
|
387
|
+
const escNotRisk = store.has(ex('ticket-esc-002'), RDF + 'type', ex('EscalationRisk')); // 关联工单也应传递风险
|
|
388
|
+
|
|
389
|
+
// 6) 场景C:跨渠道关联 —— RL transitive 传递 sameIssueAs
|
|
390
|
+
const ccRl = new OWL2RLReasoner(store);
|
|
391
|
+
ccRl.materialize();
|
|
392
|
+
const crossChannelLinked = store.has(ex('ticket-app-001'), ex('sameIssueAs'), ex('ticket-weibo-001')); // 传递关联
|
|
393
|
+
const appChannel = store.has(ex('ticket-app-001'), ex('fromChannel'), ex('channel-app'));
|
|
394
|
+
const weiboChannel = store.has(ex('ticket-weibo-001'), ex('fromChannel'), ex('channel-weibo'));
|
|
395
|
+
|
|
396
|
+
// 7) 导出工单分派记录为 RDF/XML
|
|
397
|
+
const ont = triplesToOntology(store);
|
|
398
|
+
const xmlOut = writeRDFXML(ont);
|
|
399
|
+
const exported = xmlOut.trim().startsWith('<?xml');
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
newFacts, goldRouted, normalRouted,
|
|
403
|
+
orderIsMaterialEntity, custIsMaterialEntity,
|
|
404
|
+
planIsPlanSpec, slaIsDIE, logIsDocument,
|
|
405
|
+
consistentBefore, consistentAfter, conflictFound,
|
|
406
|
+
// 场景A:根因分析
|
|
407
|
+
rcAlert1, rcAlert2, rcAlert3, rcSupplierLinked,
|
|
408
|
+
// 场景B:升级预测
|
|
409
|
+
escRisk, escNotRisk,
|
|
410
|
+
// 场景C:跨渠道关联
|
|
411
|
+
crossChannelLinked, appChannel, weiboChannel,
|
|
412
|
+
exported, store
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// 导出供测试与复用
|
|
417
|
+
module.exports = { run, buildCSStore, routingRules, rootCauseRules, escalationRules, ex };
|
|
418
|
+
|
|
419
|
+
// 直接运行时打印业务结论
|
|
420
|
+
if (require.main === module) {
|
|
421
|
+
const r = run();
|
|
422
|
+
console.log('================ SWRL 工单分派 ================');
|
|
423
|
+
console.log('SWRL 推理新增事实数 :', r.newFacts); // > 0
|
|
424
|
+
console.log('金牌工单路由到金牌客服组 :', r.goldRouted); // true
|
|
425
|
+
console.log('普通工单路由到通用客服组 :', r.normalRouted); // true
|
|
426
|
+
console.log('');
|
|
427
|
+
console.log('================ BFO/IAO 层级归因 ================');
|
|
428
|
+
console.log('工单归为 BFO material entity:', r.orderIsMaterialEntity); // true
|
|
429
|
+
console.log('客户归为 BFO material entity:', r.custIsMaterialEntity); // true
|
|
430
|
+
console.log('分派方案归为 IAO plan spec :', r.planIsPlanSpec); // true
|
|
431
|
+
console.log('SLA 归为 IAO directive info :', r.slaIsDIE); // true
|
|
432
|
+
console.log('分派日志归为 IAO document :', r.logIsDocument); // true
|
|
433
|
+
console.log('');
|
|
434
|
+
console.log('================ 场景A:根因分析 ================');
|
|
435
|
+
console.log('缺陷批次工单1告警 :', r.rcAlert1); // true
|
|
436
|
+
console.log('缺陷批次工单2告警 :', r.rcAlert2); // true
|
|
437
|
+
console.log('正常批次工单不告警 :', !r.rcAlert3); // true
|
|
438
|
+
console.log('批次→供应商关联 :', r.rcSupplierLinked); // true
|
|
439
|
+
console.log('');
|
|
440
|
+
console.log('================ 场景B:投诉升级预测 ================');
|
|
441
|
+
console.log('高危工单标记升级风险 :', r.escRisk); // true
|
|
442
|
+
console.log('关联工单传递升级风险 :', r.escNotRisk); // true
|
|
443
|
+
console.log('');
|
|
444
|
+
console.log('================ 场景C:跨渠道问题关联 ================');
|
|
445
|
+
console.log('App→微博传递关联 :', r.crossChannelLinked); // true
|
|
446
|
+
console.log('App渠道工单 :', r.appChannel); // true
|
|
447
|
+
console.log('微博渠道工单 :', r.weiboChannel); // true
|
|
448
|
+
console.log('');
|
|
449
|
+
console.log('================ 一致性与导出 ================');
|
|
450
|
+
console.log('初始本体一致性 :', r.consistentBefore); // true
|
|
451
|
+
console.log('误标两组后一致性 :', r.consistentAfter); // false
|
|
452
|
+
console.log('推理机报告冲突 :', r.conflictFound); // true
|
|
453
|
+
console.log('分派记录可导出为 RDF/XML :', r.exported); // true
|
|
454
|
+
}
|