@orkestrel/qualifier 0.0.13 → 0.0.14
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/README.md +13 -8
- package/dist/src/core/index.cjs +98 -31
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +123 -53
- package/dist/src/core/index.d.ts +123 -53
- package/dist/src/core/index.js +98 -31
- package/dist/src/core/index.js.map +1 -1
- package/package.json +15 -16
package/README.md
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# @orkestrel/qualifier
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
> A synchronous, deterministic eligibility engine that runs a pure,
|
|
4
|
+
> JSON-serializable `QualificationDefinition`'s ordered `passes` against one
|
|
5
|
+
> subject through one `@orkestrel/reason` engine and returns a fresh
|
|
6
|
+
> `QualificationResult` carrying global and scoped eligibility, evidence-rich
|
|
7
|
+
> `findings`, and quantitative `derivations`.
|
|
8
|
+
|
|
9
|
+
Author the passes — quantitative derivations and logical rule gates — hand a subject
|
|
10
|
+
(a plain data record) to `qualify`, and read what comes back. The caller supplies the
|
|
11
|
+
definition; `Qualifier` only evaluates what it is given. Inject a
|
|
12
|
+
[`@orkestrel/reason`](https://github.com/orkestrel/reason) `ReasonInterface` where
|
|
13
|
+
qualification shares an engine with the rest of your reasoning, and call `destroy()`
|
|
14
|
+
when the qualifier's work is done. Environment-agnostic — no I/O, no browser or
|
|
15
|
+
server assumptions. Part of the `@orkestrel` line.
|
|
11
16
|
|
|
12
17
|
## Install
|
|
13
18
|
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -3,17 +3,26 @@ let _orkestrel_contract = require("@orkestrel/contract");
|
|
|
3
3
|
let _orkestrel_reason = require("@orkestrel/reason");
|
|
4
4
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
5
5
|
//#region src/core/constants.ts
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Holds the default definition validation policy for `createQualifier` and
|
|
8
|
+
* `Qualifier.qualify`, `true`.
|
|
9
|
+
*/
|
|
7
10
|
var DEFAULT_QUALIFIER_VALIDATE = true;
|
|
8
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Names `'qualification'`, the reserved internal projection namespace a pass's working
|
|
13
|
+
* projection is written under.
|
|
14
|
+
*/
|
|
9
15
|
var QUALIFICATION_KEY = "qualification";
|
|
10
|
-
/** Lists the eligibility severities
|
|
16
|
+
/** Lists the eligibility severities most to least severe: `ineligible`, `referral`, `eligible`. */
|
|
11
17
|
var ELIGIBILITY_PRECEDENCE = Object.freeze([
|
|
12
18
|
"ineligible",
|
|
13
19
|
"referral",
|
|
14
20
|
"eligible"
|
|
15
21
|
]);
|
|
16
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Maps each {@link QualificationEffect} to its eligibility impact — `restriction` to
|
|
24
|
+
* `ineligible`, `referral` to `referral`, and `condition` to `eligible`.
|
|
25
|
+
*/
|
|
17
26
|
var EFFECT_ELIGIBILITIES = Object.freeze({
|
|
18
27
|
restriction: "ineligible",
|
|
19
28
|
referral: "referral",
|
|
@@ -22,7 +31,8 @@ var EFFECT_ELIGIBILITIES = Object.freeze({
|
|
|
22
31
|
//#endregion
|
|
23
32
|
//#region src/core/errors.ts
|
|
24
33
|
/**
|
|
25
|
-
* Represents a coded programmer error thrown by the qualifier layer
|
|
34
|
+
* Represents a coded programmer error thrown by the qualifier layer, carrying a
|
|
35
|
+
* `QualifierErrorCode` and an optional context record.
|
|
26
36
|
*
|
|
27
37
|
* @remarks
|
|
28
38
|
* `DEFINITION` — a qualification definition failed semantic validation; when
|
|
@@ -60,6 +70,10 @@ function isQualifierError(value) {
|
|
|
60
70
|
/**
|
|
61
71
|
* Determines whether a value is an {@link Eligibility} literal.
|
|
62
72
|
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* The guard is total and leaves nothing unchecked: a scalar union has no open or exact
|
|
75
|
+
* axis.
|
|
76
|
+
*
|
|
63
77
|
* @param value - The value to test
|
|
64
78
|
* @returns True if `value` is one of the eligibility literals; false otherwise
|
|
65
79
|
*/
|
|
@@ -67,6 +81,10 @@ var isEligibility = (0, _orkestrel_contract.literalOf)("eligible", "ineligible",
|
|
|
67
81
|
/**
|
|
68
82
|
* Determines whether a value is a {@link QualificationEffect} literal.
|
|
69
83
|
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* The guard is total and leaves nothing unchecked: a scalar union has no open or exact
|
|
86
|
+
* axis.
|
|
87
|
+
*
|
|
70
88
|
* @param value - The value to test
|
|
71
89
|
* @returns True if `value` is one of the effect literals; false otherwise
|
|
72
90
|
*/
|
|
@@ -75,8 +93,9 @@ var isQualificationEffect = (0, _orkestrel_contract.literalOf)("restriction", "r
|
|
|
75
93
|
* Determines whether a value is an open string-keyed record of {@link Eligibility} values.
|
|
76
94
|
*
|
|
77
95
|
* @remarks
|
|
78
|
-
* Every own string-named
|
|
79
|
-
* symbol-named members are left unchecked because they
|
|
96
|
+
* Every own string-named value is checked through {@link isEligibility}, including
|
|
97
|
+
* non-enumerable values. Inherited and symbol-named members are left unchecked because they
|
|
98
|
+
* are outside the own string-keyed record this guard certifies.
|
|
80
99
|
*
|
|
81
100
|
* @param value - The value to test
|
|
82
101
|
* @returns True if `value` is a non-array object whose own string-named values are
|
|
@@ -89,9 +108,11 @@ function isEligibilityRecord(value) {
|
|
|
89
108
|
* Determines whether a value is an open result-side {@link Premise}.
|
|
90
109
|
*
|
|
91
110
|
* @remarks
|
|
92
|
-
* `
|
|
93
|
-
*
|
|
94
|
-
*
|
|
111
|
+
* The optional `field`, `label`, `description`, `comparison`, and `met` members are checked
|
|
112
|
+
* when they are defined. `expected` is unchecked and may be absent or contain any value
|
|
113
|
+
* because its published type is `unknown`. `actual` is unchecked and may be absent or contain
|
|
114
|
+
* any value for the same reason. Unknown members are admitted, because an open result admits
|
|
115
|
+
* what the published contract does not name.
|
|
95
116
|
*
|
|
96
117
|
* @param value - The value to test
|
|
97
118
|
* @returns True if every checked premise member follows its published type; false otherwise
|
|
@@ -115,8 +136,10 @@ function isPremise(value) {
|
|
|
115
136
|
* Determines whether a value is an open result-side {@link Finding}.
|
|
116
137
|
*
|
|
117
138
|
* @remarks
|
|
118
|
-
*
|
|
119
|
-
* `undefined`; a present defined value must be a
|
|
139
|
+
* Every published member is checked, including each nested {@link Premise}. Optional `scope`
|
|
140
|
+
* and `message` members may be absent or `undefined`; a present defined value must be a
|
|
141
|
+
* string. Unknown members are admitted, because a foreign result implementation may add them
|
|
142
|
+
* without changing the published contract.
|
|
120
143
|
*
|
|
121
144
|
* @param value - The value to test
|
|
122
145
|
* @returns True if every published finding member is valid; false otherwise
|
|
@@ -137,8 +160,10 @@ function isFinding(value) {
|
|
|
137
160
|
* Determines whether a value is an open result-side {@link Derivation}.
|
|
138
161
|
*
|
|
139
162
|
* @remarks
|
|
140
|
-
*
|
|
141
|
-
* infinities.
|
|
163
|
+
* Every published member is checked: `value` follows the published `number` type, including
|
|
164
|
+
* `NaN` and the infinities, and `trace` and `errors` hold strings. Unknown members are
|
|
165
|
+
* admitted, because a foreign result implementation may add them without changing the
|
|
166
|
+
* published contract.
|
|
142
167
|
*
|
|
143
168
|
* @param value - The value to test
|
|
144
169
|
* @returns True if every published derivation member is valid; false otherwise
|
|
@@ -157,7 +182,10 @@ function isDerivation(value) {
|
|
|
157
182
|
*
|
|
158
183
|
* @remarks
|
|
159
184
|
* This guard is result-postured for values returned through a borrowed qualifier interface. It
|
|
160
|
-
*
|
|
185
|
+
* checks the complete published result closure — eligibility, scope values, findings,
|
|
186
|
+
* derivations, success, and the audit trail — and admits unknown members and class instances,
|
|
187
|
+
* because a borrowed `QualifierInterface` implementation may add them beyond the
|
|
188
|
+
* published contract.
|
|
161
189
|
*
|
|
162
190
|
* @param value - The value to test
|
|
163
191
|
* @returns True if every published qualification-result member is valid; false otherwise
|
|
@@ -178,6 +206,11 @@ function isQualificationResult(value) {
|
|
|
178
206
|
/**
|
|
179
207
|
* Determines whether a value is a {@link QualificationPass} (a quantitative or logical definition).
|
|
180
208
|
*
|
|
209
|
+
* @remarks
|
|
210
|
+
* The guard is exact and requires either complete reason shape, the quantitative definition or
|
|
211
|
+
* the logical one. Nothing is left unchecked, because the reason package owns both authored
|
|
212
|
+
* input shapes.
|
|
213
|
+
*
|
|
181
214
|
* @param value - The value to test
|
|
182
215
|
* @returns True if `value` is a complete reason quantitative or logical definition; false otherwise
|
|
183
216
|
*/
|
|
@@ -187,6 +220,10 @@ function isQualificationPass(value) {
|
|
|
187
220
|
/**
|
|
188
221
|
* Determines whether a value is an exact {@link Ruling} record.
|
|
189
222
|
*
|
|
223
|
+
* @remarks
|
|
224
|
+
* Every authored ruling member and the complete key set are checked. Nothing is left
|
|
225
|
+
* unchecked, because this package owns the authored input record.
|
|
226
|
+
*
|
|
190
227
|
* @param value - The value to test
|
|
191
228
|
* @returns True if `value` carries every ruling member and no unknown key; false otherwise
|
|
192
229
|
*/
|
|
@@ -203,6 +240,11 @@ function isRuling(value) {
|
|
|
203
240
|
/**
|
|
204
241
|
* Determines whether a value is an exact {@link QualificationDefinition} record.
|
|
205
242
|
*
|
|
243
|
+
* @remarks
|
|
244
|
+
* Every authored definition member, the nested pass and ruling shapes, and the complete key
|
|
245
|
+
* set are checked. Nothing is left unchecked, because this package owns the authored input
|
|
246
|
+
* record.
|
|
247
|
+
*
|
|
206
248
|
* @param value - The value to test
|
|
207
249
|
* @returns True if `value` carries every definition member and no unknown key; false otherwise
|
|
208
250
|
*/
|
|
@@ -227,10 +269,10 @@ function isQualificationDefinition(value) {
|
|
|
227
269
|
*
|
|
228
270
|
* @remarks
|
|
229
271
|
* Each token is split on `.` into a `FieldPath` array and resolved with
|
|
230
|
-
* `resolveField` (a plain string field is
|
|
272
|
+
* `resolveField` (a plain string field is one key, never dot-split — the split
|
|
231
273
|
* here is the token-to-path bridge). A finite number renders with `en-US`
|
|
232
274
|
* thousand grouping (`5010` → `5,010`); any other resolved value String-coerces.
|
|
233
|
-
* An
|
|
275
|
+
* An unresolved path renders as the empty string.
|
|
234
276
|
*
|
|
235
277
|
* @param template - The message template carrying `{{dotted.path}}` tokens
|
|
236
278
|
* @param subject - The record tokens resolve against
|
|
@@ -287,7 +329,7 @@ function renderComparison(comparison) {
|
|
|
287
329
|
* A reason `Bounds`-shaped record (the `between` / `outside` expected value)
|
|
288
330
|
* renders its present sides joined with the word `and`; an absent side is
|
|
289
331
|
* omitted. A finite number and everything else fall back to plain `String()` —
|
|
290
|
-
* a finite number is
|
|
332
|
+
* a finite number is not grouped here (grouping is {@link interpolateMessage}'s
|
|
291
333
|
* message-token-only behavior).
|
|
292
334
|
*
|
|
293
335
|
* @param value - The value to render
|
|
@@ -316,7 +358,7 @@ function renderValue(value) {
|
|
|
316
358
|
* Renders one {@link Premise} into a display-neutral sentence.
|
|
317
359
|
*
|
|
318
360
|
* @remarks
|
|
319
|
-
* The checked form renders only when `field` and `comparison` are
|
|
361
|
+
* The checked form renders only when `field` and `comparison` are both
|
|
320
362
|
* present, and `description` then goes unused; a premise missing either half
|
|
321
363
|
* of the checked pair renders as described instead.
|
|
322
364
|
*
|
|
@@ -381,7 +423,7 @@ function checkToPremise(check, result, labels) {
|
|
|
381
423
|
* A reason rule result carries only booleans, so this is the qualifier's own
|
|
382
424
|
* premise-audit projection: each authored premise expression is flattened to its
|
|
383
425
|
* atom leaves through `extractAtoms`, and each leaf's `Check` is re-evaluated through
|
|
384
|
-
* the injected `evaluator`. A membership check (`any` / `none`) over an
|
|
426
|
+
* the injected `evaluator`. A membership check (`any` / `none`) over an empty
|
|
385
427
|
* array value is content-free and is skipped.
|
|
386
428
|
*
|
|
387
429
|
* @param rule - The authored rule
|
|
@@ -550,7 +592,7 @@ function mergeQualificationContext(context, id, projection) {
|
|
|
550
592
|
*
|
|
551
593
|
* @remarks
|
|
552
594
|
* `applied` reflects whether the ruling's rule fired. Premises are re-evaluated
|
|
553
|
-
* against the
|
|
595
|
+
* against the same subject snapshot the pass evaluated, so a conclusion the pass
|
|
554
596
|
* derived can never satisfy its own reported premise. The message interpolates
|
|
555
597
|
* against that snapshot.
|
|
556
598
|
*
|
|
@@ -863,8 +905,9 @@ function describeUnreadDerivations(definition) {
|
|
|
863
905
|
* Runs ordered passes over one reason engine and returns eligibility.
|
|
864
906
|
*
|
|
865
907
|
* @remarks
|
|
866
|
-
* The engine is
|
|
867
|
-
* when injected (never destroyed).
|
|
908
|
+
* The engine is owned when self-created (destroyed on `destroy()`) and borrowed
|
|
909
|
+
* when injected (never destroyed). Semantic validation runs before the first pass
|
|
910
|
+
* whenever the `validate` option is on. `qualify` builds its working subject through
|
|
868
911
|
* copy-on-write overlays only — the caller's `subject` is never mutated, and the
|
|
869
912
|
* qualifier's internal projection is discarded after each call.
|
|
870
913
|
*/
|
|
@@ -1092,10 +1135,10 @@ var Qualifier = class {
|
|
|
1092
1135
|
//#endregion
|
|
1093
1136
|
//#region src/core/factories.ts
|
|
1094
1137
|
/**
|
|
1095
|
-
* Creates one
|
|
1138
|
+
* Creates one {@link QualifierInterface} over a reason engine.
|
|
1096
1139
|
*
|
|
1097
1140
|
* @remarks
|
|
1098
|
-
* A standalone qualifier creates and
|
|
1141
|
+
* A standalone qualifier creates and owns one shared quantitative-plus-logical
|
|
1099
1142
|
* reason engine, destroying it on `destroy()`. An injected `options.engine`
|
|
1100
1143
|
* remains caller-owned and is never destroyed. `validate` runs semantic
|
|
1101
1144
|
* validation before every qualification. Default: `true`. `labels` overrides the
|
|
@@ -1105,12 +1148,37 @@ var Qualifier = class {
|
|
|
1105
1148
|
* @param options - Optional injected engine, validation, labels, and emitter hooks
|
|
1106
1149
|
* @returns A {@link QualifierInterface}
|
|
1107
1150
|
*
|
|
1108
|
-
* @example
|
|
1151
|
+
* @example Create a qualifier
|
|
1109
1152
|
* ```ts
|
|
1110
|
-
* import { createQualifier } from '@orkestrel/qualifier'
|
|
1153
|
+
* import { createQualificationDefinition, createQualifier, createRuling } from '@orkestrel/qualifier'
|
|
1154
|
+
* import { createAtom, createLogicalDefinition, createRule } from '@orkestrel/reason'
|
|
1155
|
+
*
|
|
1156
|
+
* const gates = createLogicalDefinition('gates', 'Eligibility gates', [
|
|
1157
|
+
* createRule(
|
|
1158
|
+
* 'licensed',
|
|
1159
|
+
* [createAtom('licensed', 'equals', false)],
|
|
1160
|
+
* createAtom('blocked', 'equals', true),
|
|
1161
|
+
* ),
|
|
1162
|
+
* ])
|
|
1163
|
+
*
|
|
1164
|
+
* const bare = createRuling('license', 'gates', 'licensed', 'restriction')
|
|
1165
|
+
* const messaged = createRuling('license', 'gates', 'licensed', 'restriction', {
|
|
1166
|
+
* message: 'A license is required',
|
|
1167
|
+
* })
|
|
1168
|
+
*
|
|
1169
|
+
* 'message' in bare // false — an absent optional key is omitted, never written as undefined
|
|
1170
|
+
* messaged.message // 'A license is required'
|
|
1171
|
+
*
|
|
1172
|
+
* const passes = [gates]
|
|
1173
|
+
* const definition = createQualificationDefinition('standard', 'Standard eligibility', passes, {
|
|
1174
|
+
* rulings: [messaged],
|
|
1175
|
+
* })
|
|
1176
|
+
*
|
|
1177
|
+
* 'description' in definition // false
|
|
1178
|
+
* definition.passes === passes // false — the factory copies what it is handed
|
|
1111
1179
|
*
|
|
1112
1180
|
* const qualifier = createQualifier()
|
|
1113
|
-
* qualifier.qualify({ id: 'risk-1' }, definition)
|
|
1181
|
+
* qualifier.qualify({ id: 'risk-1', licensed: false }, definition)
|
|
1114
1182
|
* qualifier.destroy()
|
|
1115
1183
|
* ```
|
|
1116
1184
|
*/
|
|
@@ -1118,10 +1186,9 @@ function createQualifier(options) {
|
|
|
1118
1186
|
return new Qualifier(options);
|
|
1119
1187
|
}
|
|
1120
1188
|
/**
|
|
1121
|
-
* Creates a {@link QualificationDefinition}.
|
|
1189
|
+
* Creates a fresh {@link QualificationDefinition}, omitting every absent optional key.
|
|
1122
1190
|
*
|
|
1123
1191
|
* @remarks
|
|
1124
|
-
* Returns a fresh top-level definition, omitting absent optional keys;
|
|
1125
1192
|
* `passes` and `rulings` arrays are copied, and record `metadata` is
|
|
1126
1193
|
* shallow-copied so nested values are not deep-cloned.
|
|
1127
1194
|
*
|
|
@@ -1149,7 +1216,7 @@ function createQualificationDefinition(id, name, passes, input) {
|
|
|
1149
1216
|
};
|
|
1150
1217
|
}
|
|
1151
1218
|
/**
|
|
1152
|
-
* Creates a {@link Ruling}
|
|
1219
|
+
* Creates a fresh {@link Ruling} from the rule it reacts to and the effect it applies.
|
|
1153
1220
|
*
|
|
1154
1221
|
* @param id - The ruling id
|
|
1155
1222
|
* @param pass - The logical pass id the rule lives in
|