eyeling 1.22.0 → 1.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/arcling/README.md +5 -4
- package/examples/arcling/calidor/calidor.data.json +79 -0
- package/examples/arcling/calidor/calidor.expected.json +94 -0
- package/examples/arcling/calidor/calidor.model.go +612 -0
- package/examples/arcling/calidor/calidor.spec.md +166 -0
- package/examples/arcling/delfour/delfour.data.json +0 -1
- package/examples/arcling/delfour/delfour.model.go +18 -0
- package/examples/arcling/flandor/flandor.data.json +0 -1
- package/examples/arcling/flandor/flandor.model.go +25 -0
- package/examples/arcling/medior/medior.data.json +0 -1
- package/examples/arcling/medior/medior.model.go +26 -0
- package/examples/calidor.n3 +500 -0
- package/examples/output/calidor.n3 +29 -0
- package/package.json +1 -1
- package/test/arcling.test.js +50 -15
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Calidor — ARC Specification
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
This document is the normative specification for the Calidor case. The file `calidor.model.go` is the reference Go implementation of these clauses. The file `calidor.data.json` is the instance evaluated in this bundle. The file `calidor.expected.json` is the conformance vector for that instance.
|
|
6
|
+
|
|
7
|
+
## Insight Economy context
|
|
8
|
+
|
|
9
|
+
This case models municipal heatwave support. A household gateway observes local indoor heat stress, local vulnerability signals, and local prepaid-energy stress. Those raw details remain local. The system shares only a narrow, expiring insight that the household qualifies for priority cooling support during the current heat-alert window.
|
|
10
|
+
|
|
11
|
+
The city may use that insight for heatwave response. It may not reuse it for unrelated purposes such as tenant screening.
|
|
12
|
+
|
|
13
|
+
## Conventions
|
|
14
|
+
|
|
15
|
+
- “iff” means “if and only if”.
|
|
16
|
+
- A clause identifier such as `R1` or `G2` is normative.
|
|
17
|
+
- A conforming implementation may be written in any language, but it shall produce the same derived values and pass/fail outcomes for the supplied instance.
|
|
18
|
+
- The reference implementation in this bundle is written in Go.
|
|
19
|
+
- Input validation is part of the reference model. A malformed instance shall fail before evaluation.
|
|
20
|
+
|
|
21
|
+
## Vocabulary
|
|
22
|
+
|
|
23
|
+
**V1. Heat alert** is the municipal emergency context.
|
|
24
|
+
|
|
25
|
+
**V2. Unsafe indoor heat** is sustained indoor temperature at or above the configured threshold.
|
|
26
|
+
|
|
27
|
+
**V3. Vulnerability presence** is the existence of at least one local heat-sensitivity or mobility-related flag.
|
|
28
|
+
|
|
29
|
+
**V4. Energy constraint** is insufficient prepaid energy credit to sustain cooling use.
|
|
30
|
+
|
|
31
|
+
**V5. Support package** is a municipal assistance option with a cost and a set of capabilities.
|
|
32
|
+
|
|
33
|
+
**V6. Insight envelope** is the ordered pair `(insight, policy)` together with integrity metadata.
|
|
34
|
+
|
|
35
|
+
## Input instance
|
|
36
|
+
|
|
37
|
+
**I1.** The municipality is `Calidor`.
|
|
38
|
+
|
|
39
|
+
**I2.** The current heat alert level is `4`.
|
|
40
|
+
|
|
41
|
+
**I3.** The alert threshold is `3`.
|
|
42
|
+
|
|
43
|
+
**I4.** The current indoor temperature is `31.4` °C.
|
|
44
|
+
|
|
45
|
+
**I5.** Unsafe indoor heat requires at least `30.0` °C for at least `6` hours.
|
|
46
|
+
|
|
47
|
+
**I6.** The household has local vulnerability flags.
|
|
48
|
+
|
|
49
|
+
**I7.** Remaining prepaid energy credit is `3.2` EUR.
|
|
50
|
+
|
|
51
|
+
**I8.** Energy constraint holds at or below `5.0` EUR.
|
|
52
|
+
|
|
53
|
+
**I9.** Priority cooling support requires at least `3` active needs.
|
|
54
|
+
|
|
55
|
+
**I10.** The support catalog is the one listed in `calidor.data.json`.
|
|
56
|
+
|
|
57
|
+
## Derivation clauses
|
|
58
|
+
|
|
59
|
+
**R1. HeatAlertActive.** `HeatAlertActive` holds iff `currentAlertLevel ≥ alertLevelAtLeast`.
|
|
60
|
+
|
|
61
|
+
**R2. UnsafeIndoorHeat.** `UnsafeIndoorHeat` holds iff:
|
|
62
|
+
|
|
63
|
+
1. `currentIndoorTempC ≥ indoorTempCAtLeast`; and
|
|
64
|
+
2. `hoursAtOrAboveThreshold ≥ hoursAtOrAboveThresholdAtLeast`.
|
|
65
|
+
|
|
66
|
+
**R3. VulnerabilityPresent.** `VulnerabilityPresent` holds iff the local vulnerability flag list is non-empty.
|
|
67
|
+
|
|
68
|
+
**R4. EnergyConstraint.** `EnergyConstraint` holds iff `remainingPrepaidCreditEur ≤ energyCreditEurAtMost`.
|
|
69
|
+
|
|
70
|
+
**R5. ActiveNeedCount.** `ActiveNeedCount` is the number of true predicates among:
|
|
71
|
+
|
|
72
|
+
- `HeatAlertActive`
|
|
73
|
+
- `UnsafeIndoorHeat`
|
|
74
|
+
- `VulnerabilityPresent`
|
|
75
|
+
- `EnergyConstraint`
|
|
76
|
+
|
|
77
|
+
**R6. PriorityCoolingSupportNeeded.** `PriorityCoolingSupportNeeded` holds iff `ActiveNeedCount ≥ minimumActiveNeedCount`.
|
|
78
|
+
|
|
79
|
+
**R7. RequiredCapabilities.** The required capability set is formed as follows:
|
|
80
|
+
|
|
81
|
+
1. if `HeatAlertActive` and `UnsafeIndoorHeat`, include `cooling_kit`;
|
|
82
|
+
2. if `VulnerabilityPresent`, include `welfare_check` and `transport`;
|
|
83
|
+
3. if `EnergyConstraint`, include `bill_credit`.
|
|
84
|
+
|
|
85
|
+
The resulting set is sorted lexically for reporting.
|
|
86
|
+
|
|
87
|
+
**R8. EligiblePackage(p).** For a package `p`, `EligiblePackage(p)` holds iff:
|
|
88
|
+
|
|
89
|
+
1. `p.costEur ≤ maxPackageCostEur`; and
|
|
90
|
+
2. `p.capabilities` cover every entry in `RequiredCapabilities`.
|
|
91
|
+
|
|
92
|
+
**R9. RecommendedPackage.** `RecommendedPackage` is the eligible package with minimum `costEur`, breaking ties lexically by `id`.
|
|
93
|
+
|
|
94
|
+
## Governance clauses
|
|
95
|
+
|
|
96
|
+
**G1. AuthorizedUse.** `AuthorizedUse` holds iff:
|
|
97
|
+
|
|
98
|
+
1. the requested action is `odrl:use`;
|
|
99
|
+
2. the requested purpose is `heatwave_response`; and
|
|
100
|
+
3. the authorization time is not later than the expiry time.
|
|
101
|
+
|
|
102
|
+
**G2. TenantScreeningProhibited.** `TenantScreeningProhibited` holds iff the policy prohibits distribution for purpose `tenant_screening`.
|
|
103
|
+
|
|
104
|
+
**G3. DutyTimely.** `DutyTimely` holds iff the duty-performance time is not later than the expiry time.
|
|
105
|
+
|
|
106
|
+
## Integrity and minimization clauses
|
|
107
|
+
|
|
108
|
+
**M1. CanonicalEnvelope.** The canonical envelope string is the JSON serialization of the ordered pair `(insight, policy)` with keys emitted in this exact sequence:
|
|
109
|
+
|
|
110
|
+
- insight: `createdAt`, `expiresAt`, `id`, `metric`, `municipality`, `scopeDevice`, `scopeEvent`, `supportPolicy`, `threshold`, `type`
|
|
111
|
+
- policy: `duty`, `permission`, `profile`, `prohibition`, `type`
|
|
112
|
+
|
|
113
|
+
For this case, `threshold` is serialized lexically as `3.0` rather than `3`, because the integrity vector is defined over those exact envelope bytes.
|
|
114
|
+
|
|
115
|
+
**M2. PayloadHashMatches.** `PayloadHashMatches` holds iff the model-computed SHA-256 of `CanonicalEnvelope` equals the expected SHA-256 value recorded in the conformance vector.
|
|
116
|
+
|
|
117
|
+
**M3. SignatureVerifies.** `SignatureVerifies` holds iff the model-computed HMAC-SHA-256 of `CanonicalEnvelope` equals the expected HMAC value recorded in the conformance vector.
|
|
118
|
+
|
|
119
|
+
**M4. MinimizationRespected.** `MinimizationRespected` holds iff the serialized insight contains none of the forbidden terms:
|
|
120
|
+
|
|
121
|
+
- `heat_sensitive_condition`
|
|
122
|
+
- `mobility_limitation`
|
|
123
|
+
- `credit`
|
|
124
|
+
- `meter_trace`
|
|
125
|
+
|
|
126
|
+
**M5. ScopeComplete.** `ScopeComplete` holds iff the insight contains `scopeDevice`, `scopeEvent`, and `expiresAt`.
|
|
127
|
+
|
|
128
|
+
## Output contract
|
|
129
|
+
|
|
130
|
+
**O1. Answer.** A conforming renderer shall expose:
|
|
131
|
+
|
|
132
|
+
- the main recommendation sentence
|
|
133
|
+
- recommended package
|
|
134
|
+
- required capabilities
|
|
135
|
+
- payload hash
|
|
136
|
+
- envelope HMAC
|
|
137
|
+
|
|
138
|
+
**O2. Reason Why.** A conforming renderer shall explain that raw household heat, vulnerability, and prepaid-energy details remain local and that only a narrow heatwave-response insight is shared.
|
|
139
|
+
|
|
140
|
+
**O3. Check.** A conforming renderer shall expose a named yes/no or PASS/FAIL outcome for each of:
|
|
141
|
+
|
|
142
|
+
- `signatureVerifies`
|
|
143
|
+
- `payloadHashMatches`
|
|
144
|
+
- `minimizationRespected`
|
|
145
|
+
- `scopeComplete`
|
|
146
|
+
- `authorizationAllowed`
|
|
147
|
+
- `heatAlertActive`
|
|
148
|
+
- `unsafeIndoorHeat`
|
|
149
|
+
- `priorityCoolingSupportNeeded`
|
|
150
|
+
- `recommendedPackageEligible`
|
|
151
|
+
- `dutyTimingConsistent`
|
|
152
|
+
- `tenantScreeningProhibited`
|
|
153
|
+
|
|
154
|
+
## Reference outcome for this instance
|
|
155
|
+
|
|
156
|
+
For the supplied instance:
|
|
157
|
+
|
|
158
|
+
- `HeatAlertActive = true`
|
|
159
|
+
- `UnsafeIndoorHeat = true`
|
|
160
|
+
- `VulnerabilityPresent = true`
|
|
161
|
+
- `EnergyConstraint = true`
|
|
162
|
+
- `ActiveNeedCount = 4`
|
|
163
|
+
- `PriorityCoolingSupportNeeded = true`
|
|
164
|
+
- `RecommendedPackage = "Calidor Priority Cooling Bundle"`
|
|
165
|
+
|
|
166
|
+
The expected ARC report and integrity values are recorded in `calidor.expected.json`.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
package main
|
|
2
2
|
|
|
3
|
+
// Delfour is a reference Arcling model written as a small CLI program.
|
|
4
|
+
// It reads delfour.data.json, derives the neutral shopping insight,
|
|
5
|
+
// computes the canonical envelope/hash/HMAC values, and emits either
|
|
6
|
+
// ARC text or a JSON result object.
|
|
7
|
+
|
|
3
8
|
import (
|
|
4
9
|
"crypto/hmac"
|
|
5
10
|
"crypto/sha256"
|
|
@@ -13,6 +18,7 @@ import (
|
|
|
13
18
|
"time"
|
|
14
19
|
)
|
|
15
20
|
|
|
21
|
+
// Data mirrors the input instance shape from delfour.data.json.
|
|
16
22
|
type Data struct {
|
|
17
23
|
CaseName string `json:"caseName"`
|
|
18
24
|
Retailer string `json:"retailer"`
|
|
@@ -77,6 +83,7 @@ type Integrity struct {
|
|
|
77
83
|
VerificationMode string `json:"verificationMode"`
|
|
78
84
|
}
|
|
79
85
|
|
|
86
|
+
// Insight is the minimized payload shared with the retailer.
|
|
80
87
|
type Insight struct {
|
|
81
88
|
CreatedAt string `json:"createdAt"`
|
|
82
89
|
ExpiresAt string `json:"expiresAt"`
|
|
@@ -191,6 +198,7 @@ func readJSON(path string) (Data, error) {
|
|
|
191
198
|
return data, err
|
|
192
199
|
}
|
|
193
200
|
|
|
201
|
+
// validate performs the structural checks that used to live in JSON Schema.
|
|
194
202
|
func validate(data Data) error {
|
|
195
203
|
if err := must(data.CaseName != "", "caseName is required"); err != nil {
|
|
196
204
|
return err
|
|
@@ -234,6 +242,7 @@ func validate(data Data) error {
|
|
|
234
242
|
return nil
|
|
235
243
|
}
|
|
236
244
|
|
|
245
|
+
// parseTime accepts RFC3339Nano timestamps from the case instance.
|
|
237
246
|
func parseTime(s string) time.Time {
|
|
238
247
|
t, err := time.Parse(time.RFC3339Nano, s)
|
|
239
248
|
if err != nil {
|
|
@@ -242,6 +251,7 @@ func parseTime(s string) time.Time {
|
|
|
242
251
|
return t
|
|
243
252
|
}
|
|
244
253
|
|
|
254
|
+
// findProduct resolves the scanned or recommended product by its catalog id.
|
|
245
255
|
func findProduct(data Data, id string) *Product {
|
|
246
256
|
for i := range data.Catalog {
|
|
247
257
|
if data.Catalog[i].ID == id {
|
|
@@ -251,6 +261,7 @@ func findProduct(data Data, id string) *Product {
|
|
|
251
261
|
return nil
|
|
252
262
|
}
|
|
253
263
|
|
|
264
|
+
// deriveInsight strips the household condition down to the neutral shopping insight.
|
|
254
265
|
func deriveInsight(data Data) Insight {
|
|
255
266
|
return Insight{
|
|
256
267
|
CreatedAt: data.Timestamps.CreatedAt,
|
|
@@ -266,6 +277,7 @@ func deriveInsight(data Data) Insight {
|
|
|
266
277
|
}
|
|
267
278
|
}
|
|
268
279
|
|
|
280
|
+
// derivePolicy builds the companion ODRL-style policy used for governance checks.
|
|
269
281
|
func derivePolicy(data Data) Policy {
|
|
270
282
|
return Policy{
|
|
271
283
|
Duty: Duty{
|
|
@@ -299,6 +311,8 @@ func derivePolicy(data Data) Policy {
|
|
|
299
311
|
}
|
|
300
312
|
}
|
|
301
313
|
|
|
314
|
+
// canonicalEnvelope returns the exact byte string used for the integrity vector.
|
|
315
|
+
// The field order and the lexical form of threshold (10.0) are intentional.
|
|
302
316
|
func canonicalEnvelope(insight Insight, policy Policy) string {
|
|
303
317
|
return fmt.Sprintf(
|
|
304
318
|
"{\"insight\":{\"createdAt\":\"%s\",\"expiresAt\":\"%s\",\"id\":\"%s\",\"metric\":\"%s\",\"retailer\":\"%s\",\"scopeDevice\":\"%s\",\"scopeEvent\":\"%s\",\"suggestionPolicy\":\"%s\",\"threshold\":10.0,\"type\":\"%s\"},\"policy\":{\"duty\":{\"action\":\"%s\",\"constraint\":{\"leftOperand\":\"%s\",\"operator\":\"%s\",\"rightOperand\":\"%s\"}},\"permission\":{\"action\":\"%s\",\"constraint\":{\"leftOperand\":\"%s\",\"operator\":\"%s\",\"rightOperand\":\"%s\"},\"target\":\"%s\"},\"profile\":\"%s\",\"prohibition\":{\"action\":\"%s\",\"constraint\":{\"leftOperand\":\"%s\",\"operator\":\"%s\",\"rightOperand\":\"%s\"},\"target\":\"%s\"},\"type\":\"%s\"}}",
|
|
@@ -348,6 +362,8 @@ func yesNo(v bool) string {
|
|
|
348
362
|
return "no"
|
|
349
363
|
}
|
|
350
364
|
|
|
365
|
+
// evaluate runs the full Arcling pipeline: derive facts, select the recommendation,
|
|
366
|
+
// build the envelope, verify integrity values, and render the final report.
|
|
351
367
|
func evaluate(data Data) (Result, error) {
|
|
352
368
|
var result Result
|
|
353
369
|
if err := validate(data); err != nil {
|
|
@@ -508,6 +524,8 @@ func evaluate(data Data) (Result, error) {
|
|
|
508
524
|
return result, nil
|
|
509
525
|
}
|
|
510
526
|
|
|
527
|
+
// main is a tiny CLI wrapper around evaluate. It defaults to delfour.data.json,
|
|
528
|
+
// prints ARC text, and switches to JSON output when --json is supplied.
|
|
511
529
|
func main() {
|
|
512
530
|
inputPath := "delfour.data.json"
|
|
513
531
|
jsonMode := false
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "./flandor.instance.schema.json",
|
|
3
2
|
"caseName": "Flandor",
|
|
4
3
|
"region": "Flanders",
|
|
5
4
|
"question": "Is the Flemish Economic Resilience Board allowed to use a neutral macro-economic insight for regional stabilization, and if so which package should it activate for Flanders?",
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
package main
|
|
2
2
|
|
|
3
|
+
// Flandor is a reference Arcling model for the regional retooling-pulse case.
|
|
4
|
+
// It evaluates whether a region needs intervention, selects the lowest-cost
|
|
5
|
+
// eligible package, and emits ARC text or a JSON report.
|
|
6
|
+
|
|
3
7
|
import (
|
|
4
8
|
"crypto/hmac"
|
|
5
9
|
"crypto/sha256"
|
|
@@ -13,6 +17,7 @@ import (
|
|
|
13
17
|
"time"
|
|
14
18
|
)
|
|
15
19
|
|
|
20
|
+
// Data mirrors the input instance shape from flandor.data.json.
|
|
16
21
|
type Data struct {
|
|
17
22
|
CaseName string `json:"caseName"`
|
|
18
23
|
Region string `json:"region"`
|
|
@@ -233,6 +238,8 @@ func parseTime(value string) time.Time {
|
|
|
233
238
|
return t
|
|
234
239
|
}
|
|
235
240
|
|
|
241
|
+
// stableStringify recursively sorts map keys so the canonical envelope is
|
|
242
|
+
// deterministic across runs and implementations.
|
|
236
243
|
func stableStringify(value any) string {
|
|
237
244
|
switch v := value.(type) {
|
|
238
245
|
case nil:
|
|
@@ -260,6 +267,8 @@ func stableStringify(value any) string {
|
|
|
260
267
|
}
|
|
261
268
|
}
|
|
262
269
|
|
|
270
|
+
// canonicalValue converts typed structs into generic JSON-like values before
|
|
271
|
+
// stable stringification.
|
|
263
272
|
func canonicalValue(value any) any {
|
|
264
273
|
b, _ := json.Marshal(value)
|
|
265
274
|
var out any
|
|
@@ -267,6 +276,7 @@ func canonicalValue(value any) any {
|
|
|
267
276
|
return out
|
|
268
277
|
}
|
|
269
278
|
|
|
279
|
+
// validateInstance performs the structural checks for the 4-file bundle.
|
|
270
280
|
func validateInstance(data Data) error {
|
|
271
281
|
if err := assertTrue(data.CaseName != "", "caseName is required"); err != nil {
|
|
272
282
|
return err
|
|
@@ -283,6 +293,7 @@ func validateInstance(data Data) error {
|
|
|
283
293
|
return nil
|
|
284
294
|
}
|
|
285
295
|
|
|
296
|
+
// countTrue is used for the active-need threshold logic in the spec.
|
|
286
297
|
func countTrue(values ...bool) int {
|
|
287
298
|
total := 0
|
|
288
299
|
for _, value := range values {
|
|
@@ -293,6 +304,7 @@ func countTrue(values ...bool) int {
|
|
|
293
304
|
return total
|
|
294
305
|
}
|
|
295
306
|
|
|
307
|
+
// The R* helpers map directly to named derivation clauses in flandor.spec.md.
|
|
296
308
|
func clauseR1ExportWeakness(data Data) bool {
|
|
297
309
|
for _, cluster := range data.Signals.Clusters {
|
|
298
310
|
if cluster.ExportOrdersIndex < data.Thresholds.ExportOrdersIndexBelow {
|
|
@@ -318,6 +330,7 @@ func clauseR5NeedsRetoolingPulse(data Data, activeNeedCount int) bool {
|
|
|
318
330
|
return activeNeedCount >= data.Thresholds.ActiveNeedCountAtLeast
|
|
319
331
|
}
|
|
320
332
|
|
|
333
|
+
// deriveInsight produces the minimized regional signal shared with the recipient.
|
|
321
334
|
func deriveInsight(data Data) Insight {
|
|
322
335
|
return Insight{
|
|
323
336
|
CreatedAt: data.Timestamps.CreatedAt,
|
|
@@ -333,6 +346,7 @@ func deriveInsight(data Data) Insight {
|
|
|
333
346
|
}
|
|
334
347
|
}
|
|
335
348
|
|
|
349
|
+
// derivePolicy constructs the usage restrictions paired with the insight.
|
|
336
350
|
func derivePolicy(data Data) Policy {
|
|
337
351
|
return Policy{
|
|
338
352
|
Duty: Duty{
|
|
@@ -366,12 +380,16 @@ func derivePolicy(data Data) Policy {
|
|
|
366
380
|
}
|
|
367
381
|
}
|
|
368
382
|
|
|
383
|
+
// packageCoversAllActiveNeeds checks whether a candidate package addresses every
|
|
384
|
+
// need that is active for this specific instance.
|
|
369
385
|
func packageCoversAllActiveNeeds(pkg Package, exportWeakness, skillsStrain, gridStress bool) bool {
|
|
370
386
|
return (!exportWeakness || pkg.CoversExportWeakness) &&
|
|
371
387
|
(!skillsStrain || pkg.CoversSkillsStrain) &&
|
|
372
388
|
(!gridStress || pkg.CoversGridStress)
|
|
373
389
|
}
|
|
374
390
|
|
|
391
|
+
// clauseS1EligiblePackages filters to packages that both fit the budget and
|
|
392
|
+
// cover the active needs.
|
|
375
393
|
func clauseS1EligiblePackages(data Data, exportWeakness, skillsStrain, gridStress bool) []Package {
|
|
376
394
|
eligible := make([]Package, 0)
|
|
377
395
|
for _, pkg := range data.Packages {
|
|
@@ -383,6 +401,8 @@ func clauseS1EligiblePackages(data Data, exportWeakness, skillsStrain, gridStres
|
|
|
383
401
|
return eligible
|
|
384
402
|
}
|
|
385
403
|
|
|
404
|
+
// clauseS2RecommendedPackage applies the tie-breaker: choose the lowest-cost
|
|
405
|
+
// eligible package after sorting by cost.
|
|
386
406
|
func clauseS2RecommendedPackage(data Data, exportWeakness, skillsStrain, gridStress bool) ([]Package, *Package) {
|
|
387
407
|
eligible := clauseS1EligiblePackages(data, exportWeakness, skillsStrain, gridStress)
|
|
388
408
|
if len(eligible) == 0 {
|
|
@@ -404,6 +424,8 @@ func clauseG3DutyTimely(data Data) bool {
|
|
|
404
424
|
return !parseTime(data.Timestamps.DutyPerformedAt).After(parseTime(data.Timestamps.ExpiresAt))
|
|
405
425
|
}
|
|
406
426
|
|
|
427
|
+
// clauseM1CanonicalEnvelope returns both the structured envelope and the
|
|
428
|
+
// deterministic string hashed/signed by the integrity clauses.
|
|
407
429
|
func clauseM1CanonicalEnvelope(data Data) (Envelope, string) {
|
|
408
430
|
envelope := Envelope{Insight: deriveInsight(data), Policy: derivePolicy(data)}
|
|
409
431
|
return envelope, stableStringify(canonicalValue(envelope))
|
|
@@ -437,6 +459,8 @@ func yesNo(value bool) string {
|
|
|
437
459
|
return "FAIL"
|
|
438
460
|
}
|
|
439
461
|
|
|
462
|
+
// evaluate computes all derived facts, governance checks, integrity values,
|
|
463
|
+
// and presentation fields expected by flandor.expected.json.
|
|
440
464
|
func evaluate(data Data) (Result, error) {
|
|
441
465
|
if err := validateInstance(data); err != nil {
|
|
442
466
|
return Result{}, err
|
|
@@ -593,6 +617,7 @@ func derefIntString(value *int) string {
|
|
|
593
617
|
return fmt.Sprintf("%d", *value)
|
|
594
618
|
}
|
|
595
619
|
|
|
620
|
+
// main is the CLI entry point used by the Arcling test runner.
|
|
596
621
|
func main() {
|
|
597
622
|
inputPath := "flandor.data.json"
|
|
598
623
|
jsonMode := false
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
package main
|
|
2
2
|
|
|
3
|
+
// Medior is a reference Arcling model for the care-continuity bundle case.
|
|
4
|
+
// It derives active needs from coarse signals, selects the lowest-cost eligible
|
|
5
|
+
// package, and emits ARC text or a JSON report.
|
|
6
|
+
|
|
3
7
|
import (
|
|
4
8
|
"crypto/hmac"
|
|
5
9
|
"crypto/sha256"
|
|
@@ -13,6 +17,7 @@ import (
|
|
|
13
17
|
"time"
|
|
14
18
|
)
|
|
15
19
|
|
|
20
|
+
// Data mirrors the input instance shape from medior.data.json.
|
|
16
21
|
type Data struct {
|
|
17
22
|
CaseName string `json:"caseName"`
|
|
18
23
|
Region string `json:"region"`
|
|
@@ -234,6 +239,8 @@ func parseTime(value string) time.Time {
|
|
|
234
239
|
return t
|
|
235
240
|
}
|
|
236
241
|
|
|
242
|
+
// stableStringify recursively sorts map keys so the canonical envelope stays
|
|
243
|
+
// byte-stable across runs and languages.
|
|
237
244
|
func stableStringify(value any) string {
|
|
238
245
|
switch v := value.(type) {
|
|
239
246
|
case nil:
|
|
@@ -261,6 +268,8 @@ func stableStringify(value any) string {
|
|
|
261
268
|
}
|
|
262
269
|
}
|
|
263
270
|
|
|
271
|
+
// canonicalValue converts typed structs into generic JSON-like values before
|
|
272
|
+
// stable stringification.
|
|
264
273
|
func canonicalValue(value any) any {
|
|
265
274
|
b, _ := json.Marshal(value)
|
|
266
275
|
var out any
|
|
@@ -268,6 +277,7 @@ func canonicalValue(value any) any {
|
|
|
268
277
|
return out
|
|
269
278
|
}
|
|
270
279
|
|
|
280
|
+
// validateInstance performs the structural checks for the 4-file bundle.
|
|
271
281
|
func validateInstance(data Data) error {
|
|
272
282
|
if err := assertTrue(data.CaseName != "", "caseName is required"); err != nil {
|
|
273
283
|
return err
|
|
@@ -281,6 +291,7 @@ func validateInstance(data Data) error {
|
|
|
281
291
|
return nil
|
|
282
292
|
}
|
|
283
293
|
|
|
294
|
+
// countTrue is used for the active-need threshold logic in the spec.
|
|
284
295
|
func countTrue(values ...bool) int {
|
|
285
296
|
total := 0
|
|
286
297
|
for _, value := range values {
|
|
@@ -291,6 +302,7 @@ func countTrue(values ...bool) int {
|
|
|
291
302
|
return total
|
|
292
303
|
}
|
|
293
304
|
|
|
305
|
+
// The R* helpers map directly to named derivation clauses in medior.spec.md.
|
|
294
306
|
func clauseR1RenalSafetyConcern(data Data) bool {
|
|
295
307
|
return data.Signals.Lab.Egfr < data.Thresholds.EgfrBelow
|
|
296
308
|
}
|
|
@@ -315,6 +327,8 @@ func clauseR6NeedsContinuityBundle(data Data, activeNeedCount int) bool {
|
|
|
315
327
|
return activeNeedCount >= data.Thresholds.ActiveNeedCountAtLeast
|
|
316
328
|
}
|
|
317
329
|
|
|
330
|
+
// deriveInsight produces the minimized care-coordination signal shared with
|
|
331
|
+
// the recipient.
|
|
318
332
|
func deriveInsight(data Data) Insight {
|
|
319
333
|
return Insight{
|
|
320
334
|
CreatedAt: data.Timestamps.CreatedAt,
|
|
@@ -330,6 +344,7 @@ func deriveInsight(data Data) Insight {
|
|
|
330
344
|
}
|
|
331
345
|
}
|
|
332
346
|
|
|
347
|
+
// derivePolicy constructs the usage restrictions paired with the insight.
|
|
333
348
|
func derivePolicy(data Data) Policy {
|
|
334
349
|
return Policy{
|
|
335
350
|
Duty: Duty{
|
|
@@ -363,6 +378,8 @@ func derivePolicy(data Data) Policy {
|
|
|
363
378
|
}
|
|
364
379
|
}
|
|
365
380
|
|
|
381
|
+
// packageCoversAllActiveNeeds checks whether a candidate package addresses every
|
|
382
|
+
// active need in this instance.
|
|
366
383
|
func packageCoversAllActiveNeeds(pkg Package, renalSafetyConcern, polypharmacyRisk, readmissionHistory, recentDischargeWindow bool) bool {
|
|
367
384
|
return (!renalSafetyConcern || pkg.CoversRenalSafetyConcern) &&
|
|
368
385
|
(!polypharmacyRisk || pkg.CoversPolypharmacyRisk) &&
|
|
@@ -370,6 +387,8 @@ func packageCoversAllActiveNeeds(pkg Package, renalSafetyConcern, polypharmacyRi
|
|
|
370
387
|
(!recentDischargeWindow || pkg.CoversRecentDischargeWindow)
|
|
371
388
|
}
|
|
372
389
|
|
|
390
|
+
// clauseS1EligiblePackages filters to packages that both fit the budget and
|
|
391
|
+
// cover the active needs.
|
|
373
392
|
func clauseS1EligiblePackages(data Data, renalSafetyConcern, polypharmacyRisk, readmissionHistory, recentDischargeWindow bool) []Package {
|
|
374
393
|
eligible := make([]Package, 0)
|
|
375
394
|
for _, pkg := range data.Packages {
|
|
@@ -381,6 +400,8 @@ func clauseS1EligiblePackages(data Data, renalSafetyConcern, polypharmacyRisk, r
|
|
|
381
400
|
return eligible
|
|
382
401
|
}
|
|
383
402
|
|
|
403
|
+
// clauseS2RecommendedPackage applies the tie-breaker: choose the lowest-cost
|
|
404
|
+
// eligible package after sorting by cost.
|
|
384
405
|
func clauseS2RecommendedPackage(data Data, renalSafetyConcern, polypharmacyRisk, readmissionHistory, recentDischargeWindow bool) ([]Package, *Package) {
|
|
385
406
|
eligible := clauseS1EligiblePackages(data, renalSafetyConcern, polypharmacyRisk, readmissionHistory, recentDischargeWindow)
|
|
386
407
|
if len(eligible) == 0 {
|
|
@@ -402,6 +423,8 @@ func clauseG3DutyTimely(data Data) bool {
|
|
|
402
423
|
return !parseTime(data.Timestamps.DutyPerformedAt).After(parseTime(data.Timestamps.ExpiresAt))
|
|
403
424
|
}
|
|
404
425
|
|
|
426
|
+
// clauseM1CanonicalEnvelope returns both the structured envelope and the
|
|
427
|
+
// deterministic string hashed/signed by the integrity clauses.
|
|
405
428
|
func clauseM1CanonicalEnvelope(data Data) (Envelope, string) {
|
|
406
429
|
envelope := Envelope{Insight: deriveInsight(data), Policy: derivePolicy(data)}
|
|
407
430
|
return envelope, stableStringify(canonicalValue(envelope))
|
|
@@ -435,6 +458,8 @@ func yesNo(value bool) string {
|
|
|
435
458
|
return "FAIL"
|
|
436
459
|
}
|
|
437
460
|
|
|
461
|
+
// evaluate computes all derived facts, governance checks, integrity values,
|
|
462
|
+
// and presentation fields expected by medior.expected.json.
|
|
438
463
|
func evaluate(data Data) (Result, error) {
|
|
439
464
|
if err := validateInstance(data); err != nil {
|
|
440
465
|
return Result{}, err
|
|
@@ -589,6 +614,7 @@ func derefIntString(value *int) string {
|
|
|
589
614
|
return fmt.Sprintf("%d", *value)
|
|
590
615
|
}
|
|
591
616
|
|
|
617
|
+
// main is the CLI entry point used by the Arcling test runner.
|
|
592
618
|
func main() {
|
|
593
619
|
inputPath := "medior.data.json"
|
|
594
620
|
jsonMode := false
|