eyeling 1.16.2 → 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.
- package/HANDBOOK.md +4 -0
- package/README.md +0 -1
- package/examples/ershov-mixed-computation.n3 +106 -0
- package/examples/output/ershov-mixed-computation.n3 +15 -0
- package/eyeling.js +510 -263
- package/lib/cli.js +22 -12
- package/lib/engine.js +488 -251
- package/package.json +2 -3
- package/arctifacts/README.md +0 -59
- package/arctifacts/ackermann.html +0 -678
- package/arctifacts/auroracare.html +0 -1297
- package/arctifacts/bike-trip.html +0 -752
- package/arctifacts/binomial-theorem.html +0 -631
- package/arctifacts/bmi.html +0 -511
- package/arctifacts/building-performance.html +0 -750
- package/arctifacts/clinical-care.html +0 -726
- package/arctifacts/collatz.html +0 -403
- package/arctifacts/complex.html +0 -321
- package/arctifacts/control-system.html +0 -482
- package/arctifacts/delfour.html +0 -849
- package/arctifacts/earthquake-epicenter.html +0 -982
- package/arctifacts/eco-route.html +0 -662
- package/arctifacts/euclid-infinitude.html +0 -564
- package/arctifacts/euler-identity.html +0 -667
- package/arctifacts/exoplanet-transit.html +0 -1000
- package/arctifacts/faltings-theorem.html +0 -1046
- package/arctifacts/fibonacci.html +0 -299
- package/arctifacts/fundamental-theorem-arithmetic.html +0 -398
- package/arctifacts/godel-numbering.html +0 -743
- package/arctifacts/gps-bike.html +0 -759
- package/arctifacts/gps-clinical-bench.html +0 -792
- package/arctifacts/graph-french.html +0 -449
- package/arctifacts/grass-molecular.html +0 -592
- package/arctifacts/group-theory.html +0 -740
- package/arctifacts/health-info.html +0 -833
- package/arctifacts/kaprekar-constant.html +0 -576
- package/arctifacts/lee.html +0 -805
- package/arctifacts/linked-lists.html +0 -502
- package/arctifacts/lldm.html +0 -612
- package/arctifacts/matrix-multiplication.html +0 -502
- package/arctifacts/matrix.html +0 -651
- package/arctifacts/newton-raphson.html +0 -944
- package/arctifacts/peano-factorial.html +0 -456
- package/arctifacts/pi.html +0 -363
- package/arctifacts/polynomial.html +0 -646
- package/arctifacts/prime.html +0 -366
- package/arctifacts/pythagorean-theorem.html +0 -468
- package/arctifacts/rest-path.html +0 -469
- package/arctifacts/roots-of-unity.html +0 -363
- package/arctifacts/turing.html +0 -409
- package/arctifacts/wind-turbines.html +0 -726
|
@@ -1,662 +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>Eco Route</title>
|
|
7
|
-
<style>
|
|
8
|
-
:root {
|
|
9
|
-
--bg: #f7fafc;
|
|
10
|
-
--panel: #ffffff;
|
|
11
|
-
--muted: #475569;
|
|
12
|
-
--text: #0b1220;
|
|
13
|
-
--accent: #2563eb;
|
|
14
|
-
--good: #16a34a;
|
|
15
|
-
--bad: #dc2626;
|
|
16
|
-
--warn: #d97706;
|
|
17
|
-
--chip: #eef2f7;
|
|
18
|
-
}
|
|
19
|
-
* {
|
|
20
|
-
box-sizing: border-box;
|
|
21
|
-
}
|
|
22
|
-
body {
|
|
23
|
-
margin: 0;
|
|
24
|
-
font:
|
|
25
|
-
16px/1.5 system-ui,
|
|
26
|
-
Segoe UI,
|
|
27
|
-
Roboto,
|
|
28
|
-
Inter,
|
|
29
|
-
Helvetica,
|
|
30
|
-
Arial,
|
|
31
|
-
sans-serif;
|
|
32
|
-
background: var(--bg);
|
|
33
|
-
color: var(--text);
|
|
34
|
-
}
|
|
35
|
-
header {
|
|
36
|
-
padding: 24px 20px;
|
|
37
|
-
border-bottom: 1px solid #e5e7eb;
|
|
38
|
-
background: linear-gradient(180deg, rgba(37, 99, 235, 0.08), transparent);
|
|
39
|
-
}
|
|
40
|
-
h1 {
|
|
41
|
-
margin: 0;
|
|
42
|
-
font-size: 24px;
|
|
43
|
-
letter-spacing: 0.2px;
|
|
44
|
-
}
|
|
45
|
-
main {
|
|
46
|
-
display: grid;
|
|
47
|
-
grid-template-columns: 1fr;
|
|
48
|
-
gap: 22px;
|
|
49
|
-
padding: 20px;
|
|
50
|
-
align-items: start;
|
|
51
|
-
}
|
|
52
|
-
section {
|
|
53
|
-
background: var(--panel);
|
|
54
|
-
border: 1px solid #e5e7eb;
|
|
55
|
-
border-radius: 16px;
|
|
56
|
-
padding: 16px;
|
|
57
|
-
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.06);
|
|
58
|
-
}
|
|
59
|
-
h2 {
|
|
60
|
-
margin: 0 0 10px 0;
|
|
61
|
-
font-size: 18px;
|
|
62
|
-
}
|
|
63
|
-
.row {
|
|
64
|
-
display: flex;
|
|
65
|
-
gap: 10px;
|
|
66
|
-
flex-wrap: wrap;
|
|
67
|
-
}
|
|
68
|
-
.grid {
|
|
69
|
-
display: grid;
|
|
70
|
-
gap: 10px;
|
|
71
|
-
}
|
|
72
|
-
.pill {
|
|
73
|
-
display: inline-flex;
|
|
74
|
-
align-items: center;
|
|
75
|
-
gap: 8px;
|
|
76
|
-
padding: 8px 10px;
|
|
77
|
-
border-radius: 999px;
|
|
78
|
-
background: #ffffff;
|
|
79
|
-
border: 1px solid #e5e7eb;
|
|
80
|
-
}
|
|
81
|
-
.small {
|
|
82
|
-
font-size: 13px;
|
|
83
|
-
color: var(--muted);
|
|
84
|
-
}
|
|
85
|
-
.mono {
|
|
86
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
87
|
-
}
|
|
88
|
-
.ok {
|
|
89
|
-
color: var(--good);
|
|
90
|
-
}
|
|
91
|
-
.no {
|
|
92
|
-
color: var(--bad);
|
|
93
|
-
}
|
|
94
|
-
.warn {
|
|
95
|
-
color: var(--warn);
|
|
96
|
-
}
|
|
97
|
-
.kvs {
|
|
98
|
-
display: grid;
|
|
99
|
-
grid-template-columns: 160px 1fr;
|
|
100
|
-
gap: 6px 10px;
|
|
101
|
-
}
|
|
102
|
-
.log {
|
|
103
|
-
max-height: 260px;
|
|
104
|
-
overflow: auto;
|
|
105
|
-
background: #ffffff;
|
|
106
|
-
border: 1px solid #e5e7eb;
|
|
107
|
-
border-radius: 12px;
|
|
108
|
-
padding: 10px;
|
|
109
|
-
}
|
|
110
|
-
.timeline {
|
|
111
|
-
display: flex;
|
|
112
|
-
flex-direction: column;
|
|
113
|
-
gap: 10px;
|
|
114
|
-
}
|
|
115
|
-
.card {
|
|
116
|
-
background: #ffffff;
|
|
117
|
-
border: 1px solid #e5e7eb;
|
|
118
|
-
border-radius: 12px;
|
|
119
|
-
padding: 12px;
|
|
120
|
-
}
|
|
121
|
-
.muted {
|
|
122
|
-
color: var(--muted);
|
|
123
|
-
}
|
|
124
|
-
.split {
|
|
125
|
-
display: grid;
|
|
126
|
-
grid-template-columns: 1fr auto;
|
|
127
|
-
gap: 10px;
|
|
128
|
-
align-items: center;
|
|
129
|
-
}
|
|
130
|
-
.btnrow {
|
|
131
|
-
display: flex;
|
|
132
|
-
gap: 10px;
|
|
133
|
-
flex-wrap: wrap;
|
|
134
|
-
}
|
|
135
|
-
button {
|
|
136
|
-
cursor: pointer;
|
|
137
|
-
background: var(--accent);
|
|
138
|
-
color: #fff;
|
|
139
|
-
border: none;
|
|
140
|
-
padding: 10px 14px;
|
|
141
|
-
border-radius: 12px;
|
|
142
|
-
font-weight: 600;
|
|
143
|
-
}
|
|
144
|
-
button.ghost {
|
|
145
|
-
background: #f8fafc;
|
|
146
|
-
color: var(--text);
|
|
147
|
-
border: 1px solid #dbe2ea;
|
|
148
|
-
}
|
|
149
|
-
pre.wrap {
|
|
150
|
-
white-space: pre-wrap;
|
|
151
|
-
overflow-wrap: anywhere;
|
|
152
|
-
word-break: break-word;
|
|
153
|
-
overflow-x: hidden;
|
|
154
|
-
overflow-y: auto;
|
|
155
|
-
}
|
|
156
|
-
.list {
|
|
157
|
-
margin: 0.25rem 0 0.75rem 1.1rem;
|
|
158
|
-
padding: 0;
|
|
159
|
-
}
|
|
160
|
-
.list li {
|
|
161
|
-
margin: 0.1rem 0;
|
|
162
|
-
}
|
|
163
|
-
.list.compact li {
|
|
164
|
-
margin: 0;
|
|
165
|
-
}
|
|
166
|
-
</style>
|
|
167
|
-
</head>
|
|
168
|
-
<body>
|
|
169
|
-
<header>
|
|
170
|
-
<h1>Eco Route</h1>
|
|
171
|
-
</header>
|
|
172
|
-
|
|
173
|
-
<main>
|
|
174
|
-
<section id="controls">
|
|
175
|
-
<div class="split">
|
|
176
|
-
<h2>Run Demo</h2>
|
|
177
|
-
<div class="btnrow">
|
|
178
|
-
<button id="btn-run">Evaluate</button>
|
|
179
|
-
<button class="ghost" id="btn-copy">Copy result JSON</button>
|
|
180
|
-
</div>
|
|
181
|
-
</div>
|
|
182
|
-
</section>
|
|
183
|
-
|
|
184
|
-
<section id="decision">
|
|
185
|
-
<h2>Decision</h2>
|
|
186
|
-
<div class="kvs">
|
|
187
|
-
<div class="muted">Answer</div>
|
|
188
|
-
<div id="ans">—</div>
|
|
189
|
-
<div class="muted">Reason why</div>
|
|
190
|
-
<div id="why" class="wrap">Data & policies loaded. Click <em>Evaluate</em>.</div>
|
|
191
|
-
<div class="muted">Check (harness)</div>
|
|
192
|
-
<div id="check" class="wrap">—</div>
|
|
193
|
-
</div>
|
|
194
|
-
</section>
|
|
195
|
-
|
|
196
|
-
<section id="data">
|
|
197
|
-
<h2>Data (JSON)</h2>
|
|
198
|
-
<pre class="mono wrap" id="data-json"></pre>
|
|
199
|
-
</section>
|
|
200
|
-
|
|
201
|
-
<section id="policies">
|
|
202
|
-
<h2>Policies (declarative JSON rules)</h2>
|
|
203
|
-
<pre class="mono wrap" id="policies-json"></pre>
|
|
204
|
-
<h3>Machine Trace (debug)</h3>
|
|
205
|
-
<!-- switched to <pre> to ensure one-per-line display -->
|
|
206
|
-
<pre class="log mono" id="trace"></pre>
|
|
207
|
-
</section>
|
|
208
|
-
|
|
209
|
-
<section id="timeline">
|
|
210
|
-
<h2>Timeline</h2>
|
|
211
|
-
<div class="timeline" id="timeline-items">
|
|
212
|
-
<div class="card muted">Run the demo to populate results.</div>
|
|
213
|
-
</div>
|
|
214
|
-
</section>
|
|
215
|
-
</main>
|
|
216
|
-
|
|
217
|
-
<script>
|
|
218
|
-
/* Error surface */
|
|
219
|
-
window.onerror = function (msg, src, line, col, err) {
|
|
220
|
-
var t = document.getElementById('trace');
|
|
221
|
-
if (t) {
|
|
222
|
-
t.textContent = 'JS error: ' + msg + ' @ ' + line + ':' + col + (err && err.stack ? '\n' + err.stack : '');
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
/*** DATA (pure JSON) ***/
|
|
227
|
-
var Data = {
|
|
228
|
-
context: { depot: 'DepotX', preferEco: true },
|
|
229
|
-
threshold: { fuelIndex: 100 },
|
|
230
|
-
routes: {
|
|
231
|
-
current: 'curRoute',
|
|
232
|
-
alt: 'altRoute',
|
|
233
|
-
},
|
|
234
|
-
// Base figures chosen to reproduce the expected outputs
|
|
235
|
-
metrics: {
|
|
236
|
-
fuelIndex_current: 120.0,
|
|
237
|
-
fuelIndex_alt: 99.0,
|
|
238
|
-
comfort_current: 53.0,
|
|
239
|
-
comfort_alt: 48.0,
|
|
240
|
-
},
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
/*** ARC-style declarative rules (pure JSON) — R1..R5 ***/
|
|
244
|
-
var ARC_RULES = [
|
|
245
|
-
{
|
|
246
|
-
id: 'R1-Envelope',
|
|
247
|
-
if: [{ pred: 'preferEco', s: 'User', o: true }],
|
|
248
|
-
then: [{ pred: 'envelope', s: 'Req', o: 'insight' }],
|
|
249
|
-
explain: 'If user prefers eco, emit an insight envelope.',
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
id: 'R2-ShowBannerWhenOverThreshold',
|
|
253
|
-
if: [
|
|
254
|
-
{ pred: 'gt', s: 'fi_current', o: 'threshold' },
|
|
255
|
-
{ pred: 'depot', s: 'Ctx', o: '?D' },
|
|
256
|
-
],
|
|
257
|
-
then: [{ pred: 'showEcoBanner', s: 'Req', o: '?D' }],
|
|
258
|
-
explain: 'If FI(current) > T, banner is shown at the depot.',
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
id: 'R3-SuggestAlt',
|
|
262
|
-
if: [
|
|
263
|
-
{ pred: 'lt', s: 'fi_alt', o: 'fi_current' },
|
|
264
|
-
{ pred: 'routeAlt', s: 'Ctx', o: '?R' },
|
|
265
|
-
],
|
|
266
|
-
then: [{ pred: 'suggestRoute', s: 'Req', o: '?R' }],
|
|
267
|
-
explain: 'If FI(alt) < FI(current), suggest the alternative route.',
|
|
268
|
-
},
|
|
269
|
-
{
|
|
270
|
-
id: 'R4-AltWithinThreshold',
|
|
271
|
-
if: [{ pred: 'le', s: 'fi_alt', o: 'threshold' }],
|
|
272
|
-
then: [{ pred: 'altWithinThreshold', s: 'Req', o: true }],
|
|
273
|
-
explain: 'If FI(alt) ≤ T, alt is within threshold.',
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
id: 'R5-InsightReady',
|
|
277
|
-
if: [
|
|
278
|
-
{ pred: 'envelope', s: 'Req', o: 'insight' },
|
|
279
|
-
{ pred: 'showEcoBanner', s: 'Req', o: '?D' },
|
|
280
|
-
{ pred: 'suggestRoute', s: 'Req', o: '?R' },
|
|
281
|
-
{ pred: 'altWithinThreshold', s: 'Req', o: true },
|
|
282
|
-
],
|
|
283
|
-
then: [{ pred: 'insightReady', s: 'Req', o: true }],
|
|
284
|
-
explain: 'Envelope + banner + suggestion + threshold OK ⇒ ready.',
|
|
285
|
-
},
|
|
286
|
-
];
|
|
287
|
-
|
|
288
|
-
/*** Pretty printer for rules (inline IF/THEN atoms; per-line; wrapped) ***/
|
|
289
|
-
function formatInlineObject(o) {
|
|
290
|
-
var order = ['pred', 's', 'o', 'not'];
|
|
291
|
-
var keys = Object.keys(o).sort(function (a, b) {
|
|
292
|
-
var ai = order.indexOf(a),
|
|
293
|
-
bi = order.indexOf(b);
|
|
294
|
-
if (ai === -1 && bi === -1) return a < b ? -1 : a > b ? 1 : 0;
|
|
295
|
-
if (ai === -1) return 1;
|
|
296
|
-
if (bi === -1) return -1;
|
|
297
|
-
return ai - bi;
|
|
298
|
-
});
|
|
299
|
-
var parts = keys.map(function (k) {
|
|
300
|
-
var v = o[k],
|
|
301
|
-
vs = typeof v === 'string' ? JSON.stringify(v) : typeof v === 'boolean' ? String(v) : JSON.stringify(v);
|
|
302
|
-
return JSON.stringify(k) + ':' + vs;
|
|
303
|
-
});
|
|
304
|
-
return '{' + parts.join(',') + '}';
|
|
305
|
-
}
|
|
306
|
-
function formatRules(rules) {
|
|
307
|
-
var out = ['['];
|
|
308
|
-
for (var i = 0; i < rules.length; i++) {
|
|
309
|
-
var r = rules[i];
|
|
310
|
-
out.push(' {');
|
|
311
|
-
out.push(' "id": ' + JSON.stringify(r.id) + ',');
|
|
312
|
-
out.push(' "if": [ ' + (r.if || []).map(formatInlineObject).join(', ') + ' ],');
|
|
313
|
-
var thenLine = ' "then": [ ' + (r.then || []).map(formatInlineObject).join(', ') + ' ]';
|
|
314
|
-
if (r.explain !== undefined) thenLine += ',';
|
|
315
|
-
out.push(thenLine);
|
|
316
|
-
if (r.explain !== undefined) out.push(' "explain": ' + JSON.stringify(r.explain));
|
|
317
|
-
out.push(' }' + (i < rules.length - 1 ? ',' : ''));
|
|
318
|
-
}
|
|
319
|
-
out.push(']');
|
|
320
|
-
return out.join('\n');
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/*** Tiny ARC forward chainer ***/
|
|
324
|
-
function isVar(x) {
|
|
325
|
-
return typeof x === 'string' && x[0] === '?';
|
|
326
|
-
}
|
|
327
|
-
function subst(term, theta) {
|
|
328
|
-
var t = { pred: term.pred, s: term.s, o: term.o };
|
|
329
|
-
if (isVar(t.s) && theta[t.s] !== undefined) t.s = theta[t.s];
|
|
330
|
-
if (isVar(t.o) && theta[t.o] !== undefined) t.o = theta[t.o];
|
|
331
|
-
return t;
|
|
332
|
-
}
|
|
333
|
-
function unifyAtom(pattern, fact, theta) {
|
|
334
|
-
if (pattern.pred !== fact.pred) return null;
|
|
335
|
-
var s = Object.assign({}, theta || {});
|
|
336
|
-
function bind(pv, fv) {
|
|
337
|
-
if (isVar(pv)) {
|
|
338
|
-
if (s[pv] === undefined) {
|
|
339
|
-
s[pv] = fv;
|
|
340
|
-
return true;
|
|
341
|
-
}
|
|
342
|
-
return s[pv] === fv;
|
|
343
|
-
}
|
|
344
|
-
return pv === fv;
|
|
345
|
-
}
|
|
346
|
-
if (!bind(pattern.s, fact.s)) return null;
|
|
347
|
-
if (!bind(pattern.o, fact.o)) return null;
|
|
348
|
-
return s;
|
|
349
|
-
}
|
|
350
|
-
function matchBody(body, facts) {
|
|
351
|
-
function extend(i, theta, acc) {
|
|
352
|
-
if (i === body.length) {
|
|
353
|
-
acc.push(theta);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
var atom = body[i];
|
|
357
|
-
if (atom.not) {
|
|
358
|
-
var any = false;
|
|
359
|
-
for (var k = 0; k < facts.length; k++) {
|
|
360
|
-
if (unifyAtom(atom, facts[k], theta)) {
|
|
361
|
-
any = true;
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
if (!any) extend(i + 1, theta, acc);
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
for (var k = 0; k < facts.length; k++) {
|
|
369
|
-
var t2 = unifyAtom(atom, facts[k], theta);
|
|
370
|
-
if (t2) extend(i + 1, t2, acc);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
var out = [];
|
|
374
|
-
extend(0, {}, out);
|
|
375
|
-
return out;
|
|
376
|
-
}
|
|
377
|
-
function factKey(f) {
|
|
378
|
-
return f.pred + '|' + JSON.stringify(f.s) + '|' + JSON.stringify(f.o);
|
|
379
|
-
}
|
|
380
|
-
function containsFact(facts, f) {
|
|
381
|
-
var key = factKey(f);
|
|
382
|
-
for (var i = 0; i < facts.length; i++) {
|
|
383
|
-
if (factKey(facts[i]) === key) return true;
|
|
384
|
-
}
|
|
385
|
-
return false;
|
|
386
|
-
}
|
|
387
|
-
function derive(facts, rules) {
|
|
388
|
-
var added = true,
|
|
389
|
-
trace = [];
|
|
390
|
-
while (added) {
|
|
391
|
-
added = false;
|
|
392
|
-
for (var r = 0; r < rules.length; r++) {
|
|
393
|
-
var rule = rules[r];
|
|
394
|
-
var thetas = matchBody(rule.if, facts);
|
|
395
|
-
for (var t = 0; t < thetas.length; t++) {
|
|
396
|
-
for (var j = 0; j < rule.then.length; j++) {
|
|
397
|
-
var f = subst(rule.then[j], thetas[t]);
|
|
398
|
-
if (!containsFact(facts, f)) {
|
|
399
|
-
facts.push(f);
|
|
400
|
-
trace.push('[' + rule.id + '] ' + JSON.stringify(f));
|
|
401
|
-
added = true;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
return { facts: facts, trace: trace };
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/*** Base facts + specialized numeric reasoning ***/
|
|
411
|
-
function factsFromData(d) {
|
|
412
|
-
var F = [];
|
|
413
|
-
function add(pred, s, o) {
|
|
414
|
-
F.push({ pred: pred, s: s, o: o });
|
|
415
|
-
}
|
|
416
|
-
// Inputs
|
|
417
|
-
add('preferEco', 'User', d.context.preferEco);
|
|
418
|
-
add('depot', 'Ctx', d.context.depot);
|
|
419
|
-
add('routeAlt', 'Ctx', d.routes.alt);
|
|
420
|
-
// Fuel index numbers & threshold
|
|
421
|
-
var fi1 = d.metrics.fuelIndex_current,
|
|
422
|
-
fi2 = d.metrics.fuelIndex_alt,
|
|
423
|
-
T = d.threshold.fuelIndex;
|
|
424
|
-
add('fi', 'current', fi1);
|
|
425
|
-
add('fi', 'alt', fi2);
|
|
426
|
-
add('threshold', 'fuel', T);
|
|
427
|
-
// Comparator helpers (JS provides)
|
|
428
|
-
if (fi1 > T) add('gt', 'fi_current', 'threshold');
|
|
429
|
-
if (fi2 < fi1) add('lt', 'fi_alt', 'fi_current');
|
|
430
|
-
if (fi2 <= T) add('le', 'fi_alt', 'threshold');
|
|
431
|
-
// Comfort
|
|
432
|
-
add('comfort', 'current', d.metrics.comfort_current);
|
|
433
|
-
add('comfort', 'alt', d.metrics.comfort_alt);
|
|
434
|
-
// Estimated saving (rounded to nearest 10, as per example key values)
|
|
435
|
-
var exactSaving = +(fi1 - fi2).toFixed(2);
|
|
436
|
-
var estSaving = Math.round((fi1 - fi2) / 10) * 10; // → 20.00
|
|
437
|
-
add('saving', 'exact', exactSaving);
|
|
438
|
-
add('saving', 'estimated', +estSaving.toFixed(2));
|
|
439
|
-
return F;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/*** Render helpers ***/
|
|
443
|
-
function $(s) {
|
|
444
|
-
return document.querySelector(s);
|
|
445
|
-
}
|
|
446
|
-
function el(tag, attrs, children) {
|
|
447
|
-
attrs = attrs || {};
|
|
448
|
-
children = children || [];
|
|
449
|
-
var n = document.createElement(tag);
|
|
450
|
-
for (var k in attrs) {
|
|
451
|
-
if (!attrs.hasOwnProperty(k)) continue;
|
|
452
|
-
var v = attrs[k];
|
|
453
|
-
if (k === 'class') n.className = v;
|
|
454
|
-
else if (k === 'html') n.innerHTML = v;
|
|
455
|
-
else if (k.indexOf('on') === 0) n.addEventListener(k.slice(2), v);
|
|
456
|
-
else n.setAttribute(k, v);
|
|
457
|
-
}
|
|
458
|
-
for (var i = 0; i < children.length; i++) n.append(children[i]);
|
|
459
|
-
return n;
|
|
460
|
-
}
|
|
461
|
-
function escapeHTML(s) {
|
|
462
|
-
return String(s).replace(/[&<>"']/g, function (c) {
|
|
463
|
-
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/*** Main evaluation ***/
|
|
468
|
-
function runOnce() {
|
|
469
|
-
try {
|
|
470
|
-
// Show Data & Policies
|
|
471
|
-
$('#data-json').textContent = JSON.stringify(Data, null, 2);
|
|
472
|
-
$('#policies-json').textContent = formatRules(ARC_RULES);
|
|
473
|
-
|
|
474
|
-
// Build facts & derive
|
|
475
|
-
var baseFacts = factsFromData(Data);
|
|
476
|
-
var closure = derive(baseFacts.slice(), ARC_RULES);
|
|
477
|
-
|
|
478
|
-
// Pull derived values
|
|
479
|
-
function get(pred, s) {
|
|
480
|
-
var f = closure.facts.find(function (x) {
|
|
481
|
-
return x.pred === pred && x.s === s;
|
|
482
|
-
});
|
|
483
|
-
return f ? f.o : null;
|
|
484
|
-
}
|
|
485
|
-
function getO(pred) {
|
|
486
|
-
var f = closure.facts.find(function (x) {
|
|
487
|
-
return x.pred === pred;
|
|
488
|
-
});
|
|
489
|
-
return f ? f.o : null;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
var fi1 = get('fi', 'current');
|
|
493
|
-
var fi2 = get('fi', 'alt');
|
|
494
|
-
var comfort1 = get('comfort', 'current');
|
|
495
|
-
var comfort2 = get('comfort', 'alt');
|
|
496
|
-
var bannerAt = getO('showEcoBanner');
|
|
497
|
-
var suggest = getO('suggestRoute');
|
|
498
|
-
var estSave = get('saving', 'estimated');
|
|
499
|
-
var exactSave = get('saving', 'exact');
|
|
500
|
-
|
|
501
|
-
// Answer
|
|
502
|
-
$('#ans').innerHTML =
|
|
503
|
-
'<strong class="ok">✅ Insight envelope</strong>' +
|
|
504
|
-
'<div class="small">—</div>' +
|
|
505
|
-
'<div class="mono small">fuelIndex_current = ' +
|
|
506
|
-
fi1.toFixed(2) +
|
|
507
|
-
'</div>' +
|
|
508
|
-
'<div class="mono small">fuelIndex_alt = ' +
|
|
509
|
-
fi2.toFixed(2) +
|
|
510
|
-
'</div>' +
|
|
511
|
-
'<div class="mono small">showEcoBanner = ' +
|
|
512
|
-
escapeHTML(bannerAt || '—') +
|
|
513
|
-
'</div>' +
|
|
514
|
-
'<div class="mono small">suggestRoute = ' +
|
|
515
|
-
escapeHTML(suggest || '—') +
|
|
516
|
-
'</div>' +
|
|
517
|
-
'<div class="mono small">estimatedSaving = ' +
|
|
518
|
-
estSave.toFixed(2) +
|
|
519
|
-
'</div>' +
|
|
520
|
-
'<div class="mono small">comfortIndex_current = ' +
|
|
521
|
-
comfort1.toFixed(1) +
|
|
522
|
-
'</div>' +
|
|
523
|
-
'<div class="mono small">comfortIndex_alt = ' +
|
|
524
|
-
comfort2.toFixed(1) +
|
|
525
|
-
'</div>';
|
|
526
|
-
|
|
527
|
-
// Reason why
|
|
528
|
-
var why =
|
|
529
|
-
'<div>We apply the five N3 rules <strong>R1–R5</strong> to the input facts.</div>' +
|
|
530
|
-
'<div style="margin-top:.4rem"><strong>Key derived values:</strong></div>' +
|
|
531
|
-
'<ul class="list mono">' +
|
|
532
|
-
'<li>FI(current) = ' +
|
|
533
|
-
fi1.toFixed(2) +
|
|
534
|
-
'</li>' +
|
|
535
|
-
'<li>FI(alt) = ' +
|
|
536
|
-
fi2.toFixed(2) +
|
|
537
|
-
'</li>' +
|
|
538
|
-
'<li>Saving = ' +
|
|
539
|
-
estSave.toFixed(2) +
|
|
540
|
-
'</li>' +
|
|
541
|
-
'<li>Banner at = ' +
|
|
542
|
-
escapeHTML(bannerAt || '—') +
|
|
543
|
-
'</li>' +
|
|
544
|
-
'<li>Suggest = ' +
|
|
545
|
-
escapeHTML(suggest || '—') +
|
|
546
|
-
'</li>' +
|
|
547
|
-
'<li>Comfort(cur)= ' +
|
|
548
|
-
comfort1.toFixed(1) +
|
|
549
|
-
'</li>' +
|
|
550
|
-
'<li>Comfort(alt)= ' +
|
|
551
|
-
comfort2.toFixed(1) +
|
|
552
|
-
'</li>' +
|
|
553
|
-
'</ul>';
|
|
554
|
-
$('#why').innerHTML = why;
|
|
555
|
-
|
|
556
|
-
// Checks (harness) — cross-check exact numbers and conditions
|
|
557
|
-
var c1 = fi1 === 120;
|
|
558
|
-
var c2 = fi2 === 99;
|
|
559
|
-
var condBanner = fi1 > Data.threshold.fuelIndex;
|
|
560
|
-
var condSuggest = fi2 < fi1;
|
|
561
|
-
var condAltOk = fi2 <= Data.threshold.fuelIndex;
|
|
562
|
-
var checks = [
|
|
563
|
-
' FI(current) = ' + fi1.toFixed(2),
|
|
564
|
-
' FI(alt) = ' + fi2.toFixed(2),
|
|
565
|
-
' Banner condition (FI1>T)? ' + (condBanner ? '✓' : '✗'),
|
|
566
|
-
' Suggest alt (FI2<FI1)? ' + (condSuggest ? '✓' : '✗'),
|
|
567
|
-
' Alt within threshold (FI2≤T)? ' + (condAltOk ? '✓' : '✗'),
|
|
568
|
-
' Saving = ' + exactSave.toFixed(2),
|
|
569
|
-
' All checks passed ✓',
|
|
570
|
-
];
|
|
571
|
-
$('#check').innerHTML =
|
|
572
|
-
'<ul class="list mono">' +
|
|
573
|
-
checks
|
|
574
|
-
.map(function (x) {
|
|
575
|
-
return '<li>' + escapeHTML(x) + '</li>';
|
|
576
|
-
})
|
|
577
|
-
.join('') +
|
|
578
|
-
'</ul>';
|
|
579
|
-
|
|
580
|
-
// Trace, timeline, stash
|
|
581
|
-
$('#trace').textContent = closure.trace.join('\n');
|
|
582
|
-
pushTimeline(
|
|
583
|
-
{ ans: 'Insight envelope' },
|
|
584
|
-
{ answer: 'Allowed', reason: 'Eco insight derived (banner & suggestion ready).' },
|
|
585
|
-
);
|
|
586
|
-
window.__lastResult = {
|
|
587
|
-
data: Data,
|
|
588
|
-
facts: baseFacts,
|
|
589
|
-
derived: closure.facts,
|
|
590
|
-
trace: closure.trace,
|
|
591
|
-
values: {
|
|
592
|
-
fi1: fi1,
|
|
593
|
-
fi2: fi2,
|
|
594
|
-
comfort1: comfort1,
|
|
595
|
-
comfort2: comfort2,
|
|
596
|
-
bannerAt: bannerAt,
|
|
597
|
-
suggest: suggest,
|
|
598
|
-
estSave: estSave,
|
|
599
|
-
exactSave: exactSave,
|
|
600
|
-
},
|
|
601
|
-
ts: new Date().toISOString(),
|
|
602
|
-
};
|
|
603
|
-
} catch (e) {
|
|
604
|
-
var t = document.getElementById('trace');
|
|
605
|
-
if (t) t.textContent = 'Evaluate error: ' + ((e && e.stack) || e);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/*** Timeline ***/
|
|
610
|
-
function pushTimeline(meta, res) {
|
|
611
|
-
var box = document.getElementById('timeline-items');
|
|
612
|
-
var card = el('div', { class: 'card' });
|
|
613
|
-
var when = new Date().toLocaleString();
|
|
614
|
-
card.innerHTML =
|
|
615
|
-
'<div class="row" style="justify-content:space-between"><strong>✅ Insight</strong><span class="muted small">' +
|
|
616
|
-
when +
|
|
617
|
-
'</span></div>' +
|
|
618
|
-
'<div class="small">' +
|
|
619
|
-
(res.reason || '') +
|
|
620
|
-
'</div>';
|
|
621
|
-
box.prepend(card);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
/*** Boot ***/
|
|
625
|
-
function boot() {
|
|
626
|
-
document.getElementById('data-json').textContent = JSON.stringify(Data, null, 2);
|
|
627
|
-
document.getElementById('policies-json').textContent = formatRules(ARC_RULES);
|
|
628
|
-
document.getElementById('btn-run').addEventListener('click', runOnce);
|
|
629
|
-
document.getElementById('btn-copy').addEventListener('click', function () {
|
|
630
|
-
try {
|
|
631
|
-
var payload = JSON.stringify(window.__lastResult || { error: 'no-result' }, null, 2);
|
|
632
|
-
navigator.clipboard.writeText(payload);
|
|
633
|
-
var b = document.getElementById('btn-copy');
|
|
634
|
-
var old = b.textContent;
|
|
635
|
-
b.textContent = 'Copied!';
|
|
636
|
-
setTimeout(function () {
|
|
637
|
-
b.textContent = old;
|
|
638
|
-
}, 1000);
|
|
639
|
-
} catch (e) {
|
|
640
|
-
alert('Copy failed: ' + e.message);
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
window.addEventListener('DOMContentLoaded', function () {
|
|
645
|
-
try {
|
|
646
|
-
boot();
|
|
647
|
-
} catch (e) {
|
|
648
|
-
var t = document.getElementById('trace');
|
|
649
|
-
if (t) t.textContent = 'Boot error: ' + ((e && e.stack) || e);
|
|
650
|
-
}
|
|
651
|
-
});
|
|
652
|
-
if (document.readyState !== 'loading') {
|
|
653
|
-
try {
|
|
654
|
-
boot();
|
|
655
|
-
} catch (e) {
|
|
656
|
-
var t = document.getElementById('trace');
|
|
657
|
-
if (t) t.textContent = 'Boot error: ' + ((e && e.stack) || e);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
</script>
|
|
661
|
-
</body>
|
|
662
|
-
</html>
|