eyeling 1.24.25 → 1.24.27
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/README.md +2 -0
- package/dist/browser/eyeling.browser.js +76 -2
- package/examples/input/rdf-message-flow.trig +87 -59
- package/examples/input/rdf-message-microgrid.trig +89 -0
- package/examples/input/rdf-messages.trig +61 -40
- package/examples/output/rdf-message-flow.md +2 -2
- package/examples/output/rdf-message-microgrid.md +14 -0
- package/examples/output/rdf-messages.md +4 -2
- package/examples/rdf-message-flow.n3 +84 -69
- package/examples/rdf-message-microgrid.n3 +141 -0
- package/examples/rdf-messages.n3 +72 -52
- package/eyeling.js +76 -2
- package/lib/builtins.js +62 -2
- package/lib/parser.js +14 -0
- package/package.json +1 -1
- package/test/api.test.js +166 -29
package/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/eyeling)
|
|
4
4
|
[](https://doi.org/10.5281/zenodo.19068086)
|
|
5
5
|
|
|
6
|
+

|
|
7
|
+
|
|
6
8
|
A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
|
|
7
9
|
|
|
8
10
|
## Quick start
|
|
@@ -844,6 +844,62 @@ function compileSwapRegex(pattern, extraFlags) {
|
|
|
844
844
|
}
|
|
845
845
|
}
|
|
846
846
|
|
|
847
|
+
function expandSwapRegexReplacement(template, captures) {
|
|
848
|
+
// SWAP/N3 examples use $1-style capture references, but tests also rely on
|
|
849
|
+
// Perl-ish escaping in replacement strings: \\$ means a literal dollar sign
|
|
850
|
+
// and \\ means a literal backslash. JavaScript's replacement strings do not
|
|
851
|
+
// interpret those escapes, so expand the replacement explicitly in a callback.
|
|
852
|
+
let out = '';
|
|
853
|
+
const text = String(template);
|
|
854
|
+
|
|
855
|
+
for (let i = 0; i < text.length; i++) {
|
|
856
|
+
const ch = text[i];
|
|
857
|
+
|
|
858
|
+
if (ch === '\\') {
|
|
859
|
+
if (i + 1 < text.length && (text[i + 1] === '$' || text[i + 1] === '\\')) {
|
|
860
|
+
out += text[++i];
|
|
861
|
+
} else {
|
|
862
|
+
out += ch;
|
|
863
|
+
}
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (ch === '$') {
|
|
868
|
+
if (i + 1 < text.length && text[i + 1] === '$') {
|
|
869
|
+
out += '$';
|
|
870
|
+
i++;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
let j = i + 1;
|
|
875
|
+
while (j < text.length && /[0-9]/.test(text[j])) j++;
|
|
876
|
+
if (j > i + 1) {
|
|
877
|
+
const digits = text.slice(i + 1, j);
|
|
878
|
+
let chosen = null;
|
|
879
|
+
for (let k = digits.length; k > 0; k--) {
|
|
880
|
+
const n = Number(digits.slice(0, k));
|
|
881
|
+
if (n > 0 && n < captures.length) {
|
|
882
|
+
chosen = { n, len: k };
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (chosen) {
|
|
887
|
+
out += captures[chosen.n] == null ? '' : String(captures[chosen.n]);
|
|
888
|
+
out += digits.slice(chosen.len);
|
|
889
|
+
} else {
|
|
890
|
+
out += '$' + digits;
|
|
891
|
+
}
|
|
892
|
+
i = j - 1;
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
out += ch;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return out;
|
|
901
|
+
}
|
|
902
|
+
|
|
847
903
|
// -----------------------------------------------------------------------------
|
|
848
904
|
// Strict numeric literal parsing for math: builtins
|
|
849
905
|
// -----------------------------------------------------------------------------
|
|
@@ -1699,7 +1755,7 @@ function __pushBuiltinDeltaLimited(out, delta, maxResults) {
|
|
|
1699
1755
|
|
|
1700
1756
|
function __collectListLikeTermsFromTerm(t, out, seen) {
|
|
1701
1757
|
if (t instanceof ListTerm || t instanceof OpenListTerm) {
|
|
1702
|
-
const k = termFastKey
|
|
1758
|
+
const k = termFastKey(t);
|
|
1703
1759
|
if (k === null || !seen.has(k)) {
|
|
1704
1760
|
if (k !== null) seen.add(k);
|
|
1705
1761
|
out.push(t);
|
|
@@ -4222,7 +4278,11 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
4222
4278
|
const re = compileSwapRegex(searchStr, 'g');
|
|
4223
4279
|
if (!re) return [];
|
|
4224
4280
|
|
|
4225
|
-
const outStr = dataStr.replace(re,
|
|
4281
|
+
const outStr = dataStr.replace(re, (...args) => {
|
|
4282
|
+
const captureEnd = args.length - (typeof args.at(-1) === 'object' ? 3 : 2);
|
|
4283
|
+
const captures = args.slice(0, Math.max(1, captureEnd));
|
|
4284
|
+
return expandSwapRegexReplacement(replStr, captures);
|
|
4285
|
+
});
|
|
4226
4286
|
const lit = makeStringLiteral(outStr);
|
|
4227
4287
|
|
|
4228
4288
|
if (g.o instanceof Var) {
|
|
@@ -11498,6 +11558,20 @@ class Parser {
|
|
|
11498
11558
|
if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'a') {
|
|
11499
11559
|
this.next();
|
|
11500
11560
|
pred = internIri(RDF_NS + 'type');
|
|
11561
|
+
} else if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'has') {
|
|
11562
|
+
// N3 syntactic sugar is also valid in predicate-object lists,
|
|
11563
|
+
// including blank node property lists: [ has :p :o ] means _:b :p :o.
|
|
11564
|
+
this.next();
|
|
11565
|
+
pred = this.parseTerm();
|
|
11566
|
+
} else if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'is') {
|
|
11567
|
+
// N3 syntactic sugar: [ is :p of :s ] means :s :p _:b.
|
|
11568
|
+
this.next();
|
|
11569
|
+
pred = this.parseTerm();
|
|
11570
|
+
if (!(this.peek().typ === 'Ident' && (this.peek().value || '') === 'of')) {
|
|
11571
|
+
this.fail(`Expected 'of' after 'is <expr>', got ${this.peek().toString()}`);
|
|
11572
|
+
}
|
|
11573
|
+
this.next();
|
|
11574
|
+
invert = true;
|
|
11501
11575
|
} else if (this.peek().typ === 'OpPredInvert') {
|
|
11502
11576
|
this.next();
|
|
11503
11577
|
pred = this.parseTerm();
|
|
@@ -1,5 +1,29 @@
|
|
|
1
|
+
# ==============================
|
|
2
|
+
# RDF Message Flow input sidecar
|
|
3
|
+
# ==============================
|
|
4
|
+
#
|
|
5
|
+
# This file is the data half of the runnable example:
|
|
6
|
+
#
|
|
7
|
+
# eyeling -r examples/rdf-message-flow.n3 examples/input/rdf-message-flow.trig
|
|
8
|
+
#
|
|
9
|
+
# It is intentionally plain TriG rather than a parser-level RDF Message Log with
|
|
10
|
+
# VERSION "1.2-messages" and MESSAGE delimiters. The RDF Messages draft says an
|
|
11
|
+
# RDF Message is an RDF dataset interpreted atomically, that a stream is an
|
|
12
|
+
# ordered sequence of such messages, and that messages in a stream should not be
|
|
13
|
+
# combined by default. To demonstrate those ideas in Eyeling today, the default
|
|
14
|
+
# graph below contains example-local envelope records (:m001 ... :m005) and each
|
|
15
|
+
# non-empty message payload is placed in its own named graph (in:payload001 ...
|
|
16
|
+
# in:payload005).
|
|
17
|
+
#
|
|
18
|
+
# The envelope IRIs are not identifiers for RDF Messages defined by the spec;
|
|
19
|
+
# they are application-level records used by the flow-control rules. The named
|
|
20
|
+
# payload graphs are the datasets/messages being interpreted atomically. The
|
|
21
|
+
# third envelope is an empty heartbeat, which is valid because RDF Messages may
|
|
22
|
+
# be empty. Blank node labels are kept unique because this sidecar is ordinary
|
|
23
|
+
# TriG; a true RDF Message Log parser would reset blank-node scope per MESSAGE.
|
|
24
|
+
|
|
1
25
|
@prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow#> .
|
|
2
|
-
@prefix
|
|
26
|
+
@prefix flow: <https://eyereasoner.github.io/eyeling/examples/rdf-message-flow/vocab#> .
|
|
3
27
|
@prefix prov: <http://www.w3.org/ns/prov#> .
|
|
4
28
|
@prefix sosa: <http://www.w3.org/ns/sosa/> .
|
|
5
29
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
@@ -10,77 +34,87 @@
|
|
|
10
34
|
@prefix see: <https://example.org/see#> .
|
|
11
35
|
@prefix in: <https://example.org/see/input#> .
|
|
12
36
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
:temperatureFlow
|
|
17
|
-
:temperatureFlow
|
|
18
|
-
:temperatureFlow
|
|
19
|
-
:temperatureFlow
|
|
20
|
-
:temperatureFlow
|
|
21
|
-
:temperatureFlow
|
|
22
|
-
:temperatureFlow msg:message :m005 .
|
|
37
|
+
:temperatureFlow a flow:RDFMessageStream .
|
|
38
|
+
:temperatureFlow flow:orderedEnvelopes (:m001 :m002 :m003 :m004 :m005) .
|
|
39
|
+
:temperatureFlow flow:envelope :m001 .
|
|
40
|
+
:temperatureFlow flow:envelope :m002 .
|
|
41
|
+
:temperatureFlow flow:envelope :m003 .
|
|
42
|
+
:temperatureFlow flow:envelope :m004 .
|
|
43
|
+
:temperatureFlow flow:envelope :m005 .
|
|
44
|
+
:temperatureFlow :producer :thermometerA .
|
|
45
|
+
:temperatureFlow :consumer :flowProcessor .
|
|
23
46
|
:temperatureFlow :pipeline (:ingest :validate :interpret :route :sink) .
|
|
24
47
|
:temperatureFlow :highThreshold 26 .
|
|
48
|
+
|
|
49
|
+
:thermometerA a flow:StreamProducer .
|
|
50
|
+
:flowProcessor a flow:StreamConsumer .
|
|
51
|
+
:archiveSink a flow:StreamConsumer .
|
|
52
|
+
:alertSink a flow:StreamConsumer .
|
|
53
|
+
:heartbeatSink a flow:StreamConsumer .
|
|
54
|
+
|
|
55
|
+
:m001 a flow:MessageEnvelope .
|
|
25
56
|
:m001 :atStage :ingest .
|
|
26
|
-
:m001
|
|
27
|
-
:m001
|
|
28
|
-
:m001
|
|
29
|
-
:m001
|
|
30
|
-
:m001
|
|
31
|
-
:m001
|
|
32
|
-
|
|
33
|
-
:m002 a
|
|
34
|
-
:m002
|
|
35
|
-
:m002
|
|
36
|
-
:m002 prov:generatedAtTime "2026-05-12T18:21:00Z" .
|
|
37
|
-
:m002
|
|
38
|
-
:m002
|
|
39
|
-
:m002
|
|
40
|
-
|
|
41
|
-
:m003
|
|
42
|
-
:m003
|
|
43
|
-
:m003
|
|
44
|
-
:m003
|
|
45
|
-
:
|
|
46
|
-
|
|
47
|
-
:m004
|
|
48
|
-
:m004
|
|
49
|
-
:m004
|
|
50
|
-
:m004
|
|
51
|
-
:m004
|
|
52
|
-
:
|
|
53
|
-
:
|
|
54
|
-
|
|
55
|
-
:m005
|
|
56
|
-
:m005
|
|
57
|
-
:m005
|
|
57
|
+
:m001 flow:offset 1 .
|
|
58
|
+
:m001 flow:nextEnvelope :m002 .
|
|
59
|
+
:m001 prov:generatedAtTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
|
|
60
|
+
:m001 flow:payloadKind :observation .
|
|
61
|
+
:m001 flow:expectedResult 21 .
|
|
62
|
+
:m001 flow:payloadGraph in:payload001 .
|
|
63
|
+
|
|
64
|
+
:m002 a flow:MessageEnvelope .
|
|
65
|
+
:m002 flow:offset 2 .
|
|
66
|
+
:m002 flow:nextEnvelope :m003 .
|
|
67
|
+
:m002 prov:generatedAtTime "2026-05-12T18:21:00Z"^^xsd:dateTime .
|
|
68
|
+
:m002 flow:payloadKind :observation .
|
|
69
|
+
:m002 flow:expectedResult 22 .
|
|
70
|
+
:m002 flow:payloadGraph in:payload002 .
|
|
71
|
+
|
|
72
|
+
:m003 a flow:MessageEnvelope .
|
|
73
|
+
:m003 flow:offset 3 .
|
|
74
|
+
:m003 flow:nextEnvelope :m004 .
|
|
75
|
+
:m003 prov:generatedAtTime "2026-05-12T18:22:00Z"^^xsd:dateTime .
|
|
76
|
+
:m003 flow:payloadKind :heartbeat .
|
|
77
|
+
|
|
78
|
+
:m004 a flow:MessageEnvelope .
|
|
79
|
+
:m004 flow:offset 4 .
|
|
80
|
+
:m004 flow:nextEnvelope :m005 .
|
|
81
|
+
:m004 prov:generatedAtTime "2026-05-12T18:23:00Z"^^xsd:dateTime .
|
|
82
|
+
:m004 flow:payloadKind :observation .
|
|
83
|
+
:m004 flow:expectedResult 28 .
|
|
84
|
+
:m004 flow:payloadGraph in:payload004 .
|
|
85
|
+
|
|
86
|
+
:m005 a flow:MessageEnvelope .
|
|
87
|
+
:m005 flow:offset 5 .
|
|
88
|
+
:m005 prov:generatedAtTime "2026-05-12T18:24:00Z"^^xsd:dateTime .
|
|
89
|
+
:m005 flow:payloadKind :observation .
|
|
90
|
+
:m005 flow:expectedResult 29 .
|
|
91
|
+
:m005 flow:payloadGraph in:payload005 .
|
|
58
92
|
|
|
59
|
-
in:
|
|
93
|
+
in:payload001 {
|
|
60
94
|
_:m001b0 a sosa:Observation .
|
|
61
95
|
_:m001b0 sosa:madeBySensor :thermometerA .
|
|
62
|
-
_:m001b0 sosa:resultTime "2026-05-12T18:20:00Z" .
|
|
96
|
+
_:m001b0 sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
|
|
63
97
|
_:m001b0 sosa:hasSimpleResult 21 .
|
|
64
98
|
}
|
|
65
99
|
|
|
66
|
-
in:
|
|
100
|
+
in:payload002 {
|
|
67
101
|
_:m002b0 a sosa:Observation .
|
|
68
102
|
_:m002b0 sosa:madeBySensor :thermometerA .
|
|
69
|
-
_:m002b0 sosa:resultTime "2026-05-12T18:21:00Z" .
|
|
103
|
+
_:m002b0 sosa:resultTime "2026-05-12T18:21:00Z"^^xsd:dateTime .
|
|
70
104
|
_:m002b0 sosa:hasSimpleResult 22 .
|
|
71
105
|
}
|
|
72
106
|
|
|
73
|
-
in:
|
|
107
|
+
in:payload004 {
|
|
74
108
|
_:m004b0 a sosa:Observation .
|
|
75
109
|
_:m004b0 sosa:madeBySensor :thermometerA .
|
|
76
|
-
_:m004b0 sosa:resultTime "2026-05-12T18:23:00Z" .
|
|
110
|
+
_:m004b0 sosa:resultTime "2026-05-12T18:23:00Z"^^xsd:dateTime .
|
|
77
111
|
_:m004b0 sosa:hasSimpleResult 28 .
|
|
78
112
|
}
|
|
79
113
|
|
|
80
|
-
in:
|
|
114
|
+
in:payload005 {
|
|
81
115
|
_:m005b0 a sosa:Observation .
|
|
82
116
|
_:m005b0 sosa:madeBySensor :thermometerA .
|
|
83
|
-
_:m005b0 sosa:resultTime "2026-05-12T18:24:00Z" .
|
|
117
|
+
_:m005b0 sosa:resultTime "2026-05-12T18:24:00Z"^^xsd:dateTime .
|
|
84
118
|
_:m005b0 sosa:hasSimpleResult 29 .
|
|
85
119
|
}
|
|
86
120
|
|
|
@@ -88,13 +122,7 @@ in:metadata {
|
|
|
88
122
|
in:run a see:InputDataset .
|
|
89
123
|
in:run see:name "rdf_message_flow" .
|
|
90
124
|
in:run see:title "RDF Message Flow" .
|
|
91
|
-
in:run see:sourceFile "examples/
|
|
92
|
-
in:run see:
|
|
93
|
-
in:run see:description "A companion to rdf_messages.n3. This example focuses on a live stream where\nRDF Messages continuously flow through a small processing pipeline. The next\nmessage is released only after the current message reaches the sink, so the\nstream behaves as an ordered, replayable flow rather than a single merged RDF\ngraph." .
|
|
125
|
+
in:run see:sourceFile "examples/rdf-message-flow.n3" .
|
|
126
|
+
in:run see:description "A single Eyeling example split across an N3 rule file and a TriG input sidecar. It models an ordered RDF Message Stream using application-level envelopes and named payload graphs so that each payload can be interpreted atomically without merging all message contents into one graph." .
|
|
94
127
|
in:run see:compiler "Eyeling RDF/TriG input sidecar" .
|
|
95
|
-
in:run see:inputFacts 42 .
|
|
96
|
-
in:run see:compiledRules 9 .
|
|
97
|
-
in:run see:compiledBackwardRules 0 .
|
|
98
|
-
in:run see:compiledFuses 0 .
|
|
99
|
-
in:run see:compiledQueries 1 .
|
|
100
128
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# ===================================
|
|
2
|
+
# RDF Message Microgrid input sidecar
|
|
3
|
+
# ===================================
|
|
4
|
+
#
|
|
5
|
+
# This file is the data half of the runnable example:
|
|
6
|
+
#
|
|
7
|
+
# eyeling -r examples/rdf-message-microgrid.n3 examples/input/rdf-message-microgrid.trig
|
|
8
|
+
#
|
|
9
|
+
# The scene is a clinic operating as an islanded microgrid after a storm. The
|
|
10
|
+
# data arrives as a replayable stream of atomic messages: life-safety loads,
|
|
11
|
+
# power status, flexible demand, and an empty heartbeat. The default graph holds
|
|
12
|
+
# application-local envelope facts, while each non-empty message payload is kept
|
|
13
|
+
# in a named graph.
|
|
14
|
+
#
|
|
15
|
+
# The envelope IRIs are not RDF Message identifiers defined by the specification;
|
|
16
|
+
# they are local replay records for this example. The named graphs are the
|
|
17
|
+
# datasets/messages interpreted atomically by the N3 rules.
|
|
18
|
+
|
|
19
|
+
@prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-message-microgrid#> .
|
|
20
|
+
@prefix rmsg: <https://eyereasoner.github.io/eyeling/examples/rdf-message-microgrid/vocab#> .
|
|
21
|
+
@prefix prov: <http://www.w3.org/ns/prov#> .
|
|
22
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
23
|
+
@prefix see: <https://example.org/see#> .
|
|
24
|
+
@prefix in: <https://example.org/see/input#> .
|
|
25
|
+
|
|
26
|
+
:stormClinicLog a rmsg:MessageLog .
|
|
27
|
+
:stormClinicLog rmsg:orderedMessages (:m001 :m002 :m003 :m004) .
|
|
28
|
+
:stormClinicLog rmsg:message :m001 .
|
|
29
|
+
:stormClinicLog rmsg:message :m002 .
|
|
30
|
+
:stormClinicLog rmsg:message :m003 .
|
|
31
|
+
:stormClinicLog rmsg:message :m004 .
|
|
32
|
+
:stormClinicLog rmsg:retentionPolicy "storm replay archive" .
|
|
33
|
+
|
|
34
|
+
:m001 a rmsg:MessageEnvelope .
|
|
35
|
+
:m001 rmsg:offset 1 .
|
|
36
|
+
:m001 prov:generatedAtTime "2026-05-15T19:00:00Z"^^xsd:dateTime .
|
|
37
|
+
:m001 rmsg:payloadKind :lifeSafetyLoads .
|
|
38
|
+
:m001 rmsg:payloadGraph in:lifeSafetyPayload .
|
|
39
|
+
|
|
40
|
+
:m002 a rmsg:MessageEnvelope .
|
|
41
|
+
:m002 rmsg:offset 2 .
|
|
42
|
+
:m002 prov:generatedAtTime "2026-05-15T19:01:00Z"^^xsd:dateTime .
|
|
43
|
+
:m002 rmsg:payloadKind :powerStatus .
|
|
44
|
+
:m002 rmsg:payloadGraph in:powerPayload .
|
|
45
|
+
|
|
46
|
+
:m003 a rmsg:MessageEnvelope .
|
|
47
|
+
:m003 rmsg:offset 3 .
|
|
48
|
+
:m003 prov:generatedAtTime "2026-05-15T19:02:00Z"^^xsd:dateTime .
|
|
49
|
+
:m003 rmsg:payloadKind :flexibleDemand .
|
|
50
|
+
:m003 rmsg:payloadGraph in:flexPayload .
|
|
51
|
+
|
|
52
|
+
:m004 a rmsg:MessageEnvelope .
|
|
53
|
+
:m004 rmsg:offset 4 .
|
|
54
|
+
:m004 prov:generatedAtTime "2026-05-15T19:03:00Z"^^xsd:dateTime .
|
|
55
|
+
:m004 rmsg:payloadKind :heartbeat .
|
|
56
|
+
|
|
57
|
+
in:lifeSafetyPayload {
|
|
58
|
+
:oxygenConcentrator a :LifeSafetyLoad .
|
|
59
|
+
:oxygenConcentrator :requiresWatts 500 .
|
|
60
|
+
:oxygenConcentrator :serves :respiratoryCareRoom .
|
|
61
|
+
|
|
62
|
+
:vaccineFridge a :ColdChainLoad .
|
|
63
|
+
:vaccineFridge :requiresWatts 120 .
|
|
64
|
+
:vaccineFridge :serves :vaccineColdChain .
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
in:powerPayload {
|
|
68
|
+
:batteryBank a :PowerReserve .
|
|
69
|
+
:batteryBank :availableWatts 650 .
|
|
70
|
+
:batteryBank :state :islanded .
|
|
71
|
+
|
|
72
|
+
:solarForecast a :NearTermForecast .
|
|
73
|
+
:solarForecast :expectedWatts 150 .
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
in:flexPayload {
|
|
77
|
+
:evChargers a :FlexibleLoad .
|
|
78
|
+
:evChargers :shedWatts 600 .
|
|
79
|
+
:evChargers :serves :staffVehicles .
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
in:metadata {
|
|
83
|
+
in:run a see:InputDataset .
|
|
84
|
+
in:run see:name "rdf_message_microgrid" .
|
|
85
|
+
in:run see:title "RDF Message Microgrid" .
|
|
86
|
+
in:run see:sourceFile "examples/rdf-message-microgrid.n3" .
|
|
87
|
+
in:run see:description "A storm clinic microgrid example using application-local RDF Message envelopes and named payload graphs so a reasoner can make a bounded, explainable load-shedding decision." .
|
|
88
|
+
in:run see:compiler "Eyeling RDF/TriG input sidecar" .
|
|
89
|
+
}
|
|
@@ -1,5 +1,32 @@
|
|
|
1
|
+
# ==========================
|
|
2
|
+
# RDF Messages input sidecar
|
|
3
|
+
# ==========================
|
|
4
|
+
#
|
|
5
|
+
# This file is the data half of the runnable example:
|
|
6
|
+
#
|
|
7
|
+
# eyeling -r examples/rdf-messages.n3 examples/input/rdf-messages.trig
|
|
8
|
+
#
|
|
9
|
+
# It is intentionally plain TriG rather than a parser-level RDF Message Log with
|
|
10
|
+
# VERSION "1.2-messages" and MESSAGE delimiters. The RDF Messages draft says an
|
|
11
|
+
# RDF Message is an RDF dataset interpreted atomically, that a message stream is
|
|
12
|
+
# an ordered sequence of such messages, that messages are not combined by
|
|
13
|
+
# default, and that blank-node labels are scoped to the message.
|
|
14
|
+
#
|
|
15
|
+
# To demonstrate those ideas in Eyeling today, the default graph contains
|
|
16
|
+
# application-local envelope records (:m001 ... :m003) plus replay order and
|
|
17
|
+
# offsets. Each non-empty message payload is placed in its own named graph
|
|
18
|
+
# (in:payload001 and in:payload003). The second envelope is an empty heartbeat,
|
|
19
|
+
# which is valid because RDF Messages may be empty.
|
|
20
|
+
#
|
|
21
|
+
# The envelope IRIs are not message identifiers defined by the spec; they are
|
|
22
|
+
# application-level records used by the rules. The named payload graphs are the
|
|
23
|
+
# datasets/messages being interpreted atomically. The rmsg:localBlankLabel values
|
|
24
|
+
# show that the same source-local label "_:b0" can recur in different messages;
|
|
25
|
+
# the concrete TriG blank nodes themselves are unique because this sidecar is
|
|
26
|
+
# ordinary TriG rather than an RDF Message Log parser resetting blank-node scope.
|
|
27
|
+
|
|
1
28
|
@prefix : <https://eyereasoner.github.io/eyeling/examples/rdf-messages#> .
|
|
2
|
-
@prefix
|
|
29
|
+
@prefix rmsg: <https://eyereasoner.github.io/eyeling/examples/rdf-messages/vocab#> .
|
|
3
30
|
@prefix prov: <http://www.w3.org/ns/prov#> .
|
|
4
31
|
@prefix sosa: <http://www.w3.org/ns/sosa/> .
|
|
5
32
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
@@ -10,46 +37,46 @@
|
|
|
10
37
|
@prefix see: <https://example.org/see#> .
|
|
11
38
|
@prefix in: <https://example.org/see/input#> .
|
|
12
39
|
|
|
13
|
-
|
|
14
|
-
|
|
40
|
+
:temperatureLog a rmsg:MessageLog .
|
|
41
|
+
:temperatureLog rmsg:orderedMessages (:m001 :m002 :m003) .
|
|
42
|
+
:temperatureLog rmsg:message :m001 .
|
|
43
|
+
:temperatureLog rmsg:message :m002 .
|
|
44
|
+
:temperatureLog rmsg:message :m003 .
|
|
45
|
+
:temperatureLog rmsg:profile :generatedAtTimeProfile .
|
|
46
|
+
:temperatureLog rmsg:retentionPolicy "replayable archive" .
|
|
47
|
+
|
|
48
|
+
:m001 a rmsg:MessageEnvelope .
|
|
49
|
+
:m001 rmsg:offset 1 .
|
|
50
|
+
:m001 prov:generatedAtTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
|
|
51
|
+
:m001 rmsg:payloadKind :observation .
|
|
52
|
+
:m001 rmsg:localBlankLabel "_:b0" .
|
|
53
|
+
:m001 rmsg:expectedResult 22 .
|
|
54
|
+
:m001 rmsg:payloadGraph in:payload001 .
|
|
55
|
+
|
|
56
|
+
:m002 a rmsg:MessageEnvelope .
|
|
57
|
+
:m002 rmsg:offset 2 .
|
|
58
|
+
:m002 prov:generatedAtTime "2026-05-12T18:22:00Z"^^xsd:dateTime .
|
|
59
|
+
:m002 rmsg:payloadKind :heartbeat .
|
|
15
60
|
|
|
16
|
-
:
|
|
17
|
-
:
|
|
18
|
-
:
|
|
19
|
-
:
|
|
20
|
-
:
|
|
21
|
-
:
|
|
22
|
-
:
|
|
23
|
-
:m001 a msg:RDFMessage .
|
|
24
|
-
:m001 msg:offset 1 .
|
|
25
|
-
:m001 prov:generatedAtTime "2026-05-12T18:20:00Z" .
|
|
26
|
-
:m001 msg:payloadKind :observation .
|
|
27
|
-
:m001 msg:localBlankLabel "_:b0" .
|
|
28
|
-
:m001 msg:expectedResult 22 .
|
|
29
|
-
:m001 msg:payload in:formula1 .
|
|
30
|
-
:m002 a msg:RDFMessage .
|
|
31
|
-
:m002 msg:offset 2 .
|
|
32
|
-
:m002 prov:generatedAtTime "2026-05-12T18:22:00Z" .
|
|
33
|
-
:m002 msg:payloadKind :heartbeat .
|
|
34
|
-
:m003 a msg:RDFMessage .
|
|
35
|
-
:m003 msg:offset 3 .
|
|
36
|
-
:m003 prov:generatedAtTime "2026-05-12T18:25:00Z" .
|
|
37
|
-
:m003 msg:payloadKind :observation .
|
|
38
|
-
:m003 msg:localBlankLabel "_:b0" .
|
|
39
|
-
:m003 msg:expectedResult 23 .
|
|
40
|
-
:m003 msg:payload in:formula2 .
|
|
61
|
+
:m003 a rmsg:MessageEnvelope .
|
|
62
|
+
:m003 rmsg:offset 3 .
|
|
63
|
+
:m003 prov:generatedAtTime "2026-05-12T18:25:00Z"^^xsd:dateTime .
|
|
64
|
+
:m003 rmsg:payloadKind :observation .
|
|
65
|
+
:m003 rmsg:localBlankLabel "_:b0" .
|
|
66
|
+
:m003 rmsg:expectedResult 23 .
|
|
67
|
+
:m003 rmsg:payloadGraph in:payload003 .
|
|
41
68
|
|
|
42
|
-
in:
|
|
69
|
+
in:payload001 {
|
|
43
70
|
_:m001b0 a sosa:Observation .
|
|
44
71
|
_:m001b0 sosa:madeBySensor :thermometerA .
|
|
45
|
-
_:m001b0 sosa:resultTime "2026-05-12T18:20:00Z" .
|
|
72
|
+
_:m001b0 sosa:resultTime "2026-05-12T18:20:00Z"^^xsd:dateTime .
|
|
46
73
|
_:m001b0 sosa:hasSimpleResult 22 .
|
|
47
74
|
}
|
|
48
75
|
|
|
49
|
-
in:
|
|
76
|
+
in:payload003 {
|
|
50
77
|
_:m003b0 a sosa:Observation .
|
|
51
78
|
_:m003b0 sosa:madeBySensor :thermometerA .
|
|
52
|
-
_:m003b0 sosa:resultTime "2026-05-12T18:25:00Z" .
|
|
79
|
+
_:m003b0 sosa:resultTime "2026-05-12T18:25:00Z"^^xsd:dateTime .
|
|
53
80
|
_:m003b0 sosa:hasSimpleResult 23 .
|
|
54
81
|
}
|
|
55
82
|
|
|
@@ -57,13 +84,7 @@ in:metadata {
|
|
|
57
84
|
in:run a see:InputDataset .
|
|
58
85
|
in:run see:name "rdf_messages" .
|
|
59
86
|
in:run see:title "RDF Messages" .
|
|
60
|
-
in:run see:sourceFile "examples/
|
|
61
|
-
in:run see:
|
|
62
|
-
in:run see:description "This Eyeling example models the main idea from\nhttps://pietercolpaert.be/papers/eswc2026-rdf-messages/:\na message stream/log is not just one freely mergeable RDF graph. It is an\nordered sequence of RDF Datasets that are interpreted atomically, one message\nat a time. The middle message is deliberately empty to model a heartbeat, and\nthe local blank-node label \"_:b0\" is deliberately reused by two messages to\nshow message-scoped blank nodes." .
|
|
87
|
+
in:run see:sourceFile "examples/rdf-messages.n3" .
|
|
88
|
+
in:run see:description "A single Eyeling example split across an N3 rule file and a TriG input sidecar. It models a replayable RDF Message Log with application-level envelopes and named payload graphs so that each payload can be interpreted atomically without merging all message contents into one graph." .
|
|
63
89
|
in:run see:compiler "Eyeling RDF/TriG input sidecar" .
|
|
64
|
-
in:run see:inputFacts 25 .
|
|
65
|
-
in:run see:compiledRules 6 .
|
|
66
|
-
in:run see:compiledBackwardRules 0 .
|
|
67
|
-
in:run see:compiledFuses 0 .
|
|
68
|
-
in:run see:compiledQueries 1 .
|
|
69
90
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- [Input TriG](../input/rdf-message-flow.trig)
|
|
7
7
|
|
|
8
8
|
## Answer
|
|
9
|
-
Continuous RDF Message flow accepted: 5 ordered
|
|
9
|
+
Continuous RDF Message flow accepted: 5 ordered message envelopes moved through the ingest → validate → interpret → route → sink pipeline. The threshold was 26, so results 21 and 22 were archived, the heartbeat kept the stream alive, and results 28 and 29 were emitted as alerts.
|
|
10
10
|
|
|
11
11
|
## Explanation
|
|
12
|
-
The N3
|
|
12
|
+
The input is a single runnable example split across an N3 rule file and a TriG sidecar. The TriG file uses example-local envelope facts for stream order and processing state, while each named payload graph is treated as an atomic RDF Message dataset. Only :m001 starts at ingress; each envelope must reach :sink before the continuous-flow rule releases its flow:nextEnvelope. Observation payloads are inspected with log:includes inside their own payload formula, and the empty heartbeat advances without a payload graph. This keeps message boundaries visible to the reasoner instead of merging all payload triples into one global graph.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# rdf-message-microgrid
|
|
2
|
+
|
|
3
|
+
## Source files
|
|
4
|
+
|
|
5
|
+
- [N3 rules](../rdf-message-microgrid.n3)
|
|
6
|
+
- [Input TriG](../input/rdf-message-microgrid.trig)
|
|
7
|
+
|
|
8
|
+
## Answer
|
|
9
|
+
Storm clinic microgrid accepted: 4 RDF Message envelopes were replayed atomically. Critical care needs 620 W, current battery plus solar gives 800 W, and deferring the EV chargers frees 600 W, so the protected budget is 1400 W. The reasoned action is to keep the oxygen concentrator and vaccine fridge online, while deferring EV charging.
|
|
10
|
+
|
|
11
|
+
## Why this is an RDF Messages example
|
|
12
|
+
The input is a single runnable example split across an N3 rule file and a TriG sidecar. The default graph records stream order, offsets, and envelope metadata. Each non-empty named graph is treated as an atomic message payload, and the fourth message is an empty heartbeat. The rules inspect each payload with log:includes inside its own formula, then combine only the derived conclusions needed for the microgrid decision. This keeps the explanation tied to message boundaries instead of silently flattening the stream into one global graph.
|
|
13
|
+
|
|
14
|
+
This is intentionally not a parser-level VERSION \"1.2-messages\" / MESSAGE delimiter test. It is a reasoning example over an already-materialized sidecar representation of a message log.
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
- [Input TriG](../input/rdf-messages.trig)
|
|
7
7
|
|
|
8
8
|
## Answer
|
|
9
|
-
RDF Message
|
|
9
|
+
RDF Message replay archive accepted: 3 explicit message boundaries are preserved. Message :m002 is an empty heartbeat, and the local blank-node label _:b0 is safely reused in separate message envelopes.
|
|
10
10
|
|
|
11
11
|
## Explanation
|
|
12
|
-
The
|
|
12
|
+
The input is a single runnable example split across an N3 rule file and a TriG sidecar. The TriG file uses application-local envelope facts for stream order and replay metadata, while each non-empty named payload graph is treated as an atomic RDF Message dataset. Payloads are inspected with log:includes inside their own formulas, so the observation data stays inside the message boundary instead of being treated as one global graph. The two temperature results, 22 and 23, are different observations from the same stream but are contextualized by their message boundaries.
|
|
13
|
+
|
|
14
|
+
This is intentionally not a parser-level VERSION \"1.2-messages\" / MESSAGE delimiter test. It is a reasoning example over an already-materialized sidecar representation of a message log.
|