@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,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
|
|
6
|
+
const { writeRDFXML } = require('../../src/io/RDFXMLWriter');
|
|
7
|
+
const { OWLOntology, OWLOntologyID } = require('../../src/model/OWLOntology');
|
|
8
|
+
const { OWLClass, OWLObjectProperty, OWLNamedIndividual } = require('../../src/model/OWLEntity');
|
|
9
|
+
const AX = require('../../src/model/OWLAxiom');
|
|
10
|
+
const { IRI } = require('../../src/model/IRI');
|
|
11
|
+
|
|
12
|
+
const EX = 'http://ex.org/';
|
|
13
|
+
const c = (n) => new OWLClass(EX + n);
|
|
14
|
+
const p = (n) => new OWLObjectProperty(EX + n);
|
|
15
|
+
const ind = (n) => new OWLNamedIndividual(EX + n);
|
|
16
|
+
|
|
17
|
+
function ontWith(...axioms) {
|
|
18
|
+
const ont = new OWLOntology(new OWLOntologyID(IRI.create(EX + 'ont')));
|
|
19
|
+
for (const a of axioms) ont.addAxiom(a);
|
|
20
|
+
return ont;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test('Declaration serialized', () => {
|
|
24
|
+
const xml = writeRDFXML(ontWith(new AX.OWLDeclarationAxiom(c('Person'))));
|
|
25
|
+
assert.ok(xml.includes('<owl:Class rdf:about="http://ex.org/Person"/>'));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('SubClassOf named classes', () => {
|
|
29
|
+
const xml = writeRDFXML(ontWith(new AX.OWLSubClassOfAxiom(c('A'), c('B'))));
|
|
30
|
+
assert.ok(xml.includes('rdfs:subClassOf'));
|
|
31
|
+
assert.ok(xml.includes('rdf:resource="http://ex.org/B"'));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('Ontology header with IRI', () => {
|
|
35
|
+
const xml = writeRDFXML(ontWith(new AX.OWLDeclarationAxiom(c('X'))));
|
|
36
|
+
assert.ok(xml.includes('owl:Ontology rdf:about="http://ex.org/ont"'));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('ClassAssertion', () => {
|
|
40
|
+
const xml = writeRDFXML(ontWith(new AX.OWLClassAssertionAxiom(ind('alice'), c('Person'))));
|
|
41
|
+
assert.ok(xml.includes('owl:NamedIndividual rdf:about="http://ex.org/alice"'));
|
|
42
|
+
assert.ok(xml.includes('rdf:type rdf:resource="http://ex.org/Person"'));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('TransitiveObjectProperty', () => {
|
|
46
|
+
const xml = writeRDFXML(ontWith(new AX.OWLTransitiveObjectPropertyAxiom(p('hasAncestor'))));
|
|
47
|
+
assert.ok(xml.includes('TransitiveProperty'));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('XML is well-formed-ish (balanced root)', () => {
|
|
51
|
+
const xml = writeRDFXML(ontWith(new AX.OWLDeclarationAxiom(c('Y'))));
|
|
52
|
+
assert.ok(xml.startsWith('<?xml version="1.0"?>'));
|
|
53
|
+
assert.ok(xml.includes('<rdf:RDF'));
|
|
54
|
+
assert.ok(xml.trim().endsWith('</rdf:RDF>'));
|
|
55
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test');
|
|
4
|
+
const assert = require('node:assert');
|
|
5
|
+
const { parseRDFXML } = require('../../src/io/RDFXMLParser');
|
|
6
|
+
const { AxiomType } = require('../../src/model/OWLAxiom');
|
|
7
|
+
|
|
8
|
+
const wrap = (body) => `<?xml version="1.0"?>
|
|
9
|
+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
10
|
+
xmlns:owl="http://www.w3.org/2002/07/owl#"
|
|
11
|
+
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
|
|
12
|
+
xmlns:ex="http://example.org/">
|
|
13
|
+
${body}
|
|
14
|
+
</rdf:RDF>`;
|
|
15
|
+
|
|
16
|
+
test('RDFXML: owl:disjointWith → DisjointClasses', () => {
|
|
17
|
+
const xml = wrap(`
|
|
18
|
+
<owl:Class rdf:about="http://example.org/Cat">
|
|
19
|
+
<owl:disjointWith rdf:resource="http://example.org/Dog"/>
|
|
20
|
+
</owl:Class>
|
|
21
|
+
`);
|
|
22
|
+
const o = parseRDFXML(xml);
|
|
23
|
+
const disjoint = o.getAxioms().filter(a => a.getAxiomType() === AxiomType.DISJOINT_CLASSES);
|
|
24
|
+
assert.ok(disjoint.length >= 1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('RDFXML: owl:inverseOf', () => {
|
|
28
|
+
const xml = wrap(`
|
|
29
|
+
<owl:ObjectProperty rdf:about="http://example.org/hasParent">
|
|
30
|
+
<owl:inverseOf rdf:resource="http://example.org/hasChild"/>
|
|
31
|
+
</owl:ObjectProperty>
|
|
32
|
+
`);
|
|
33
|
+
const o = parseRDFXML(xml);
|
|
34
|
+
const inv = o.getAxioms().filter(a => a.getAxiomType() === AxiomType.INVERSE_OBJECT_PROPERTIES);
|
|
35
|
+
assert.ok(inv.length >= 1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('RDFXML: owl:sameAs between individuals', () => {
|
|
39
|
+
const xml = wrap(`
|
|
40
|
+
<owl:NamedIndividual rdf:about="http://example.org/alice">
|
|
41
|
+
<owl:sameAs rdf:resource="http://example.org/alicia"/>
|
|
42
|
+
</owl:NamedIndividual>
|
|
43
|
+
`);
|
|
44
|
+
const o = parseRDFXML(xml);
|
|
45
|
+
const sa = o.getAxioms().filter(a => a.getAxiomType() === AxiomType.SAME_INDIVIDUAL);
|
|
46
|
+
assert.ok(sa.length >= 1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('RDFXML: owl:differentFrom between individuals', () => {
|
|
50
|
+
const xml = wrap(`
|
|
51
|
+
<owl:NamedIndividual rdf:about="http://example.org/alice">
|
|
52
|
+
<owl:differentFrom rdf:resource="http://example.org/bob"/>
|
|
53
|
+
</owl:NamedIndividual>
|
|
54
|
+
`);
|
|
55
|
+
const o = parseRDFXML(xml);
|
|
56
|
+
const d = o.getAxioms().filter(a => a.getAxiomType() === AxiomType.DIFFERENT_INDIVIDUALS);
|
|
57
|
+
assert.ok(d.length >= 1);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('RDFXML: TransitiveProperty characteristic', () => {
|
|
61
|
+
const xml = wrap(`
|
|
62
|
+
<owl:ObjectProperty rdf:about="http://example.org/hasAncestor">
|
|
63
|
+
<rdf:type rdf:resource="http://www.w3.org/2002/07/owl#TransitiveProperty"/>
|
|
64
|
+
</owl:ObjectProperty>
|
|
65
|
+
`);
|
|
66
|
+
const o = parseRDFXML(xml);
|
|
67
|
+
const t = o.getAxioms().filter(a => a.getAxiomType() === AxiomType.TRANSITIVE_OBJECT_PROPERTY);
|
|
68
|
+
assert.ok(t.length >= 1);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('RDFXML: FunctionalProperty on object property', () => {
|
|
72
|
+
const xml = wrap(`
|
|
73
|
+
<owl:ObjectProperty rdf:about="http://example.org/hasMother">
|
|
74
|
+
<rdf:type rdf:resource="http://www.w3.org/2002/07/owl#FunctionalProperty"/>
|
|
75
|
+
</owl:ObjectProperty>
|
|
76
|
+
`);
|
|
77
|
+
const o = parseRDFXML(xml);
|
|
78
|
+
const t = o.getAxioms().filter(a => a.getAxiomType() === AxiomType.FUNCTIONAL_OBJECT_PROPERTY);
|
|
79
|
+
assert.ok(t.length >= 1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('RDFXML: SymmetricProperty', () => {
|
|
83
|
+
const xml = wrap(`
|
|
84
|
+
<owl:ObjectProperty rdf:about="http://example.org/knows">
|
|
85
|
+
<rdf:type rdf:resource="http://www.w3.org/2002/07/owl#SymmetricProperty"/>
|
|
86
|
+
</owl:ObjectProperty>
|
|
87
|
+
`);
|
|
88
|
+
const o = parseRDFXML(xml);
|
|
89
|
+
const t = o.getAxioms().filter(a => a.getAxiomType() === AxiomType.SYMMETRIC_OBJECT_PROPERTY);
|
|
90
|
+
assert.ok(t.length >= 1);
|
|
91
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
|
|
6
|
+
const { TripleStore } = require('../../src/inference/TripleStore');
|
|
7
|
+
const { OWL2RLReasoner } = require('../../src/inference/OWL2RLReasoner');
|
|
8
|
+
const { ReasonerQueries } = require('../../src/inference/ReasonerQueries');
|
|
9
|
+
const { NS } = require('../../src/inference/rdf');
|
|
10
|
+
|
|
11
|
+
const RDF = NS.RDF, RDFS = NS.RDFS, OWL = NS.OWL;
|
|
12
|
+
const EX = 'http://ex.org/';
|
|
13
|
+
|
|
14
|
+
function build() {
|
|
15
|
+
const s = new TripleStore();
|
|
16
|
+
s.add(EX + 'A', RDF + 'type', OWL + 'Class');
|
|
17
|
+
s.add(EX + 'B', RDF + 'type', OWL + 'Class');
|
|
18
|
+
s.add(EX + 'C', RDF + 'type', OWL + 'Class');
|
|
19
|
+
s.add(EX + 'A', RDFS + 'subClassOf', EX + 'B');
|
|
20
|
+
s.add(EX + 'B', RDFS + 'subClassOf', EX + 'C');
|
|
21
|
+
s.add(EX + 'i', RDF + 'type', EX + 'A');
|
|
22
|
+
const r = new OWL2RLReasoner(s);
|
|
23
|
+
r.materialize();
|
|
24
|
+
return new ReasonerQueries(r);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('getSuperClasses transitive via RL materialization', () => {
|
|
28
|
+
const q = build();
|
|
29
|
+
const sups = q.getSuperClasses(EX + 'A');
|
|
30
|
+
assert.ok(sups.includes(EX + 'B'));
|
|
31
|
+
assert.ok(sups.includes(EX + 'C')); // transitive inferred by cax-sco / scm-sco
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('getInstances inferred through subclass chain', () => {
|
|
35
|
+
const q = build();
|
|
36
|
+
assert.ok(q.getInstances(EX + 'A').includes(EX + 'i'));
|
|
37
|
+
assert.ok(q.getInstances(EX + 'B').includes(EX + 'i'));
|
|
38
|
+
assert.ok(q.getInstances(EX + 'C').includes(EX + 'i'));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('getTypes returns named classes only', () => {
|
|
42
|
+
const q = build();
|
|
43
|
+
const types = q.getTypes(EX + 'i');
|
|
44
|
+
assert.ok(types.includes(EX + 'A'));
|
|
45
|
+
assert.ok(!types.includes(OWL + 'NamedIndividual'));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('isSubClassOf', () => {
|
|
49
|
+
const q = build();
|
|
50
|
+
assert.equal(q.isSubClassOf(EX + 'A', EX + 'B'), true);
|
|
51
|
+
assert.equal(q.isSubClassOf(EX + 'A', EX + 'C'), true);
|
|
52
|
+
assert.equal(q.isSubClassOf(EX + 'C', EX + 'A'), false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('isSatisfiable', () => {
|
|
56
|
+
const q = build();
|
|
57
|
+
assert.equal(q.isSatisfiable(EX + 'A'), true);
|
|
58
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ===========================================================================
|
|
4
|
+
// 业务端到端测试 —— 对 sample/ 下 5 个真实业务案例的自动化断言
|
|
5
|
+
//
|
|
6
|
+
// 每个案例对应一个独立业务场景,覆盖 OWL 2 的不同能力面:
|
|
7
|
+
// 案例1 电商订单风控 → OWL 2 RL 分类 + disjoint 一致性检测
|
|
8
|
+
// 案例2 家庭关系推理 → SWRL 规则 + builtin + inverseOf
|
|
9
|
+
// 案例3 医疗用药安全 → disjoint 冲突 + owl:hasKey 个体合并
|
|
10
|
+
// 案例4 知识库内容发布 → Turtle 解析→桥接→三种序列化 round-trip + 前缀保真
|
|
11
|
+
// 案例5 企业数据集成 OBDA → OWL 2 QL 查询 + EL 传递分类 + Profile 校验
|
|
12
|
+
// 案例6 医疗本体层 → BFO/DOID 风格疾病骨架 + OWL 文件导入导出 round-trip
|
|
13
|
+
// 案例7 制造业本体层 → PPR 三角 + BOM hasPart 传递 + OWL 文件导入导出 round-trip
|
|
14
|
+
// 案例8 真实医疗本体层 → 加载真实下载的 BFO+OGMS+RO 公开 OWL,真实类层级 RL 归因
|
|
15
|
+
// 案例9 真实制造本体层 → 加载真实 BFO+IAO,工艺文档/批记录沿真实 IAO 层级归因
|
|
16
|
+
// 案例10 电商客服工单 → 加载真实 BFO+IAO,SWRL 技能路由 + 根因分析 + 升级预测 + 跨渠道关联 + disjoint 一致性 + 导出
|
|
17
|
+
// 案例11 政务云运维巡检 → 加载真实 BFO+IAO,SWRL 告警路由 + 根因传播 + 合规风险 + 变更影响 + disjoint 一致性 + 导出
|
|
18
|
+
//
|
|
19
|
+
// 运行业务案例本身:node sample/caseN-*.js (会打印中文业务结论)
|
|
20
|
+
// 运行本测试套件: node --test test/owl2/sample-e2e.test.js
|
|
21
|
+
// ===========================================================================
|
|
22
|
+
|
|
23
|
+
const { test } = require('node:test');
|
|
24
|
+
const assert = require('node:assert/strict');
|
|
25
|
+
|
|
26
|
+
const case1 = require('../../sample/case1-ecommerce-risk');
|
|
27
|
+
const case2 = require('../../sample/case2-family-swrl');
|
|
28
|
+
const case3 = require('../../sample/case3-pharma-safety');
|
|
29
|
+
const case4 = require('../../sample/case4-knowledge-publishing');
|
|
30
|
+
const case5 = require('../../sample/case5-obda-profiles');
|
|
31
|
+
const case6 = require('../../sample/case6-medical-ontology');
|
|
32
|
+
const case7 = require('../../sample/case7-manufacturing-ppr');
|
|
33
|
+
const case8 = require('../../sample/case8-real-medical-bfo-ogms');
|
|
34
|
+
const case9 = require('../../sample/case9-real-manufacturing-iao');
|
|
35
|
+
const case10 = require('../../sample/case10-ecommerce-customer-service');
|
|
36
|
+
|
|
37
|
+
test('案例1 电商风控:黑名单订单被识别为高风险', () => {
|
|
38
|
+
const r = case1.run();
|
|
39
|
+
assert.equal(r.isHighRisk, true, 'ord-001 应被归类为 HighRiskOrder');
|
|
40
|
+
assert.equal(r.isOrder, true, 'ord-001 应仍是 Order');
|
|
41
|
+
assert.equal(r.ord2HighRisk, false, '正常订单 ord-002 不应误判为高风险');
|
|
42
|
+
assert.equal(r.badIsBuyer, true, '黑名单买家应归类为 Buyer');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('案例1 电商风控:误标可信订单触发 disjoint 不一致告警', () => {
|
|
46
|
+
const r = case1.run();
|
|
47
|
+
assert.equal(r.consistentBefore, true, '初始本体应一致');
|
|
48
|
+
assert.equal(r.consistentAfter, false, '误标 TrustedOrder 后应触发 disjoint 冲突');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('案例2 家庭关系:SWRL 推出叔叔关系', () => {
|
|
52
|
+
const r = case2.run();
|
|
53
|
+
assert.equal(r.uncleInferred, true, '应推出 alice -hasUncle-> carol');
|
|
54
|
+
assert.equal(r.childInferred, true, 'inverseOf 应推出 bob -hasChild-> alice');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('案例2 家庭关系:SWRL builtin 判定成年', () => {
|
|
58
|
+
const r = case2.run();
|
|
59
|
+
assert.equal(r.bobAdult, true, 'bob(40) 应判定为 Adult');
|
|
60
|
+
assert.equal(r.carolAdult, false, 'carol(12) 不应判定为 Adult');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('案例3 医疗用药:相同 ssn 病人记录被 hasKey 合并', () => {
|
|
64
|
+
const r = case3.run();
|
|
65
|
+
assert.equal(r.merged, true, '两条 ssn 相同的记录应被识别为同一个体(owl:sameAs)');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('案例3 医疗用药:药物相互作用触发一致性告警', () => {
|
|
69
|
+
const r = case3.run();
|
|
70
|
+
assert.equal(r.consistentBefore, true, '初始合法方案应一致');
|
|
71
|
+
assert.equal(r.consistentAfter, false, '叠加禁忌后应不一致');
|
|
72
|
+
assert.equal(r.conflictFound, true, '推理机应记录冲突明细');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('案例4 知识发布:Turtle 解析→桥接得到非空本体', () => {
|
|
76
|
+
const r = case4.run();
|
|
77
|
+
assert.ok(r.axiomCount > 0, '应解析出至少一条公理');
|
|
78
|
+
assert.equal(r.prefixes.ex, 'http://edu.example.com/kg#', '前缀应保真记录');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('案例4 知识发布:三种序列化器导出且 Turtle 可 round-trip', () => {
|
|
82
|
+
const r = case4.run();
|
|
83
|
+
assert.ok(r.turtleHasCourse, 'Turtle 导出应含 Course');
|
|
84
|
+
assert.ok(r.fsHasSubClass, 'Functional Syntax 导出应含 SubClassOf');
|
|
85
|
+
assert.ok(r.xmlIsXML, 'RDF/XML 导出应以 <?xml 开头');
|
|
86
|
+
assert.ok(r.roundTripOK, '导出的 Turtle 应能再次解析为非空本体');
|
|
87
|
+
assert.ok(r.roundTripAxiomCount > 0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('案例5 OBDA:QL 查询召回全部子类员工实例', () => {
|
|
91
|
+
const r = case5.run();
|
|
92
|
+
assert.equal(r.employeeCount, 3, '应召回全部 3 名员工(含子类实例)');
|
|
93
|
+
assert.equal(r.qlCarolIsEmployee, true, '合同工应归为 Employee');
|
|
94
|
+
assert.equal(r.qlCarolIsPerson, true, '合同工应归为 Person(多级子类)');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('案例5 OBDA:EL 传递分类 + Profile 校验', () => {
|
|
98
|
+
const r = case5.run();
|
|
99
|
+
assert.equal(r.teamInCompany, true, 'partOf 传递性应推出 小组 partOf 公司');
|
|
100
|
+
assert.equal(r.qlViolationsIsArray, true, 'checkQL 应返回违规数组');
|
|
101
|
+
assert.equal(r.elViolationsIsArray, true, 'checkEL 应返回违规数组');
|
|
102
|
+
assert.equal(r.qlViolationCount, 0, '该本体不应违反 QL profile');
|
|
103
|
+
assert.equal(r.elViolationCount, 0, '该本体不应违反 EL profile');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('案例6 医疗本体层:Turtle 导入 → RDF/XML 导出 → 再导入 round-trip', () => {
|
|
107
|
+
const r = case6.run();
|
|
108
|
+
assert.ok(r.originalAxioms > 0, 'Turtle 导入应得到非空本体');
|
|
109
|
+
assert.ok(r.rdfxmlValid, 'RDF/XML 导出应以 <?xml 开头');
|
|
110
|
+
assert.ok(r.rdfxmlHasDisease, 'RDF/XML 导出应含 Disease 类');
|
|
111
|
+
assert.ok(r.roundTripPreserves, 'RDF/XML 再导入应得到非空本体');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('案例6 医疗本体层:患者沿疾病类层级推理归因', () => {
|
|
115
|
+
const r = case6.run();
|
|
116
|
+
assert.equal(r.instIsDiabetes, true, '2型糖尿病实例应归为 DiabetesMellitus');
|
|
117
|
+
assert.equal(r.instIsEndocrine, true, '应进一步归为 EndocrineDisease');
|
|
118
|
+
assert.equal(r.instIsDisease, true, '应进一步归为顶层 Disease');
|
|
119
|
+
assert.equal(r.consistent, true, '本体应一致');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('案例7 制造业本体层:Turtle 导入 → RDF/XML 导出 → 再导入 round-trip', () => {
|
|
123
|
+
const r = case7.run();
|
|
124
|
+
assert.ok(r.originalAxioms > 0, 'Turtle 导入应得到非空本体');
|
|
125
|
+
assert.ok(r.rdfxmlValid, 'RDF/XML 导出应以 <?xml 开头');
|
|
126
|
+
assert.ok(r.rdfxmlHasProduct, 'RDF/XML 导出应含 Product 类');
|
|
127
|
+
assert.ok(r.roundTripPreserves, 'RDF/XML 再导入应得到非空本体');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('案例7 制造业本体层:BOM 传递 + 逆关系 + 工艺顺序推理', () => {
|
|
131
|
+
const r = case7.run();
|
|
132
|
+
assert.equal(r.transitiveHasPart, true, '整车应传递 hasPart 车门');
|
|
133
|
+
assert.equal(r.inversePartOf, true, '车门应逆关系 partOf 整车');
|
|
134
|
+
assert.equal(r.precedesTransitive, true, '冲压 precedes 涂装(工艺顺序传递)');
|
|
135
|
+
assert.equal(r.compIsProduct, true, '部件应被归类为 Product(子类推理)');
|
|
136
|
+
assert.equal(r.consistent, true, '本体应一致');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('案例8 真实医疗本体层:感冒临床诊疗路径', () => {
|
|
140
|
+
const r = case8.run();
|
|
141
|
+
assert.ok(r.counts.bfo > 0, 'BFO 应解析出非空公理');
|
|
142
|
+
assert.ok(r.counts.ogms > 0, 'OGMS 应解析出非空公理');
|
|
143
|
+
assert.ok(r.edges > 100, '应从真实本体抽取出大量类层级边');
|
|
144
|
+
assert.equal(r.hasStdClasses, true, 'OGMS 应含 symptom/diagnosis/treatment 等临床核心类');
|
|
145
|
+
assert.equal(r.treatmentAttributed, true, '治疗方案应沿真实 OGMS 层级归因');
|
|
146
|
+
assert.equal(r.diagnosisAttributed, true, '诊断应沿真实 IDO 层级归因');
|
|
147
|
+
assert.equal(r.consistent, true, '合并本体应一致');
|
|
148
|
+
assert.equal(r.exported, true, '病历应可导出为 RDF/XML');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('案例9 真实制造本体层:加载真实 BFO/IAO 公开 OWL 文件', () => {
|
|
152
|
+
const r = case9.run();
|
|
153
|
+
assert.ok(r.counts.bfo > 0, 'BFO 应解析出非空公理');
|
|
154
|
+
assert.ok(r.counts.iao > 0, 'IAO 应解析出非空公理');
|
|
155
|
+
assert.ok(r.edges > 100, '应从真实本体抽取出大量类层级边');
|
|
156
|
+
assert.equal(r.hasStdClasses, true, 'IAO 应含 directive information/document/plan specification 等标准类');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('案例9 真实制造本体层:批记录沿真实 IAO 层级归因 + 导出', () => {
|
|
160
|
+
const r = case9.run();
|
|
161
|
+
assert.equal(r.isBatchRecord, true, '实例应归为企业 BatchRecord');
|
|
162
|
+
assert.equal(r.isDoc, true, '应归为 IAO document');
|
|
163
|
+
assert.equal(r.isICE, true, '应归为 IAO information content entity');
|
|
164
|
+
assert.equal(r.isGDC, true, '应归为 BFO generically dependent continuant');
|
|
165
|
+
assert.equal(r.attributed, true, '应沿真实 IAO 层级完整归因');
|
|
166
|
+
assert.equal(r.consistent, true, '扩展本体应一致');
|
|
167
|
+
assert.equal(r.exported, true, '扩展本体应可导出为 RDF/XML');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('案例10 电商客服:SWRL 技能路由 + BFO/IAO 归因 + disjoint 一致性', () => {
|
|
171
|
+
const r = case10.run();
|
|
172
|
+
assert.equal(r.goldRouted, true, '金牌工单应路由到金牌客服组');
|
|
173
|
+
assert.equal(r.normalRouted, true, '普通工单应路由到通用客服组');
|
|
174
|
+
assert.equal(r.orderIsMaterialEntity, true, '工单应归为 BFO material entity');
|
|
175
|
+
assert.equal(r.custIsMaterialEntity, true, '客户应归为 BFO material entity');
|
|
176
|
+
assert.equal(r.planIsPlanSpec, true, '分派方案应归为 IAO plan specification');
|
|
177
|
+
assert.equal(r.slaIsDIE, true, 'SLA 应归为 IAO directive information entity');
|
|
178
|
+
assert.equal(r.logIsDocument, true, '分派日志应归为 IAO document');
|
|
179
|
+
assert.equal(r.consistentBefore, true, '初始本体应一致');
|
|
180
|
+
assert.equal(r.consistentAfter, false, '同一工单标记两组后应不一致');
|
|
181
|
+
assert.equal(r.conflictFound, true, '推理机应报告 disjoint 冲突');
|
|
182
|
+
// 场景A:根因分析
|
|
183
|
+
assert.equal(r.rcAlert1, true, '缺陷批次工单1应告警');
|
|
184
|
+
assert.equal(r.rcAlert2, true, '缺陷批次工单2应告警');
|
|
185
|
+
assert.equal(r.rcAlert3, false, '正常批次工单不应告警');
|
|
186
|
+
assert.equal(r.rcSupplierLinked, true, '批次应关联供应商');
|
|
187
|
+
// 场景B:投诉升级预测
|
|
188
|
+
assert.equal(r.escRisk, true, '高危工单应标记升级风险');
|
|
189
|
+
assert.equal(r.escNotRisk, true, '关联工单应传递升级风险');
|
|
190
|
+
// 场景C:跨渠道问题关联
|
|
191
|
+
assert.equal(r.crossChannelLinked, true, 'App工单应传递关联到微博工单');
|
|
192
|
+
assert.equal(r.appChannel, true, 'App工单应标记渠道');
|
|
193
|
+
assert.equal(r.weiboChannel, true, '微博工单应标记渠道');
|
|
194
|
+
assert.equal(r.exported, true, '分派记录应可导出为 RDF/XML');
|
|
195
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
|
|
6
|
+
const { SWRLParser } = require('../../src/io/SWRLParser');
|
|
7
|
+
const { AtomType } = require('../../src/model/SWRL');
|
|
8
|
+
|
|
9
|
+
const EX = 'http://ex.org/';
|
|
10
|
+
const parser = new SWRLParser({
|
|
11
|
+
prefixes: { ex: EX },
|
|
12
|
+
resolve: (iri) => {
|
|
13
|
+
if (iri === EX + 'Person' || iri === EX + 'Parent') return 'class';
|
|
14
|
+
if (iri === EX + 'age') return 'dataProperty';
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('class atom ^ class atom -> class atom', () => {
|
|
20
|
+
const r = parser.parse('ex:Person(?x) ^ ex:hasParent(?x,?y) -> ex:Child(?x)');
|
|
21
|
+
assert.equal(r.body.length, 2);
|
|
22
|
+
assert.equal(r.head.length, 1);
|
|
23
|
+
assert.equal(r.body[0].type, AtomType.CLASS);
|
|
24
|
+
assert.equal(r.body[1].type, AtomType.OBJECT_PROPERTY);
|
|
25
|
+
assert.equal(r.head[0].type, AtomType.CLASS);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('data property atom resolved via resolve()', () => {
|
|
29
|
+
const r = parser.parse('ex:Person(?x) ^ ex:age(?x,?a) -> ex:Adult(?x)');
|
|
30
|
+
assert.equal(r.body[1].type, AtomType.DATA_PROPERTY);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('builtin atom swrlb:greaterThan', () => {
|
|
34
|
+
const r = parser.parse('ex:Person(?x) ^ ex:age(?x,?a) ^ swrlb:greaterThan(?a,18) -> ex:Adult(?x)');
|
|
35
|
+
const b = r.body[2];
|
|
36
|
+
assert.equal(b.type, AtomType.BUILTIN);
|
|
37
|
+
assert.equal(b.builtin.toString(), 'http://www.w3.org/2003/11/swrlb#greaterThan');
|
|
38
|
+
assert.equal(b.args.length, 2);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('sameAs / differentFrom', () => {
|
|
42
|
+
const r = parser.parse('ex:Person(?x) ^ ex:Person(?y) ^ differentFrom(?x,?y) -> sameAs(?x,?x)');
|
|
43
|
+
assert.equal(r.body[2].type, AtomType.DIFFERENT_FROM);
|
|
44
|
+
assert.equal(r.head[0].type, AtomType.SAME_AS);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('parseAll multiple rules', () => {
|
|
48
|
+
const rules = parser.parseAll(`
|
|
49
|
+
ex:Person(?x) ^ ex:hasParent(?x,?y) -> ex:Child(?x)
|
|
50
|
+
ex:Person(?x) ^ ex:age(?x,?a) ^ swrlb:greaterThan(?a,18) -> ex:Adult(?x)
|
|
51
|
+
`);
|
|
52
|
+
assert.equal(rules.length, 2);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('literal argument', () => {
|
|
56
|
+
const r = parser.parse('ex:Person(?x) -> ex:greeting(?x,"hello")');
|
|
57
|
+
const atom = r.head[0];
|
|
58
|
+
// greeting not resolved -> object property, but arg is literal
|
|
59
|
+
assert.equal(atom.arg2.lexicalValue, 'hello');
|
|
60
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test');
|
|
4
|
+
const assert = require('node:assert');
|
|
5
|
+
const { TripleStore } = require('../../src/inference/TripleStore');
|
|
6
|
+
const {
|
|
7
|
+
SWRLRule, SWRLClassAtom, SWRLObjectPropertyAtom, SWRLDataPropertyAtom,
|
|
8
|
+
SWRLBuiltInAtom, SWRLVariable, AtomType
|
|
9
|
+
} = require('../../src/model/SWRL');
|
|
10
|
+
const { SWRLReasoner, SWRLBuiltins } = require('../../src/inference/SWRLReasoner');
|
|
11
|
+
const { OWLClass, OWLObjectProperty, OWLDataProperty } = require('../../src/model/OWLEntity');
|
|
12
|
+
const { IRI } = require('../../src/model/IRI');
|
|
13
|
+
const { NS } = require('../../src/inference/rdf');
|
|
14
|
+
|
|
15
|
+
const ex = (s) => 'http://example.org/' + s;
|
|
16
|
+
const cls = (s) => new OWLClass(IRI.create(ex(s)));
|
|
17
|
+
const op = (s) => new OWLObjectProperty(IRI.create(ex(s)));
|
|
18
|
+
const dp = (s) => new OWLDataProperty(IRI.create(ex(s)));
|
|
19
|
+
const v = (n) => new SWRLVariable(IRI.create('urn:swrl:var:' + n));
|
|
20
|
+
|
|
21
|
+
test('SWRL model: rule creation', () => {
|
|
22
|
+
const rule = new SWRLRule(
|
|
23
|
+
[new SWRLClassAtom(cls('Person'), v('x'))],
|
|
24
|
+
[new SWRLClassAtom(cls('Human'), v('x'))]
|
|
25
|
+
);
|
|
26
|
+
assert.strictEqual(rule.body.length, 1);
|
|
27
|
+
assert.strictEqual(rule.head.length, 1);
|
|
28
|
+
assert.match(rule.toString(), /->/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('SWRL: chain inference (uncle rule)', () => {
|
|
32
|
+
const S = new TripleStore();
|
|
33
|
+
S.add(ex('a'), NS.RDF + 'type', ex('Person'));
|
|
34
|
+
S.add(ex('a'), ex('hasParent'), ex('b'));
|
|
35
|
+
S.add(ex('b'), ex('hasBrother'), ex('c'));
|
|
36
|
+
const rule = new SWRLRule(
|
|
37
|
+
[
|
|
38
|
+
new SWRLClassAtom(cls('Person'), v('x')),
|
|
39
|
+
new SWRLObjectPropertyAtom(op('hasParent'), v('x'), v('y')),
|
|
40
|
+
new SWRLObjectPropertyAtom(op('hasBrother'), v('y'), v('z'))
|
|
41
|
+
],
|
|
42
|
+
[new SWRLObjectPropertyAtom(op('hasUncle'), v('x'), v('z'))]
|
|
43
|
+
);
|
|
44
|
+
const r = new SWRLReasoner(S);
|
|
45
|
+
const added = r.run([rule]);
|
|
46
|
+
assert.strictEqual(added, 1);
|
|
47
|
+
assert.strictEqual(S.match(ex('a'), ex('hasUncle'), ex('c')).length, 1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('SWRL: builtin greaterThanOrEqual', () => {
|
|
51
|
+
const S = new TripleStore();
|
|
52
|
+
S.add(ex('p1'), ex('age'), '"30"^^<' + NS.XSD + 'integer>');
|
|
53
|
+
const rule = new SWRLRule(
|
|
54
|
+
[
|
|
55
|
+
new SWRLDataPropertyAtom(dp('age'), v('p'), v('age')),
|
|
56
|
+
new SWRLBuiltInAtom(IRI.create('http://www.w3.org/2003/11/swrlb#greaterThanOrEqual'), [
|
|
57
|
+
v('age'),
|
|
58
|
+
{ lexicalValue: '18', datatype: { iri: IRI.create(NS.XSD + 'integer') } }
|
|
59
|
+
])
|
|
60
|
+
],
|
|
61
|
+
[new SWRLClassAtom(cls('Adult'), v('p'))]
|
|
62
|
+
);
|
|
63
|
+
const r = new SWRLReasoner(S);
|
|
64
|
+
const added = r.run([rule]);
|
|
65
|
+
assert.strictEqual(added, 1);
|
|
66
|
+
assert.strictEqual(S.match(ex('p1'), NS.RDF + 'type', ex('Adult')).length, 1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('SWRL: builtin add assigns new var', () => {
|
|
70
|
+
const S = new TripleStore();
|
|
71
|
+
S.add(ex('x'), ex('val'), '"5"^^<' + NS.XSD + 'integer>');
|
|
72
|
+
const rule = new SWRLRule(
|
|
73
|
+
[
|
|
74
|
+
new SWRLDataPropertyAtom(dp('val'), v('s'), v('v')),
|
|
75
|
+
new SWRLBuiltInAtom(IRI.create('http://www.w3.org/2003/11/swrlb#add'), [
|
|
76
|
+
v('result'), v('v'),
|
|
77
|
+
{ lexicalValue: '3', datatype: { iri: IRI.create(NS.XSD + 'integer') } }
|
|
78
|
+
])
|
|
79
|
+
],
|
|
80
|
+
[new SWRLDataPropertyAtom(dp('computed'), v('s'), v('result'))]
|
|
81
|
+
);
|
|
82
|
+
const r = new SWRLReasoner(S);
|
|
83
|
+
r.run([rule]);
|
|
84
|
+
const triples = S.match(ex('x'), ex('computed'), null);
|
|
85
|
+
assert.strictEqual(triples.length, 1);
|
|
86
|
+
assert.match(triples[0][2], /8/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('SWRL: builtins library', () => {
|
|
90
|
+
assert.strictEqual(SWRLBuiltins.call('equal', [5, 5]), true);
|
|
91
|
+
assert.strictEqual(SWRLBuiltins.call('lessThan', [3, 7]), true);
|
|
92
|
+
assert.strictEqual(SWRLBuiltins.call('add', [2, 3]), 5);
|
|
93
|
+
assert.strictEqual(SWRLBuiltins.call('multiply', [4, 5]), 20);
|
|
94
|
+
assert.strictEqual(SWRLBuiltins.call('upperCase', ['abc']), 'ABC');
|
|
95
|
+
assert.strictEqual(SWRLBuiltins.call('stringLength', ['hello']), 5);
|
|
96
|
+
assert.strictEqual(SWRLBuiltins.call('contains', ['hello world', 'world']), true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('SWRL: fixpoint — multi-hop transitive-like rule', () => {
|
|
100
|
+
const S = new TripleStore();
|
|
101
|
+
S.add(ex('a'), ex('p'), ex('b'));
|
|
102
|
+
S.add(ex('b'), ex('p'), ex('c'));
|
|
103
|
+
S.add(ex('c'), ex('p'), ex('d'));
|
|
104
|
+
const rule = new SWRLRule(
|
|
105
|
+
[
|
|
106
|
+
new SWRLObjectPropertyAtom(op('p'), v('x'), v('y')),
|
|
107
|
+
new SWRLObjectPropertyAtom(op('p'), v('y'), v('z'))
|
|
108
|
+
],
|
|
109
|
+
[new SWRLObjectPropertyAtom(op('p2'), v('x'), v('z'))]
|
|
110
|
+
);
|
|
111
|
+
const r = new SWRLReasoner(S);
|
|
112
|
+
r.run([rule]);
|
|
113
|
+
assert.strictEqual(S.match(ex('a'), ex('p2'), ex('c')).length, 1);
|
|
114
|
+
assert.strictEqual(S.match(ex('b'), ex('p2'), ex('d')).length, 1);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('SWRL: class membership propagation', () => {
|
|
118
|
+
const S = new TripleStore();
|
|
119
|
+
S.add(ex('s1'), NS.RDF + 'type', ex('Student'));
|
|
120
|
+
const rule = new SWRLRule(
|
|
121
|
+
[new SWRLClassAtom(cls('Student'), v('x'))],
|
|
122
|
+
[new SWRLClassAtom(cls('Person'), v('x'))]
|
|
123
|
+
);
|
|
124
|
+
const r = new SWRLReasoner(S);
|
|
125
|
+
r.run([rule]);
|
|
126
|
+
assert.strictEqual(S.match(ex('s1'), NS.RDF + 'type', ex('Person')).length, 1);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('SWRL: AtomType enum has 7 values', () => {
|
|
130
|
+
assert.strictEqual(Object.keys(AtomType).length, 7);
|
|
131
|
+
});
|