eyeling 1.16.3 → 1.16.4

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.
Files changed (45) hide show
  1. package/README.md +0 -1
  2. package/package.json +2 -3
  3. package/arctifacts/README.md +0 -59
  4. package/arctifacts/ackermann.html +0 -678
  5. package/arctifacts/auroracare.html +0 -1297
  6. package/arctifacts/bike-trip.html +0 -752
  7. package/arctifacts/binomial-theorem.html +0 -631
  8. package/arctifacts/bmi.html +0 -511
  9. package/arctifacts/building-performance.html +0 -750
  10. package/arctifacts/clinical-care.html +0 -726
  11. package/arctifacts/collatz.html +0 -403
  12. package/arctifacts/complex.html +0 -321
  13. package/arctifacts/control-system.html +0 -482
  14. package/arctifacts/delfour.html +0 -849
  15. package/arctifacts/earthquake-epicenter.html +0 -982
  16. package/arctifacts/eco-route.html +0 -662
  17. package/arctifacts/euclid-infinitude.html +0 -564
  18. package/arctifacts/euler-identity.html +0 -667
  19. package/arctifacts/exoplanet-transit.html +0 -1000
  20. package/arctifacts/faltings-theorem.html +0 -1046
  21. package/arctifacts/fibonacci.html +0 -299
  22. package/arctifacts/fundamental-theorem-arithmetic.html +0 -398
  23. package/arctifacts/godel-numbering.html +0 -743
  24. package/arctifacts/gps-bike.html +0 -759
  25. package/arctifacts/gps-clinical-bench.html +0 -792
  26. package/arctifacts/graph-french.html +0 -449
  27. package/arctifacts/grass-molecular.html +0 -592
  28. package/arctifacts/group-theory.html +0 -740
  29. package/arctifacts/health-info.html +0 -833
  30. package/arctifacts/kaprekar-constant.html +0 -576
  31. package/arctifacts/lee.html +0 -805
  32. package/arctifacts/linked-lists.html +0 -502
  33. package/arctifacts/lldm.html +0 -612
  34. package/arctifacts/matrix-multiplication.html +0 -502
  35. package/arctifacts/matrix.html +0 -651
  36. package/arctifacts/newton-raphson.html +0 -944
  37. package/arctifacts/peano-factorial.html +0 -456
  38. package/arctifacts/pi.html +0 -363
  39. package/arctifacts/polynomial.html +0 -646
  40. package/arctifacts/prime.html +0 -366
  41. package/arctifacts/pythagorean-theorem.html +0 -468
  42. package/arctifacts/rest-path.html +0 -469
  43. package/arctifacts/roots-of-unity.html +0 -363
  44. package/arctifacts/turing.html +0 -409
  45. package/arctifacts/wind-turbines.html +0 -726
@@ -1,750 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>Building Performance</title>
7
- <style>
8
- :root {
9
- --bg: #f7f9fc;
10
- --card: #ffffff;
11
- --muted: #5b6b87;
12
- --text: #0f172a;
13
- --accent: #0ea5e9;
14
- --ok: #16a34a;
15
- --bad: #dc2626;
16
- --border: #e5e7eb;
17
- }
18
- html,
19
- body {
20
- height: 100%;
21
- background: var(--bg);
22
- color: var(--text);
23
- font:
24
- 15px/1.5 system-ui,
25
- -apple-system,
26
- Segoe UI,
27
- Roboto,
28
- Inter,
29
- Helvetica,
30
- Arial,
31
- sans-serif;
32
- }
33
- h1 {
34
- font-weight: 700;
35
- letter-spacing: 0.2px;
36
- margin: 18px 0 6px;
37
- }
38
- h2 {
39
- font-size: 13px;
40
- color: var(--muted);
41
- margin: 0 0 8px;
42
- letter-spacing: 0.2px;
43
- text-transform: uppercase;
44
- }
45
- .wrap {
46
- max-width: 980px;
47
- margin: 0 auto;
48
- padding: 24px;
49
- }
50
- .stack {
51
- display: grid;
52
- grid-template-columns: 1fr;
53
- gap: 14px;
54
- }
55
- .card {
56
- background: var(--card);
57
- border: 1px solid var(--border);
58
- border-radius: 14px;
59
- padding: 14px;
60
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.04);
61
- }
62
- .row {
63
- display: flex;
64
- gap: 10px;
65
- align-items: center;
66
- flex-wrap: wrap;
67
- }
68
- button {
69
- border: 0;
70
- background: linear-gradient(180deg, #7dd3fc, #38bdf8);
71
- color: #052436;
72
- padding: 10px 14px;
73
- border-radius: 12px;
74
- font-weight: 700;
75
- cursor: pointer;
76
- box-shadow: 0 3px 12px rgba(56, 189, 248, 0.35);
77
- }
78
- button.secondary {
79
- background: #f3f4f6;
80
- color: #111827;
81
- border: 1px solid var(--border);
82
- box-shadow: none;
83
- }
84
- .output {
85
- min-height: 0;
86
- white-space: pre-wrap;
87
- background: #fbfdff;
88
- border: 1px solid var(--border);
89
- border-radius: 10px;
90
- padding: 10px;
91
- overflow: auto;
92
- }
93
- .output.tall {
94
- min-height: 160px;
95
- }
96
- .muted {
97
- color: var(--muted);
98
- }
99
- .tiny {
100
- font-size: 12px;
101
- }
102
- .diag {
103
- font-size: 12px;
104
- color: #6b7280;
105
- }
106
- textarea,
107
- pre,
108
- code,
109
- input,
110
- button {
111
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Courier New', monospace;
112
- }
113
- textarea {
114
- width: 100%;
115
- min-height: 0;
116
- height: auto;
117
- overflow: hidden;
118
- resize: none;
119
- border-radius: 10px;
120
- padding: 10px;
121
- border: 1px solid var(--border);
122
- background: #fbfdff;
123
- color: var(--text);
124
- box-sizing: border-box;
125
- white-space: pre-wrap;
126
- }
127
- </style>
128
- </head>
129
- <body>
130
- <div class="wrap">
131
- <h1>Building Performance</h1>
132
-
133
- <div class="stack">
134
- <!-- What this is? -->
135
- <div class="card" id="what">
136
- <h2>What this is?</h2>
137
- <p>
138
- One-file, pure JS + JSON building-performance reasoning using ARC: <strong>Answer</strong> •
139
- <strong>Reason</strong> • <strong>Check</strong>.
140
- </p>
141
- <ul>
142
- <li><strong>Data</strong> — buildings, zones, envelope/equipment, observations, tags.</li>
143
- <li>
144
- <strong>Policies</strong> — declarative JSON rules (<code>if</code> atoms, optional
145
- <code>"not": true</code> ⇒ <code>then</code> atoms).
146
- </li>
147
- <li><strong>Answer</strong> — newly derived facts (keeps strongest priority per building).</li>
148
- <li><strong>Reason Why</strong> — mathematical-English derivation snippets.</li>
149
- <li><strong>Check</strong> — ≥10 JSON assertions over the full fact set.</li>
150
- <li><strong>Building Performance Summary</strong> — per-building best priority & tasks.</li>
151
- </ul>
152
- <p class="tiny muted">
153
- Engine: small forward-chainer with unification, subclassing (<code>subClass</code>), transitive
154
- <code>partOf</code>, observation→building mapping, and negation-as-failure for <code>"not": true</code>.
155
- </p>
156
- </div>
157
-
158
- <div class="card">
159
- <h2>Data (JSON)</h2>
160
- <textarea id="dataTA" spellcheck="false">
161
- {
162
- "classes": [
163
- "Building","Component","Zone","Equipment","Envelope","Window","Wall","Roof",
164
- "Observation","LifeSafety","MajorIssue","MinorIssue",
165
- "CO2VeryHigh","HighEnergyUse","Overheating","Underheating","HighHumidity","AirLeakage","LowInsulation","FilterDirty",
166
- "Tag","NoShading","SingleGlazed","HighSolarGain",
167
- "Task","Priority","Critical","High","Normal","Low"
168
- ],
169
-
170
- "subclassOf": [
171
- ["Zone","Component"], ["Equipment","Component"], ["Envelope","Component"], ["Window","Envelope"], ["Wall","Envelope"], ["Roof","Envelope"],
172
- ["CO2VeryHigh","LifeSafety"],
173
- ["HighEnergyUse","MajorIssue"],
174
- ["Overheating","MajorIssue"],
175
- ["Underheating","MajorIssue"],
176
- ["HighHumidity","MajorIssue"],
177
- ["AirLeakage","MajorIssue"],
178
- ["LowInsulation","MajorIssue"],
179
- ["FilterDirty","MinorIssue"],
180
- ["LifeSafety","Observation"], ["MajorIssue","Observation"], ["MinorIssue","Observation"]
181
- ],
182
-
183
- "assets": {
184
- "B1": {"type":"Building"},
185
- "Z1": {"type":"Zone","partOf":"B1"},
186
- "WN1": {"type":"Window","partOf":"B1"},
187
- "AHU1": {"type":"Equipment","partOf":"B1"},
188
-
189
- "B2": {"type":"Building"},
190
- "Z2": {"type":"Zone","partOf":"B2"},
191
- "WN2": {"type":"Window","partOf":"B2"},
192
-
193
- "B3": {"type":"Building"},
194
- "AHU3": {"type":"Equipment","partOf":"B3"}
195
- },
196
-
197
- "componentTags": [
198
- ["WN2","NoShading"],
199
- ["Z2","HighSolarGain"],
200
- ["WN1","SingleGlazed"]
201
- ],
202
-
203
- "observations": [
204
- {"id":"ObsCO2Z1","type":"CO2VeryHigh","about":"Z1"},
205
- {"id":"ObsHumZ1","type":"HighHumidity","about":"Z1"},
206
- {"id":"ObsLeakWN1","type":"AirLeakage","about":"WN1"},
207
-
208
- {"id":"ObsEnergyB2","type":"HighEnergyUse","about":"B2"},
209
- {"id":"ObsHeatZ2","type":"Overheating","about":"Z2"},
210
-
211
- {"id":"ObsFilterAHU3","type":"FilterDirty","about":"AHU3"}
212
- ]
213
- }</textarea
214
- >
215
- </div>
216
-
217
- <div class="card">
218
- <h2>Policies (declarative JSON rules)</h2>
219
- <textarea id="policyTA" spellcheck="false">
220
- [
221
- {
222
- "id":"R1-Subclass",
223
- "if":[ {"pred":"subClass","s":"?A","o":"?B"}, {"pred":"isA","s":"?S","o":"?A"} ],
224
- "then":[ {"pred":"isA","s":"?S","o":"?B"} ],
225
- "explain":"Since ?A ⊑ ?B and ?S ∈ ?A, infer ?S ∈ ?B."
226
- },
227
- {
228
- "id":"R2-PartOfTransitive",
229
- "if":[ {"pred":"partOf","s":"?x","o":"?y"}, {"pred":"partOf","s":"?y","o":"?z"} ],
230
- "then":[ {"pred":"partOf","s":"?x","o":"?z"} ],
231
- "explain":"Because ?x is part of ?y and ?y is part of ?z, by transitivity ?x is part of ?z."
232
- },
233
- {
234
- "id":"R3-ObsToBuilding",
235
- "if":[ {"pred":"about","s":"?obs","o":"?comp"}, {"pred":"partOf","s":"?comp","o":"?B"} ],
236
- "then":[ {"pred":"aboutBuilding","s":"?obs","o":"?B"} ],
237
- "explain":"Observation ?obs concerns component ?comp of building ?B; therefore it is about ?B."
238
- },
239
- {
240
- "id":"R4-TagExpose",
241
- "if":[ {"pred":"tag","s":"?S","o":"?T"} ],
242
- "then":[ {"pred":"hasTag","s":"?S","o":"?T"} ],
243
- "explain":"Expose component tag ?T on ?S as a fact."
244
- },
245
-
246
- {
247
- "id":"T-CO2-Critical",
248
- "if":[ {"pred":"isA","s":"?obs","o":"CO2VeryHigh"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"} ],
249
- "then":[ {"pred":"requiresTask","s":"?B","o":"IncreaseVentilation"},
250
- {"pred":"requiresTask","s":"?B","o":"CheckHVAC"},
251
- {"pred":"priority","s":"?B","o":"Critical"} ],
252
- "explain":"Severely elevated CO₂ for ?B requires increased ventilation, HVAC check, and Critical priority."
253
- },
254
- {
255
- "id":"T-Humidity",
256
- "if":[ {"pred":"isA","s":"?obs","o":"HighHumidity"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"} ],
257
- "then":[ {"pred":"requiresTask","s":"?B","o":"Dehumidify"} ],
258
- "explain":"High humidity for ?B suggests dehumidification."
259
- },
260
- {
261
- "id":"T-AirLeakage",
262
- "if":[ {"pred":"isA","s":"?obs","o":"AirLeakage"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"} ],
263
- "then":[ {"pred":"requiresTask","s":"?B","o":"SealEnvelope"} ],
264
- "explain":"Air leakage for ?B implies sealing envelope."
265
- },
266
- {
267
- "id":"T-LowInsulation",
268
- "if":[ {"pred":"isA","s":"?obs","o":"LowInsulation"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"} ],
269
- "then":[ {"pred":"requiresTask","s":"?B","o":"AddInsulation"} ],
270
- "explain":"Low insulation for ?B implies adding insulation."
271
- },
272
- {
273
- "id":"T-EnergyUse",
274
- "if":[ {"pred":"isA","s":"?obs","o":"HighEnergyUse"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"} ],
275
- "then":[ {"pred":"requiresTask","s":"?B","o":"EnergyAudit"},
276
- {"pred":"requiresTask","s":"?B","o":"TuneBAS"} ],
277
- "explain":"High energy use for ?B implies an energy audit and BAS tuning."
278
- },
279
- {
280
- "id":"T-Overheating",
281
- "if":[ {"pred":"isA","s":"?obs","o":"Overheating"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"} ],
282
- "then":[ {"pred":"requiresTask","s":"?B","o":"CalibrateThermostat"},
283
- {"pred":"requiresTask","s":"?B","o":"EvaluateGlazing"} ],
284
- "explain":"Overheating for ?B suggests thermostat calibration and glazing evaluation."
285
- },
286
- {
287
- "id":"T-Overheat-NoShading",
288
- "if":[ {"pred":"isA","s":"?obs","o":"Overheating"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"},
289
- {"pred":"hasTag","s":"?S","o":"NoShading"}, {"pred":"partOf","s":"?S","o":"?B"} ],
290
- "then":[ {"pred":"requiresTask","s":"?B","o":"AddExternalShading"} ],
291
- "explain":"Overheating and an unshaded component in ?B imply adding external shading."
292
- },
293
- {
294
- "id":"T-FilterDirty",
295
- "if":[ {"pred":"isA","s":"?obs","o":"FilterDirty"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"} ],
296
- "then":[ {"pred":"requiresTask","s":"?B","o":"CleanFilters"} ],
297
- "explain":"Dirty filters in ?B require cleaning."
298
- },
299
-
300
- {
301
- "id":"P-LifeSafety→Critical",
302
- "if":[ {"pred":"isA","s":"?obs","o":"LifeSafety"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"} ],
303
- "then":[ {"pred":"priority","s":"?B","o":"Critical"} ],
304
- "explain":"Any life-safety issue for ?B sets Critical priority."
305
- },
306
- {
307
- "id":"P-Major-NotLife→High",
308
- "if":[ {"pred":"isA","s":"?obs","o":"MajorIssue"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"},
309
- {"not":true, "pred":"isA","s":"?obs","o":"LifeSafety"} ],
310
- "then":[ {"pred":"priority","s":"?B","o":"High"} ],
311
- "explain":"A major (but not life-safety) issue for ?B sets High priority."
312
- },
313
- {
314
- "id":"P-MinorOnly→Low",
315
- "if":[ {"pred":"isA","s":"?obs","o":"MinorIssue"}, {"pred":"aboutBuilding","s":"?obs","o":"?B"},
316
- {"not":true, "pred":"isA","s":"?any","o":"MajorIssue"},
317
- {"not":true, "pred":"isA","s":"?any2","o":"LifeSafety"} ],
318
- "then":[ {"pred":"priority","s":"?B","o":"Low"} ],
319
- "explain":"If ?B has only minor issues, set Low priority."
320
- },
321
- {
322
- "id":"P-Default-Normal",
323
- "if":[ {"pred":"requiresTask","s":"?B","o":"?X"},
324
- {"not":true, "pred":"priority","s":"?B","o":"?Any"} ],
325
- "then":[ {"pred":"priority","s":"?B","o":"Normal"} ],
326
- "explain":"If ?B has tasks but no priority, default to Normal."
327
- }
328
- ]</textarea
329
- >
330
- </div>
331
-
332
- <div class="card">
333
- <h2>Checks (JSON)</h2>
334
- <textarea id="checksTA" spellcheck="false">
335
- [
336
- {"name":"ObsCO2Z1 about B1", "pattern":{"pred":"aboutBuilding","s":"ObsCO2Z1","o":"B1"}},
337
- {"name":"ObsHumZ1 about B1", "pattern":{"pred":"aboutBuilding","s":"ObsHumZ1","o":"B1"}},
338
- {"name":"ObsLeakWN1 about B1", "pattern":{"pred":"aboutBuilding","s":"ObsLeakWN1","o":"B1"}},
339
- {"name":"ObsEnergyB2 about B2", "pattern":{"pred":"aboutBuilding","s":"ObsEnergyB2","o":"B2"}},
340
- {"name":"ObsHeatZ2 about B2", "pattern":{"pred":"aboutBuilding","s":"ObsHeatZ2","o":"B2"}},
341
-
342
- {"name":"B1 IncreaseVentilation", "pattern":{"pred":"requiresTask","s":"B1","o":"IncreaseVentilation"}},
343
- {"name":"B1 Dehumidify", "pattern":{"pred":"requiresTask","s":"B1","o":"Dehumidify"}},
344
- {"name":"B1 SealEnvelope", "pattern":{"pred":"requiresTask","s":"B1","o":"SealEnvelope"}},
345
-
346
- {"name":"B2 EnergyAudit", "pattern":{"pred":"requiresTask","s":"B2","o":"EnergyAudit"}},
347
- {"name":"B2 TuneBAS", "pattern":{"pred":"requiresTask","s":"B2","o":"TuneBAS"}},
348
- {"name":"B2 CalibrateThermostat", "pattern":{"pred":"requiresTask","s":"B2","o":"CalibrateThermostat"}},
349
- {"name":"B2 AddExternalShading", "pattern":{"pred":"requiresTask","s":"B2","o":"AddExternalShading"}},
350
-
351
- {"name":"B3 CleanFilters", "pattern":{"pred":"requiresTask","s":"B3","o":"CleanFilters"}},
352
-
353
- {"name":"B1 priority Critical", "pattern":{"pred":"priority","s":"B1","o":"Critical"}},
354
- {"name":"B2 priority High", "pattern":{"pred":"priority","s":"B2","o":"High"}},
355
- {"name":"B3 priority Low", "pattern":{"pred":"priority","s":"B3","o":"Low"}}
356
- ]</textarea
357
- >
358
- </div>
359
-
360
- <div class="card">
361
- <h2>Controls</h2>
362
- <div class="row">
363
- <button id="runBtn">▶ Run ARC</button>
364
- <button id="reasonBtn" class="secondary">Show Reason only</button>
365
- <span id="status" class="muted tiny" style="margin-left: auto"></span>
366
- </div>
367
- <div id="diag" class="diag"></div>
368
- </div>
369
-
370
- <div class="card">
371
- <h2>Answer (newly derived facts)</h2>
372
- <div id="answer" class="output tall">computing…</div>
373
- </div>
374
-
375
- <div class="card">
376
- <h2>Reason Why (mathematical English)</h2>
377
- <div id="reason" class="output tall">(click “Run ARC”)</div>
378
- </div>
379
-
380
- <div class="card">
381
- <h2>Check</h2>
382
- <div id="checks" class="output tall">computing…</div>
383
- </div>
384
-
385
- <div class="card" id="summaryCard">
386
- <h2>Building Performance Summary</h2>
387
- <div id="summary" class="output">(run to populate)</div>
388
- </div>
389
- </div>
390
- </div>
391
-
392
- <script>
393
- const $ = (id) => document.getElementById(id);
394
- const els = {
395
- dataTA: $('dataTA'),
396
- policyTA: $('policyTA'),
397
- checksTA: $('checksTA'),
398
- runBtn: $('runBtn'),
399
- reasonBtn: $('reasonBtn'),
400
- status: $('status'),
401
- diag: $('diag'),
402
- answer: $('answer'),
403
- reason: $('reason'),
404
- checks: $('checks'),
405
- summary: $('summary'),
406
- };
407
-
408
- // Auto-grow textareas
409
- function autoResize(el) {
410
- el.style.height = 'auto';
411
- el.style.height = el.scrollHeight + 'px';
412
- }
413
- ['dataTA', 'policyTA', 'checksTA'].forEach((id) => {
414
- const el = $(id);
415
- el.addEventListener('input', () => autoResize(el));
416
- setTimeout(() => autoResize(el), 0);
417
- });
418
-
419
- // --- KB helpers
420
- function key(f) {
421
- return `${f.pred}|${f.s}|${f.o}`;
422
- }
423
-
424
- function buildFactsFromData(spec) {
425
- const facts = [];
426
- (spec.subclassOf || []).forEach(([a, b]) => facts.push({ pred: 'subClass', s: a, o: b, _base: true }));
427
- for (const [id, info] of Object.entries(spec.assets || {})) {
428
- if (info.type) facts.push({ pred: 'isA', s: id, o: info.type, _base: true });
429
- if (info.partOf) facts.push({ pred: 'partOf', s: id, o: info.partOf, _base: true });
430
- }
431
- // component tags
432
- (spec.componentTags || []).forEach(([s, t]) => facts.push({ pred: 'tag', s: s, o: t, _base: true }));
433
- // observations
434
- (spec.observations || []).forEach((o) => {
435
- facts.push({ pred: 'isA', s: o.id, o: o.type, _base: true });
436
- facts.push({ pred: 'about', s: o.id, o: o.about, _base: true });
437
- // if directly about a Building, also expose aboutBuilding
438
- if (spec.assets[o.about]?.type === 'Building') {
439
- facts.push({ pred: 'aboutBuilding', s: o.id, o: o.about, _base: true });
440
- }
441
- });
442
- return facts;
443
- }
444
-
445
- function indexFacts(facts) {
446
- const byPred = new Map();
447
- for (const f of facts) {
448
- if (!byPred.has(f.pred)) byPred.set(f.pred, []);
449
- byPred.get(f.pred).push(f);
450
- }
451
- return { byPred };
452
- }
453
-
454
- function isVar(x) {
455
- return typeof x === 'string' && x.startsWith('?');
456
- }
457
-
458
- function unifyAtomWithFact(atom, f, env) {
459
- const out = { ...env };
460
- const slots = ['pred', 's', 'o'];
461
- for (const slot of slots) {
462
- const val = atom[slot];
463
- const factVal = f[slot] ?? (slot === 'pred' ? f.pred : slot === 's' ? f.s : f.o);
464
- if (isVar(val)) {
465
- if (out[val] !== undefined && out[val] !== factVal) return null;
466
- out[val] = factVal;
467
- } else {
468
- if (val !== factVal) return null;
469
- }
470
- }
471
- return out;
472
- }
473
-
474
- function matchPositives(rule, factsIdx, env = {}, i = 0) {
475
- if (i >= rule.if.length) return [env];
476
- const atom = rule.if[i];
477
- if (atom.not) return matchPositives(rule, factsIdx, env, i + 1);
478
- const candidates = factsIdx.byPred.get(atom.pred) || [];
479
- const out = [];
480
- for (const f of candidates) {
481
- const env2 = unifyAtomWithFact(atom, f, env);
482
- if (env2) out.push(...matchPositives(rule, factsIdx, env2, i + 1));
483
- }
484
- return out;
485
- }
486
-
487
- function negHolds(atom, factsIdx, env) {
488
- const candidates = factsIdx.byPred.get(atom.pred) || [];
489
- for (const f of candidates) {
490
- if (unifyAtomWithFact(atom, f, env)) return true;
491
- }
492
- return false;
493
- }
494
-
495
- function substitute(t, env) {
496
- const sub = (v) => (isVar(v) ? env[v] : v);
497
- return { pred: t.pred, s: sub(t.s), o: sub(t.o) };
498
- }
499
-
500
- // ---- Priority handling (Critical > High > Low > Normal)
501
- const PRIORITY_RANK = { Critical: 4, High: 3, Low: 2, Normal: 1 };
502
-
503
- function bestPriorities(facts) {
504
- const best = new Map();
505
- for (const f of facts) {
506
- if (f.pred !== 'priority') continue;
507
- const cur = best.get(f.s);
508
- if (!cur || (PRIORITY_RANK[f.o] || 0) > (PRIORITY_RANK[cur] || 0)) {
509
- best.set(f.s, f.o);
510
- }
511
- }
512
- return best;
513
- }
514
-
515
- function derive(data, policies) {
516
- let facts = buildFactsFromData(data);
517
- const baseSet = new Set(facts.map(key));
518
- const factSet = new Set(baseSet);
519
- const factsIdx = indexFacts(facts);
520
- const proofs = new Map(); // key -> explanation
521
-
522
- let changed = true,
523
- guard = 0;
524
- while (changed && guard++ < 200) {
525
- changed = false;
526
- for (const rule of policies) {
527
- const posBindings = matchPositives(rule, factsIdx, {}, 0);
528
- for (const env of posBindings) {
529
- const negs = rule.if.filter((a) => a.not);
530
- let ok = true,
531
- negUsed = [];
532
- for (const n of negs) {
533
- const nSub = substitute(n, env);
534
- if (negHolds(nSub, factsIdx, env)) {
535
- ok = false;
536
- break;
537
- }
538
- negUsed.push(nSub);
539
- }
540
- if (!ok) continue;
541
-
542
- for (const t of rule.then) {
543
- const concl = substitute(t, env);
544
- const k = key(concl);
545
- if (!factSet.has(k)) {
546
- facts.push({ ...concl, _base: false });
547
- factSet.add(k);
548
- changed = true;
549
- if (!factsIdx.byPred.has(concl.pred)) factsIdx.byPred.set(concl.pred, []);
550
- factsIdx.byPred.get(concl.pred).push({ ...concl, _base: false });
551
- const exp = buildExplanation(rule, env, negUsed);
552
- proofs.set(k, exp);
553
- }
554
- }
555
- }
556
- }
557
- }
558
-
559
- const derived = facts.filter((f) => !f._base);
560
-
561
- // Keep only strongest priority per building for Answer/Reason
562
- const best = bestPriorities(facts);
563
- const derivedFiltered = derived.filter((f) => f.pred !== 'priority' || best.get(f.s) === f.o);
564
-
565
- const answerLines = formatFacts(derivedFiltered);
566
-
567
- const reasonLines = [];
568
- for (const f of derivedFiltered) {
569
- const k = key(f);
570
- const exp = proofs.get(k);
571
- if (exp) reasonLines.push(`• ${exp} ⇒ therefore ${prettyFact(f)}.`);
572
- else reasonLines.push(`• Derived ${prettyFact(f)}.`);
573
- }
574
-
575
- return { facts, derived, answerText: answerLines.join('\n'), reasonText: reasonLines.join('\n') };
576
- }
577
-
578
- function buildExplanation(rule, env, negUsed) {
579
- const fill = (s) => s.replace(/\?[A-Za-z0-9_]+/g, (m) => env[m] ?? m);
580
- let text = fill(rule.explain || `Applied ${rule.id}`);
581
- if (negUsed && negUsed.length) {
582
- const negBits = negUsed.map((n) => `no fact ${n.pred}(${n.s}, ${n.o})`).join(' and ');
583
- text += `, and ${negBits}`;
584
- }
585
- return text;
586
- }
587
-
588
- function prettyFact(f) {
589
- const p = f.pred,
590
- s = f.s,
591
- o = f.o;
592
- const tri = {
593
- isA: `${s} ∈ ${o}`,
594
- subClass: `${s} ⊑ ${o}`,
595
- partOf: `${s} partOf ${o}`,
596
- about: `${s} about ${o}`,
597
- aboutBuilding: `${s} aboutBuilding ${o}`,
598
- tag: `${s} tag ${o}`,
599
- hasTag: `${s} hasTag ${o}`,
600
- requiresTask: `${s} requires ${o}`,
601
- priority: `${s} priority ${o}`,
602
- };
603
- return tri[p] || `${s} ${p} ${o}`;
604
- }
605
-
606
- function formatFacts(facts) {
607
- const groups = {};
608
- for (const f of facts) {
609
- (groups[f.pred] ||= []).push(f);
610
- }
611
- const order = [
612
- 'isA',
613
- 'subClass',
614
- 'partOf',
615
- 'tag',
616
- 'hasTag',
617
- 'about',
618
- 'aboutBuilding',
619
- 'requiresTask',
620
- 'priority',
621
- ];
622
- const lines = [];
623
- for (const pred of order) {
624
- if (!groups[pred]) continue;
625
- const sorted = groups[pred].slice().sort((a, b) => (a.s + a.o).localeCompare(b.s + b.o));
626
- for (const f of sorted) lines.push(prettyFact(f));
627
- }
628
- for (const pred of Object.keys(groups)) {
629
- if (order.includes(pred)) continue;
630
- const sorted = groups[pred].slice().sort((a, b) => (a.s + a.o).localeCompare(b.s + b.o));
631
- for (const f of sorted) lines.push(prettyFact(f));
632
- }
633
- return lines;
634
- }
635
-
636
- // ---- Checks
637
- function runChecks(facts, checksSpec) {
638
- const factSet = new Set(facts.map(key));
639
- const out = [];
640
- for (let i = 0; i < checksSpec.length; i++) {
641
- const chk = checksSpec[i];
642
- const k = key(chk.pattern);
643
- const passed = factSet.has(k);
644
- out.push({ i: i + 1, name: chk.name, passed });
645
- }
646
- return out;
647
- }
648
- function renderChecks(results) {
649
- if (!results.length) return '(no checks)';
650
- const lines = [];
651
- for (const r of results) {
652
- lines.push(`${r.passed ? '✅' : '❌'} ${String(r.i).padStart(2, ' ')} — ${r.name}`);
653
- }
654
- const passCt = results.filter((r) => r.passed).length;
655
- lines.push(`\nSummary: ${passCt}/${results.length} PASS`);
656
- return lines.join('\n');
657
- }
658
-
659
- // ---- Summary (uses strongest priority per building)
660
- function summarizeBuildings(facts) {
661
- const tasksByB = new Map();
662
- for (const f of facts) {
663
- if (f.pred === 'requiresTask') {
664
- if (!tasksByB.has(f.s)) tasksByB.set(f.s, new Set());
665
- tasksByB.get(f.s).add(f.o);
666
- }
667
- }
668
- const best = bestPriorities(facts);
669
- const buildings = new Set([...tasksByB.keys(), ...best.keys()]);
670
- if (!buildings.size) return '(no derived tasks/priority)';
671
- const lines = [];
672
- for (const b of [...buildings].sort()) {
673
- const pr = best.get(b) || 'Normal';
674
- const tasks = [...(tasksByB.get(b) || [])].sort().join(', ');
675
- lines.push(`${b}: priority ${pr}${tasks ? ' — tasks: ' + tasks : ''}`);
676
- }
677
- return lines.join('\n');
678
- }
679
-
680
- // ---- Orchestration
681
- function parseJSON(text, label) {
682
- try {
683
- return JSON.parse(text);
684
- } catch (e) {
685
- throw new Error(`${label} JSON error: ${e.message}`);
686
- }
687
- }
688
- function toPolicies(spec) {
689
- return spec.map((r) => ({
690
- id: r.id || '(unnamed)',
691
- if: r.if || [],
692
- then: r.then || [],
693
- explain: r.explain || '',
694
- }));
695
- }
696
- function buildDataObj(spec) {
697
- return spec;
698
- }
699
-
700
- async function runARC() {
701
- els.status.textContent = 'Parsing JSON…';
702
- els.answer.textContent = els.reason.textContent = els.checks.textContent = 'computing…';
703
- els.summary.textContent = '(run to populate)';
704
- els.diag.textContent = '';
705
- try {
706
- const dataSpec = parseJSON(els.dataTA.value, 'Data');
707
- const polSpec = parseJSON(els.policyTA.value, 'Policies');
708
- const checksSpec = parseJSON(els.checksTA.value, 'Checks');
709
- const data = buildDataObj(dataSpec);
710
- const policies = toPolicies(polSpec);
711
-
712
- els.status.textContent = 'Reasoning…';
713
- const { facts, derived, answerText, reasonText } = derive(data, policies);
714
-
715
- els.answer.textContent = answerText || '(no new derivations)';
716
- els.reason.textContent = reasonText || '(no explanations available)';
717
-
718
- els.status.textContent = 'Running checks…';
719
- const checkResults = runChecks(facts, checksSpec);
720
- els.checks.textContent = renderChecks(checkResults);
721
-
722
- els.summary.textContent = summarizeBuildings(facts);
723
-
724
- els.status.textContent = 'Done.';
725
- } catch (e) {
726
- console.error(e);
727
- els.status.textContent = 'Error';
728
- els.answer.textContent = '(failed)';
729
- els.reason.textContent = '(failed)';
730
- els.checks.textContent = '(failed)';
731
- els.summary.textContent = '(failed)';
732
- els.diag.textContent = e.message;
733
- }
734
- autoResize(els.dataTA);
735
- autoResize(els.policyTA);
736
- autoResize(els.checksTA);
737
- }
738
-
739
- function showReasonOnly() {
740
- runARC().then(() => {
741
- window.scrollTo({ top: $('reason').getBoundingClientRect().top + window.scrollY - 12, behavior: 'smooth' });
742
- });
743
- }
744
-
745
- els.runBtn.addEventListener('click', runARC);
746
- els.reasonBtn.addEventListener('click', showReasonOnly);
747
- window.addEventListener('DOMContentLoaded', runARC);
748
- </script>
749
- </body>
750
- </html>