eyeling 1.15.11 → 1.15.13

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 (50) hide show
  1. package/README.md +3 -3
  2. package/follows-from/artifacts/ackermann.html +678 -0
  3. package/follows-from/artifacts/auroracare.html +1297 -0
  4. package/follows-from/artifacts/bike-trip.html +752 -0
  5. package/follows-from/artifacts/binomial-theorem.html +631 -0
  6. package/follows-from/artifacts/bmi.html +511 -0
  7. package/follows-from/artifacts/building-performance.html +750 -0
  8. package/follows-from/artifacts/clinical-care.html +726 -0
  9. package/follows-from/artifacts/collatz.html +403 -0
  10. package/follows-from/artifacts/complex.html +321 -0
  11. package/follows-from/artifacts/control-system.html +482 -0
  12. package/follows-from/artifacts/delfour.html +849 -0
  13. package/follows-from/artifacts/earthquake-epicenter.html +982 -0
  14. package/follows-from/artifacts/eco-route.html +662 -0
  15. package/follows-from/artifacts/euclid-infinitude.html +564 -0
  16. package/follows-from/artifacts/euler-identity.html +667 -0
  17. package/follows-from/artifacts/exoplanet-transit.html +1000 -0
  18. package/follows-from/artifacts/faltings-theorem.html +1046 -0
  19. package/follows-from/artifacts/fibonacci.html +299 -0
  20. package/follows-from/artifacts/fundamental-theorem-arithmetic.html +398 -0
  21. package/follows-from/artifacts/godel-numbering.html +743 -0
  22. package/follows-from/artifacts/gps-bike.html +759 -0
  23. package/follows-from/artifacts/gps-clinical-bench.html +792 -0
  24. package/follows-from/artifacts/graph-french.html +449 -0
  25. package/follows-from/artifacts/grass-molecular.html +592 -0
  26. package/follows-from/artifacts/group-theory.html +740 -0
  27. package/follows-from/artifacts/health-info.html +833 -0
  28. package/follows-from/artifacts/kaprekar-constant.html +576 -0
  29. package/follows-from/artifacts/lee.html +805 -0
  30. package/follows-from/artifacts/linked-lists.html +502 -0
  31. package/follows-from/artifacts/lldm.html +612 -0
  32. package/follows-from/artifacts/matrix-multiplication.html +502 -0
  33. package/follows-from/artifacts/matrix.html +651 -0
  34. package/follows-from/artifacts/newton-raphson.html +944 -0
  35. package/follows-from/artifacts/peano-factorial.html +456 -0
  36. package/follows-from/artifacts/pi.html +363 -0
  37. package/follows-from/artifacts/polynomial.html +646 -0
  38. package/follows-from/artifacts/prime.html +366 -0
  39. package/follows-from/artifacts/pythagorean-theorem.html +468 -0
  40. package/follows-from/artifacts/rest-path.html +469 -0
  41. package/follows-from/artifacts/roots-of-unity.html +363 -0
  42. package/follows-from/artifacts/turing.html +409 -0
  43. package/follows-from/artifacts/wind-turbines.html +726 -0
  44. package/follows-from/index.html +549 -0
  45. package/follows-from/library/index.md +22 -0
  46. package/follows-from/logo.svg +12 -0
  47. package/follows-from/manifesto.md +48 -0
  48. package/follows-from/method/index.md +30 -0
  49. package/follows-from/path/index.md +20 -0
  50. package/package.json +4 -3
@@ -0,0 +1,726 @@
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>Wind-Turbine Maintenance</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
+ /* outputs */
85
+ .output {
86
+ min-height: 0;
87
+ white-space: pre-wrap;
88
+ background: #fbfdff;
89
+ border: 1px solid var(--border);
90
+ border-radius: 10px;
91
+ padding: 10px;
92
+ overflow: auto;
93
+ }
94
+ .output.tall {
95
+ min-height: 160px;
96
+ }
97
+ .muted {
98
+ color: var(--muted);
99
+ }
100
+ .badge {
101
+ display: inline-block;
102
+ padding: 2px 8px;
103
+ border-radius: 999px;
104
+ font-size: 12px;
105
+ font-weight: 700;
106
+ letter-spacing: 0.3px;
107
+ border: 1px solid var(--border);
108
+ }
109
+ .ok {
110
+ background: rgba(22, 163, 74, 0.08);
111
+ color: var(--ok);
112
+ border-color: rgba(22, 163, 74, 0.35);
113
+ }
114
+ .bad {
115
+ background: rgba(220, 38, 38, 0.08);
116
+ color: var(--bad);
117
+ border-color: rgba(220, 38, 38, 0.35);
118
+ }
119
+ .tiny {
120
+ font-size: 12px;
121
+ }
122
+ .diag {
123
+ font-size: 12px;
124
+ color: #6b7280;
125
+ }
126
+ /* Auto-growing textareas */
127
+ textarea,
128
+ pre,
129
+ code,
130
+ input,
131
+ button {
132
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Courier New', monospace;
133
+ }
134
+ textarea {
135
+ width: 100%;
136
+ min-height: 0;
137
+ height: auto;
138
+ overflow: hidden;
139
+ resize: none;
140
+ border-radius: 10px;
141
+ padding: 10px;
142
+ border: 1px solid var(--border);
143
+ background: #fbfdff;
144
+ color: var(--text);
145
+ box-sizing: border-box;
146
+ white-space: pre-wrap;
147
+ }
148
+ </style>
149
+ </head>
150
+ <body>
151
+ <div class="wrap">
152
+ <h1>Wind-Turbine Maintenance</h1>
153
+
154
+ <div class="stack">
155
+ <!-- What this is? -->
156
+ <div class="card" id="what">
157
+ <h2>What this is?</h2>
158
+ <p>
159
+ One-file, pure JS + JSON reasoning demo for wind-turbine maintenance using ARC: <strong>Answer</strong> •
160
+ <strong>Reason</strong> • <strong>Check</strong>.
161
+ </p>
162
+ <ul>
163
+ <li><strong>Data</strong> — JSON assets, components, and observations.</li>
164
+ <li>
165
+ <strong>Policies</strong> — declarative JSON rules (<code>if</code> atoms with optional
166
+ <code>"not": true</code> negations ⇒ <code>then</code> atoms).
167
+ </li>
168
+ <li>
169
+ <strong>Answer</strong> — only the <em>newly derived</em> facts (strongest priority kept per turbine).
170
+ </li>
171
+ <li>
172
+ <strong>Reason Why</strong> — explanations in mathematical English (rule templates with variable
173
+ bindings).
174
+ </li>
175
+ <li><strong>Check</strong> — runs JSON assertions (≥ 10) against the full fact set.</li>
176
+ <li>
177
+ <strong>Maintenance Summary</strong> — per-turbine priority and task roll-up (chooses strongest priority).
178
+ </li>
179
+ </ul>
180
+ <p class="tiny muted">
181
+ Engine: small forward-chainer with unification, subclassing (<code>subClass</code>), transitive
182
+ <code>partOf</code>, observation-to-turbine mapping, and negation-as-failure for <code>"not": true</code>.
183
+ </p>
184
+ </div>
185
+
186
+ <div class="card">
187
+ <h2>Data (JSON)</h2>
188
+ <textarea id="dataTA" spellcheck="false">
189
+ {
190
+ "classes": ["Turbine","Component","Gearbox","Generator","MainBearing","Blade","YawSystem",
191
+ "Condition","CriticalCondition","MajorCondition","EarlyWarningCondition",
192
+ "HighVibration","Overheat","OilDebris","PitchError","YawMisalign",
193
+ "RequiresShutdown","Priority","Urgent","High","Normal","Task"],
194
+ "subclassOf": [
195
+ ["Gearbox","Component"], ["Generator","Component"], ["MainBearing","Component"], ["Blade","Component"], ["YawSystem","Component"],
196
+ ["HighVibration","CriticalCondition"],
197
+ ["Overheat","MajorCondition"],
198
+ ["OilDebris","EarlyWarningCondition"],
199
+ ["PitchError","MajorCondition"],
200
+ ["YawMisalign","EarlyWarningCondition"],
201
+ ["CriticalCondition","Condition"], ["MajorCondition","Condition"], ["EarlyWarningCondition","Condition"]
202
+ ],
203
+ "assets": {
204
+ "T1": {"type":"Turbine"},
205
+ "GB1": {"type":"Gearbox","partOf":"T1"},
206
+ "MB1": {"type":"MainBearing","partOf":"T1"},
207
+
208
+ "T2": {"type":"Turbine"},
209
+ "GEN2": {"type":"Generator","partOf":"T2"},
210
+ "B2": {"type":"Blade","partOf":"T2"},
211
+ "YS2": {"type":"YawSystem","partOf":"T2"},
212
+
213
+ "T3": {"type":"Turbine"},
214
+ "GEN3": {"type":"Generator","partOf":"T3"},
215
+ "GB3": {"type":"Gearbox","partOf":"T3"}
216
+ },
217
+ "observations": [
218
+ {"id":"ObsVibGB1","type":"HighVibration","about":"GB1"},
219
+ {"id":"ObsTempGEN2","type":"Overheat","about":"GEN2"},
220
+ {"id":"ObsOilMB1","type":"OilDebris","about":"MB1"},
221
+ {"id":"ObsPitchB2","type":"PitchError","about":"B2"},
222
+ {"id":"ObsYawYS2","type":"YawMisalign","about":"YS2"}
223
+ ]
224
+ }</textarea
225
+ >
226
+ </div>
227
+
228
+ <div class="card">
229
+ <h2>Policies (declarative JSON rules)</h2>
230
+ <textarea id="policyTA" spellcheck="false">
231
+ [
232
+ {
233
+ "id": "R1-Subclass",
234
+ "if": [ {"pred":"subClass","s":"?A","o":"?B"}, {"pred":"isA","s":"?S","o":"?A"} ],
235
+ "then": [ {"pred":"isA","s":"?S","o":"?B"} ],
236
+ "explain": "Since ?A ⊑ ?B and ?S ∈ ?A, infer ?S ∈ ?B."
237
+ },
238
+ {
239
+ "id": "R2-PartOfTransitive",
240
+ "if": [ {"pred":"partOf","s":"?x","o":"?y"}, {"pred":"partOf","s":"?y","o":"?z"} ],
241
+ "then": [ {"pred":"partOf","s":"?x","o":"?z"} ],
242
+ "explain": "Because ?x is part of ?y and ?y is part of ?z, by transitivity ?x is part of ?z."
243
+ },
244
+ {
245
+ "id": "R3-ObsToTurbine",
246
+ "if": [ {"pred":"about","s":"?obs","o":"?comp"}, {"pred":"partOf","s":"?comp","o":"?T"} ],
247
+ "then": [ {"pred":"aboutTurbine","s":"?obs","o":"?T"} ],
248
+ "explain": "Observation ?obs concerns component ?comp of turbine ?T; therefore ?obs is about turbine ?T."
249
+ },
250
+ {
251
+ "id": "P-HV-Tasks",
252
+ "if": [ {"pred":"isA","s":"?obs","o":"HighVibration"}, {"pred":"aboutTurbine","s":"?obs","o":"?T"} ],
253
+ "then": [
254
+ {"pred":"requiresTask","s":"?T","o":"BalanceRotor"},
255
+ {"pred":"requiresTask","s":"?T","o":"InspectGearbox"},
256
+ {"pred":"requiresTask","s":"?T","o":"TightenFoundation"}
257
+ ],
258
+ "explain": "High vibration on ?T implies balancing, gearbox inspection, and foundation tightening tasks."
259
+ },
260
+ {
261
+ "id": "P-Overheat-Tasks",
262
+ "if": [ {"pred":"isA","s":"?obs","o":"Overheat"}, {"pred":"aboutTurbine","s":"?obs","o":"?T"} ],
263
+ "then": [
264
+ {"pred":"requiresTask","s":"?T","o":"ThermalScanGenerator"},
265
+ {"pred":"requiresTask","s":"?T","o":"ChangeFilter"}
266
+ ],
267
+ "explain": "Overheating on ?T implies a thermal scan and filter change."
268
+ },
269
+ {
270
+ "id": "P-Oil-Tasks",
271
+ "if": [ {"pred":"isA","s":"?obs","o":"OilDebris"}, {"pred":"aboutTurbine","s":"?obs","o":"?T"} ],
272
+ "then": [
273
+ {"pred":"requiresTask","s":"?T","o":"OilAnalysis"},
274
+ {"pred":"requiresTask","s":"?T","o":"InspectMainBearing"}
275
+ ],
276
+ "explain": "Oil debris on ?T implies oil analysis and main bearing inspection."
277
+ },
278
+ {
279
+ "id": "P-Pitch-Task",
280
+ "if": [ {"pred":"isA","s":"?obs","o":"PitchError"}, {"pred":"aboutTurbine","s":"?obs","o":"?T"} ],
281
+ "then": [ {"pred":"requiresTask","s":"?T","o":"InspectPitchActuator"} ],
282
+ "explain": "Pitch error on ?T implies pitch actuator inspection."
283
+ },
284
+ {
285
+ "id": "P-Yaw-Task",
286
+ "if": [ {"pred":"isA","s":"?obs","o":"YawMisalign"}, {"pred":"aboutTurbine","s":"?obs","o":"?T"} ],
287
+ "then": [ {"pred":"requiresTask","s":"?T","o":"CalibrateYawCompass"} ],
288
+ "explain": "Yaw misalignment on ?T implies yaw compass calibration."
289
+ },
290
+ {
291
+ "id": "P-Safety-Critical",
292
+ "if": [ {"pred":"isA","s":"?obs","o":"CriticalCondition"}, {"pred":"aboutTurbine","s":"?obs","o":"?T"} ],
293
+ "then": [ {"pred":"isA","s":"?T","o":"RequiresShutdown"}, {"pred":"priority","s":"?T","o":"Urgent"} ],
294
+ "explain": "If some observation of ?T is critical, then ?T must be shut down and prioritized as Urgent."
295
+ },
296
+ {
297
+ "id": "P-Major-NotCritical",
298
+ "if": [
299
+ {"pred":"isA","s":"?obs","o":"MajorCondition"},
300
+ {"pred":"aboutTurbine","s":"?obs","o":"?T"},
301
+ {"not": true, "pred":"isA","s":"?obs","o":"CriticalCondition"}
302
+ ],
303
+ "then": [ {"pred":"priority","s":"?T","o":"High"} ],
304
+ "explain": "If an observation on ?T is major and not critical, assign High priority to ?T."
305
+ },
306
+ {
307
+ "id": "P-Default-Normal",
308
+ "if": [
309
+ {"pred":"requiresTask","s":"?T","o":"?X"},
310
+ {"not": true, "pred":"priority","s":"?T","o":"?P"}
311
+ ],
312
+ "then": [ {"pred":"priority","s":"?T","o":"Normal"} ],
313
+ "explain": "If ?T has tasks but no priority yet, assign the default Normal priority."
314
+ }
315
+ ]</textarea
316
+ >
317
+ </div>
318
+
319
+ <div class="card">
320
+ <h2>Checks (JSON)</h2>
321
+ <textarea id="checksTA" spellcheck="false">
322
+ [
323
+ {"name":"GB1 is Component", "pattern":{"pred":"isA","s":"GB1","o":"Component"}},
324
+ {"name":"GB3 is Component", "pattern":{"pred":"isA","s":"GB3","o":"Component"}},
325
+ {"name":"GEN2 is Component", "pattern":{"pred":"isA","s":"GEN2","o":"Component"}},
326
+ {"name":"GEN3 is Component", "pattern":{"pred":"isA","s":"GEN3","o":"Component"}},
327
+ {"name":"MB1 is Component", "pattern":{"pred":"isA","s":"MB1","o":"Component"}},
328
+ {"name":"B2 is Component", "pattern":{"pred":"isA","s":"B2","o":"Component"}},
329
+ {"name":"YS2 is Component", "pattern":{"pred":"isA","s":"YS2","o":"Component"}},
330
+
331
+ {"name":"ObsVibGB1 about T1", "pattern":{"pred":"aboutTurbine","s":"ObsVibGB1","o":"T1"}},
332
+ {"name":"ObsTempGEN2 about T2", "pattern":{"pred":"aboutTurbine","s":"ObsTempGEN2","o":"T2"}},
333
+ {"name":"ObsOilMB1 about T1", "pattern":{"pred":"aboutTurbine","s":"ObsOilMB1","o":"T1"}},
334
+ {"name":"ObsPitchB2 about T2", "pattern":{"pred":"aboutTurbine","s":"ObsPitchB2","o":"T2"}},
335
+ {"name":"ObsYawYS2 about T2", "pattern":{"pred":"aboutTurbine","s":"ObsYawYS2","o":"T2"}},
336
+
337
+ {"name":"T1 RequiresShutdown", "pattern":{"pred":"isA","s":"T1","o":"RequiresShutdown"}},
338
+ {"name":"T1 Urgent priority", "pattern":{"pred":"priority","s":"T1","o":"Urgent"}},
339
+ {"name":"T2 High priority", "pattern":{"pred":"priority","s":"T2","o":"High"}},
340
+
341
+ {"name":"T1 BalanceRotor", "pattern":{"pred":"requiresTask","s":"T1","o":"BalanceRotor"}},
342
+ {"name":"T2 ThermalScanGenerator", "pattern":{"pred":"requiresTask","s":"T2","o":"ThermalScanGenerator"}},
343
+ {"name":"T1 OilAnalysis", "pattern":{"pred":"requiresTask","s":"T1","o":"OilAnalysis"}},
344
+ {"name":"T2 CalibrateYawCompass", "pattern":{"pred":"requiresTask","s":"T2","o":"CalibrateYawCompass"}}
345
+ ]</textarea
346
+ >
347
+ </div>
348
+
349
+ <div class="card">
350
+ <h2>Controls</h2>
351
+ <div class="row">
352
+ <button id="runBtn">▶ Run ARC</button>
353
+ <button id="reasonBtn" class="secondary">Show Reason only</button>
354
+ <span id="status" class="muted tiny" style="margin-left: auto"></span>
355
+ </div>
356
+ <div id="diag" class="diag"></div>
357
+ </div>
358
+
359
+ <div class="card">
360
+ <h2>Answer (newly derived facts)</h2>
361
+ <div id="answer" class="output tall">computing…</div>
362
+ </div>
363
+
364
+ <div class="card">
365
+ <h2>Reason Why (mathematical English)</h2>
366
+ <div id="reason" class="output tall">(click “Run ARC”)</div>
367
+ </div>
368
+
369
+ <div class="card">
370
+ <h2>Check</h2>
371
+ <div id="checks" class="output tall">computing…</div>
372
+ </div>
373
+
374
+ <div class="card" id="summaryCard">
375
+ <h2>Maintenance Summary</h2>
376
+ <div id="summary" class="output">(run to populate)</div>
377
+ </div>
378
+ </div>
379
+ </div>
380
+
381
+ <script>
382
+ const $ = (id) => document.getElementById(id);
383
+ const els = {
384
+ dataTA: $('dataTA'),
385
+ policyTA: $('policyTA'),
386
+ checksTA: $('checksTA'),
387
+ runBtn: $('runBtn'),
388
+ reasonBtn: $('reasonBtn'),
389
+ status: $('status'),
390
+ diag: $('diag'),
391
+ answer: $('answer'),
392
+ reason: $('reason'),
393
+ checks: $('checks'),
394
+ summary: $('summary'),
395
+ };
396
+
397
+ // Auto-grow textareas
398
+ function autoResize(el) {
399
+ el.style.height = 'auto';
400
+ el.style.height = el.scrollHeight + 'px';
401
+ }
402
+ ['dataTA', 'policyTA', 'checksTA'].forEach((id) => {
403
+ const el = $(id);
404
+ el.addEventListener('input', () => autoResize(el));
405
+ setTimeout(() => autoResize(el), 0);
406
+ });
407
+
408
+ // ---- Tiny fact KB
409
+ function key(f) {
410
+ return `${f.pred}|${f.s}|${f.o}`;
411
+ }
412
+
413
+ function buildFactsFromData(data) {
414
+ const facts = [];
415
+ (data.subclassOf || []).forEach(([a, b]) => facts.push({ pred: 'subClass', s: a, o: b, _base: true }));
416
+ for (const [id, info] of Object.entries(data.assets || {})) {
417
+ if (info.type) facts.push({ pred: 'isA', s: id, o: info.type, _base: true });
418
+ if (info.partOf) facts.push({ pred: 'partOf', s: id, o: info.partOf, _base: true });
419
+ }
420
+ (data.observations || []).forEach((o) => {
421
+ facts.push({ pred: 'isA', s: o.id, o: o.type, _base: true });
422
+ facts.push({ pred: 'about', s: o.id, o: o.about, _base: true });
423
+ });
424
+ return facts;
425
+ }
426
+
427
+ function indexFacts(facts) {
428
+ const byPred = new Map();
429
+ for (const f of facts) {
430
+ if (!byPred.has(f.pred)) byPred.set(f.pred, []);
431
+ byPred.get(f.pred).push(f);
432
+ }
433
+ return { byPred };
434
+ }
435
+
436
+ function isVar(x) {
437
+ return typeof x === 'string' && x.startsWith('?');
438
+ }
439
+
440
+ function unifyAtomWithFact(atom, f, env) {
441
+ const out = { ...env };
442
+ const slots = ['pred', 's', 'o'];
443
+ for (const slot of slots) {
444
+ const val = atom[slot];
445
+ const factVal = f[slot] ?? (slot === 'pred' ? f.pred : slot === 's' ? f.s : f.o);
446
+ if (isVar(val)) {
447
+ if (out[val] !== undefined && out[val] !== factVal) return null;
448
+ out[val] = factVal;
449
+ } else {
450
+ if (val !== factVal) return null;
451
+ }
452
+ }
453
+ return out;
454
+ }
455
+
456
+ function matchPositives(rule, factsIdx, env = {}, i = 0) {
457
+ if (i >= rule.if.length) return [env];
458
+ const atom = rule.if[i];
459
+ if (atom.not) return matchPositives(rule, factsIdx, env, i + 1);
460
+ const candidates = factsIdx.byPred.get(atom.pred) || [];
461
+ const out = [];
462
+ for (const f of candidates) {
463
+ const env2 = unifyAtomWithFact(atom, f, env);
464
+ if (env2) out.push(...matchPositives(rule, factsIdx, env2, i + 1));
465
+ }
466
+ return out;
467
+ }
468
+
469
+ function negHolds(atom, factsIdx, env) {
470
+ const candidates = factsIdx.byPred.get(atom.pred) || [];
471
+ for (const f of candidates) {
472
+ if (unifyAtomWithFact(atom, f, env)) return true;
473
+ }
474
+ return false;
475
+ }
476
+
477
+ function substitute(t, env) {
478
+ const sub = (v) => (isVar(v) ? env[v] : v);
479
+ return { pred: t.pred, s: sub(t.s), o: sub(t.o) };
480
+ }
481
+
482
+ // ---- Priority handling
483
+ const PRIORITY_RANK = { Urgent: 3, High: 2, Normal: 1 };
484
+
485
+ function bestPriorities(facts) {
486
+ const best = new Map();
487
+ for (const f of facts) {
488
+ if (f.pred !== 'priority') continue;
489
+ const cur = best.get(f.s);
490
+ if (!cur || (PRIORITY_RANK[f.o] || 0) > (PRIORITY_RANK[cur] || 0)) {
491
+ best.set(f.s, f.o);
492
+ }
493
+ }
494
+ return best;
495
+ }
496
+
497
+ function derive(data, policies) {
498
+ let facts = buildFactsFromData(data);
499
+ const baseSet = new Set(facts.map(key));
500
+ const factSet = new Set(baseSet);
501
+ const factsIdx = indexFacts(facts);
502
+ const proofs = new Map(); // key -> explanation
503
+
504
+ let changed = true,
505
+ guard = 0;
506
+ while (changed && guard++ < 200) {
507
+ changed = false;
508
+ for (const rule of policies) {
509
+ const posBindings = matchPositives(rule, factsIdx, {}, 0);
510
+ for (const env of posBindings) {
511
+ const negs = rule.if.filter((a) => a.not);
512
+ let ok = true,
513
+ negUsed = [];
514
+ for (const n of negs) {
515
+ const nSub = substitute(n, env);
516
+ if (negHolds(nSub, factsIdx, env)) {
517
+ ok = false;
518
+ break;
519
+ }
520
+ negUsed.push(nSub);
521
+ }
522
+ if (!ok) continue;
523
+
524
+ for (const t of rule.then) {
525
+ const concl = substitute(t, env);
526
+ const k = key(concl);
527
+ if (!factSet.has(k)) {
528
+ facts.push({ ...concl, _base: false });
529
+ factSet.add(k);
530
+ changed = true;
531
+ if (!factsIdx.byPred.has(concl.pred)) factsIdx.byPred.set(concl.pred, []);
532
+ factsIdx.byPred.get(concl.pred).push({ ...concl, _base: false });
533
+
534
+ const exp = buildExplanation(rule, env, negUsed);
535
+ proofs.set(k, exp);
536
+ }
537
+ }
538
+ }
539
+ }
540
+ }
541
+
542
+ const derived = facts.filter((f) => !f._base);
543
+
544
+ // Keep only strongest priority per turbine for Answer/Reason
545
+ const best = bestPriorities(facts);
546
+ const derivedFiltered = derived.filter((f) => f.pred !== 'priority' || best.get(f.s) === f.o);
547
+
548
+ const answerLines = formatFacts(derivedFiltered);
549
+
550
+ const reasonLines = [];
551
+ for (const f of derivedFiltered) {
552
+ const k = key(f);
553
+ const exp = proofs.get(k);
554
+ if (exp) reasonLines.push(`• ${exp} ⇒ therefore ${prettyFact(f)}.`);
555
+ else reasonLines.push(`• Derived ${prettyFact(f)}.`);
556
+ }
557
+
558
+ return { facts, baseSet, derived, answerText: answerLines.join('\n'), reasonText: reasonLines.join('\n') };
559
+ }
560
+
561
+ function buildExplanation(rule, env, negUsed) {
562
+ const fill = (s) => s.replace(/\?[A-Za-z0-9_]+/g, (m) => env[m] ?? m);
563
+ let text = fill(rule.explain || `Applied ${rule.id}`);
564
+ if (negUsed && negUsed.length) {
565
+ const negBits = negUsed.map((n) => `no fact ${n.pred}(${n.s}, ${n.o})`).join(' and ');
566
+ text += `, and ${negBits}`;
567
+ }
568
+ return text;
569
+ }
570
+
571
+ function prettyFact(f) {
572
+ const p = f.pred,
573
+ s = f.s,
574
+ o = f.o;
575
+ const tri = {
576
+ isA: `${s} ∈ ${o}`,
577
+ subClass: `${s} ⊑ ${o}`,
578
+ partOf: `${s} partOf ${o}`,
579
+ about: `${s} about ${o}`,
580
+ aboutTurbine: `${s} aboutTurbine ${o}`,
581
+ requiresTask: `${s} requires ${o}`,
582
+ priority: `${s} priority ${o}`,
583
+ };
584
+ return tri[p] || `${s} ${p} ${o}`;
585
+ }
586
+
587
+ function formatFacts(facts) {
588
+ const groups = {};
589
+ for (const f of facts) {
590
+ (groups[f.pred] ||= []).push(f);
591
+ }
592
+ const order = ['isA', 'subClass', 'partOf', 'about', 'aboutTurbine', 'requiresTask', 'priority'];
593
+ const lines = [];
594
+ for (const pred of order) {
595
+ if (!groups[pred]) continue;
596
+ const sorted = groups[pred].slice().sort((a, b) => (a.s + a.o).localeCompare(b.s + b.o));
597
+ for (const f of sorted) lines.push(prettyFact(f));
598
+ }
599
+ for (const pred of Object.keys(groups)) {
600
+ if (order.includes(pred)) continue;
601
+ const sorted = groups[pred].slice().sort((a, b) => (a.s + a.o).localeCompare(b.s + b.o));
602
+ for (const f of sorted) lines.push(prettyFact(f));
603
+ }
604
+ return lines;
605
+ }
606
+
607
+ // ---- Checks
608
+ function runChecks(facts, checksSpec) {
609
+ const factSet = new Set(facts.map(key));
610
+ const out = [];
611
+ for (let i = 0; i < checksSpec.length; i++) {
612
+ const chk = checksSpec[i];
613
+ const k = key(chk.pattern);
614
+ const passed = factSet.has(k);
615
+ out.push({ i: i + 1, name: chk.name, passed });
616
+ }
617
+ return out;
618
+ }
619
+
620
+ function renderChecks(results) {
621
+ if (!results.length) return '(no checks)';
622
+ const lines = [];
623
+ for (const r of results) {
624
+ lines.push(`${r.passed ? '✅' : '❌'} ${String(r.i).padStart(2, ' ')} — ${r.name}`);
625
+ }
626
+ const passCt = results.filter((r) => r.passed).length;
627
+ lines.push(`\nSummary: ${passCt}/${results.length} PASS`);
628
+ return lines.join('\n');
629
+ }
630
+
631
+ // ---- Maintenance Summary (uses strongest priority per turbine)
632
+ function summarizeMaintenance(facts) {
633
+ const tasksByT = new Map();
634
+ for (const f of facts) {
635
+ if (f.pred === 'requiresTask') {
636
+ if (!tasksByT.has(f.s)) tasksByT.set(f.s, new Set());
637
+ tasksByT.get(f.s).add(f.o);
638
+ }
639
+ }
640
+ const best = bestPriorities(facts);
641
+ const turbines = new Set([...tasksByT.keys(), ...best.keys()]);
642
+ if (!turbines.size) return '(no turbines require maintenance)';
643
+ const lines = [];
644
+ for (const t of [...turbines].sort()) {
645
+ const pr = best.get(t) || 'Normal';
646
+ const tasks = [...(tasksByT.get(t) || [])].sort().join(', ');
647
+ lines.push(`${t}: priority ${pr}${tasks ? ' — tasks: ' + tasks : ''}`);
648
+ }
649
+ return lines.join('\n');
650
+ }
651
+
652
+ // ---- Orchestration
653
+ function parseJSON(text, label) {
654
+ try {
655
+ return JSON.parse(text);
656
+ } catch (e) {
657
+ throw new Error(`${label} JSON error: ${e.message}`);
658
+ }
659
+ }
660
+
661
+ function toPolicies(spec) {
662
+ return spec.map((r) => ({
663
+ id: r.id || '(unnamed)',
664
+ if: r.if || [],
665
+ then: r.then || [],
666
+ explain: r.explain || '',
667
+ }));
668
+ }
669
+
670
+ function buildDataObj(spec) {
671
+ return spec;
672
+ }
673
+
674
+ async function runARC() {
675
+ els.status.textContent = 'Parsing JSON…';
676
+ els.answer.textContent = els.reason.textContent = els.checks.textContent = 'computing…';
677
+ els.summary.textContent = '(run to populate)';
678
+ els.diag.textContent = '';
679
+ try {
680
+ const dataSpec = parseJSON(els.dataTA.value, 'Data');
681
+ const polSpec = parseJSON(els.policyTA.value, 'Policies');
682
+ const checksSpec = parseJSON(els.checksTA.value, 'Checks');
683
+ const data = buildDataObj(dataSpec);
684
+ const policies = toPolicies(polSpec);
685
+
686
+ els.status.textContent = 'Reasoning…';
687
+ const { facts, derived, answerText, reasonText } = derive(data, policies);
688
+
689
+ els.answer.textContent = answerText || '(no new derivations)';
690
+ els.reason.textContent = reasonText || '(no explanations available)';
691
+
692
+ els.status.textContent = 'Running checks…';
693
+ const checkResults = runChecks(facts, checksSpec);
694
+ els.checks.textContent = renderChecks(checkResults);
695
+
696
+ // Summary (strongest priority)
697
+ els.summary.textContent = summarizeMaintenance(facts);
698
+
699
+ els.status.textContent = 'Done.';
700
+ } catch (e) {
701
+ console.error(e);
702
+ els.status.textContent = 'Error';
703
+ els.answer.textContent = '(failed)';
704
+ els.reason.textContent = '(failed)';
705
+ els.checks.textContent = '(failed)';
706
+ els.summary.textContent = '(failed)';
707
+ els.diag.textContent = e.message;
708
+ }
709
+ // keep textareas height fresh
710
+ autoResize(els.dataTA);
711
+ autoResize(els.policyTA);
712
+ autoResize(els.checksTA);
713
+ }
714
+
715
+ function showReasonOnly() {
716
+ runARC().then(() => {
717
+ window.scrollTo({ top: $('reason').getBoundingClientRect().top + window.scrollY - 12, behavior: 'smooth' });
718
+ });
719
+ }
720
+
721
+ els.runBtn.addEventListener('click', runARC);
722
+ els.reasonBtn.addEventListener('click', showReasonOnly);
723
+ window.addEventListener('DOMContentLoaded', runARC);
724
+ </script>
725
+ </body>
726
+ </html>