eyeling 1.5.32 → 1.5.34
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/examples/json-pointer.n3 +2 -1
- package/examples/json-reconcile-vat.n3 +356 -0
- package/examples/output/age.n3 +2 -2
- package/examples/output/json-pointer.n3 +103 -103
- package/examples/output/json-reconcile-vat.n3 +33530 -0
- package/examples/output/math-builtins-tests.n3 +21 -20
- package/eyeling.js +63 -119
- package/package.json +1 -1
package/examples/json-pointer.n3
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
1
2
|
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
2
3
|
@prefix list: <http://www.w3.org/2000/10/swap/list#> .
|
|
3
4
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
@@ -9,7 +10,7 @@ ex:doc ex:json """{
|
|
|
9
10
|
{ "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
|
|
10
11
|
],
|
|
11
12
|
"policy": { "allowedDomains": ["example.org", "example.com"] }
|
|
12
|
-
}""" .
|
|
13
|
+
}"""^^rdf:JSON .
|
|
13
14
|
|
|
14
15
|
# Sanity check: key contains "/" so JSON Pointer must use "~1"
|
|
15
16
|
{
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
3
|
+
@prefix list: <http://www.w3.org/2000/10/swap/list#> .
|
|
4
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
5
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
6
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
7
|
+
@prefix ex: <http://example.org/> .
|
|
8
|
+
|
|
9
|
+
# ------------------------------------------------------------------
|
|
10
|
+
# Master data (canonical N3 graph)
|
|
11
|
+
# ------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
ex:policy ex:euCountries ("BE" "NL" "DE" "FR" "LU") .
|
|
14
|
+
|
|
15
|
+
ex:Widget a ex:Product ;
|
|
16
|
+
ex:sku "WID-001" ;
|
|
17
|
+
ex:listPriceCents 999 ;
|
|
18
|
+
ex:vatBps 2100 .
|
|
19
|
+
|
|
20
|
+
ex:Gadget a ex:Product ;
|
|
21
|
+
ex:sku "GAD-002" ;
|
|
22
|
+
ex:listPriceCents 1500 ;
|
|
23
|
+
ex:vatBps 600 .
|
|
24
|
+
|
|
25
|
+
# Registry customer keyed by normalized VAT key (alnum only)
|
|
26
|
+
ex:RegistryNL
|
|
27
|
+
ex:vatKey "NL999999999B01" ;
|
|
28
|
+
ex:legalName "Example BV" .
|
|
29
|
+
|
|
30
|
+
# ------------------------------------------------------------------
|
|
31
|
+
# JSON feed (operational system)
|
|
32
|
+
# ------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
ex:feed ex:json """{
|
|
35
|
+
"meta~data": { "batch": "2025-12-18" },
|
|
36
|
+
"orders": [
|
|
37
|
+
{
|
|
38
|
+
"orderId": "A100",
|
|
39
|
+
"shipTo": { "country": "BE" },
|
|
40
|
+
"customer": { "email": "Ada@Example.ORG", "vatId": null },
|
|
41
|
+
"lines": [
|
|
42
|
+
{ "sku": "wid-001", "qty": 2, "unitCents": 999 },
|
|
43
|
+
{ "sku": "GAD-002", "qty": 1, "unitCents": 1500 }
|
|
44
|
+
],
|
|
45
|
+
"declared": { "netCents": 3498, "vatCents": 509, "grossCents": 4007 }
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"orderId": "A200",
|
|
49
|
+
"shipTo": { "country": "US" },
|
|
50
|
+
"customer": { "email": "bob@example.org", "vatId": null },
|
|
51
|
+
"lines": [
|
|
52
|
+
{ "sku": "WID-001", "qty": 2, "unitCents": 999 },
|
|
53
|
+
{ "sku": "GAD-002", "qty": 1, "unitCents": 1500 }
|
|
54
|
+
],
|
|
55
|
+
"declared": { "netCents": 3498, "vatCents": 0, "grossCents": 3498 }
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"orderId": "A300",
|
|
59
|
+
"shipTo": { "country": "NL" },
|
|
60
|
+
"customer": { "email": "acct@company.nl", "vatId": "NL999999999B01" },
|
|
61
|
+
"lines": [
|
|
62
|
+
{ "sku": "WID-001", "qty": 2, "unitCents": 999 },
|
|
63
|
+
{ "sku": "GAD-002", "qty": 1, "unitCents": 1500 }
|
|
64
|
+
],
|
|
65
|
+
"declared": { "netCents": 3498, "vatCents": 0, "grossCents": 3498 }
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"orderId": "A301",
|
|
69
|
+
"shipTo": { "country": "NL" },
|
|
70
|
+
"customer": { "email": "billing@company.nl", "vatId": "NL999999999B01" },
|
|
71
|
+
"lines": [
|
|
72
|
+
{ "sku": "WID-001", "qty": 2, "unitCents": 999 },
|
|
73
|
+
{ "sku": "GAD-002", "qty": 1, "unitCents": 1500 }
|
|
74
|
+
],
|
|
75
|
+
"declared": { "netCents": 3498, "vatCents": 0, "grossCents": 3498 }
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"orderId": "A400",
|
|
79
|
+
"shipTo": { "country": "BE" },
|
|
80
|
+
"customer": { "email": "ada@other.example", "vatId": null },
|
|
81
|
+
"lines": [
|
|
82
|
+
{ "sku": "WID-001", "qty": 2, "unitCents": 999 },
|
|
83
|
+
{ "sku": "GAD-002", "qty": 1, "unitCents": 1500 }
|
|
84
|
+
],
|
|
85
|
+
"declared": { "netCents": 3498, "vatCents": 508, "grossCents": 4006 }
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}"""^^rdf:JSON .
|
|
89
|
+
|
|
90
|
+
# ------------------------------------------------------------------
|
|
91
|
+
# Sanity checks for JSON Pointer edge-cases
|
|
92
|
+
# ------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
ex:feed ex:json ?J .
|
|
96
|
+
(?J "#/orders/0/shipTo/country") string:jsonPointer "BE" .
|
|
97
|
+
} => {
|
|
98
|
+
ex:checks ex:fragmentPointerWorks true .
|
|
99
|
+
} .
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
ex:feed ex:json ?J .
|
|
103
|
+
(?J "/meta~0data/batch") string:jsonPointer "2025-12-18" .
|
|
104
|
+
} => {
|
|
105
|
+
ex:checks ex:tildeEscapeWorks true .
|
|
106
|
+
} .
|
|
107
|
+
|
|
108
|
+
# ------------------------------------------------------------------
|
|
109
|
+
# 1) Materialize orders (JSON -> RDF-ish facts)
|
|
110
|
+
# ------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
{
|
|
113
|
+
ex:feed ex:json ?J .
|
|
114
|
+
(?J "/orders") string:jsonPointer ?Orders .
|
|
115
|
+
?Orders list:iterate (?OrderIndex ?OrderJson) .
|
|
116
|
+
|
|
117
|
+
(?OrderJson "/orderId") string:jsonPointer ?OrderId .
|
|
118
|
+
(?OrderJson "/shipTo/country") string:jsonPointer ?Country .
|
|
119
|
+
|
|
120
|
+
(?OrderJson "/customer") string:jsonPointer ?CustJson .
|
|
121
|
+
(?CustJson "/email") string:jsonPointer ?Email .
|
|
122
|
+
(?CustJson "/vatId") string:jsonPointer ?VatIdRaw .
|
|
123
|
+
|
|
124
|
+
(?OrderJson "/declared") string:jsonPointer ?Decl .
|
|
125
|
+
(?Decl "/netCents") string:jsonPointer ?NetD .
|
|
126
|
+
(?Decl "/vatCents") string:jsonPointer ?VatD .
|
|
127
|
+
(?Decl "/grossCents") string:jsonPointer ?GrossD .
|
|
128
|
+
|
|
129
|
+
("urn:example:order:%s" ?OrderId) string:format ?OrderUriStr .
|
|
130
|
+
?Order log:uri ?OrderUriStr .
|
|
131
|
+
} => {
|
|
132
|
+
?Order a ex:Order ;
|
|
133
|
+
ex:orderId ?OrderId ;
|
|
134
|
+
ex:orderIndex ?OrderIndex ;
|
|
135
|
+
ex:shipCountry ?Country ;
|
|
136
|
+
ex:email ?Email ;
|
|
137
|
+
ex:vatIdRaw ?VatIdRaw ;
|
|
138
|
+
ex:declaredNetCents ?NetD ;
|
|
139
|
+
ex:declaredVatCents ?VatD ;
|
|
140
|
+
ex:declaredGrossCents ?GrossD .
|
|
141
|
+
} .
|
|
142
|
+
|
|
143
|
+
# ------------------------------------------------------------------
|
|
144
|
+
# 2) Materialize customers
|
|
145
|
+
# - If vatId is present -> customer IRI from normalized VAT key
|
|
146
|
+
# - Else -> customer IRI is log:skolem(email)
|
|
147
|
+
# ------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
# VAT customer
|
|
150
|
+
{
|
|
151
|
+
?Order a ex:Order ; ex:vatIdRaw ?VatIdRaw ; ex:email ?Email .
|
|
152
|
+
?VatIdRaw string:notEqualIgnoringCase "null" .
|
|
153
|
+
(?VatIdRaw "[^0-9A-Za-z]" "") string:replace ?VatKey .
|
|
154
|
+
|
|
155
|
+
("urn:example:customer:vat:%s" ?VatKey) string:format ?CustUriStr .
|
|
156
|
+
?Cust log:uri ?CustUriStr .
|
|
157
|
+
} => {
|
|
158
|
+
?Cust a ex:Customer ;
|
|
159
|
+
ex:vatKey ?VatKey ;
|
|
160
|
+
ex:vatIdRaw ?VatIdRaw ;
|
|
161
|
+
ex:email ?Email .
|
|
162
|
+
?Order ex:customer ?Cust .
|
|
163
|
+
} .
|
|
164
|
+
|
|
165
|
+
# Non-VAT customer
|
|
166
|
+
{
|
|
167
|
+
?Order a ex:Order ; ex:vatIdRaw "null" ; ex:email ?Email .
|
|
168
|
+
?Email log:skolem ?Cust .
|
|
169
|
+
} => {
|
|
170
|
+
?Cust a ex:Customer ;
|
|
171
|
+
ex:vatIdRaw "null" ;
|
|
172
|
+
ex:email ?Email .
|
|
173
|
+
?Order ex:customer ?Cust .
|
|
174
|
+
} .
|
|
175
|
+
|
|
176
|
+
# Registry reconciliation (VAT customers only)
|
|
177
|
+
{
|
|
178
|
+
?Cust a ex:Customer ; ex:vatKey ?VatKey .
|
|
179
|
+
?Reg ex:vatKey ?VatKey .
|
|
180
|
+
} => {
|
|
181
|
+
?Cust owl:sameAs ?Reg .
|
|
182
|
+
} .
|
|
183
|
+
|
|
184
|
+
# Detect conflicting emails for the SAME VAT-keyed customer
|
|
185
|
+
# (Use string:lessThan to avoid duplicate symmetric pairs.)
|
|
186
|
+
{
|
|
187
|
+
?Cust a ex:Customer ; ex:vatKey ?VatKey ; ex:email ?E1 ; ex:email ?E2 .
|
|
188
|
+
?E1 string:lessThan ?E2 .
|
|
189
|
+
?E1 string:notEqualIgnoringCase ?E2 .
|
|
190
|
+
} => {
|
|
191
|
+
?Cust a ex:CustomerEmailConflict ;
|
|
192
|
+
ex:conflictingEmail ?E1, ?E2 .
|
|
193
|
+
} .
|
|
194
|
+
|
|
195
|
+
# ------------------------------------------------------------------
|
|
196
|
+
# 3) Materialize order lines + map SKU -> canonical product + compute money
|
|
197
|
+
# VAT logic:
|
|
198
|
+
# - ship outside EU -> VAT = 0 (export)
|
|
199
|
+
# - ship in EU and customer has VAT -> VAT = 0 (reverse charge)
|
|
200
|
+
# - ship in EU and customer has no VAT -> VAT = product vatBps
|
|
201
|
+
# ------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
# Helper macro: create line IRI + compute netCents
|
|
204
|
+
{
|
|
205
|
+
ex:feed ex:json ?J .
|
|
206
|
+
(?J "/orders") string:jsonPointer ?Orders .
|
|
207
|
+
?Orders list:iterate (?OrderIndex ?OrderJson) .
|
|
208
|
+
|
|
209
|
+
(?OrderJson "/orderId") string:jsonPointer ?OrderId .
|
|
210
|
+
("urn:example:order:%s" ?OrderId) string:format ?OrderUriStr .
|
|
211
|
+
?Order log:uri ?OrderUriStr .
|
|
212
|
+
|
|
213
|
+
(?OrderJson "/lines") string:jsonPointer ?Lines .
|
|
214
|
+
?Lines list:iterate (?LineIndex ?LineJson) .
|
|
215
|
+
|
|
216
|
+
(?LineJson "/sku") string:jsonPointer ?SkuJson .
|
|
217
|
+
(?LineJson "/qty") string:jsonPointer ?Qty .
|
|
218
|
+
(?LineJson "/unitCents") string:jsonPointer ?UnitCents .
|
|
219
|
+
|
|
220
|
+
# SKU reconciliation is case-insensitive
|
|
221
|
+
# (Guard with a type, otherwise already-derived OrderLine nodes can start acting as “products”.)
|
|
222
|
+
?Product a ex:Product ; ex:sku ?SkuCat .
|
|
223
|
+
?SkuJson string:equalIgnoringCase ?SkuCat .
|
|
224
|
+
|
|
225
|
+
(?Qty ?UnitCents) math:product ?NetCents .
|
|
226
|
+
|
|
227
|
+
("urn:example:order:%s#line:%s" ?OrderId ?LineIndex) string:format ?LineUriStr .
|
|
228
|
+
?Line log:uri ?LineUriStr .
|
|
229
|
+
} => {
|
|
230
|
+
?Order ex:line ?Line .
|
|
231
|
+
|
|
232
|
+
?Line a ex:OrderLine ;
|
|
233
|
+
ex:lineIndex ?LineIndex ;
|
|
234
|
+
ex:product ?Product ;
|
|
235
|
+
ex:sku ?SkuCat ;
|
|
236
|
+
ex:qty ?Qty ;
|
|
237
|
+
ex:unitCents ?UnitCents ;
|
|
238
|
+
ex:netCents ?NetCents .
|
|
239
|
+
} .
|
|
240
|
+
|
|
241
|
+
# Export (outside EU): VAT=0
|
|
242
|
+
{
|
|
243
|
+
?Order a ex:Order ; ex:shipCountry ?Country .
|
|
244
|
+
ex:policy ex:euCountries ?EU .
|
|
245
|
+
?EU list:notMember ?Country .
|
|
246
|
+
|
|
247
|
+
?Order ex:line ?Line .
|
|
248
|
+
?Line ex:netCents ?NetCents .
|
|
249
|
+
} => {
|
|
250
|
+
?Line ex:vatBps 0 ;
|
|
251
|
+
ex:vatCents 0 ;
|
|
252
|
+
ex:grossCents ?NetCents ;
|
|
253
|
+
ex:vatReason ex:Export .
|
|
254
|
+
} .
|
|
255
|
+
|
|
256
|
+
# Reverse charge (EU + VAT id present): VAT=0
|
|
257
|
+
{
|
|
258
|
+
?Order a ex:Order ; ex:shipCountry ?Country ; ex:vatIdRaw ?VatIdRaw .
|
|
259
|
+
ex:policy ex:euCountries ?EU .
|
|
260
|
+
?EU list:member ?Country .
|
|
261
|
+
?VatIdRaw string:notEqualIgnoringCase "null" .
|
|
262
|
+
|
|
263
|
+
?Order ex:line ?Line .
|
|
264
|
+
?Line ex:netCents ?NetCents .
|
|
265
|
+
} => {
|
|
266
|
+
?Line ex:vatBps 0 ;
|
|
267
|
+
ex:vatCents 0 ;
|
|
268
|
+
ex:grossCents ?NetCents ;
|
|
269
|
+
ex:vatReason ex:ReverseCharge .
|
|
270
|
+
} .
|
|
271
|
+
|
|
272
|
+
# Domestic EU consumer (EU + no VAT): VAT = product vatBps
|
|
273
|
+
{
|
|
274
|
+
?Order a ex:Order ; ex:shipCountry ?Country ; ex:vatIdRaw "null" .
|
|
275
|
+
ex:policy ex:euCountries ?EU .
|
|
276
|
+
?EU list:member ?Country .
|
|
277
|
+
|
|
278
|
+
?Order ex:line ?Line .
|
|
279
|
+
?Line ex:product ?Product ; ex:netCents ?NetCents .
|
|
280
|
+
?Product ex:vatBps ?VatBps .
|
|
281
|
+
|
|
282
|
+
(?NetCents ?VatBps) math:product ?NetTimesBps .
|
|
283
|
+
(?NetTimesBps 10000) math:integerQuotient ?VatCents .
|
|
284
|
+
(?NetCents ?VatCents) math:sum ?GrossCents .
|
|
285
|
+
} => {
|
|
286
|
+
?Line ex:vatBps ?VatBps ;
|
|
287
|
+
ex:vatCents ?VatCents ;
|
|
288
|
+
ex:grossCents ?GrossCents ;
|
|
289
|
+
ex:vatReason ex:Domestic .
|
|
290
|
+
} .
|
|
291
|
+
|
|
292
|
+
# ------------------------------------------------------------------
|
|
293
|
+
# 4) Reconcile computed totals vs declared totals
|
|
294
|
+
# (Assumes 2 lines per order: index 0 and 1 — on purpose, to keep the example finite.)
|
|
295
|
+
# ------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
{
|
|
298
|
+
?Order a ex:Order .
|
|
299
|
+
|
|
300
|
+
?Order ex:line ?L0 . ?L0 ex:lineIndex 0 ; ex:netCents ?N0 ; ex:vatCents ?V0 ; ex:grossCents ?G0 .
|
|
301
|
+
?Order ex:line ?L1 . ?L1 ex:lineIndex 1 ; ex:netCents ?N1 ; ex:vatCents ?V1 ; ex:grossCents ?G1 .
|
|
302
|
+
|
|
303
|
+
(?N0 ?N1) math:sum ?Net .
|
|
304
|
+
(?V0 ?V1) math:sum ?Vat .
|
|
305
|
+
(?G0 ?G1) math:sum ?Gross .
|
|
306
|
+
} => {
|
|
307
|
+
?Order ex:computedNetCents ?Net ;
|
|
308
|
+
ex:computedVatCents ?Vat ;
|
|
309
|
+
ex:computedGrossCents ?Gross .
|
|
310
|
+
} .
|
|
311
|
+
|
|
312
|
+
# Mark OK orders
|
|
313
|
+
{
|
|
314
|
+
?Order a ex:Order ;
|
|
315
|
+
ex:declaredNetCents ?DN ; ex:declaredVatCents ?DV ; ex:declaredGrossCents ?DG ;
|
|
316
|
+
ex:computedNetCents ?CN ; ex:computedVatCents ?CV ; ex:computedGrossCents ?CG .
|
|
317
|
+
|
|
318
|
+
?DN math:equalTo ?CN .
|
|
319
|
+
?DV math:equalTo ?CV .
|
|
320
|
+
?DG math:equalTo ?CG .
|
|
321
|
+
} => {
|
|
322
|
+
?Order a ex:OrderOk .
|
|
323
|
+
} .
|
|
324
|
+
|
|
325
|
+
# Emit issues for VAT mismatch
|
|
326
|
+
{
|
|
327
|
+
?Order a ex:Order ; ex:orderId ?Oid ; ex:declaredVatCents ?DV ; ex:computedVatCents ?CV .
|
|
328
|
+
?DV math:notEqualTo ?CV .
|
|
329
|
+
|
|
330
|
+
("urn:example:issue:%s:vat" ?Oid) string:format ?IssueUriStr .
|
|
331
|
+
?Issue log:uri ?IssueUriStr .
|
|
332
|
+
} => {
|
|
333
|
+
?Issue a ex:Issue ;
|
|
334
|
+
ex:onOrder ?Order ;
|
|
335
|
+
ex:field "vatCents" ;
|
|
336
|
+
ex:declared ?DV ;
|
|
337
|
+
ex:computed ?CV .
|
|
338
|
+
?Order ex:hasIssue ?Issue .
|
|
339
|
+
} .
|
|
340
|
+
|
|
341
|
+
# Emit issues for GROSS mismatch
|
|
342
|
+
{
|
|
343
|
+
?Order a ex:Order ; ex:orderId ?Oid ; ex:declaredGrossCents ?DG ; ex:computedGrossCents ?CG .
|
|
344
|
+
?DG math:notEqualTo ?CG .
|
|
345
|
+
|
|
346
|
+
("urn:example:issue:%s:gross" ?Oid) string:format ?IssueUriStr .
|
|
347
|
+
?Issue log:uri ?IssueUriStr .
|
|
348
|
+
} => {
|
|
349
|
+
?Issue a ex:Issue ;
|
|
350
|
+
ex:onOrder ?Order ;
|
|
351
|
+
ex:field "grossCents" ;
|
|
352
|
+
ex:declared ?DG ;
|
|
353
|
+
ex:computed ?CG .
|
|
354
|
+
?Order ex:hasIssue ?Issue .
|
|
355
|
+
} .
|
|
356
|
+
|
package/examples/output/age.n3
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
# Proof for derived triple:
|
|
5
5
|
# :test :is true .
|
|
6
6
|
# It holds because the following instance of the rule body is provable:
|
|
7
|
-
# :patH :ageAbove "P80Y"
|
|
7
|
+
# :patH :ageAbove "P80Y"^^xsd:duration .
|
|
8
8
|
# via the schematic forward rule:
|
|
9
9
|
# {
|
|
10
|
-
# ?S :ageAbove "P80Y"
|
|
10
|
+
# ?S :ageAbove "P80Y"^^xsd:duration .
|
|
11
11
|
# } => {
|
|
12
12
|
# :test :is true .
|
|
13
13
|
# } .
|