@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,147 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ===========================================================================
|
|
4
|
+
// 业务案例 2:家庭关系推理 —— SWRL 规则推导「叔叔」与「成年」
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// 【业务背景】
|
|
7
|
+
// 家谱/社交应用需要从一个家庭的显式关系里,自动推导出隐含亲属关系,
|
|
8
|
+
// 并根据年龄自动判断是否为「成年人」,用于权限与内容分级。
|
|
9
|
+
//
|
|
10
|
+
// 【业务规则】
|
|
11
|
+
// 1. 叔叔规则:x 的父亲/母亲是 y,y 的兄弟是 z ⇒ z 是 x 的叔叔(hasUncle)。
|
|
12
|
+
// 2. 成年规则:p 的年龄 age >= 18 ⇒ p 是 成年人(Adult)。(SWRL builtin)
|
|
13
|
+
// 3. 家庭关系:hasParent 的逆关系是 hasChild(owl:inverseOf)。
|
|
14
|
+
// 4. 标准本体对齐:Person ⊑ BFO material entity(人是一种物质实体)。
|
|
15
|
+
//
|
|
16
|
+
// 【用到的 OWL 2 能力】
|
|
17
|
+
// SWRL 规则(ClassAtom + ObjectPropertyAtom + DataPropertyAtom + BuiltInAtom)
|
|
18
|
+
// owl:inverseOf (prp-inv1/inv2) / SWRLReasoner 前向链执行
|
|
19
|
+
// BFO 顶层本体导入(Person ⊑ BFO_0000040 material entity)
|
|
20
|
+
//
|
|
21
|
+
// 【预期结果】
|
|
22
|
+
// - 由 alice -hasParent-> bob,bob -hasBrother-> carol,推出 alice -hasUncle-> carol。
|
|
23
|
+
// - bob 年龄 40 ⇒ 推出 bob 是 Adult;carol 年龄 12 ⇒ 不是 Adult。
|
|
24
|
+
// - alice/bob/carol 沿 BFO 层级被归因为 material entity。
|
|
25
|
+
// ===========================================================================
|
|
26
|
+
|
|
27
|
+
// 引入推理引擎与 SWRL 模型组件
|
|
28
|
+
const fs = require('fs'); // 文件系统:读取 BFO 本体
|
|
29
|
+
const path = require('path'); // 路径处理
|
|
30
|
+
const { parseRDFXML } = require('../src/io/RDFXMLParser'); // RDF/XML 解析器
|
|
31
|
+
const { TripleStore } = require('../src/inference/TripleStore');
|
|
32
|
+
const {
|
|
33
|
+
SWRLRule, SWRLClassAtom, SWRLObjectPropertyAtom, SWRLDataPropertyAtom,
|
|
34
|
+
SWRLBuiltInAtom, SWRLVariable
|
|
35
|
+
} = require('../src/model/SWRL'); // SWRL 规则模型(规则体/规则头原子)
|
|
36
|
+
const { SWRLReasoner } = require('../src/inference/SWRLReasoner'); // SWRL 前向链推理机
|
|
37
|
+
const { OWLClass, OWLObjectProperty, OWLDataProperty } = require('../src/model/OWLEntity'); // OWL 实体
|
|
38
|
+
const { IRI } = require('../src/model/IRI'); // IRI 构造器
|
|
39
|
+
const { NS } = require('../src/inference/rdf'); // 命名空间常量
|
|
40
|
+
|
|
41
|
+
// 简化命名空间引用
|
|
42
|
+
const RDF = NS.RDF, RDFS = NS.RDFS, OWL = NS.OWL, XSD = NS.XSD;
|
|
43
|
+
// 本案例统一前缀:http://family.example.com/#
|
|
44
|
+
const ex = (s) => 'http://family.example.com/#' + s;
|
|
45
|
+
// BFO 标准类 IRI(顶层本体)
|
|
46
|
+
const BFO_MATERIAL_ENTITY = 'http://purl.obolibrary.org/obo/BFO_0000040'; // material entity
|
|
47
|
+
|
|
48
|
+
// ========== 快捷构造函数:把字符串包装成模型对象 ==========
|
|
49
|
+
const cls = (s) => new OWLClass(IRI.create(ex(s))); // 构造 OWL 类
|
|
50
|
+
const op = (s) => new OWLObjectProperty(IRI.create(ex(s))); // 构造 OWL 对象属性
|
|
51
|
+
const dp = (s) => new OWLDataProperty(IRI.create(ex(s))); // 构造 OWL 数据属性
|
|
52
|
+
const v = (n) => new SWRLVariable(IRI.create('urn:swrl:var:' + n)); // 构造 SWRL 变量 ?x ?y ?z
|
|
53
|
+
const intLit = (n) => ({ lexicalValue: String(n), datatype: { iri: IRI.create(XSD + 'integer') } }); // 整数字面量
|
|
54
|
+
|
|
55
|
+
// 构建知识图谱(实例 + 关系 + 逆关系声明)
|
|
56
|
+
function buildStore() {
|
|
57
|
+
const s = new TripleStore();
|
|
58
|
+
// 加载 BFO 顶层本体(Person ⊑ material entity)
|
|
59
|
+
const bfoPath = path.join(__dirname, 'ontologies', 'bfo.owl');
|
|
60
|
+
const bfoOnt = parseRDFXML(fs.readFileSync(bfoPath, 'utf8'));
|
|
61
|
+
for (const ax of bfoOnt.getAxioms()) {
|
|
62
|
+
if (ax.constructor.name === 'OWLSubClassOfAxiom') {
|
|
63
|
+
const sub = typeof ax.subClass === 'string' ? ax.subClass : (ax.subClass && ax.subClass.iri && ax.subClass.iri._iri);
|
|
64
|
+
const sup = typeof ax.superClass === 'string' ? ax.superClass : (ax.superClass && ax.superClass.iri && ax.superClass.iri._iri);
|
|
65
|
+
if (sub && sup && sub.startsWith('http') && sup.startsWith('http')) s.add(sub, RDFS + 'subClassOf', sup);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 声明 Person ⊑ BFO material entity
|
|
69
|
+
s.add(ex('Person'), RDFS + 'subClassOf', BFO_MATERIAL_ENTITY);
|
|
70
|
+
// 三个人物实例
|
|
71
|
+
s.add(ex('alice'), RDF + 'type', ex('Person'));
|
|
72
|
+
s.add(ex('bob'), RDF + 'type', ex('Person'));
|
|
73
|
+
s.add(ex('carol'), RDF + 'type', ex('Person'));
|
|
74
|
+
// 家庭关系:alice 的父亲是 bob;bob 的兄弟是 carol
|
|
75
|
+
s.add(ex('alice'), ex('hasParent'), ex('bob'));
|
|
76
|
+
s.add(ex('bob'), ex('hasBrother'), ex('carol'));
|
|
77
|
+
// hasParent 与 hasChild 互逆(OWL inverseOf)
|
|
78
|
+
s.add(ex('hasParent'), OWL + 'inverseOf', ex('hasChild'));
|
|
79
|
+
// 年龄(用 xsd:integer 字面量编码,与 SWRLReasoner 的字面量解析一致)
|
|
80
|
+
s.add(ex('bob'), ex('age'), '"40"^^<' + XSD + 'integer>'); // bob 40 岁
|
|
81
|
+
s.add(ex('carol'), ex('age'), '"12"^^<' + XSD + 'integer>'); // carol 12 岁
|
|
82
|
+
return s;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 定义 SWRL 规则集
|
|
86
|
+
function rules() {
|
|
87
|
+
const SWRLB = 'http://www.w3.org/2003/11/swrlb#'; // SWRL builtin 命名空间
|
|
88
|
+
// 规则1:叔叔规则
|
|
89
|
+
// body: Person(?x) ∧ hasParent(?x,?y) ∧ hasBrother(?y,?z)
|
|
90
|
+
// head: hasUncle(?x,?z)
|
|
91
|
+
const uncle = new SWRLRule(
|
|
92
|
+
[
|
|
93
|
+
new SWRLClassAtom(cls('Person'), v('x')), // ?x 是 Person
|
|
94
|
+
new SWRLObjectPropertyAtom(op('hasParent'), v('x'), v('y')), // ?x 的父亲是 ?y
|
|
95
|
+
new SWRLObjectPropertyAtom(op('hasBrother'), v('y'), v('z')) // ?y 的兄弟是 ?z
|
|
96
|
+
],
|
|
97
|
+
[new SWRLObjectPropertyAtom(op('hasUncle'), v('x'), v('z'))] // 推出 ?x 的叔叔是 ?z
|
|
98
|
+
);
|
|
99
|
+
// 规则2:成年规则(age >= 18 ⇒ Adult)
|
|
100
|
+
// body: Person(?p) ∧ age(?p,?a) ∧ greaterThanOrEqual(?a,18)
|
|
101
|
+
// head: Adult(?p)
|
|
102
|
+
const adult = new SWRLRule(
|
|
103
|
+
[
|
|
104
|
+
new SWRLClassAtom(cls('Person'), v('p')), // ?p 是 Person
|
|
105
|
+
new SWRLDataPropertyAtom(dp('age'), v('p'), v('a')), // ?p 的年龄是 ?a
|
|
106
|
+
new SWRLBuiltInAtom(IRI.create(SWRLB + 'greaterThanOrEqual'), [v('a'), intLit(18)]) // ?a >= 18
|
|
107
|
+
],
|
|
108
|
+
[new SWRLClassAtom(cls('Adult'), v('p'))] // 推出 ?p 是 Adult
|
|
109
|
+
);
|
|
110
|
+
return [uncle, adult];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 执行推理并返回业务结论
|
|
114
|
+
function run() {
|
|
115
|
+
const store = buildStore();
|
|
116
|
+
const reasoner = new SWRLReasoner(store);
|
|
117
|
+
reasoner.run(rules()); // 执行 SWRL 前向链推理
|
|
118
|
+
|
|
119
|
+
// 1) 叔叔推理:alice 的叔叔应是 carol
|
|
120
|
+
const uncleInferred = store.match(ex('alice'), ex('hasUncle'), ex('carol')).length === 1;
|
|
121
|
+
// 2) 成年判断:bob(40) 应是 Adult;carol(12) 不应是 Adult
|
|
122
|
+
const bobAdult = store.match(ex('bob'), RDF + 'type', ex('Adult')).length === 1;
|
|
123
|
+
const carolAdult = store.match(ex('carol'), RDF + 'type', ex('Adult')).length === 1;
|
|
124
|
+
// 3) 逆关系:hasParent 的逆是 hasChild → bob 的孩子应包含 alice
|
|
125
|
+
// SWRLReasoner 不跑 RL 规则,单独用 OWL2RLReasoner 验证 inverseOf
|
|
126
|
+
const { OWL2RLReasoner } = require('../src/inference/OWL2RLReasoner');
|
|
127
|
+
const rl = new OWL2RLReasoner(store);
|
|
128
|
+
rl.materialize();
|
|
129
|
+
const childInferred = store.match(ex('bob'), ex('hasChild'), ex('alice')).length === 1;
|
|
130
|
+
// 4) BFO 层级归因:alice 应被归为 material entity
|
|
131
|
+
const aliceIsMaterialEntity = store.has(ex('alice'), RDF + 'type', BFO_MATERIAL_ENTITY);
|
|
132
|
+
|
|
133
|
+
return { uncleInferred, bobAdult, carolAdult, childInferred, aliceIsMaterialEntity, store };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 导出供测试与复用
|
|
137
|
+
module.exports = { run, buildStore, rules, ex };
|
|
138
|
+
|
|
139
|
+
// 直接运行时打印业务结论
|
|
140
|
+
if (require.main === module) {
|
|
141
|
+
const r = run();
|
|
142
|
+
console.log('推出 alice 的叔叔是 carol :', r.uncleInferred); // true
|
|
143
|
+
console.log('bob(40岁) 被判定为成年人 :', r.bobAdult); // true
|
|
144
|
+
console.log('carol(12岁) 被判定为成年人 :', r.carolAdult, '(应为 false)'); // false
|
|
145
|
+
console.log('逆关系推出 bob 的孩子是 alice:', r.childInferred); // true
|
|
146
|
+
console.log('alice 归为 BFO material entity:', r.aliceIsMaterialEntity); // true
|
|
147
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ===========================================================================
|
|
4
|
+
// 业务案例 3:医疗用药安全 —— 药物相互作用冲突检测(OWL 2 RL 一致性)
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// 【业务背景】
|
|
7
|
+
// 医院开药系统要防止给同一病人同时开具两种「相互作用禁忌」的药物,
|
|
8
|
+
// 否则会造成医疗事故。系统在开处方时做一致性校验。
|
|
9
|
+
//
|
|
10
|
+
// 【业务规则】
|
|
11
|
+
// 1. 药物分类:阿司匹林(Aspirin) 与 华法林(Warfarin) 都是 抗凝药(Anticoagulant)。
|
|
12
|
+
// 2. 抗凝药 与 抗凝增效剂(AnticoagBooster) 联合使用属禁忌 → 用 disjoint
|
|
13
|
+
// 表达「安全合用(SafeCombo)」与「禁忌合用(ForbiddenCombo)」互斥。
|
|
14
|
+
// 3. 病人 pat-1 的用药方案 同时被判定为 SafeCombo 与 ForbiddenCombo 时,
|
|
15
|
+
// 推理机应报告本体不一致,系统据此拦截处方。
|
|
16
|
+
// 4. owl:hasKey:病人用 身份证号(ssn) 唯一标识;两个 ssn 相同的病人记录
|
|
17
|
+
// 应被识别为同一个人(prp-key)。
|
|
18
|
+
// 5. 标准本体对齐:药物 ⊑ OGMS medication role(OGMS_0000148)。
|
|
19
|
+
//
|
|
20
|
+
// 【用到的 OWL 2 能力】
|
|
21
|
+
// owl:disjointWith 一致性 (cax-dw) / owl:hasKey 唯一识别 (prp-key) /
|
|
22
|
+
// rdfs:subClassOf 分类 (cax-sco) / owl:sameAs 等价合并 (eq-*) /
|
|
23
|
+
// OGMS 医学本体导入(Drug ⊑ OGMS_0000148 medication role)
|
|
24
|
+
//
|
|
25
|
+
// 【预期结果】
|
|
26
|
+
// - 合法用药方案一致;叠加禁忌判定后 isConsistent()=false。
|
|
27
|
+
// - 两条 ssn 相同的病人记录被 prp-key 合并为同一个体(owl:sameAs)。
|
|
28
|
+
// - 药物沿 OGMS 层级被归因为 medication role。
|
|
29
|
+
// ===========================================================================
|
|
30
|
+
|
|
31
|
+
// 引入推理引擎核心组件
|
|
32
|
+
const fs = require('fs'); // 文件系统:读取 OGMS 本体
|
|
33
|
+
const path = require('path'); // 路径处理
|
|
34
|
+
const { parseRDFXML } = require('../src/io/RDFXMLParser'); // RDF/XML 解析器
|
|
35
|
+
const { TripleStore } = require('../src/inference/TripleStore'); // 三元组存储
|
|
36
|
+
const { OWL2RLReasoner } = require('../src/inference/OWL2RLReasoner'); // OWL 2 RL 推理机
|
|
37
|
+
const { NS } = require('../src/inference/rdf'); // 命名空间常量
|
|
38
|
+
|
|
39
|
+
// 简化命名空间引用
|
|
40
|
+
const RDF = NS.RDF, RDFS = NS.RDFS, OWL = NS.OWL, XSD = NS.XSD;
|
|
41
|
+
// 本案例统一前缀:http://hospital.example.com/rx#
|
|
42
|
+
const ex = (s) => 'http://hospital.example.com/rx#' + s;
|
|
43
|
+
// OGMS 标准类 IRI
|
|
44
|
+
const OGMS_MEDICATION_ROLE = 'http://purl.obolibrary.org/obo/OGMS_0000148'; // medication role
|
|
45
|
+
|
|
46
|
+
// 构建知识图谱(药物分类 + 病人记录 + 用药方案)
|
|
47
|
+
function buildStore() {
|
|
48
|
+
const s = new TripleStore();
|
|
49
|
+
|
|
50
|
+
// 加载 OGMS 医学本体(药物 ⊑ medication role)
|
|
51
|
+
const ogmsPath = path.join(__dirname, 'ontologies', 'ogms.owl');
|
|
52
|
+
const ogmsOnt = parseRDFXML(fs.readFileSync(ogmsPath, 'utf8'));
|
|
53
|
+
for (const ax of ogmsOnt.getAxioms()) {
|
|
54
|
+
if (ax.constructor.name === 'OWLSubClassOfAxiom') {
|
|
55
|
+
const sub = typeof ax.subClass === 'string' ? ax.subClass : (ax.subClass && ax.subClass.iri && ax.subClass.iri._iri);
|
|
56
|
+
const sup = typeof ax.superClass === 'string' ? ax.superClass : (ax.superClass && ax.superClass.iri && ax.superClass.iri._iri);
|
|
57
|
+
if (sub && sup && sub.startsWith('http') && sup.startsWith('http')) s.add(sub, RDFS + 'subClassOf', sup);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 声明 Anticoagulant ⊑ OGMS medication role
|
|
61
|
+
s.add(ex('Anticoagulant'), RDFS + 'subClassOf', OGMS_MEDICATION_ROLE);
|
|
62
|
+
|
|
63
|
+
// ========== TBox:药物分类与方案约束 ==========
|
|
64
|
+
// 阿司匹林是抗凝药
|
|
65
|
+
s.add(ex('Aspirin'), RDFS + 'subClassOf', ex('Anticoagulant'));
|
|
66
|
+
// 华法林是抗凝药
|
|
67
|
+
s.add(ex('Warfarin'), RDFS + 'subClassOf', ex('Anticoagulant'));
|
|
68
|
+
// 安全合用 与 禁忌合用 互斥(disjointWith)
|
|
69
|
+
s.add(ex('SafeCombo'), OWL + 'disjointWith', ex('ForbiddenCombo'));
|
|
70
|
+
// 安全合用是用药方案的一种
|
|
71
|
+
s.add(ex('SafeCombo'), RDFS + 'subClassOf', ex('PrescriptionPlan'));
|
|
72
|
+
// 禁忌合用是用药方案的一种
|
|
73
|
+
s.add(ex('ForbiddenCombo'), RDFS + 'subClassOf', ex('PrescriptionPlan'));
|
|
74
|
+
|
|
75
|
+
// ========== hasKey:病人用 ssn 唯一标识 ==========
|
|
76
|
+
// 声明 Patient 的 hasKey 是 ssn 属性
|
|
77
|
+
s.add(ex('Patient'), OWL + 'hasKey', '_:key1');
|
|
78
|
+
// key 列表 _:key1 = ( ssn ),RDF List 结构
|
|
79
|
+
s.add('_:key1', RDF + 'first', ex('ssn'));
|
|
80
|
+
s.add('_:key1', RDF + 'rest', RDF + 'nil');
|
|
81
|
+
|
|
82
|
+
// ========== ABox:病人记录与用药方案 ==========
|
|
83
|
+
// 第一条病人记录(ssn: 110101199001011234)
|
|
84
|
+
s.add(ex('pat-record-1'), RDF + 'type', ex('Patient'));
|
|
85
|
+
s.add(ex('pat-record-1'), ex('ssn'), '"110101199001011234"^^<' + XSD + 'string>');
|
|
86
|
+
// 第二条病人记录,ssn 相同 → 应被 hasKey 识别为同一人
|
|
87
|
+
s.add(ex('pat-record-2'), RDF + 'type', ex('Patient'));
|
|
88
|
+
s.add(ex('pat-record-2'), ex('ssn'), '"110101199001011234"^^<' + XSD + 'string>');
|
|
89
|
+
|
|
90
|
+
// 一个用药方案,初始标记为安全合用
|
|
91
|
+
s.add(ex('plan-001'), RDF + 'type', ex('SafeCombo'));
|
|
92
|
+
return s;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 执行推理并返回业务结论
|
|
96
|
+
function run() {
|
|
97
|
+
const store = buildStore();
|
|
98
|
+
const reasoner = new OWL2RLReasoner(store);
|
|
99
|
+
reasoner.materialize(); // 执行 OWL 2 RL 物化
|
|
100
|
+
|
|
101
|
+
// 1) 初始:方案仅标记为 SafeCombo,应一致
|
|
102
|
+
const consistentBefore = reasoner.isConsistent();
|
|
103
|
+
|
|
104
|
+
// 2) hasKey 合并:两条 ssn 相同的病人记录应被识别为同一个体(owl:sameAs)
|
|
105
|
+
const merged = store.has(ex('pat-record-1'), OWL + 'sameAs', ex('pat-record-2'))
|
|
106
|
+
|| store.has(ex('pat-record-2'), OWL + 'sameAs', ex('pat-record-1'));
|
|
107
|
+
|
|
108
|
+
// 3) 制造冲突:医生又开了相互作用的药,方案被追加判定为 ForbiddenCombo
|
|
109
|
+
store.add(ex('plan-001'), RDF + 'type', ex('ForbiddenCombo'));
|
|
110
|
+
const r2 = new OWL2RLReasoner(store); // 重建推理机(因 store 已改)
|
|
111
|
+
r2.materialize();
|
|
112
|
+
const consistentAfter = r2.isConsistent(); // 现在应不一致
|
|
113
|
+
const conflictFound = r2.inconsistencies.length > 0; // 推理机应记录冲突明细
|
|
114
|
+
|
|
115
|
+
// 4) OGMS 层级归因:Anticoagulant 应被归为 medication role
|
|
116
|
+
const anticoagulantIsMedRole = store.has(ex('Anticoagulant'), RDFS + 'subClassOf', OGMS_MEDICATION_ROLE);
|
|
117
|
+
|
|
118
|
+
return { consistentBefore, merged, consistentAfter, conflictFound, anticoagulantIsMedRole, store };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 导出供测试与复用
|
|
122
|
+
module.exports = { run, buildStore, ex };
|
|
123
|
+
|
|
124
|
+
// 直接运行时打印业务结论
|
|
125
|
+
if (require.main === module) {
|
|
126
|
+
const r = run();
|
|
127
|
+
console.log('初始合法用药方案一致 :', r.consistentBefore); // true
|
|
128
|
+
console.log('相同ssn病人记录被识别为同一人:', r.merged); // true
|
|
129
|
+
console.log('叠加禁忌后触发不一致 :', !r.consistentAfter); // true(取反后)
|
|
130
|
+
console.log('推理机报告了冲突明细 :', r.conflictFound); // true
|
|
131
|
+
console.log('抗凝药归为 OGMS medication role:', r.anticoagulantIsMedRole); // true
|
|
132
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ===========================================================================
|
|
4
|
+
// 业务案例 4:知识库内容发布 —— Turtle 解析 → 本体建模 → 序列化 round-trip
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// 【业务背景】
|
|
7
|
+
// 内容团队用 Turtle 手写一份「课程知识图谱」本体(类、层级、属性、个体),
|
|
8
|
+
// 发布平台要:(a) 解析为结构化本体做校验,(b) 再用序列化器导出为
|
|
9
|
+
// Turtle / Functional Syntax / RDF/XML,供下游系统消费。要求解析-导出
|
|
10
|
+
// 往返(round-trip)后核心公理不丢失。
|
|
11
|
+
//
|
|
12
|
+
// 【业务规则】
|
|
13
|
+
// 1. 类层级:编程课(ProgrammingCourse) / 数据课(DataCourse) ⊑ 课程(Course)。
|
|
14
|
+
// 2. 属性:授课(teaches) 的 domain 是 讲师(Instructor),range 是 Course。
|
|
15
|
+
// 3. 个体:讲师 alice 讲授 课程 cs101。
|
|
16
|
+
// 4. 前缀保真:解析时记录的 ex: 前缀,导出时应保留缩写。
|
|
17
|
+
// 5. 标准本体对齐:Course ⊑ IAO document(IAO_0000310),Instructor ⊑ BFO role(BFO_0000023)。
|
|
18
|
+
//
|
|
19
|
+
// 【用到的 OWL 2 能力】
|
|
20
|
+
// TurtleParser / triplesToOntology 桥接 / TurtleWriter / FunctionalSyntaxWriter
|
|
21
|
+
// / RDFXMLWriter / OWLOntology 前缀保真 (addPrefix/getPrefixes)
|
|
22
|
+
// IAO 信息本体导入(Course ⊑ IAO_0000310 document)
|
|
23
|
+
//
|
|
24
|
+
// 【预期结果】
|
|
25
|
+
// - Turtle 解析 + 桥接得到包含类/属性/个体的本体(公理数 > 0)。
|
|
26
|
+
// - TurtleWriter 导出文本可被 TurtleParser 再次解析(round-trip)。
|
|
27
|
+
// - 三种 Writer 均产出非空且含关键标识符的文本。
|
|
28
|
+
// - Course 沿 IAO 层级被归因为 document。
|
|
29
|
+
// ===========================================================================
|
|
30
|
+
|
|
31
|
+
// 引入解析器与序列化器
|
|
32
|
+
const fs = require('fs'); // 文件系统:读取 IAO 本体
|
|
33
|
+
const path = require('path'); // 路径处理
|
|
34
|
+
const { parseRDFXML } = require('../src/io/RDFXMLParser'); // RDF/XML 解析器
|
|
35
|
+
const { TurtleParser } = require('../src/io/TurtleParser'); // Turtle 解析器
|
|
36
|
+
const { triplesToOntology } = require('../src/io/RDFGraphToOntology'); // TripleStore → OWLOntology 桥接
|
|
37
|
+
const { writeTurtle } = require('../src/io/TurtleWriter'); // Turtle 序列化器
|
|
38
|
+
const { writeFunctionalSyntax } = require('../src/io/FunctionalSyntaxWriter'); // OWL Functional Syntax 序列化器
|
|
39
|
+
const { writeRDFXML } = require('../src/io/RDFXMLWriter'); // RDF/XML 序列化器
|
|
40
|
+
|
|
41
|
+
// ========== 输入:课程知识图谱 Turtle 文本(含 IAO 标准本体对齐) ==========
|
|
42
|
+
// 内容团队手写的 Turtle,声明了类、层级、属性和个体,并挂到 IAO 标准类
|
|
43
|
+
const COURSE_TTL = [
|
|
44
|
+
'@prefix ex: <http://edu.example.com/kg#> .', // 定义 ex 前缀
|
|
45
|
+
'@prefix owl: <http://www.w3.org/2002/07/owl#> .', // 定义 owl 前缀
|
|
46
|
+
'@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .', // 定义 rdfs 前缀
|
|
47
|
+
'@prefix obo: <http://purl.obolibrary.org/obo/> .', // 定义 obo 前缀(OBO 标准 IRI)
|
|
48
|
+
'',
|
|
49
|
+
'ex:Course a owl:Class ; rdfs:subClassOf obo:IAO_0000310 .', // Course ⊑ IAO document
|
|
50
|
+
'ex:ProgrammingCourse a owl:Class ; rdfs:subClassOf ex:Course .', // ProgrammingCourse ⊑ Course
|
|
51
|
+
'ex:DataCourse a owl:Class ; rdfs:subClassOf ex:Course .', // DataCourse ⊑ Course
|
|
52
|
+
'ex:Instructor a owl:Class ; rdfs:subClassOf obo:BFO_0000023 .', // Instructor ⊑ BFO role
|
|
53
|
+
'ex:teaches a owl:ObjectProperty ; rdfs:domain ex:Instructor ; rdfs:range ex:Course .', // teaches 属性定义
|
|
54
|
+
'ex:alice a ex:Instructor .', // alice 是讲师
|
|
55
|
+
'ex:cs101 a ex:ProgrammingCourse .', // cs101 是编程课
|
|
56
|
+
'ex:alice ex:teaches ex:cs101 .', // alice 讲授 cs101
|
|
57
|
+
''
|
|
58
|
+
].join('\n');
|
|
59
|
+
|
|
60
|
+
// 执行解析 → 建模 → 序列化 → round-trip 全流程
|
|
61
|
+
function run() {
|
|
62
|
+
// 1) Turtle → TripleStore:把文本解析成三元组存储
|
|
63
|
+
const store = new TurtleParser().parse(COURSE_TTL);
|
|
64
|
+
|
|
65
|
+
// 2) TripleStore → OWLOntology:结构化桥接为 OWL 本体对象
|
|
66
|
+
const ont = triplesToOntology(store);
|
|
67
|
+
const axiomCount = ont.getAxiomCount(); // 统计公理数(应 > 0)
|
|
68
|
+
|
|
69
|
+
// 3) 前缀保真演示:发布平台记录命名空间缩写
|
|
70
|
+
ont.addPrefix('ex', 'http://edu.example.com/kg#');
|
|
71
|
+
const prefixes = ont.getPrefixes(); // 读取已记录的前缀映射
|
|
72
|
+
|
|
73
|
+
// 4) 三种序列化导出:Turtle / Functional Syntax / RDF/XML
|
|
74
|
+
const turtleOut = writeTurtle(ont); // 导出 Turtle
|
|
75
|
+
const fsOut = writeFunctionalSyntax(ont); // 导出 OWL Functional Syntax
|
|
76
|
+
const xmlOut = writeRDFXML(ont); // 导出 RDF/XML
|
|
77
|
+
|
|
78
|
+
// 5) round-trip:导出的 Turtle 能再次被解析,验证核心公理不丢失
|
|
79
|
+
const store2 = new TurtleParser().parse(turtleOut);
|
|
80
|
+
const ont2 = triplesToOntology(store2);
|
|
81
|
+
|
|
82
|
+
// 6) IAO 层级归因验证:Course 应被归为 IAO document
|
|
83
|
+
const courseIsDocument = turtleOut.includes('IAO_0000310');
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
axiomCount, // 原始公理数
|
|
87
|
+
prefixes, // 前缀映射
|
|
88
|
+
turtleOut, fsOut, xmlOut, // 三种导出文本
|
|
89
|
+
turtleHasCourse: turtleOut.includes('Course'), // Turtle 导出含 Course
|
|
90
|
+
fsHasSubClass: /SubClassOf/.test(fsOut), // FS 导出含 SubClassOf
|
|
91
|
+
xmlIsXML: xmlOut.trim().startsWith('<?xml'), // RDF/XML 是合法 XML
|
|
92
|
+
roundTripAxiomCount: ont2.getAxiomCount(), // round-trip 后公理数
|
|
93
|
+
roundTripOK: ont2.getAxiomCount() > 0, // round-trip 成功标志
|
|
94
|
+
courseIsDocument // Course ⊑ IAO document
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 导出供测试与复用
|
|
99
|
+
module.exports = { run, COURSE_TTL };
|
|
100
|
+
|
|
101
|
+
// 直接运行时打印业务结论
|
|
102
|
+
if (require.main === module) {
|
|
103
|
+
const r = run();
|
|
104
|
+
console.log('解析得到公理数 :', r.axiomCount); // > 0
|
|
105
|
+
console.log('记录的前缀 :', JSON.stringify(r.prefixes)); // 含 ex 前缀
|
|
106
|
+
console.log('Turtle 导出含 Course :', r.turtleHasCourse); // true
|
|
107
|
+
console.log('FS 导出含 SubClassOf :', r.fsHasSubClass); // true
|
|
108
|
+
console.log('RDF/XML 导出为合法XML :', r.xmlIsXML); // true
|
|
109
|
+
console.log('round-trip 后公理数 :', r.roundTripAxiomCount, '(>0 即往返成功)'); // > 0
|
|
110
|
+
console.log('Course ⊑ IAO document :', r.courseIsDocument); // true
|
|
111
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ===========================================================================
|
|
4
|
+
// 业务案例 5:企业数据集成(OBDA)—— OWL 2 QL 查询 + EL 分类 + Profile 校验
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// 【业务背景】
|
|
7
|
+
// 企业把分散的数据库统一映射到一个「领域本体」上做集成查询(OBDA:
|
|
8
|
+
// Ontology-Based Data Access)。为了在大数据量下仍高效,限定使用
|
|
9
|
+
// OWL 2 QL profile(适合查询重写)与 OWL 2 EL profile(适合大规模分类),
|
|
10
|
+
// 并先用 profile 检查器确认本体没有超出对应 profile 的表达力。
|
|
11
|
+
//
|
|
12
|
+
// 【业务规则】
|
|
13
|
+
// 1. 组织层级:正式员工(RegularEmployee) / 合同工(Contractor) ⊑ 员工(Employee)
|
|
14
|
+
// ⊑ 人员(Person)。部门(Department) 通过 employs 关联员工。
|
|
15
|
+
// 2. QL 场景:查询「所有员工」时,正式员工与合同工的实例都应被召回
|
|
16
|
+
// (子类实例自动归属父类)。
|
|
17
|
+
// 3. EL 场景:传递闭包 —— partOf 具有传递性,「小组 ⊑ 部门 ⊑ 公司」
|
|
18
|
+
// 应推出「小组 partOf 公司」(prp-trp)。
|
|
19
|
+
// 4. Profile 校验:确认该本体不含 RL/QL/EL 之外的构造。
|
|
20
|
+
// 5. 标准本体对齐:Person ⊑ BFO material entity,Department ⊑ BFO object aggregate。
|
|
21
|
+
//
|
|
22
|
+
// 【用到的 OWL 2 能力】
|
|
23
|
+
// OWL2QLReasoner / OWL2ELReasoner / ReasonerQueries 查询 API /
|
|
24
|
+
// prp-trp 传递性 / OWL2Profiles profile 检查 (checkQL / checkEL) /
|
|
25
|
+
// BFO 顶层本体导入(Person ⊑ BFO_0000040 material entity)
|
|
26
|
+
//
|
|
27
|
+
// 【预期结果】
|
|
28
|
+
// - QL:查询 Employee 召回全部 3 名员工(含子类实例)。
|
|
29
|
+
// - EL:小组 partOf 公司 经传递性被推导出来。
|
|
30
|
+
// - profile 检查器对本体的违规列表可正常返回(数组)。
|
|
31
|
+
// - 员工沿 BFO 层级被归因为 material entity。
|
|
32
|
+
// ===========================================================================
|
|
33
|
+
|
|
34
|
+
// 引入推理引擎与 Profile 检查器
|
|
35
|
+
const fs = require('fs'); // 文件系统:读取 BFO 本体
|
|
36
|
+
const path = require('path'); // 路径处理
|
|
37
|
+
const { parseRDFXML } = require('../src/io/RDFXMLParser'); // RDF/XML 解析器
|
|
38
|
+
const { TripleStore } = require('../src/inference/TripleStore'); // 三元组存储
|
|
39
|
+
const { OWL2QLReasoner, OWL2ELReasoner } = require('../src/inference/OWL2ProfileReasoners'); // QL/EL 专用推理机
|
|
40
|
+
const { ReasonerQueries } = require('../src/inference/ReasonerQueries'); // 查询 API
|
|
41
|
+
const { checkQL, checkEL } = require('../src/profiles/OWL2Profiles'); // QL/EL Profile 检查器
|
|
42
|
+
const { triplesToOntology } = require('../src/io/RDFGraphToOntology'); // TripleStore → OWLOntology 桥接
|
|
43
|
+
const { NS } = require('../src/inference/rdf'); // 命名空间常量
|
|
44
|
+
|
|
45
|
+
// 简化命名空间引用
|
|
46
|
+
const RDF = NS.RDF, RDFS = NS.RDFS, OWL = NS.OWL;
|
|
47
|
+
// 本案例统一前缀:http://corp.example.com/org#
|
|
48
|
+
const ex = (s) => 'http://corp.example.com/org#' + s;
|
|
49
|
+
// BFO 标准类 IRI
|
|
50
|
+
const BFO_MATERIAL_ENTITY = 'http://purl.obolibrary.org/obo/BFO_0000040'; // material entity
|
|
51
|
+
const BFO_OBJECT_AGGREGATE = 'http://purl.obolibrary.org/obo/BFO_0000027'; // object aggregate
|
|
52
|
+
|
|
53
|
+
// 构建知识图谱(组织层级 + 员工实例 + partOf 传递链)
|
|
54
|
+
function buildStore() {
|
|
55
|
+
const s = new TripleStore();
|
|
56
|
+
|
|
57
|
+
// 加载 BFO 顶层本体(Person ⊑ material entity,Department ⊑ object aggregate)
|
|
58
|
+
const bfoPath = path.join(__dirname, 'ontologies', 'bfo.owl');
|
|
59
|
+
const bfoOnt = parseRDFXML(fs.readFileSync(bfoPath, 'utf8'));
|
|
60
|
+
for (const ax of bfoOnt.getAxioms()) {
|
|
61
|
+
if (ax.constructor.name === 'OWLSubClassOfAxiom') {
|
|
62
|
+
const sub = typeof ax.subClass === 'string' ? ax.subClass : (ax.subClass && ax.subClass.iri && ax.subClass.iri._iri);
|
|
63
|
+
const sup = typeof ax.superClass === 'string' ? ax.superClass : (ax.superClass && ax.superClass.iri && ax.superClass.iri._iri);
|
|
64
|
+
if (sub && sup && sub.startsWith('http') && sup.startsWith('http')) s.add(sub, RDFS + 'subClassOf', sup);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ========== TBox:类层级(含 BFO 对齐) ==========
|
|
69
|
+
// 人员 ⊑ BFO material entity
|
|
70
|
+
s.add(ex('Person'), RDFS + 'subClassOf', BFO_MATERIAL_ENTITY);
|
|
71
|
+
// 部门 ⊑ BFO object aggregate
|
|
72
|
+
s.add(ex('Department'), RDFS + 'subClassOf', BFO_OBJECT_AGGREGATE);
|
|
73
|
+
// 正式员工 ⊑ 员工
|
|
74
|
+
s.add(ex('RegularEmployee'), RDFS + 'subClassOf', ex('Employee'));
|
|
75
|
+
// 合同工 ⊑ 员工
|
|
76
|
+
s.add(ex('Contractor'), RDFS + 'subClassOf', ex('Employee'));
|
|
77
|
+
// 员工 ⊑ 人员
|
|
78
|
+
s.add(ex('Employee'), RDFS + 'subClassOf', ex('Person'));
|
|
79
|
+
|
|
80
|
+
// ========== ABox:员工实例(分散在各子类) ==========
|
|
81
|
+
s.add(ex('emp-alice'), RDF + 'type', ex('RegularEmployee')); // alice 是正式员工
|
|
82
|
+
s.add(ex('emp-bob'), RDF + 'type', ex('RegularEmployee')); // bob 是正式员工
|
|
83
|
+
s.add(ex('emp-carol'), RDF + 'type', ex('Contractor')); // carol 是合同工
|
|
84
|
+
|
|
85
|
+
// ========== EL 场景:partOf 传递链 ==========
|
|
86
|
+
// 声明 partOf 是传递属性
|
|
87
|
+
s.add(ex('partOf'), RDF + 'type', OWL + 'TransitiveProperty');
|
|
88
|
+
// 小组 partOf 部门
|
|
89
|
+
s.add(ex('team-ml'), ex('partOf'), ex('dept-ai'));
|
|
90
|
+
// 部门 partOf 公司
|
|
91
|
+
s.add(ex('dept-ai'), ex('partOf'), ex('company-hq'));
|
|
92
|
+
// 声明部门实例类型
|
|
93
|
+
s.add(ex('dept-ai'), RDF + 'type', ex('Department'));
|
|
94
|
+
return s;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 执行 QL 查询 + EL 分类 + Profile 校验
|
|
98
|
+
function run() {
|
|
99
|
+
const store = buildStore();
|
|
100
|
+
|
|
101
|
+
// --- QL:集成查询「所有员工」(子类实例自动归属父类) ---
|
|
102
|
+
const ql = new OWL2QLReasoner(store);
|
|
103
|
+
ql.materialize(); // QL 物化
|
|
104
|
+
const qQL = new ReasonerQueries(ql); // 查询 API
|
|
105
|
+
const allEmployees = qQL.getInstances(ex('Employee')); // 召回所有员工实例
|
|
106
|
+
const qlCarolIsEmployee = qQL.getTypes(ex('emp-carol')).includes(ex('Employee')); // carol 被归为 Employee
|
|
107
|
+
const qlCarolIsPerson = qQL.getTypes(ex('emp-carol')).includes(ex('Person')); // carol 被归为 Person
|
|
108
|
+
|
|
109
|
+
// --- EL:传递闭包分类 ---
|
|
110
|
+
const el = new OWL2ELReasoner(store);
|
|
111
|
+
el.materialize(); // EL 物化(含 prp-trp 传递性)
|
|
112
|
+
const teamInCompany = store.has(ex('team-ml'), ex('partOf'), ex('company-hq')); // 小组 partOf 公司
|
|
113
|
+
|
|
114
|
+
// --- Profile 校验:确认本体是否超出 QL/EL 表达力 ---
|
|
115
|
+
const ont = triplesToOntology(store); // 桥接为 OWLOntology
|
|
116
|
+
const qlViolations = checkQL(ont); // QL 违规列表
|
|
117
|
+
const elViolations = checkEL(ont); // EL 违规列表
|
|
118
|
+
|
|
119
|
+
// --- BFO 层级归因:员工应被归为 material entity ---
|
|
120
|
+
const aliceIsMaterialEntity = store.has(ex('emp-alice'), RDF + 'type', BFO_MATERIAL_ENTITY);
|
|
121
|
+
const deptIsObjectAggregate = store.has(ex('dept-ai'), RDF + 'type', BFO_OBJECT_AGGREGATE);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
employeeCount: allEmployees.length, // 召回员工数(应=3)
|
|
125
|
+
allEmployees, // 员工实例列表
|
|
126
|
+
qlCarolIsEmployee, // carol 归为 Employee
|
|
127
|
+
qlCarolIsPerson, // carol 归为 Person
|
|
128
|
+
teamInCompany, // 小组 partOf 公司被传递推出
|
|
129
|
+
qlViolationsIsArray: Array.isArray(qlViolations), // QL 校验返回数组
|
|
130
|
+
elViolationsIsArray: Array.isArray(elViolations), // EL 校验返回数组
|
|
131
|
+
qlViolationCount: qlViolations.length, // QL 违规数
|
|
132
|
+
elViolationCount: elViolations.length, // EL 违规数
|
|
133
|
+
aliceIsMaterialEntity, // 员工归为 BFO material entity
|
|
134
|
+
deptIsObjectAggregate, // 部门归为 BFO object aggregate
|
|
135
|
+
store
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 导出供测试与复用
|
|
140
|
+
module.exports = { run, buildStore, ex };
|
|
141
|
+
|
|
142
|
+
// 直接运行时打印业务结论
|
|
143
|
+
if (require.main === module) {
|
|
144
|
+
const r = run();
|
|
145
|
+
console.log('QL 集成查询召回员工数 :', r.employeeCount, '(含子类实例, 应为3)'); // 3
|
|
146
|
+
console.log(' 合同工 carol 归为 Employee:', r.qlCarolIsEmployee); // true
|
|
147
|
+
console.log(' 合同工 carol 归为 Person :', r.qlCarolIsPerson); // true
|
|
148
|
+
console.log('EL 传递推出 小组partOf公司 :', r.teamInCompany); // true
|
|
149
|
+
console.log('QL profile 校验返回数组 :', r.qlViolationsIsArray, '(违规数', r.qlViolationCount + ')'); // true
|
|
150
|
+
console.log('EL profile 校验返回数组 :', r.elViolationsIsArray, '(违规数', r.elViolationCount + ')'); // true
|
|
151
|
+
console.log('员工归为 BFO material entity :', r.aliceIsMaterialEntity); // true
|
|
152
|
+
console.log('部门归为 BFO object aggregate:', r.deptIsObjectAggregate); // true
|
|
153
|
+
}
|