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,833 +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>Health Info Processing (FHIR + LOINC/SNOMED)</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
|
-
/* badges */
|
|
107
|
-
.badge {
|
|
108
|
-
display: inline-block;
|
|
109
|
-
padding: 2px 8px;
|
|
110
|
-
border-radius: 999px;
|
|
111
|
-
font-size: 12px;
|
|
112
|
-
font-weight: 700;
|
|
113
|
-
letter-spacing: 0.3px;
|
|
114
|
-
border: 1px solid var(--border);
|
|
115
|
-
background: #f5f7fb;
|
|
116
|
-
color: #334155;
|
|
117
|
-
}
|
|
118
|
-
/* textareas */
|
|
119
|
-
textarea,
|
|
120
|
-
pre,
|
|
121
|
-
code,
|
|
122
|
-
input,
|
|
123
|
-
button {
|
|
124
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Courier New', monospace;
|
|
125
|
-
}
|
|
126
|
-
textarea {
|
|
127
|
-
width: 100%;
|
|
128
|
-
min-height: 0;
|
|
129
|
-
height: auto;
|
|
130
|
-
overflow: hidden;
|
|
131
|
-
resize: none;
|
|
132
|
-
border-radius: 10px;
|
|
133
|
-
padding: 10px;
|
|
134
|
-
border: 1px solid var(--border);
|
|
135
|
-
background: #fbfdff;
|
|
136
|
-
color: var(--text);
|
|
137
|
-
box-sizing: border-box;
|
|
138
|
-
white-space: pre-wrap;
|
|
139
|
-
}
|
|
140
|
-
</style>
|
|
141
|
-
</head>
|
|
142
|
-
<body>
|
|
143
|
-
<div class="wrap">
|
|
144
|
-
<h1>Health Info Processing — FHIR + LOINC/SNOMED</h1>
|
|
145
|
-
|
|
146
|
-
<div class="stack">
|
|
147
|
-
<!-- What this is? -->
|
|
148
|
-
<div class="card" id="what">
|
|
149
|
-
<h2>What this is?</h2>
|
|
150
|
-
<p>
|
|
151
|
-
One-file, pure JS + JSON demo that mimics FHIR-ish resources and coding. Edit the JSON below and click
|
|
152
|
-
<b>Run ARC</b>:
|
|
153
|
-
</p>
|
|
154
|
-
<ul>
|
|
155
|
-
<li>
|
|
156
|
-
<b>Data</b> — FHIR-flavored resources: <code>Patient</code>, <code>Encounter</code>,
|
|
157
|
-
<code>DocumentReference</code>, <code>Observation</code>, <code>Condition</code>, <code>Consent</code>;
|
|
158
|
-
lab codes (LOINC), condition codes (SNOMED).
|
|
159
|
-
</li>
|
|
160
|
-
<li>
|
|
161
|
-
<b>Policies</b> — declarative JSON rules: an array of objects with <code>if</code> atoms (allowing
|
|
162
|
-
<code>"not": true</code>) ⇒ <code>then</code> atoms, plus an <code>explain</code> template.
|
|
163
|
-
</li>
|
|
164
|
-
<li>
|
|
165
|
-
<b>Answer</b> — the <em>newly derived</em> facts only (keeps the strongest <code>priority</code> per
|
|
166
|
-
patient).
|
|
167
|
-
</li>
|
|
168
|
-
<li><b>Reason Why</b> — mathematical-English derivations based on bound variables per rule firing.</li>
|
|
169
|
-
<li>
|
|
170
|
-
<b>Check</b> — JSON assertions evaluated against the full fact set (<span
|
|
171
|
-
id="checksCountInline"
|
|
172
|
-
class="badge"
|
|
173
|
-
>…</span
|
|
174
|
-
>
|
|
175
|
-
items).
|
|
176
|
-
</li>
|
|
177
|
-
<li>
|
|
178
|
-
<b>Health Info Summary</b> — per-patient strongest priority and tasks. Priority order: Blocked > High
|
|
179
|
-
> Normal > Low.
|
|
180
|
-
</li>
|
|
181
|
-
</ul>
|
|
182
|
-
<p class="tiny muted">
|
|
183
|
-
Engine: tiny forward-chainer with unification, subclassing (<code>subClass</code>), transitive
|
|
184
|
-
<code>partOf</code>, observation→patient mapping, and negation-as-failure for <code>"not": true</code>.
|
|
185
|
-
</p>
|
|
186
|
-
</div>
|
|
187
|
-
|
|
188
|
-
<div class="card">
|
|
189
|
-
<h2>Data (JSON)</h2>
|
|
190
|
-
<textarea id="dataTA" spellcheck="false">
|
|
191
|
-
{
|
|
192
|
-
"classes": [
|
|
193
|
-
"Patient","Encounter","DocumentReference","Observation","Condition","Consent","Component",
|
|
194
|
-
"ObservationClass","ClinicalIssue","PrivacyIssue","DataQualityIssue","WorkflowEvent",
|
|
195
|
-
"ShareRequested",
|
|
196
|
-
"Task","Priority","Blocked","High","Normal","Low"
|
|
197
|
-
],
|
|
198
|
-
|
|
199
|
-
"subclassOf": [
|
|
200
|
-
["Encounter","Component"], ["DocumentReference","Component"],
|
|
201
|
-
["ObservationClass","Observation"],
|
|
202
|
-
["ShareRequested","WorkflowEvent"],
|
|
203
|
-
["PrivacyIssue","Observation"], ["DataQualityIssue","Observation"], ["ClinicalIssue","Observation"], ["WorkflowEvent","Observation"]
|
|
204
|
-
],
|
|
205
|
-
|
|
206
|
-
"assets": {
|
|
207
|
-
"P1": {"type":"Patient"},
|
|
208
|
-
"Enc1":{"type":"Encounter","partOf":"P1"},
|
|
209
|
-
"Note1":{"type":"DocumentReference","partOf":"Enc1"},
|
|
210
|
-
|
|
211
|
-
"P2": {"type":"Patient"},
|
|
212
|
-
"Enc2":{"type":"Encounter","partOf":"P2"},
|
|
213
|
-
"Note2":{"type":"DocumentReference","partOf":"Enc2"}
|
|
214
|
-
},
|
|
215
|
-
|
|
216
|
-
"consents": [
|
|
217
|
-
{"id":"C1","type":"Consent","partOf":"P1","scope":"treatment","status":"active"},
|
|
218
|
-
{"id":"C3","type":"Consent","partOf":"P2","scope":"research","status":"active","permittedOrg":"OrgR1"}
|
|
219
|
-
],
|
|
220
|
-
|
|
221
|
-
"conditions": [
|
|
222
|
-
{"id":"CondDM2","type":"Condition","partOf":"P2","codeSystem":"unknown","code":"E11"}
|
|
223
|
-
],
|
|
224
|
-
|
|
225
|
-
"observations": [
|
|
226
|
-
{"id":"ObsPHI1","type":"PrivacyIssue","about":"Note1","codeSystem":"custom","code":"PHIInNote"},
|
|
227
|
-
{"id":"SR1","type":"ShareRequested","about":"P1","sharePurpose":"treatment","shareOrg":"OrgH1"},
|
|
228
|
-
{"id":"ObsHb1","type":"Observation","about":"Enc1","codeSystem":"loinc","code":"718-7","category":"laboratory","valueFlag":"criticalLow"},
|
|
229
|
-
{"id":"ObsCovid1","type":"Observation","about":"Enc1","codeSystem":"loinc","code":"94531-1","category":"laboratory","valueFlag":"positive"},
|
|
230
|
-
|
|
231
|
-
{"id":"SR2","type":"ShareRequested","about":"P2","sharePurpose":"research","shareOrg":"OrgR1"},
|
|
232
|
-
{"id":"ObsUnknownLab2","type":"Observation","about":"Enc2","codeSystem":"unknown","code":"X123","category":"laboratory"},
|
|
233
|
-
{"id":"ObsPHI2","type":"PrivacyIssue","about":"Note2","codeSystem":"custom","code":"PHIInNote"}
|
|
234
|
-
]
|
|
235
|
-
}</textarea
|
|
236
|
-
>
|
|
237
|
-
</div>
|
|
238
|
-
|
|
239
|
-
<div class="card">
|
|
240
|
-
<h2>Policies (declarative JSON rules)</h2>
|
|
241
|
-
<textarea id="policyTA" spellcheck="false">
|
|
242
|
-
[
|
|
243
|
-
{ "id":"R1-Subclass",
|
|
244
|
-
"if":[{"pred":"subClass","s":"?A","o":"?B"},{"pred":"isA","s":"?S","o":"?A"}],
|
|
245
|
-
"then":[{"pred":"isA","s":"?S","o":"?B"}],
|
|
246
|
-
"explain":"Since ?A ⊑ ?B and ?S ∈ ?A, infer ?S ∈ ?B."
|
|
247
|
-
},
|
|
248
|
-
{ "id":"R2-PartOfTransitive",
|
|
249
|
-
"if":[{"pred":"partOf","s":"?x","o":"?y"},{"pred":"partOf","s":"?y","o":"?z"}],
|
|
250
|
-
"then":[{"pred":"partOf","s":"?x","o":"?z"}],
|
|
251
|
-
"explain":"Because ?x is part of ?y and ?y is part of ?z, ?x is part of ?z."
|
|
252
|
-
},
|
|
253
|
-
{ "id":"R3-ObsToPatient",
|
|
254
|
-
"if":[{"pred":"about","s":"?obs","o":"?comp"},{"pred":"partOf","s":"?comp","o":"?P"}],
|
|
255
|
-
"then":[{"pred":"aboutPatient","s":"?obs","o":"?P"}],
|
|
256
|
-
"explain":"Observation ?obs concerns component ?comp of patient ?P; thus aboutPatient(?obs,?P)."
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
{ "id":"R4-ConsentToPatient",
|
|
260
|
-
"if":[{"pred":"isA","s":"?C","o":"Consent"},{"pred":"partOf","s":"?C","o":"?P"}],
|
|
261
|
-
"then":[{"pred":"consentOfPatient","s":"?C","o":"?P"}],
|
|
262
|
-
"explain":"Consent ?C belongs to patient ?P."
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
{ "id":"R5-ActiveResearchConsent-Org",
|
|
266
|
-
"if":[{"pred":"isA","s":"?C","o":"Consent"},{"pred":"consentOfPatient","s":"?C","o":"?P"},
|
|
267
|
-
{"pred":"consentScope","s":"?C","o":"research"},{"pred":"consentStatus","s":"?C","o":"active"},
|
|
268
|
-
{"pred":"consentPermittedOrg","s":"?C","o":"?Org"}],
|
|
269
|
-
"then":[{"pred":"hasActiveResearchConsentFor","s":"?P","o":"?Org"}],
|
|
270
|
-
"explain":"?P has an active research consent permitting org ?Org."
|
|
271
|
-
},
|
|
272
|
-
{ "id":"R6-ActiveResearchConsent-AnyOrg",
|
|
273
|
-
"if":[{"pred":"isA","s":"?C","o":"Consent"},{"pred":"consentOfPatient","s":"?C","o":"?P"},
|
|
274
|
-
{"pred":"consentScope","s":"?C","o":"research"},{"pred":"consentStatus","s":"?C","o":"active"},
|
|
275
|
-
{"not":true,"pred":"consentPermittedOrg","s":"?C","o":"?x"}],
|
|
276
|
-
"then":[{"pred":"hasActiveResearchConsentFor","s":"?P","o":"ANY"}],
|
|
277
|
-
"explain":"?P has an active research consent without org restriction."
|
|
278
|
-
},
|
|
279
|
-
{ "id":"R7-ActiveTreatmentConsent",
|
|
280
|
-
"if":[{"pred":"isA","s":"?C","o":"Consent"},{"pred":"consentOfPatient","s":"?C","o":"?P"},
|
|
281
|
-
{"pred":"consentScope","s":"?C","o":"treatment"},{"pred":"consentStatus","s":"?C","o":"active"}],
|
|
282
|
-
"then":[{"pred":"hasActiveTreatmentConsent","s":"?P","o":"yes"}],
|
|
283
|
-
"explain":"?P has an active treatment consent."
|
|
284
|
-
},
|
|
285
|
-
|
|
286
|
-
{ "id":"C-PHI-Handling",
|
|
287
|
-
"if":[{"pred":"isA","s":"?obs","o":"PrivacyIssue"},{"pred":"aboutPatient","s":"?obs","o":"?P"}],
|
|
288
|
-
"then":[{"pred":"requiresTask","s":"?P","o":"DeidentifyText"}],
|
|
289
|
-
"explain":"PHI present for ?P requires de-identification."
|
|
290
|
-
},
|
|
291
|
-
|
|
292
|
-
{ "id":"Share-Research-Allow-Org",
|
|
293
|
-
"if":[{"pred":"isA","s":"?sr","o":"ShareRequested"},{"pred":"aboutPatient","s":"?sr","o":"?P"},
|
|
294
|
-
{"pred":"sharePurpose","s":"?sr","o":"research"},{"pred":"shareOrg","s":"?sr","o":"?Org"},
|
|
295
|
-
{"pred":"hasActiveResearchConsentFor","s":"?P","o":"?Org"}],
|
|
296
|
-
"then":[{"pred":"requiresTask","s":"?P","o":"AllowResearchShare"},{"pred":"priority","s":"?P","o":"Normal"}],
|
|
297
|
-
"explain":"Research share for ?P allowed via matching active consent to org ?Org."
|
|
298
|
-
},
|
|
299
|
-
{ "id":"Share-Research-Allow-Any",
|
|
300
|
-
"if":[{"pred":"isA","s":"?sr","o":"ShareRequested"},{"pred":"aboutPatient","s":"?sr","o":"?P"},
|
|
301
|
-
{"pred":"sharePurpose","s":"?sr","o":"research"},{"pred":"shareOrg","s":"?sr","o":"?Org"},
|
|
302
|
-
{"pred":"hasActiveResearchConsentFor","s":"?P","o":"ANY"}],
|
|
303
|
-
"then":[{"pred":"requiresTask","s":"?P","o":"AllowResearchShare"},{"pred":"priority","s":"?P","o":"Normal"}],
|
|
304
|
-
"explain":"Research share for ?P allowed by active consent without org restriction."
|
|
305
|
-
},
|
|
306
|
-
{ "id":"Share-Research-Block",
|
|
307
|
-
"if":[{"pred":"isA","s":"?sr","o":"ShareRequested"},{"pred":"aboutPatient","s":"?sr","o":"?P"},
|
|
308
|
-
{"pred":"sharePurpose","s":"?sr","o":"research"},{"pred":"shareOrg","s":"?sr","o":"?Org"},
|
|
309
|
-
{"not":true,"pred":"hasActiveResearchConsentFor","s":"?P","o":"?Org"},
|
|
310
|
-
{"not":true,"pred":"hasActiveResearchConsentFor","s":"?P","o":"ANY"}],
|
|
311
|
-
"then":[{"pred":"requiresTask","s":"?P","o":"ObtainResearchConsent"},{"pred":"priority","s":"?P","o":"Blocked"}],
|
|
312
|
-
"explain":"Research share for ?P is blocked: no suitable active research consent."
|
|
313
|
-
},
|
|
314
|
-
|
|
315
|
-
{ "id":"Share-Treatment-Allow",
|
|
316
|
-
"if":[{"pred":"isA","s":"?sr","o":"ShareRequested"},{"pred":"aboutPatient","s":"?sr","o":"?P"},
|
|
317
|
-
{"pred":"sharePurpose","s":"?sr","o":"treatment"},{"pred":"hasActiveTreatmentConsent","s":"?P","o":"yes"}],
|
|
318
|
-
"then":[{"pred":"requiresTask","s":"?P","o":"AllowTreatmentShare"},{"pred":"priority","s":"?P","o":"Normal"}],
|
|
319
|
-
"explain":"Treatment share for ?P allowed by active consent."
|
|
320
|
-
},
|
|
321
|
-
{ "id":"Share-Treatment-Block",
|
|
322
|
-
"if":[{"pred":"isA","s":"?sr","o":"ShareRequested"},{"pred":"aboutPatient","s":"?sr","o":"?P"},
|
|
323
|
-
{"pred":"sharePurpose","s":"?sr","o":"treatment"},
|
|
324
|
-
{"not":true,"pred":"hasActiveTreatmentConsent","s":"?P","o":"yes"}],
|
|
325
|
-
"then":[{"pred":"requiresTask","s":"?P","o":"ObtainTreatmentConsent"},{"pred":"priority","s":"?P","o":"Blocked"}],
|
|
326
|
-
"explain":"Treatment share for ?P is blocked: no active treatment consent."
|
|
327
|
-
},
|
|
328
|
-
|
|
329
|
-
{ "id":"Map-UnknownLab→LOINC",
|
|
330
|
-
"if":[{"pred":"isA","s":"?obs","o":"Observation"},{"pred":"aboutPatient","s":"?obs","o":"?P"},
|
|
331
|
-
{"pred":"category","s":"?obs","o":"laboratory"},{"pred":"codeSystem","s":"?obs","o":"unknown"}],
|
|
332
|
-
"then":[{"pred":"requiresTask","s":"?P","o":"MapToLOINC"}],
|
|
333
|
-
"explain":"Lab observation for ?P has unknown coding; map to LOINC."
|
|
334
|
-
},
|
|
335
|
-
{ "id":"Map-UnknownCondition→SNOMED",
|
|
336
|
-
"if":[{"pred":"isA","s":"?cond","o":"Condition"},{"pred":"partOf","s":"?cond","o":"?P"},
|
|
337
|
-
{"pred":"codeSystem","s":"?cond","o":"unknown"}],
|
|
338
|
-
"then":[{"pred":"requiresTask","s":"?P","o":"MapToSNOMED"}],
|
|
339
|
-
"explain":"Condition for ?P has unknown code; map to SNOMED CT."
|
|
340
|
-
},
|
|
341
|
-
|
|
342
|
-
{ "id":"Clinical-COVIDPCR-Positive",
|
|
343
|
-
"if":[{"pred":"isA","s":"?obs","o":"Observation"},{"pred":"aboutPatient","s":"?obs","o":"?P"},
|
|
344
|
-
{"pred":"codeSystem","s":"?obs","o":"loinc"},{"pred":"code","s":"?obs","o":"94531-1"},
|
|
345
|
-
{"pred":"valueFlag","s":"?obs","o":"positive"}],
|
|
346
|
-
"then":[{"pred":"requiresTask","s":"?P","o":"IsolatePatient"},
|
|
347
|
-
{"pred":"requiresTask","s":"?P","o":"NotifyPublicHealth"},
|
|
348
|
-
{"pred":"priority","s":"?P","o":"High"}],
|
|
349
|
-
"explain":"LOINC 94531-1 is positive for ?P → isolation, public health notification (High)."
|
|
350
|
-
},
|
|
351
|
-
{ "id":"Clinical-Hb-CriticalLow",
|
|
352
|
-
"if":[{"pred":"isA","s":"?obs","o":"Observation"},{"pred":"aboutPatient","s":"?obs","o":"?P"},
|
|
353
|
-
{"pred":"codeSystem","s":"?obs","o":"loinc"},{"pred":"code","s":"?obs","o":"718-7"},
|
|
354
|
-
{"pred":"valueFlag","s":"?obs","o":"criticalLow"}],
|
|
355
|
-
"then":[{"pred":"requiresTask","s":"?P","o":"UrgentTransfusionConsult"},
|
|
356
|
-
{"pred":"priority","s":"?P","o":"High"}],
|
|
357
|
-
"explain":"LOINC 718-7 critical low for ?P → urgent transfusion consult (High)."
|
|
358
|
-
},
|
|
359
|
-
|
|
360
|
-
{ "id":"P-Default-Normal",
|
|
361
|
-
"if":[{"pred":"requiresTask","s":"?P","o":"?X"},
|
|
362
|
-
{"not":true,"pred":"priority","s":"?P","o":"?Any"}],
|
|
363
|
-
"then":[{"pred":"priority","s":"?P","o":"Normal"}],
|
|
364
|
-
"explain":"If ?P has tasks but no priority, default to Normal."
|
|
365
|
-
}
|
|
366
|
-
]</textarea
|
|
367
|
-
>
|
|
368
|
-
</div>
|
|
369
|
-
|
|
370
|
-
<div class="card">
|
|
371
|
-
<h2>Checks <span id="checksCount" class="badge">0</span></h2>
|
|
372
|
-
<textarea id="checksTA" spellcheck="false">
|
|
373
|
-
[
|
|
374
|
-
{"name":"PHI in Note1 → about P1", "pattern":{"pred":"aboutPatient","s":"ObsPHI1","o":"P1"}},
|
|
375
|
-
{"name":"SR1 (treatment) → about P1", "pattern":{"pred":"aboutPatient","s":"SR1","o":"P1"}},
|
|
376
|
-
{"name":"P1 has active treatment consent", "pattern":{"pred":"hasActiveTreatmentConsent","s":"P1","o":"yes"}},
|
|
377
|
-
{"name":"Allow treatment share for P1", "pattern":{"pred":"requiresTask","s":"P1","o":"AllowTreatmentShare"}},
|
|
378
|
-
{"name":"P1 priority High (labs)", "pattern":{"pred":"priority","s":"P1","o":"High"}},
|
|
379
|
-
{"name":"P1 needs UrgentTransfusionConsult", "pattern":{"pred":"requiresTask","s":"P1","o":"UrgentTransfusionConsult"}},
|
|
380
|
-
{"name":"P1 needs Isolation (COVID+)", "pattern":{"pred":"requiresTask","s":"P1","o":"IsolatePatient"}},
|
|
381
|
-
{"name":"P1 notify public health", "pattern":{"pred":"requiresTask","s":"P1","o":"NotifyPublicHealth"}},
|
|
382
|
-
{"name":"P2 has active research consent for OrgR1","pattern":{"pred":"hasActiveResearchConsentFor","s":"P2","o":"OrgR1"}},
|
|
383
|
-
{"name":"Allow research share for P2", "pattern":{"pred":"requiresTask","s":"P2","o":"AllowResearchShare"}},
|
|
384
|
-
{"name":"P2 map unknown lab to LOINC", "pattern":{"pred":"requiresTask","s":"P2","o":"MapToLOINC"}},
|
|
385
|
-
{"name":"P2 map unknown condition to SNOMED", "pattern":{"pred":"requiresTask","s":"P2","o":"MapToSNOMED"}},
|
|
386
|
-
{"name":"P2 deidentify text", "pattern":{"pred":"requiresTask","s":"P2","o":"DeidentifyText"}},
|
|
387
|
-
{"name":"P2 priority Normal", "pattern":{"pred":"priority","s":"P2","o":"Normal"}}
|
|
388
|
-
]</textarea
|
|
389
|
-
>
|
|
390
|
-
</div>
|
|
391
|
-
|
|
392
|
-
<div class="card">
|
|
393
|
-
<h2>Controls</h2>
|
|
394
|
-
<div class="row">
|
|
395
|
-
<button id="runBtn">▶ Run ARC</button>
|
|
396
|
-
<button id="reasonBtn" class="secondary">Show Reason only</button>
|
|
397
|
-
<span id="status" class="muted tiny" style="margin-left: auto"></span>
|
|
398
|
-
</div>
|
|
399
|
-
<div id="diag" class="diag"></div>
|
|
400
|
-
</div>
|
|
401
|
-
|
|
402
|
-
<div class="card">
|
|
403
|
-
<h2>Answer (newly derived facts)</h2>
|
|
404
|
-
<div id="answer" class="output tall">computing…</div>
|
|
405
|
-
</div>
|
|
406
|
-
|
|
407
|
-
<div class="card">
|
|
408
|
-
<h2>Reason Why (mathematical English)</h2>
|
|
409
|
-
<div id="reason" class="output tall">(click “Run ARC”)</div>
|
|
410
|
-
</div>
|
|
411
|
-
|
|
412
|
-
<div class="card">
|
|
413
|
-
<h2>Check</h2>
|
|
414
|
-
<div id="checks" class="output tall">computing…</div>
|
|
415
|
-
</div>
|
|
416
|
-
|
|
417
|
-
<div class="card" id="summaryCard">
|
|
418
|
-
<h2>Health Info Summary</h2>
|
|
419
|
-
<div id="summary" class="output">(run to populate)</div>
|
|
420
|
-
</div>
|
|
421
|
-
</div>
|
|
422
|
-
</div>
|
|
423
|
-
|
|
424
|
-
<script>
|
|
425
|
-
const $ = (id) => document.getElementById(id);
|
|
426
|
-
const els = {
|
|
427
|
-
dataTA: $('dataTA'),
|
|
428
|
-
policyTA: $('policyTA'),
|
|
429
|
-
checksTA: $('checksTA'),
|
|
430
|
-
runBtn: $('runBtn'),
|
|
431
|
-
reasonBtn: $('reasonBtn'),
|
|
432
|
-
status: $('status'),
|
|
433
|
-
diag: $('diag'),
|
|
434
|
-
answer: $('answer'),
|
|
435
|
-
reason: $('reason'),
|
|
436
|
-
checks: $('checks'),
|
|
437
|
-
summary: $('summary'),
|
|
438
|
-
checksCount: $('checksCount'),
|
|
439
|
-
checksCountInline: $('checksCountInline'),
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
// Auto-grow textareas
|
|
443
|
-
function autoResize(el) {
|
|
444
|
-
el.style.height = 'auto';
|
|
445
|
-
el.style.height = el.scrollHeight + 'px';
|
|
446
|
-
}
|
|
447
|
-
['dataTA', 'policyTA', 'checksTA'].forEach((id) => {
|
|
448
|
-
const el = $(id);
|
|
449
|
-
el.addEventListener('input', () => autoResize(el));
|
|
450
|
-
setTimeout(() => autoResize(el), 0);
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
// ---- KB helpers
|
|
454
|
-
function key(f) {
|
|
455
|
-
return `${f.pred}|${f.s}|${f.o}`;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function buildFactsFromData(spec) {
|
|
459
|
-
const facts = [];
|
|
460
|
-
// subclass axioms
|
|
461
|
-
(spec.subclassOf || []).forEach(([a, b]) => facts.push({ pred: 'subClass', s: a, o: b, _base: true }));
|
|
462
|
-
// core assets
|
|
463
|
-
for (const [id, info] of Object.entries(spec.assets || {})) {
|
|
464
|
-
if (info.type) facts.push({ pred: 'isA', s: id, o: info.type, _base: true });
|
|
465
|
-
if (info.partOf) facts.push({ pred: 'partOf', s: id, o: info.partOf, _base: true });
|
|
466
|
-
}
|
|
467
|
-
// consents
|
|
468
|
-
(spec.consents || []).forEach((c) => {
|
|
469
|
-
facts.push({ pred: 'isA', s: c.id, o: c.type || 'Consent', _base: true });
|
|
470
|
-
if (c.partOf) facts.push({ pred: 'partOf', s: c.id, o: c.partOf, _base: true });
|
|
471
|
-
if (c.scope) facts.push({ pred: 'consentScope', s: c.id, o: c.scope, _base: true });
|
|
472
|
-
if (c.status) facts.push({ pred: 'consentStatus', s: c.id, o: c.status, _base: true });
|
|
473
|
-
if (c.permittedOrg) facts.push({ pred: 'consentPermittedOrg', s: c.id, o: c.permittedOrg, _base: true });
|
|
474
|
-
});
|
|
475
|
-
// conditions
|
|
476
|
-
(spec.conditions || []).forEach((d) => {
|
|
477
|
-
facts.push({ pred: 'isA', s: d.id, o: d.type || 'Condition', _base: true });
|
|
478
|
-
if (d.partOf) facts.push({ pred: 'partOf', s: d.id, o: d.partOf, _base: true });
|
|
479
|
-
if (d.codeSystem) facts.push({ pred: 'codeSystem', s: d.id, o: d.codeSystem, _base: true });
|
|
480
|
-
if (d.code) facts.push({ pred: 'code', s: d.id, o: d.code, _base: true });
|
|
481
|
-
});
|
|
482
|
-
// observations/events
|
|
483
|
-
(spec.observations || []).forEach((o) => {
|
|
484
|
-
facts.push({ pred: 'isA', s: o.id, o: o.type || 'Observation', _base: true });
|
|
485
|
-
if (o.about) facts.push({ pred: 'about', s: o.id, o: o.about, _base: true });
|
|
486
|
-
if (spec.assets[o.about]?.type === 'Patient') {
|
|
487
|
-
facts.push({ pred: 'aboutPatient', s: o.id, o: o.about, _base: true });
|
|
488
|
-
}
|
|
489
|
-
if (o.codeSystem) facts.push({ pred: 'codeSystem', s: o.id, o: o.codeSystem, _base: true });
|
|
490
|
-
if (o.code) facts.push({ pred: 'code', s: o.id, o: o.code, _base: true });
|
|
491
|
-
if (o.category) facts.push({ pred: 'category', s: o.id, o: o.category, _base: true });
|
|
492
|
-
if (o.valueFlag) facts.push({ pred: 'valueFlag', s: o.id, o: o.valueFlag, _base: true });
|
|
493
|
-
if (o.sharePurpose) facts.push({ pred: 'sharePurpose', s: o.id, o: o.sharePurpose, _base: true });
|
|
494
|
-
if (o.shareOrg) facts.push({ pred: 'shareOrg', s: o.id, o: o.shareOrg, _base: true });
|
|
495
|
-
});
|
|
496
|
-
return facts;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function indexFacts(facts) {
|
|
500
|
-
const byPred = new Map();
|
|
501
|
-
for (const f of facts) {
|
|
502
|
-
if (!byPred.has(f.pred)) byPred.set(f.pred, []);
|
|
503
|
-
byPred.get(f.pred).push(f);
|
|
504
|
-
}
|
|
505
|
-
return { byPred };
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function isVar(x) {
|
|
509
|
-
return typeof x === 'string' && x.startsWith('?');
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function unifyAtomWithFact(atom, f, env) {
|
|
513
|
-
const out = { ...env };
|
|
514
|
-
const slots = ['pred', 's', 'o'];
|
|
515
|
-
for (const slot of slots) {
|
|
516
|
-
const val = atom[slot];
|
|
517
|
-
const factVal = f[slot] ?? (slot === 'pred' ? f.pred : slot === 's' ? f.s : f.o);
|
|
518
|
-
if (isVar(val)) {
|
|
519
|
-
if (out[val] !== undefined && out[val] !== factVal) return null;
|
|
520
|
-
out[val] = factVal;
|
|
521
|
-
} else {
|
|
522
|
-
if (val !== factVal) return null;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
return out;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function matchPositives(rule, factsIdx, env = {}, i = 0) {
|
|
529
|
-
if (i >= rule.if.length) return [env];
|
|
530
|
-
const atom = rule.if[i];
|
|
531
|
-
if (atom.not) return matchPositives(rule, factsIdx, env, i + 1);
|
|
532
|
-
const candidates = factsIdx.byPred.get(atom.pred) || [];
|
|
533
|
-
const out = [];
|
|
534
|
-
for (const f of candidates) {
|
|
535
|
-
const env2 = unifyAtomWithFact(atom, f, env);
|
|
536
|
-
if (env2) out.push(...matchPositives(rule, factsIdx, env2, i + 1));
|
|
537
|
-
}
|
|
538
|
-
return out;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function negHolds(atom, factsIdx, env) {
|
|
542
|
-
const candidates = factsIdx.byPred.get(atom.pred) || [];
|
|
543
|
-
for (const f of candidates) {
|
|
544
|
-
if (unifyAtomWithFact(atom, f, env)) return true;
|
|
545
|
-
}
|
|
546
|
-
return false;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function substitute(t, env) {
|
|
550
|
-
const sub = (v) => (isVar(v) ? env[v] : v);
|
|
551
|
-
return { pred: t.pred, s: sub(t.s), o: sub(t.o) };
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// ---- Priority handling (Blocked > High > Normal > Low)
|
|
555
|
-
const PRIORITY_RANK = { Blocked: 4, High: 3, Normal: 2, Low: 1 };
|
|
556
|
-
|
|
557
|
-
function bestPriorities(facts) {
|
|
558
|
-
const best = new Map();
|
|
559
|
-
for (const f of facts) {
|
|
560
|
-
if (f.pred !== 'priority') continue;
|
|
561
|
-
const cur = best.get(f.s);
|
|
562
|
-
if (!cur || (PRIORITY_RANK[f.o] || 0) > (PRIORITY_RANK[cur] || 0)) {
|
|
563
|
-
best.set(f.s, f.o);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
return best;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function derive(data, policies) {
|
|
570
|
-
let facts = buildFactsFromData(data);
|
|
571
|
-
const baseSet = new Set(facts.map(key));
|
|
572
|
-
const factSet = new Set(baseSet);
|
|
573
|
-
const factsIdx = indexFacts(facts);
|
|
574
|
-
const proofs = new Map();
|
|
575
|
-
|
|
576
|
-
let changed = true,
|
|
577
|
-
guard = 0;
|
|
578
|
-
while (changed && guard++ < 200) {
|
|
579
|
-
changed = false;
|
|
580
|
-
for (const rule of policies) {
|
|
581
|
-
const posBindings = matchPositives(rule, factsIdx, {}, 0);
|
|
582
|
-
for (const env of posBindings) {
|
|
583
|
-
const negs = rule.if.filter((a) => a.not);
|
|
584
|
-
let ok = true,
|
|
585
|
-
negUsed = [];
|
|
586
|
-
for (const n of negs) {
|
|
587
|
-
const nSub = substitute(n, env);
|
|
588
|
-
if (negHolds(nSub, factsIdx, env)) {
|
|
589
|
-
ok = false;
|
|
590
|
-
break;
|
|
591
|
-
}
|
|
592
|
-
negUsed.push(nSub);
|
|
593
|
-
}
|
|
594
|
-
if (!ok) continue;
|
|
595
|
-
|
|
596
|
-
for (const t of rule.then) {
|
|
597
|
-
const concl = substitute(t, env);
|
|
598
|
-
const k = key(concl);
|
|
599
|
-
if (!factSet.has(k)) {
|
|
600
|
-
facts.push({ ...concl, _base: false });
|
|
601
|
-
factSet.add(k);
|
|
602
|
-
changed = true;
|
|
603
|
-
if (!factsIdx.byPred.has(concl.pred)) factsIdx.byPred.set(concl.pred, []);
|
|
604
|
-
factsIdx.byPred.get(concl.pred).push({ ...concl, _base: false });
|
|
605
|
-
const exp = buildExplanation(rule, env, negUsed);
|
|
606
|
-
proofs.set(k, exp);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const derived = facts.filter((f) => !f._base);
|
|
614
|
-
|
|
615
|
-
// Only keep strongest priority per patient in Answer/Reason
|
|
616
|
-
const best = bestPriorities(facts);
|
|
617
|
-
const derivedFiltered = derived.filter((f) => f.pred !== 'priority' || best.get(f.s) === f.o);
|
|
618
|
-
|
|
619
|
-
const answerLines = formatFacts(derivedFiltered);
|
|
620
|
-
const reasonLines = [];
|
|
621
|
-
for (const f of derivedFiltered) {
|
|
622
|
-
const k = key(f);
|
|
623
|
-
const exp = proofs.get(k);
|
|
624
|
-
if (exp) reasonLines.push(`• ${exp} ⇒ therefore ${prettyFact(f)}.`);
|
|
625
|
-
else reasonLines.push(`• Derived ${prettyFact(f)}.`);
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
return { facts, derived, answerText: answerLines.join('\n'), reasonText: reasonLines.join('\n') };
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
function buildExplanation(rule, env, negUsed) {
|
|
632
|
-
const fill = (s) => s.replace(/\?[A-Za-z0-9_]+/g, (m) => env[m] ?? m);
|
|
633
|
-
let text = fill(rule.explain || `Applied ${rule.id}`);
|
|
634
|
-
if (negUsed && negUsed.length) {
|
|
635
|
-
const negBits = negUsed.map((n) => `no fact ${n.pred}(${n.s}, ${n.o})`).join(' and ');
|
|
636
|
-
text += `, and ${negBits}`;
|
|
637
|
-
}
|
|
638
|
-
return text;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function prettyFact(f) {
|
|
642
|
-
const tri = {
|
|
643
|
-
isA: (s, o) => `${s} ∈ ${o}`,
|
|
644
|
-
subClass: (s, o) => `${s} ⊑ ${o}`,
|
|
645
|
-
partOf: (s, o) => `${s} partOf ${o}`,
|
|
646
|
-
about: (s, o) => `${s} about ${o}`,
|
|
647
|
-
aboutPatient: (s, o) => `${s} aboutPatient ${o}`,
|
|
648
|
-
consentOfPatient: (s, o) => `${s} consentOfPatient ${o}`,
|
|
649
|
-
consentScope: (s, o) => `${s} consentScope ${o}`,
|
|
650
|
-
consentStatus: (s, o) => `${s} consentStatus ${o}`,
|
|
651
|
-
consentPermittedOrg: (s, o) => `${s} consentPermittedOrg ${o}`,
|
|
652
|
-
hasActiveResearchConsentFor: (s, o) => `${s} hasActiveResearchConsentFor ${o}`,
|
|
653
|
-
hasActiveTreatmentConsent: (s, o) => `${s} hasActiveTreatmentConsent ${o}`,
|
|
654
|
-
sharePurpose: (s, o) => `${s} sharePurpose ${o}`,
|
|
655
|
-
shareOrg: (s, o) => `${s} shareOrg ${o}`,
|
|
656
|
-
codeSystem: (s, o) => `${s} codeSystem ${o}`,
|
|
657
|
-
code: (s, o) => `${s} code ${o}`,
|
|
658
|
-
category: (s, o) => `${s} category ${o}`,
|
|
659
|
-
valueFlag: (s, o) => `${s} valueFlag ${o}`,
|
|
660
|
-
requiresTask: (s, o) => `${s} requires ${o}`,
|
|
661
|
-
priority: (s, o) => `${s} priority ${o}`,
|
|
662
|
-
};
|
|
663
|
-
const fn = tri[f.pred] || ((s, o) => `${s} ${f.pred} ${o}`);
|
|
664
|
-
return fn(f.s, f.o);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
function formatFacts(facts) {
|
|
668
|
-
const groups = {};
|
|
669
|
-
for (const f of facts) {
|
|
670
|
-
(groups[f.pred] ||= []).push(f);
|
|
671
|
-
}
|
|
672
|
-
const order = [
|
|
673
|
-
'isA',
|
|
674
|
-
'subClass',
|
|
675
|
-
'partOf',
|
|
676
|
-
'about',
|
|
677
|
-
'aboutPatient',
|
|
678
|
-
'consentOfPatient',
|
|
679
|
-
'consentScope',
|
|
680
|
-
'consentStatus',
|
|
681
|
-
'consentPermittedOrg',
|
|
682
|
-
'sharePurpose',
|
|
683
|
-
'shareOrg',
|
|
684
|
-
'codeSystem',
|
|
685
|
-
'code',
|
|
686
|
-
'category',
|
|
687
|
-
'valueFlag',
|
|
688
|
-
'hasActiveResearchConsentFor',
|
|
689
|
-
'hasActiveTreatmentConsent',
|
|
690
|
-
'requiresTask',
|
|
691
|
-
'priority',
|
|
692
|
-
];
|
|
693
|
-
const lines = [];
|
|
694
|
-
for (const pred of order) {
|
|
695
|
-
if (!groups[pred]) continue;
|
|
696
|
-
const sorted = groups[pred].slice().sort((a, b) => (a.s + a.o).localeCompare(b.s + b.o));
|
|
697
|
-
for (const f of sorted) lines.push(prettyFact(f));
|
|
698
|
-
}
|
|
699
|
-
for (const pred of Object.keys(groups)) {
|
|
700
|
-
if (order.includes(pred)) continue;
|
|
701
|
-
const sorted = groups[pred].slice().sort((a, b) => (a.s + a.o).localeCompare(b.s + b.o));
|
|
702
|
-
for (const f of sorted) lines.push(prettyFact(f));
|
|
703
|
-
}
|
|
704
|
-
return lines;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// Checks
|
|
708
|
-
function runChecks(facts, checksSpec) {
|
|
709
|
-
const factSet = new Set(facts.map(key));
|
|
710
|
-
const out = [];
|
|
711
|
-
for (let i = 0; i < checksSpec.length; i++) {
|
|
712
|
-
const chk = checksSpec[i];
|
|
713
|
-
const k = key(chk.pattern);
|
|
714
|
-
out.push({ i: i + 1, name: chk.name, passed: factSet.has(k) });
|
|
715
|
-
}
|
|
716
|
-
return out;
|
|
717
|
-
}
|
|
718
|
-
function renderChecks(results) {
|
|
719
|
-
if (!results.length) return '(no checks)';
|
|
720
|
-
const lines = [];
|
|
721
|
-
for (const r of results) {
|
|
722
|
-
lines.push(`${r.passed ? '✅' : '❌'} ${String(r.i).padStart(2, ' ')} — ${r.name}`);
|
|
723
|
-
}
|
|
724
|
-
const passCt = results.filter((r) => r.passed).length;
|
|
725
|
-
lines.push(`\nSummary: ${passCt}/${results.length} PASS`);
|
|
726
|
-
return lines.join('\n');
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// Summary
|
|
730
|
-
function summarizePatients(facts) {
|
|
731
|
-
const PRIORITY_RANK = { Blocked: 4, High: 3, Normal: 2, Low: 1 };
|
|
732
|
-
const tasksByP = new Map();
|
|
733
|
-
for (const f of facts) {
|
|
734
|
-
if (f.pred === 'requiresTask') {
|
|
735
|
-
if (!tasksByP.has(f.s)) tasksByP.set(f.s, new Set());
|
|
736
|
-
tasksByP.get(f.s).add(f.o);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
// best priority
|
|
740
|
-
const best = new Map();
|
|
741
|
-
for (const f of facts) {
|
|
742
|
-
if (f.pred !== 'priority') continue;
|
|
743
|
-
const cur = best.get(f.s);
|
|
744
|
-
if (!cur || (PRIORITY_RANK[f.o] || 0) > (PRIORITY_RANK[cur] || 0)) best.set(f.s, f.o);
|
|
745
|
-
}
|
|
746
|
-
const patients = new Set([...tasksByP.keys(), ...best.keys()]);
|
|
747
|
-
if (!patients.size) return '(no derived tasks/priority)';
|
|
748
|
-
const lines = [];
|
|
749
|
-
for (const p of [...patients].sort()) {
|
|
750
|
-
const pr = best.get(p) || 'Normal';
|
|
751
|
-
const tasks = [...(tasksByP.get(p) || [])].sort().join(', ');
|
|
752
|
-
lines.push(`${p}: priority ${pr}${tasks ? ' — tasks: ' + tasks : ''}`);
|
|
753
|
-
}
|
|
754
|
-
return lines.join('\n');
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// Orchestration
|
|
758
|
-
function parseJSON(text, label) {
|
|
759
|
-
try {
|
|
760
|
-
return JSON.parse(text);
|
|
761
|
-
} catch (e) {
|
|
762
|
-
throw new Error(`${label} JSON error: ${e.message}`);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
function toPolicies(spec) {
|
|
766
|
-
return spec.map((r) => ({
|
|
767
|
-
id: r.id || '(unnamed)',
|
|
768
|
-
if: r.if || [],
|
|
769
|
-
then: r.then || [],
|
|
770
|
-
explain: r.explain || '',
|
|
771
|
-
}));
|
|
772
|
-
}
|
|
773
|
-
function buildDataObj(spec) {
|
|
774
|
-
return spec;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
async function runARC() {
|
|
778
|
-
els.status.textContent = 'Parsing JSON…';
|
|
779
|
-
els.answer.textContent = els.reason.textContent = els.checks.textContent = 'computing…';
|
|
780
|
-
els.summary.textContent = '(run to populate)';
|
|
781
|
-
els.diag.textContent = '';
|
|
782
|
-
try {
|
|
783
|
-
const dataSpec = parseJSON(els.dataTA.value, 'Data');
|
|
784
|
-
const polSpec = parseJSON(els.policyTA.value, 'Policies');
|
|
785
|
-
const checksSpec = parseJSON(els.checksTA.value, 'Checks');
|
|
786
|
-
|
|
787
|
-
// Update checks count badges
|
|
788
|
-
els.checksCount.textContent = String(checksSpec.length);
|
|
789
|
-
if (els.checksCountInline) els.checksCountInline.textContent = String(checksSpec.length);
|
|
790
|
-
|
|
791
|
-
const data = buildDataObj(dataSpec);
|
|
792
|
-
const policies = toPolicies(polSpec);
|
|
793
|
-
|
|
794
|
-
els.status.textContent = 'Reasoning…';
|
|
795
|
-
const { facts, derived, answerText, reasonText } = derive(data, policies);
|
|
796
|
-
|
|
797
|
-
els.answer.textContent = answerText || '(no new derivations)';
|
|
798
|
-
els.reason.textContent = reasonText || '(no explanations available)';
|
|
799
|
-
|
|
800
|
-
els.status.textContent = 'Running checks…';
|
|
801
|
-
const checkResults = runChecks(facts, checksSpec);
|
|
802
|
-
els.checks.textContent = renderChecks(checkResults);
|
|
803
|
-
|
|
804
|
-
els.summary.textContent = summarizePatients(facts);
|
|
805
|
-
|
|
806
|
-
els.status.textContent = 'Done.';
|
|
807
|
-
} catch (e) {
|
|
808
|
-
console.error(e);
|
|
809
|
-
els.status.textContent = 'Error';
|
|
810
|
-
els.answer.textContent = '(failed)';
|
|
811
|
-
els.reason.textContent = '(failed)';
|
|
812
|
-
els.checks.textContent = '(failed)';
|
|
813
|
-
els.summary.textContent = '(failed)';
|
|
814
|
-
els.diag.textContent = e.message;
|
|
815
|
-
}
|
|
816
|
-
// re-auto-size textareas after run
|
|
817
|
-
autoResize(els.dataTA);
|
|
818
|
-
autoResize(els.policyTA);
|
|
819
|
-
autoResize(els.checksTA);
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
function showReasonOnly() {
|
|
823
|
-
runARC().then(() => {
|
|
824
|
-
window.scrollTo({ top: $('reason').getBoundingClientRect().top + window.scrollY - 12, behavior: 'smooth' });
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
els.runBtn.addEventListener('click', runARC);
|
|
829
|
-
els.reasonBtn.addEventListener('click', showReasonOnly);
|
|
830
|
-
window.addEventListener('DOMContentLoaded', runARC);
|
|
831
|
-
</script>
|
|
832
|
-
</body>
|
|
833
|
-
</html>
|