eyeling 1.24.31 → 1.25.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/HANDBOOK.md +2 -2
- package/dist/browser/eyeling.browser.js +26 -7
- package/dist/browser/index.mjs +3 -0
- package/examples/deck/rdf-message-flow.md +273 -0
- package/examples/fuse.n3 +1 -1
- package/examples/input/rdf-message-microgrid.trig +39 -72
- package/examples/input/rdf-messages.trig +41 -84
- package/examples/liar.n3 +1 -1
- package/examples/output/rdf-message-microgrid.md +4 -6
- package/examples/output/rdf-messages.md +3 -5
- package/examples/rdf-message-microgrid.n3 +54 -68
- package/examples/rdf-messages.n3 +63 -76
- package/eyeling.js +26 -7
- package/index.d.ts +3 -0
- package/lib/builtins.js +18 -5
- package/lib/engine.js +7 -2
- package/lib/entry.js +1 -0
- package/package.json +1 -1
- package/test/api.test.js +34 -5
- package/test/package.test.js +2 -2
- package/tools/bundle.js +3 -0
package/HANDBOOK.md
CHANGED
|
@@ -793,7 +793,7 @@ Implementation: deterministic Skolem IDs live in `lib/skolem.js`; the per-firing
|
|
|
793
793
|
A rule whose conclusion is `false` is treated as a hard failure. During forward chaining:
|
|
794
794
|
|
|
795
795
|
- Eyeling proves the premise (it only needs one solution)
|
|
796
|
-
- if the premise is provable, it prints a message and exits with status code
|
|
796
|
+
- if the premise is provable, it prints a message and exits with status code 65 (`EX_DATAERR` in Unix `sysexits.h` terminology)
|
|
797
797
|
|
|
798
798
|
This is Eyeling’s way to express hard consistency checks and detect inconsistencies.
|
|
799
799
|
|
|
@@ -3417,7 +3417,7 @@ So Eyeling is not only implementing the semantics document; it is also defining
|
|
|
3417
3417
|
|
|
3418
3418
|
#### G.2.4 Inference fuses (`=> false`) are an engine-level procedural feature
|
|
3419
3419
|
|
|
3420
|
-
The semantics document discusses `false` in relation to implication and constraints. Eyeling turns `{ ... } => false` into an engine-level hard failure with a visible message and
|
|
3420
|
+
The semantics document discusses `false` in relation to implication and constraints. Eyeling turns `{ ... } => false` into an engine-level hard failure with a visible message and exit status 65 (`EX_DATAERR`). That is a practical tooling feature: it lets a rule act like a checked invariant.
|
|
3421
3421
|
|
|
3422
3422
|
This is very useful in real programs, but it is an operational behavior of the reasoner, not something a model-theoretic semantics “executes.”
|
|
3423
3423
|
|
|
@@ -1207,25 +1207,36 @@ function parseXsdDateTerm(t) {
|
|
|
1207
1207
|
return d;
|
|
1208
1208
|
}
|
|
1209
1209
|
|
|
1210
|
+
function isXsdDateTimeDatatype(dt) {
|
|
1211
|
+
return dt === XSD_NS + 'dateTime' || dt === XSD_NS + 'dateTimeStamp';
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1210
1214
|
function parseXsdDatetimeTerm(t) {
|
|
1211
1215
|
if (!(t instanceof Literal)) return null;
|
|
1212
1216
|
const [lex, dt] = literalParts(t.value);
|
|
1213
|
-
if (dt
|
|
1217
|
+
if (!isXsdDateTimeDatatype(dt)) return null;
|
|
1214
1218
|
const val = stripQuotes(lex);
|
|
1219
|
+
|
|
1220
|
+
// xsd:dateTimeStamp is a subtype of xsd:dateTime with a required timezone.
|
|
1221
|
+
// Keep xsd:dateTime's existing permissive behaviour, but reject stamp
|
|
1222
|
+
// lexicals that do not actually carry the required timezone.
|
|
1223
|
+
if (dt === XSD_NS + 'dateTimeStamp' && !/(Z|[+-]\d{2}:\d{2})$/.test(val)) return null;
|
|
1224
|
+
|
|
1215
1225
|
const d = new Date(val);
|
|
1216
1226
|
if (Number.isNaN(d.getTime())) return null;
|
|
1217
1227
|
return d; // Date in local/UTC, we only use timestamp
|
|
1218
1228
|
}
|
|
1219
1229
|
|
|
1220
1230
|
function parseXsdDateTimeLexParts(t) {
|
|
1221
|
-
// Parse *lexical* components of an xsd:dateTime literal without timezone normalization.
|
|
1231
|
+
// Parse *lexical* components of an xsd:dateTime/dateTimeStamp literal without timezone normalization.
|
|
1222
1232
|
// Returns { yearStr, month, day, hour, minute, second, tz } or null.
|
|
1223
1233
|
if (!(t instanceof Literal)) return null;
|
|
1224
1234
|
const [lex, dt] = literalParts(t.value);
|
|
1225
|
-
if (dt
|
|
1235
|
+
if (!isXsdDateTimeDatatype(dt)) return null;
|
|
1226
1236
|
const val = stripQuotes(lex);
|
|
1227
1237
|
|
|
1228
1238
|
// xsd:dateTime lexical: YYYY-MM-DDThh:mm:ss(.s+)?(Z|(+|-)hh:mm)?
|
|
1239
|
+
// xsd:dateTimeStamp has the same lexical form, but with the timezone required.
|
|
1229
1240
|
const m = /^(-?\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-]\d{2}:\d{2})?$/.exec(val);
|
|
1230
1241
|
if (!m) return null;
|
|
1231
1242
|
|
|
@@ -1236,6 +1247,7 @@ function parseXsdDateTimeLexParts(t) {
|
|
|
1236
1247
|
const minute = parseInt(m[5], 10);
|
|
1237
1248
|
const second = parseInt(m[6], 10);
|
|
1238
1249
|
const tz = m[7] || null;
|
|
1250
|
+
if (dt === XSD_NS + 'dateTimeStamp' && !tz) return null;
|
|
1239
1251
|
|
|
1240
1252
|
if (!(month >= 1 && month <= 12)) return null;
|
|
1241
1253
|
if (!(day >= 1 && day <= 31)) return null;
|
|
@@ -1302,7 +1314,8 @@ function parseIso8601DurationToSeconds(s) {
|
|
|
1302
1314
|
}
|
|
1303
1315
|
|
|
1304
1316
|
function parseNumericForCompareTerm(t) {
|
|
1305
|
-
// Strict: only accept xsd numeric literals, xsd:duration, xsd:date,
|
|
1317
|
+
// Strict: only accept xsd numeric literals, xsd:duration, xsd:date,
|
|
1318
|
+
// xsd:dateTime, and xsd:dateTimeStamp.
|
|
1306
1319
|
// (or untyped numeric tokens).
|
|
1307
1320
|
const bi = parseIntLiteral(t);
|
|
1308
1321
|
if (bi !== null) return { kind: 'bigint', value: bi };
|
|
@@ -1369,7 +1382,7 @@ function parseNumOrDuration(t) {
|
|
|
1369
1382
|
}
|
|
1370
1383
|
}
|
|
1371
1384
|
|
|
1372
|
-
// xsd:date / xsd:dateTime
|
|
1385
|
+
// xsd:date / xsd:dateTime / xsd:dateTimeStamp
|
|
1373
1386
|
const dtval = parseDatetimeLike(t);
|
|
1374
1387
|
if (dtval !== null) {
|
|
1375
1388
|
return dtval.getTime() / 1000.0;
|
|
@@ -5504,6 +5517,10 @@ const {
|
|
|
5504
5517
|
copyQuotedGraphMetadata,
|
|
5505
5518
|
} = require('./prelude');
|
|
5506
5519
|
|
|
5520
|
+
// Inference fuses use sysexits.h EX_DATAERR (65): input/rules made a
|
|
5521
|
+
// forbidden condition provable, rather than a generic usage/runtime error.
|
|
5522
|
+
const INFERENCE_FUSE_EXIT_CODE = 65;
|
|
5523
|
+
|
|
5507
5524
|
// In N3/Turtle, rdf:nil is the canonical IRI for the empty RDF list.
|
|
5508
5525
|
// Eyeling represents list literals with ListTerm; ensure rdf:nil unifies with ().
|
|
5509
5526
|
const RDF_NIL_IRI = RDF_NS + 'nil';
|
|
@@ -8432,7 +8449,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8432
8449
|
// Allow dynamic fuses: ... => ?X. where ?X becomes false
|
|
8433
8450
|
if (dynTerm instanceof Literal && dynTerm.value === 'false') {
|
|
8434
8451
|
__printTriggeredFuse(r, opts && opts.prefixes, s, 'Dynamic head resolved to false.');
|
|
8435
|
-
__exitReasoning(
|
|
8452
|
+
__exitReasoning(INFERENCE_FUSE_EXIT_CODE, 'Inference fuse triggered.');
|
|
8436
8453
|
}
|
|
8437
8454
|
|
|
8438
8455
|
const dynTriples = __graphTriplesOrTrue(dynTerm);
|
|
@@ -8593,7 +8610,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8593
8610
|
// Inference fuse
|
|
8594
8611
|
if (r.isFuse && sols.length) {
|
|
8595
8612
|
__printTriggeredFuse(r, opts && opts.prefixes, sols[0]);
|
|
8596
|
-
__exitReasoning(
|
|
8613
|
+
__exitReasoning(INFERENCE_FUSE_EXIT_CODE, 'Inference fuse triggered.');
|
|
8597
8614
|
}
|
|
8598
8615
|
|
|
8599
8616
|
for (const s of sols) {
|
|
@@ -9130,6 +9147,7 @@ module.exports = {
|
|
|
9130
9147
|
registerBuiltinModule,
|
|
9131
9148
|
loadBuiltinModule,
|
|
9132
9149
|
listBuiltinIris,
|
|
9150
|
+
INFERENCE_FUSE_EXIT_CODE,
|
|
9133
9151
|
};
|
|
9134
9152
|
|
|
9135
9153
|
};
|
|
@@ -9157,6 +9175,7 @@ module.exports = {
|
|
|
9157
9175
|
rdfjs: dataFactory,
|
|
9158
9176
|
main: engine.main,
|
|
9159
9177
|
version: engine.version,
|
|
9178
|
+
INFERENCE_FUSE_EXIT_CODE: engine.INFERENCE_FUSE_EXIT_CODE,
|
|
9160
9179
|
|
|
9161
9180
|
// internals for playground.html
|
|
9162
9181
|
lex: engine.lex,
|
package/dist/browser/index.mjs
CHANGED
|
@@ -10,6 +10,8 @@ function getBrowserApi() {
|
|
|
10
10
|
return api;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export const INFERENCE_FUSE_EXIT_CODE = 65;
|
|
14
|
+
|
|
13
15
|
export function reasonStream(input, opts) {
|
|
14
16
|
return getBrowserApi().reasonStream(input, opts);
|
|
15
17
|
}
|
|
@@ -64,6 +66,7 @@ const eyeling = {
|
|
|
64
66
|
get version() {
|
|
65
67
|
return getBrowserApi().version;
|
|
66
68
|
},
|
|
69
|
+
INFERENCE_FUSE_EXIT_CODE,
|
|
67
70
|
reasonStream,
|
|
68
71
|
reasonRdfJs,
|
|
69
72
|
rdfjs,
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# RDF Message Logs in Eyeling — from stream to reasoning
|
|
2
|
+
|
|
3
|
+
This deck explains the example `rdf-message-flow.n3` and its input file `input/rdf-message-flow.trig`.
|
|
4
|
+
|
|
5
|
+
The goal is to show, in plain language, how Eyeling can now read an RDF Message Log directly instead of asking the example data to describe its own message envelopes by hand.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The everyday problem
|
|
10
|
+
|
|
11
|
+
Many real systems do not receive one big dataset.
|
|
12
|
+
|
|
13
|
+
They receive a stream of small updates:
|
|
14
|
+
|
|
15
|
+
- a sensor reading,
|
|
16
|
+
- a command,
|
|
17
|
+
- a status heartbeat,
|
|
18
|
+
- an alert,
|
|
19
|
+
- another sensor reading.
|
|
20
|
+
|
|
21
|
+
Each update matters as a separate communication event.
|
|
22
|
+
|
|
23
|
+
If we simply merge everything into one graph, we lose the order and the boundary between messages.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## A message is a sealed packet
|
|
28
|
+
|
|
29
|
+
Think of an RDF Message as a sealed packet of RDF data.
|
|
30
|
+
|
|
31
|
+
Inside the packet there may be triples or named graphs.
|
|
32
|
+
|
|
33
|
+
Outside the packet there is the stream order: first message, second message, third message, and so on.
|
|
34
|
+
|
|
35
|
+
The important idea is:
|
|
36
|
+
|
|
37
|
+
> The reasoner should know when one message ends and the next one begins.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## What an RDF Message Log adds
|
|
42
|
+
|
|
43
|
+
An RDF Message Log is a replayable record of a message stream.
|
|
44
|
+
|
|
45
|
+
Instead of saying “subscribe to this live channel”, the file says:
|
|
46
|
+
|
|
47
|
+
> Here are the messages that arrived, in order.
|
|
48
|
+
|
|
49
|
+
That makes it useful for examples, tests, audits, debugging, reproducible reasoning, and explanations.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## The new syntax in the input file
|
|
54
|
+
|
|
55
|
+
The input begins with:
|
|
56
|
+
|
|
57
|
+
```trig
|
|
58
|
+
VERSION "1.2-messages"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
That tells Eyeling:
|
|
62
|
+
|
|
63
|
+
> This file contains message boundaries.
|
|
64
|
+
|
|
65
|
+
Then each boundary is written as:
|
|
66
|
+
|
|
67
|
+
```trig
|
|
68
|
+
MESSAGE
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
So the file can look like this:
|
|
72
|
+
|
|
73
|
+
```trig
|
|
74
|
+
# message 1 data
|
|
75
|
+
:temperatureFlow :highThreshold 26 .
|
|
76
|
+
_:obs sosa:hasSimpleResult 21 .
|
|
77
|
+
|
|
78
|
+
MESSAGE
|
|
79
|
+
|
|
80
|
+
# message 2 data
|
|
81
|
+
_:obs sosa:hasSimpleResult 22 .
|
|
82
|
+
|
|
83
|
+
MESSAGE
|
|
84
|
+
|
|
85
|
+
# message 3: empty heartbeat
|
|
86
|
+
MESSAGE
|
|
87
|
+
|
|
88
|
+
# message 4 data
|
|
89
|
+
_:obs sosa:hasSimpleResult 28 .
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## What Eyeling does internally
|
|
95
|
+
|
|
96
|
+
Eyeling does not treat `MESSAGE` as an ordinary RDF term.
|
|
97
|
+
|
|
98
|
+
It handles it before normal N3 reasoning starts.
|
|
99
|
+
|
|
100
|
+
Internally, Eyeling turns the log into a replay view:
|
|
101
|
+
|
|
102
|
+
- one stream resource,
|
|
103
|
+
- one envelope per message,
|
|
104
|
+
- an offset for each envelope,
|
|
105
|
+
- a link to the next envelope,
|
|
106
|
+
- a payload graph for each non-empty message,
|
|
107
|
+
- and an explicit marker for empty messages.
|
|
108
|
+
|
|
109
|
+
The rules then reason over that replay view.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Why this is better than hand-written envelopes
|
|
114
|
+
|
|
115
|
+
Before this change, the example input had to describe the stream manually:
|
|
116
|
+
|
|
117
|
+
- message `:m001`,
|
|
118
|
+
- message `:m002`,
|
|
119
|
+
- payload graph `in:payload001`,
|
|
120
|
+
- next message links,
|
|
121
|
+
- payload kind markers,
|
|
122
|
+
- offsets.
|
|
123
|
+
|
|
124
|
+
That worked, but it made the example bulky.
|
|
125
|
+
|
|
126
|
+
It also mixed two concerns:
|
|
127
|
+
|
|
128
|
+
1. the message-log machinery, and
|
|
129
|
+
2. the domain logic of routing temperature observations.
|
|
130
|
+
|
|
131
|
+
Now Eyeling handles the message-log machinery.
|
|
132
|
+
|
|
133
|
+
The N3 file can focus on the logic.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## What the temperature-flow example does
|
|
138
|
+
|
|
139
|
+
The example models a small stream processor.
|
|
140
|
+
|
|
141
|
+
Messages move through these stages:
|
|
142
|
+
|
|
143
|
+
1. ingest,
|
|
144
|
+
2. validate,
|
|
145
|
+
3. interpret,
|
|
146
|
+
4. route,
|
|
147
|
+
5. sink.
|
|
148
|
+
|
|
149
|
+
The stream contains temperature readings and one empty heartbeat.
|
|
150
|
+
|
|
151
|
+
The rules route normal readings to an archive sink and high readings to an alert sink.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## The empty heartbeat matters
|
|
156
|
+
|
|
157
|
+
One message in the example contains no RDF triples.
|
|
158
|
+
|
|
159
|
+
That is not an error.
|
|
160
|
+
|
|
161
|
+
It represents a heartbeat:
|
|
162
|
+
|
|
163
|
+
> “The stream is still alive, even though there is no new observation payload.”
|
|
164
|
+
|
|
165
|
+
Eyeling still creates an envelope for it.
|
|
166
|
+
|
|
167
|
+
That means the empty message keeps its place in the ordered replay.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Blank nodes stay message-local
|
|
172
|
+
|
|
173
|
+
The input deliberately reuses the same blank-node label in several messages:
|
|
174
|
+
|
|
175
|
+
```trig
|
|
176
|
+
_:obs sosa:hasSimpleResult 21 .
|
|
177
|
+
|
|
178
|
+
MESSAGE
|
|
179
|
+
|
|
180
|
+
_:obs sosa:hasSimpleResult 22 .
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
That does not mean both messages talk about the same blank node.
|
|
184
|
+
|
|
185
|
+
In a message log, blank-node labels are scoped to the message.
|
|
186
|
+
|
|
187
|
+
Eyeling rewrites them internally so each message gets its own blank nodes.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## How the N3 rules see the replay
|
|
192
|
+
|
|
193
|
+
The N3 rules do not see `MESSAGE` directly.
|
|
194
|
+
|
|
195
|
+
They see Eyeling’s replay vocabulary, `eymsg:`.
|
|
196
|
+
|
|
197
|
+
For example, a rule can ask:
|
|
198
|
+
|
|
199
|
+
```n3
|
|
200
|
+
?Stream a eymsg:RDFMessageStream;
|
|
201
|
+
eymsg:firstEnvelope ?Envelope.
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Another rule can inspect a payload:
|
|
205
|
+
|
|
206
|
+
```n3
|
|
207
|
+
?Envelope eymsg:payloadGraph ?Payload.
|
|
208
|
+
?Payload log:nameOf ?PayloadContext.
|
|
209
|
+
?PayloadContext log:includes {
|
|
210
|
+
?Observation sosa:hasSimpleResult ?Result.
|
|
211
|
+
}.
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
That keeps each message payload inside its own context.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Back pressure in one sentence
|
|
219
|
+
|
|
220
|
+
The example releases only the first envelope at the start.
|
|
221
|
+
|
|
222
|
+
Each envelope must reach the sink before the next envelope is released.
|
|
223
|
+
|
|
224
|
+
That gives a simple form of ordered replay or back pressure:
|
|
225
|
+
|
|
226
|
+
> process this message, then release the next one.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## What the final answer says
|
|
231
|
+
|
|
232
|
+
When the example succeeds, Eyeling reports that five parser-replayed envelopes moved through the flow.
|
|
233
|
+
|
|
234
|
+
With a threshold of 26:
|
|
235
|
+
|
|
236
|
+
- 21 is archived,
|
|
237
|
+
- 22 is archived,
|
|
238
|
+
- the empty heartbeat is accepted,
|
|
239
|
+
- 28 becomes an alert,
|
|
240
|
+
- 29 becomes an alert.
|
|
241
|
+
|
|
242
|
+
The important part is not only the routing result.
|
|
243
|
+
|
|
244
|
+
The important part is that the result was derived while preserving message boundaries.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Why a wide audience should care
|
|
249
|
+
|
|
250
|
+
This pattern is useful wherever data arrives over time:
|
|
251
|
+
|
|
252
|
+
- sensors,
|
|
253
|
+
- event logs,
|
|
254
|
+
- audit trails,
|
|
255
|
+
- clinical systems,
|
|
256
|
+
- energy systems,
|
|
257
|
+
- pub/sub channels,
|
|
258
|
+
- digital twins,
|
|
259
|
+
- provenance streams.
|
|
260
|
+
|
|
261
|
+
You can replay the stream, reason over each message atomically, and explain what happened without flattening the whole history into one graph.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## The takeaway
|
|
266
|
+
|
|
267
|
+
`MESSAGE` is the boundary.
|
|
268
|
+
|
|
269
|
+
Eyeling turns those boundaries into ordered replay envelopes.
|
|
270
|
+
|
|
271
|
+
The N3 rules consume the replay and focus on the domain logic.
|
|
272
|
+
|
|
273
|
+
That makes the example shorter, clearer, and closer to how a real pub/sub channel would be processed.
|
package/examples/fuse.n3
CHANGED
|
@@ -1,89 +1,56 @@
|
|
|
1
|
-
#
|
|
2
|
-
# RDF Message Microgrid
|
|
3
|
-
#
|
|
1
|
+
# ==================================
|
|
2
|
+
# RDF Message Microgrid message log
|
|
3
|
+
# ==================================
|
|
4
4
|
#
|
|
5
5
|
# This file is the data half of the runnable example:
|
|
6
6
|
#
|
|
7
7
|
# eyeling -r examples/rdf-message-microgrid.n3 examples/input/rdf-message-microgrid.trig
|
|
8
8
|
#
|
|
9
9
|
# The scene is a clinic operating as an islanded microgrid after a storm. The
|
|
10
|
-
# data arrives as a
|
|
11
|
-
#
|
|
12
|
-
#
|
|
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#> .
|
|
10
|
+
# data arrives as a true RDF Message Log: life-safety loads, power status,
|
|
11
|
+
# flexible demand, and an empty heartbeat. Eyeling parses the MESSAGE boundaries
|
|
12
|
+
# internally and exposes the replay to the N3 rules as eymsg: envelopes.
|
|
25
13
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
:stormClinicLog rmsg:message :m003 .
|
|
31
|
-
:stormClinicLog rmsg:message :m004 .
|
|
32
|
-
:stormClinicLog rmsg:retentionPolicy "storm replay archive" .
|
|
14
|
+
VERSION "1.2-messages"
|
|
15
|
+
PREFIX : <https://eyereasoner.github.io/eyeling/examples/rdf-message-microgrid#>
|
|
16
|
+
PREFIX see: <https://example.org/see#>
|
|
17
|
+
PREFIX in: <https://example.org/see/input#>
|
|
33
18
|
|
|
34
|
-
|
|
35
|
-
:
|
|
36
|
-
:
|
|
37
|
-
:
|
|
38
|
-
:m001 rmsg:payloadGraph in:lifeSafetyPayload .
|
|
19
|
+
# Message 1: life-safety loads plus example metadata.
|
|
20
|
+
:oxygenConcentrator a :LifeSafetyLoad ;
|
|
21
|
+
:requiresWatts 500 ;
|
|
22
|
+
:serves :respiratoryCareRoom .
|
|
39
23
|
|
|
40
|
-
:
|
|
41
|
-
:
|
|
42
|
-
:
|
|
43
|
-
:m002 rmsg:payloadKind :powerStatus .
|
|
44
|
-
:m002 rmsg:payloadGraph in:powerPayload .
|
|
24
|
+
:vaccineFridge a :ColdChainLoad ;
|
|
25
|
+
:requiresWatts 120 ;
|
|
26
|
+
:serves :vaccineColdChain .
|
|
45
27
|
|
|
46
|
-
:
|
|
47
|
-
:
|
|
48
|
-
:
|
|
49
|
-
:
|
|
50
|
-
:
|
|
28
|
+
in:run a see:InputDataset ;
|
|
29
|
+
see:name "rdf_message_microgrid" ;
|
|
30
|
+
see:title "RDF Message Microgrid" ;
|
|
31
|
+
see:sourceFile "examples/rdf-message-microgrid.n3" ;
|
|
32
|
+
see:description "A true RDF 1.2 Message Log for a storm clinic microgrid. Eyeling parses MESSAGE delimiters into replayable internal message envelopes for bounded, explainable load-shedding." ;
|
|
33
|
+
see:compiler "Eyeling RDF Message Log input" .
|
|
51
34
|
|
|
52
|
-
|
|
53
|
-
:m004 rmsg:offset 4 .
|
|
54
|
-
:m004 prov:generatedAtTime "2026-05-15T19:03:00Z"^^xsd:dateTime .
|
|
55
|
-
:m004 rmsg:payloadKind :heartbeat .
|
|
35
|
+
MESSAGE
|
|
56
36
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
:
|
|
60
|
-
:
|
|
37
|
+
# Message 2: current power status.
|
|
38
|
+
:batteryBank a :PowerReserve ;
|
|
39
|
+
:availableWatts 650 ;
|
|
40
|
+
:state :islanded .
|
|
61
41
|
|
|
62
|
-
|
|
63
|
-
:
|
|
64
|
-
:vaccineFridge :serves :vaccineColdChain .
|
|
65
|
-
}
|
|
42
|
+
:solarForecast a :NearTermForecast ;
|
|
43
|
+
:expectedWatts 150 .
|
|
66
44
|
|
|
67
|
-
|
|
68
|
-
:batteryBank a :PowerReserve .
|
|
69
|
-
:batteryBank :availableWatts 650 .
|
|
70
|
-
:batteryBank :state :islanded .
|
|
45
|
+
MESSAGE
|
|
71
46
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
47
|
+
# Message 3: flexible demand that can safely be shed.
|
|
48
|
+
:evChargers a :FlexibleLoad ;
|
|
49
|
+
:shedWatts 600 ;
|
|
50
|
+
:serves :staffVehicles .
|
|
75
51
|
|
|
76
|
-
|
|
77
|
-
:evChargers a :FlexibleLoad .
|
|
78
|
-
:evChargers :shedWatts 600 .
|
|
79
|
-
:evChargers :serves :staffVehicles .
|
|
80
|
-
}
|
|
52
|
+
MESSAGE
|
|
81
53
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
}
|
|
54
|
+
# Message 4: empty heartbeat. The next MESSAGE delimiter finalizes this empty
|
|
55
|
+
# message and leaves no trailing payload.
|
|
56
|
+
MESSAGE
|