@orkestrel/rater 0.0.1 → 0.0.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.
@@ -2,52 +2,21 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _orkestrel_contract = require("@orkestrel/contract");
3
3
  let _orkestrel_reason = require("@orkestrel/reason");
4
4
  let _orkestrel_emitter = require("@orkestrel/emitter");
5
- //#region src/core/constants.ts
6
- /** Default definition validation policy for `ProgramManager.add`. */
7
- var DEFAULT_RATER_VALIDATE = true;
8
- /** Eligibility severity order, most severe first — the {@link combineEligibilities} scan order. */
9
- var ELIGIBILITY_PRECEDENCE = Object.freeze([
10
- "ineligible",
11
- "referral",
12
- "eligible"
13
- ]);
14
- /** Status tally precedence order — least to most resolved. */
15
- var STATUS_PRECEDENCE = Object.freeze([
16
- "ineligible",
17
- "referral",
18
- "conditional",
19
- "unrated",
20
- "eligible"
21
- ]);
22
- /** The deterministic authority decision for each eligibility. */
23
- var ELIGIBILITY_DECISIONS = Object.freeze({
24
- eligible: "approved",
25
- ineligible: "denied",
26
- referral: "submitted"
27
- });
28
- /** The direct eligibility impact of an applied determination, by its effect. */
29
- var EFFECT_ELIGIBILITIES = Object.freeze({
30
- restriction: "ineligible",
31
- referral: "referral",
32
- condition: void 0,
33
- notice: void 0,
34
- limit: void 0
35
- });
36
- /** The reserved working-subject key a batch's aggregate projection is written under. */
37
- var AGGREGATE_KEY = "aggregate";
38
- /** The reserved working-subject key an authority's outcome projection is written under. */
39
- var OUTCOME_KEY = "outcome";
40
- //#endregion
41
5
  //#region src/core/errors.ts
42
6
  /**
43
7
  * A coded programmer error thrown by the rating layer.
44
8
  *
45
9
  * @remarks
46
- * `DUPLICATE` — a program id collision on `ProgramManager.add`. `MISSING` — an
47
- * unknown authored line reference at compile time.
48
- * `DEFINITION` — a program definition failed `isProgramDefinition`. `MISMATCH` —
49
- * a rated subject is not a record, or uses a reserved working-subject key.
10
+ * `DEFINITION` — the `rate` input failed both the array-of-lines and rating
11
+ * definition validation. `MISMATCH` — a rated subject is not a record.
50
12
  * `DESTROYED` — use of a destroyed entity.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { RaterError } from '@orkestrel/rater'
17
+ *
18
+ * throw new RaterError('MISMATCH', 'Subject must be a record')
19
+ * ```
51
20
  */
52
21
  var RaterError = class extends Error {
53
22
  code;
@@ -59,205 +28,98 @@ var RaterError = class extends Error {
59
28
  this.context = context;
60
29
  }
61
30
  };
62
- /** Narrow a caught value to a {@link RaterError}. */
63
- function isRaterError(value) {
64
- return value instanceof RaterError;
65
- }
66
- //#endregion
67
- //#region src/core/validators.ts
68
- /** Determine whether a value is an {@link Eligibility} literal. */
69
- var isEligibility = (0, _orkestrel_contract.literalOf)("eligible", "ineligible", "referral");
70
- /** Determine whether a value is a {@link Decision} literal. */
71
- var isDecision = (0, _orkestrel_contract.literalOf)("approved", "denied", "submitted");
72
- /** Determine whether a value is a {@link Status} literal. */
73
- var isStatus = (0, _orkestrel_contract.literalOf)("ineligible", "referral", "conditional", "unrated", "eligible");
74
- /** Determine whether a value is an {@link Effect} literal. */
75
- var isEffect = (0, _orkestrel_contract.literalOf)("restriction", "referral", "condition", "notice", "limit");
76
- /** Determine whether a value is a {@link Stage} literal. */
77
- var isStage = (0, _orkestrel_contract.literalOf)("factor", "group", "total");
78
- /** Determine whether a value is an exact {@link Ruling} record. */
79
- function isRuling(value) {
80
- return (0, _orkestrel_contract.recordOf)({
81
- effect: isEffect,
82
- line: _orkestrel_contract.isString,
83
- message: _orkestrel_contract.isString
84
- }, ["line", "message"])(value);
85
- }
86
- /** Determine whether a value is an exact {@link Notice} record. */
87
- function isNotice(value) {
88
- return (0, _orkestrel_contract.recordOf)({
89
- id: _orkestrel_contract.isString,
90
- message: _orkestrel_contract.isString,
91
- line: _orkestrel_contract.isString
92
- }, ["line"])(value);
93
- }
94
- /** Determine whether a value is an exact {@link PassDefinition} record. */
95
- function isPassDefinition(value) {
96
- return (0, _orkestrel_contract.recordOf)({
97
- line: _orkestrel_contract.isString,
98
- definition: (0, _orkestrel_contract.unionOf)(_orkestrel_reason.isLogicalDefinition, _orkestrel_reason.isQuantitativeDefinition)
99
- }, ["line"])(value);
100
- }
101
- /** Determine whether a value is an exact {@link LineDefinition} record. */
102
- function isLineDefinition(value) {
103
- return (0, _orkestrel_contract.recordOf)({
104
- id: _orkestrel_contract.isString,
105
- name: _orkestrel_contract.isString,
106
- description: _orkestrel_contract.isString,
107
- rate: _orkestrel_reason.isQuantitativeDefinition,
108
- metadata: _orkestrel_contract.isJSONValue
109
- }, ["description", "metadata"])(value);
110
- }
111
- /** Determine whether a value is an exact {@link AggregateDefinition} record. */
112
- function isAggregateDefinition(value) {
113
- return (0, _orkestrel_contract.recordOf)({
114
- fields: (0, _orkestrel_contract.arrayOf)(_orkestrel_reason.isFieldPath),
115
- by: _orkestrel_reason.isFieldPath,
116
- gates: _orkestrel_reason.isLogicalDefinition
117
- }, ["by", "gates"])(value);
118
- }
119
- /** Determine whether a value is a rule-id-keyed {@link Ruling} record. */
120
- var isRulings = (0, _orkestrel_contract.whereOf)(_orkestrel_contract.isRecord, (record) => Object.values(record).every(isRuling));
121
- /** Determine whether a value is an exact {@link ProgramDefinition} record. */
122
- function isProgramDefinition(value) {
123
- return (0, _orkestrel_contract.recordOf)({
124
- id: _orkestrel_contract.isString,
125
- name: _orkestrel_contract.isString,
126
- description: _orkestrel_contract.isString,
127
- passes: (0, _orkestrel_contract.arrayOf)(isPassDefinition),
128
- lines: (0, _orkestrel_contract.arrayOf)(isLineDefinition),
129
- rulings: isRulings,
130
- notices: (0, _orkestrel_contract.arrayOf)(isNotice),
131
- authority: _orkestrel_reason.isLogicalDefinition,
132
- aggregate: isAggregateDefinition,
133
- metadata: _orkestrel_contract.isJSONValue
134
- }, [
135
- "description",
136
- "passes",
137
- "rulings",
138
- "notices",
139
- "authority",
140
- "aggregate",
141
- "metadata"
142
- ])(value);
143
- }
144
- //#endregion
145
- //#region src/core/helpers.ts
146
31
  /**
147
- * Interpolate `{{dotted.path}}` tokens in a message template against a record.
32
+ * Narrow a caught value to a {@link RaterError}.
148
33
  *
149
- * @remarks
150
- * Each token is split on `.` into a {@link FieldPath} array and resolved with
151
- * the contracts {@link resolveField} (a plain string field is ONE key, never
152
- * dot-split — the split here is the token-to-path bridge). A finite number
153
- * renders with `en-US` thousand grouping (`5010` → `5,010`); any other resolved
154
- * value String-coerces. An UNRESOLVED path (the resolved value is `undefined`)
155
- * renders as the empty string — the deterministic "nothing to show" rule.
156
- *
157
- * @param template - The message template carrying `{{dotted.path}}` tokens
158
- * @param record - The record tokens resolve against
159
- * @returns The template with every token replaced
34
+ * @param value - The caught value to test
35
+ * @returns `true` when `value` is a `RaterError`
160
36
  *
161
37
  * @example
162
38
  * ```ts
163
- * import { interpolateMessage } from '@orkestrel/rater'
39
+ * import { isRaterError, RaterError } from '@orkestrel/rater'
164
40
  *
165
- * interpolateMessage('Limit is {{limit}}', { limit: 5010 }) // 'Limit is 5,010'
166
- * interpolateMessage('Missing {{gone}}', {}) // 'Missing '
41
+ * try {
42
+ * throw new RaterError('DESTROYED', 'Rater has been destroyed')
43
+ * } catch (error) {
44
+ * if (isRaterError(error)) error.code // 'DESTROYED'
45
+ * }
167
46
  * ```
168
47
  */
169
- function interpolateMessage(template, record) {
170
- return template.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_match, path) => {
171
- const value = (0, _orkestrel_contract.resolveField)(record, path.split("."));
172
- if (value === void 0) return "";
173
- if ((0, _orkestrel_contract.isFiniteNumber)(value)) return value.toLocaleString("en-US");
174
- return String(value);
175
- });
48
+ function isRaterError(value) {
49
+ return value instanceof RaterError;
176
50
  }
51
+ //#endregion
52
+ //#region src/core/validators.ts
177
53
  /**
178
- * Describe a {@link Premise} comparison as a display-neutral verb phrase.
54
+ * Determine whether a value is a {@link Stage} literal.
179
55
  *
180
- * @param comparison - The comparison to describe
181
- * @returns A display-neutral phrase
56
+ * @param value - The value to test
57
+ * @returns `true` when `value` is `'factor'`, `'group'`, or `'total'`
182
58
  *
183
59
  * @example
184
60
  * ```ts
185
- * import { describeComparison } from '@orkestrel/rater'
61
+ * import { isStage } from '@orkestrel/rater'
186
62
  *
187
- * describeComparison('above') // 'is more than'
63
+ * isStage('group') // true
64
+ * isStage('step') // false
188
65
  * ```
189
66
  */
190
- function describeComparison(comparison) {
191
- switch (comparison) {
192
- case "equals": return "is";
193
- case "not": return "is not";
194
- case "above": return "is more than";
195
- case "below": return "is less than";
196
- case "from": return "is at least";
197
- case "to": return "is at most";
198
- case "any": return "is any of";
199
- case "none": return "is none of";
200
- case "between": return "is between";
201
- case "outside": return "is outside";
202
- }
203
- }
67
+ var isStage = (0, _orkestrel_contract.literalOf)("factor", "group", "total");
204
68
  /**
205
- * Render a structured or scalar value display-neutrally.
69
+ * Determine whether a value is an exact {@link LineDefinition} record.
206
70
  *
207
71
  * @remarks
208
- * An array renders as its elements, each `String`-coerced, joined with
209
- * `', '`. A Bounds-shaped record (a reason `Bounds` — the `between`/`outside`
210
- * comparison's expected value shape) renders its present sides joined with
211
- * the word `and`; an absent side is omitted. A finite number and everything
212
- * else fall back to plain `String()` coercion — a finite number is NOT
213
- * grouped here (that grouping is {@link interpolateMessage}'s own,
214
- * message-token-only behavior).
72
+ * Total guard (AGENTS §14): adversarial input (cycles, hostile prototypes)
73
+ * returns `false`, never throws. The record shape is EXACT — an extra key fails.
215
74
  *
216
- * @param value - The value to render
217
- * @returns A display-neutral rendering
75
+ * @param value - The value to test
76
+ * @returns `true` when `value` is a `LineDefinition`
218
77
  *
219
78
  * @example
220
79
  * ```ts
221
- * import { describeValue } from '@orkestrel/rater'
80
+ * import { quantitativeDefinition } from '@orkestrel/reason'
81
+ * import { isLineDefinition } from '@orkestrel/rater'
222
82
  *
223
- * describeValue([18, 25, 40]) // '18, 25, 40'
224
- * describeValue({ minimum: 18, maximum: 65 }) // '18 and 65'
225
- * describeValue(42) // '42'
83
+ * isLineDefinition({ id: 'base', name: 'Base', rate: quantitativeDefinition('base', 'Base', []) }) // true
226
84
  * ```
227
85
  */
228
- function describeValue(value) {
229
- if (Array.isArray(value)) return value.map((entry) => String(entry)).join(", ");
230
- if ((0, _orkestrel_reason.isBounds)(value)) {
231
- const sides = [];
232
- if (value.minimum !== void 0) sides.push(String(value.minimum));
233
- if (value.maximum !== void 0) sides.push(String(value.maximum));
234
- return sides.join(" and ");
235
- }
236
- return String(value);
86
+ function isLineDefinition(value) {
87
+ return (0, _orkestrel_contract.recordOf)({
88
+ id: _orkestrel_contract.isString,
89
+ name: _orkestrel_contract.isString,
90
+ description: _orkestrel_contract.isString,
91
+ rate: _orkestrel_reason.isQuantitativeDefinition,
92
+ metadata: _orkestrel_contract.isJSONValue
93
+ }, ["description", "metadata"])(value);
237
94
  }
238
95
  /**
239
- * Render one {@link Premise} into a display-neutral sentence.
96
+ * Determine whether a value is an exact {@link RatingDefinition} record.
240
97
  *
241
- * @param entry - The premise to render
242
- * @param labels - Optional field-to-label overrides, keyed by dot-joined field
243
- * @returns A display-neutral sentence
98
+ * @remarks
99
+ * Total guard (AGENTS §14): adversarial input (cycles, hostile prototypes)
100
+ * returns `false`, never throws. The record shape is EXACT — an extra key fails.
101
+ *
102
+ * @param value - The value to test
103
+ * @returns `true` when `value` is a `RatingDefinition`
244
104
  *
245
105
  * @example
246
106
  * ```ts
247
- * import { describePremise } from '@orkestrel/rater'
107
+ * import { isRatingDefinition } from '@orkestrel/rater'
248
108
  *
249
- * describePremise({ field: 'age', comparison: 'above', expected: 18, actual: 25, met: true })
250
- * // 'age is more than 18 ? met'
109
+ * isRatingDefinition({ id: 'r1', name: 'Rating', lines: [] }) // true
251
110
  * ```
252
111
  */
253
- function describePremise(entry, labels) {
254
- const status = entry.met === void 0 ? "unknown" : entry.met ? "met" : "not met";
255
- if (entry.field === void 0 || entry.comparison === void 0) return `${entry.description ?? "Premise"} ? ${status}`;
256
- const field = (0, _orkestrel_reason.formatField)(entry.field);
257
- const label = labels?.[field] ?? entry.label ?? field;
258
- const expected = entry.expected === void 0 ? "" : ` ${describeValue(entry.expected)}`;
259
- return `${label} ${describeComparison(entry.comparison)}${expected} ? ${status}`;
112
+ function isRatingDefinition(value) {
113
+ return (0, _orkestrel_contract.recordOf)({
114
+ id: _orkestrel_contract.isString,
115
+ name: _orkestrel_contract.isString,
116
+ description: _orkestrel_contract.isString,
117
+ lines: (0, _orkestrel_contract.arrayOf)(isLineDefinition),
118
+ metadata: _orkestrel_contract.isJSONValue
119
+ }, ["description", "metadata"])(value);
260
120
  }
121
+ //#endregion
122
+ //#region src/core/helpers.ts
261
123
  /**
262
124
  * Build a {@link Premise} from an evaluated {@link Check}.
263
125
  *
@@ -266,6 +128,15 @@ function describePremise(entry, labels) {
266
128
  * @param met - Whether the check was met (absent when not yet evaluated)
267
129
  * @param labels - Optional field-to-label overrides, keyed by dot-joined field
268
130
  * @returns A fresh premise
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * import { check } from '@orkestrel/reason'
135
+ * import { premiseCheck } from '@orkestrel/rater'
136
+ *
137
+ * const entry = check('age', 'above', 18)
138
+ * premiseCheck(entry, 25, true) // { field: 'age', comparison: 'above', expected: 18, actual: 25, met: true }
139
+ * ```
269
140
  */
270
141
  function premiseCheck(check, actual, met, labels) {
271
142
  const field = (0, _orkestrel_reason.formatField)(check.field);
@@ -286,6 +157,15 @@ function premiseCheck(check, actual, met, labels) {
286
157
  * @param results - The corresponding {@link CheckResult}s, in the same order
287
158
  * @param labels - Optional field-to-label overrides, keyed by dot-joined field
288
159
  * @returns A fresh list of premises, one per authored check
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * import { check } from '@orkestrel/reason'
164
+ * import { checkPremises } from '@orkestrel/rater'
165
+ *
166
+ * const checks = [check('age', 'above', 18)]
167
+ * checkPremises(checks, [{ field: 'age', met: true, actual: 25 }])
168
+ * ```
289
169
  */
290
170
  function checkPremises(checks, results, labels) {
291
171
  return (checks ?? []).map((check, index) => {
@@ -294,62 +174,21 @@ function checkPremises(checks, results, labels) {
294
174
  });
295
175
  }
296
176
  /**
297
- * Describe a logical {@link Expression} tree without atom-specific evidence.
298
- *
299
- * @param expression - The expression to describe
300
- * @param labels - Optional field-to-label overrides, keyed by dot-joined field
301
- * @returns A display-neutral description
302
- */
303
- function describeExpression(expression, labels) {
304
- if (expression.form === "atom") return describePremise(premiseCheck(expression.check, void 0, void 0, labels), labels);
305
- const descriptions = expression.operands.map((operand) => describeExpression(operand, labels));
306
- return `${expression.operator} (${descriptions.join(", ")})`;
307
- }
308
- /**
309
- * Build rich premises for one fired {@link Rule} by walking its premise atoms
310
- * and re-evaluating each against the working subject.
311
- *
312
- * @remarks
313
- * A reason {@link RuleResult} carries only booleans, so this is rater's own
314
- * premise-audit projection: each authored premise expression is flattened to
315
- * its atom leaves via {@link extractAtoms}, and each leaf's {@link Check} is
316
- * re-evaluated through the injected `evaluator`. A membership check (`any` /
317
- * `none`) over an EMPTY array value is content-free — a tautology or
318
- * contradiction that reads as "is none of —" in every surface — and is skipped.
319
- *
320
- * @param rule - The authored rule
321
- * @param working - The working subject to evaluate against
322
- * @param evaluator - The shared reason check evaluator
323
- * @param labels - Optional field-to-label overrides, keyed by dot-joined field
324
- * @returns A fresh, ordered list of rich premises
325
- */
326
- function logicalPremises(rule, working, evaluator, labels) {
327
- const output = [];
328
- for (const premise of rule.premises) for (const atom of (0, _orkestrel_reason.extractAtoms)(premise)) {
329
- const { check } = atom;
330
- if ((check.operator === "any" || check.operator === "none") && Array.isArray(check.value) && check.value.length === 0) continue;
331
- const result = evaluator.evaluate(check, working);
332
- output.push(premiseCheck(check, result.actual, result.met, labels));
333
- }
334
- return output;
335
- }
336
- /**
337
- * Locate an authored {@link Rule} by id.
338
- *
339
- * @param definition - The logical definition to search
340
- * @param id - The rule id
341
- * @returns The matching rule, or `undefined`
342
- */
343
- function findRule(definition, id) {
344
- return definition.rules.find((rule) => rule.id === id);
345
- }
346
- /**
347
177
  * Join one authored quantitative factor to its evaluated {@link FactorResult}.
348
178
  *
349
179
  * @param definition - The authored factor
350
180
  * @param results - The group's factor results
351
181
  * @param labels - Optional field-to-label overrides, keyed by dot-joined field
352
182
  * @returns A fresh worksheet factor
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * import { fieldFactor } from '@orkestrel/reason'
187
+ * import { worksheetFactor } from '@orkestrel/rater'
188
+ *
189
+ * const factor = fieldFactor('seats', 'seats')
190
+ * worksheetFactor(factor, [{ id: 'seats', applied: true, value: 10 }])
191
+ * ```
353
192
  */
354
193
  function worksheetFactor(definition, results, labels) {
355
194
  const result = results.find((entry) => entry.id === definition.id);
@@ -369,6 +208,15 @@ function worksheetFactor(definition, results, labels) {
369
208
  * @param results - The definition's group results
370
209
  * @param labels - Optional field-to-label overrides, keyed by dot-joined field
371
210
  * @returns A fresh worksheet group
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * import { factorGroup, staticFactor } from '@orkestrel/reason'
215
+ * import { worksheetGroup } from '@orkestrel/rater'
216
+ *
217
+ * const group = factorGroup('charge', 'sum', [staticFactor('base', 100)])
218
+ * worksheetGroup(group, [{ id: 'charge', applied: true, value: 100, factors: [] }])
219
+ * ```
372
220
  */
373
221
  function worksheetGroup(definition, results, labels) {
374
222
  const result = results.find((entry) => entry.id === definition.id);
@@ -390,6 +238,14 @@ function worksheetGroup(definition, results, labels) {
390
238
  * @param value - The stage's resolved value
391
239
  * @param expression - A display-neutral expression string
392
240
  * @returns A fresh step
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * import { worksheetStep } from '@orkestrel/rater'
245
+ *
246
+ * worksheetStep('total', 'quote', 'Quote', 110, 'sum = 110')
247
+ * // { stage: 'total', id: 'quote', name: 'Quote', value: 110, expression: 'sum = 110' }
248
+ * ```
393
249
  */
394
250
  function worksheetStep(stage, id, name, value, expression) {
395
251
  return {
@@ -407,6 +263,18 @@ function worksheetStep(stage, id, name, value, expression) {
407
263
  * @param result - The evaluated quantitative result
408
264
  * @param groups - The definition's already-joined worksheet groups
409
265
  * @returns A fresh, ordered list of steps: applied factors, then groups, then the total
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * import { createQuantitativeReasoner, createReason, factorGroup, quantitativeDefinition, staticFactor } from '@orkestrel/reason'
270
+ * import { worksheetGroup, worksheetSteps } from '@orkestrel/rater'
271
+ *
272
+ * const definition = quantitativeDefinition('base', 'Base', [factorGroup('g', 'sum', [staticFactor('flat', 100)])])
273
+ * const result = createReason({ reasoners: [createQuantitativeReasoner()] }).reason({}, definition)
274
+ * if (result.reasoning === 'quantitative') {
275
+ * worksheetSteps(definition, result, definition.groups.map((group) => worksheetGroup(group, result.groups)))
276
+ * }
277
+ * ```
410
278
  */
411
279
  function worksheetSteps(definition, result, groups) {
412
280
  const output = [];
@@ -425,6 +293,16 @@ function worksheetSteps(definition, result, groups) {
425
293
  * @param result - The evaluated quantitative result
426
294
  * @param labels - Optional field-to-label overrides, keyed by dot-joined field
427
295
  * @returns A fresh worksheet
296
+ *
297
+ * @example
298
+ * ```ts
299
+ * import { createQuantitativeReasoner, createReason, factorGroup, quantitativeDefinition, staticFactor } from '@orkestrel/reason'
300
+ * import { resultsWorksheet } from '@orkestrel/rater'
301
+ *
302
+ * const definition = quantitativeDefinition('base', 'Base', [factorGroup('g', 'sum', [staticFactor('flat', 100)])])
303
+ * const result = createReason({ reasoners: [createQuantitativeReasoner()] }).reason({}, definition)
304
+ * if (result.reasoning === 'quantitative') resultsWorksheet(definition, result) // the full audit trail
305
+ * ```
428
306
  */
429
307
  function resultsWorksheet(definition, result, labels) {
430
308
  const groups = definition.groups.map((group) => worksheetGroup(group, result.groups, labels));
@@ -442,186 +320,31 @@ function resultsWorksheet(definition, result, labels) {
442
320
  };
443
321
  }
444
322
  /**
445
- * Convert fired logical {@link RuleResult}s into line- or program-scoped
446
- * {@link Determination}s.
447
- *
448
- * @param definition - The authored logical definition
449
- * @param result - The evaluated logical result
450
- * @param rulings - Authored consequences, keyed by rule id
451
- * @param working - The working subject the rules ran against
452
- * @param line - The line id this pass is scoped to, when it is line-scoped
453
- * @param evaluator - The shared reason check evaluator
454
- * @param labels - Optional field-to-label overrides, keyed by dot-joined field
455
- * @returns A fresh list of determinations
456
- */
457
- function rulesToDeterminations(definition, result, rulings, working, line, evaluator, labels) {
458
- const output = [];
459
- for (const entry of result.rules) {
460
- const authored = findRule(definition, entry.id);
461
- if (authored === void 0) continue;
462
- const routed = rulings?.[entry.id];
463
- if (!entry.applied && routed === void 0) continue;
464
- const resolved = routed?.line ?? line;
465
- output.push({
466
- id: entry.id,
467
- effect: routed?.effect ?? "restriction",
468
- applied: entry.applied,
469
- ...resolved === void 0 ? {} : { line: resolved },
470
- ...routed?.message === void 0 ? {} : { message: interpolateMessage(routed.message, working) },
471
- premises: logicalPremises(authored, working, evaluator, labels)
472
- });
473
- }
474
- return output;
475
- }
476
- /**
477
- * Convert an authority's fired {@link RuleResult}s into `limit`
478
- * {@link Determination}s.
479
- *
480
- * @param definition - The authority's logical definition
481
- * @param result - The evaluated logical result
482
- * @param rulings - Authored consequences, keyed by rule id
483
- * @param working - The working subject the authority ran against
484
- * @param evaluator - The shared reason check evaluator
485
- * @param labels - Optional field-to-label overrides, keyed by dot-joined field
486
- * @returns A fresh list of `limit` determinations
487
- */
488
- function authorityToDeterminations(definition, result, rulings, working, evaluator, labels) {
489
- const output = [];
490
- for (const entry of result.rules) {
491
- if (!entry.applied) continue;
492
- const authored = findRule(definition, entry.id);
493
- if (authored === void 0) continue;
494
- const routed = rulings?.[entry.id];
495
- output.push({
496
- id: entry.id,
497
- effect: "limit",
498
- applied: true,
499
- ...routed?.line === void 0 ? {} : { line: routed.line },
500
- ...routed?.message === void 0 ? {} : { message: interpolateMessage(routed.message, working) },
501
- premises: logicalPremises(authored, working, evaluator, labels)
502
- });
503
- }
504
- return output;
505
- }
506
- /**
507
- * Convert authored {@link Notice}s into unconditionally-applied
508
- * `notice` {@link Determination}s.
509
- *
510
- * @param notices - The authored notices
511
- * @param working - The working subject to interpolate messages against
512
- * @returns A fresh list of notice determinations
513
- */
514
- function noticesToDeterminations(notices, working) {
515
- return (notices ?? []).map((entry) => ({
516
- id: entry.id,
517
- effect: "notice",
518
- applied: true,
519
- ...entry.line === void 0 ? {} : { line: entry.line },
520
- message: interpolateMessage(entry.message, working),
521
- premises: []
522
- }));
523
- }
524
- /**
525
- * Keep only the determinations scoped to one line.
526
- *
527
- * @param determinations - The determinations to filter
528
- * @param id - The line id to keep
529
- * @returns The determinations whose `line` matches `id`
530
- */
531
- function filterLineDeterminations(determinations, id) {
532
- return determinations.filter((entry) => entry.line === id);
533
- }
534
- /**
535
- * Keep only program-scoped (line-unscoped) determinations.
536
- *
537
- * @param determinations - The determinations to filter
538
- * @returns The determinations with no `line`
539
- */
540
- function filterProgramDeterminations(determinations) {
541
- return determinations.filter((entry) => entry.line === void 0);
542
- }
543
- /**
544
- * Derive the eligibility impact of a set of determinations.
545
- *
546
- * @param determinations - The determinations to fold
547
- * @returns The most severe eligibility any APPLIED determination's effect carries
548
- *
549
- * @example
550
- * ```ts
551
- * import { deriveDeterminationEligibility } from '@orkestrel/rater'
552
- *
553
- * deriveDeterminationEligibility([{ id: 'r1', effect: 'restriction', applied: true, premises: [] }])
554
- * // 'ineligible'
555
- * ```
556
- */
557
- function deriveDeterminationEligibility(determinations) {
558
- const impacts = ["eligible"];
559
- for (const entry of determinations) {
560
- const eligibility = entry.applied ? EFFECT_ELIGIBILITIES[entry.effect] : void 0;
561
- if (eligibility !== void 0) impacts.push(eligibility);
562
- }
563
- return combineEligibilities(impacts);
564
- }
565
- /**
566
- * Return the most severe {@link Eligibility} in a list.
567
- *
568
- * @param eligibilities - The eligibilities to combine
569
- * @returns The most severe eligibility, or `'eligible'` for an empty list
570
- *
571
- * @example
572
- * ```ts
573
- * import { combineEligibilities } from '@orkestrel/rater'
574
- *
575
- * combineEligibilities(['eligible', 'referral']) // 'referral'
576
- * ```
577
- */
578
- function combineEligibilities(eligibilities) {
579
- for (const eligibility of ELIGIBILITY_PRECEDENCE) if (eligibilities.includes(eligibility)) return eligibility;
580
- return "eligible";
581
- }
582
- /**
583
- * Convert an {@link Eligibility} to its deterministic authority {@link Decision}.
584
- *
585
- * @param eligibility - The eligibility to convert
586
- * @returns The matching decision
587
- */
588
- function decideEligibility(eligibility) {
589
- return ELIGIBILITY_DECISIONS[eligibility];
590
- }
591
- /**
592
- * Derive the final {@link Status} from eligibility, determinations, and rated
593
- * line evidence.
594
- *
595
- * @param eligibility - The program's combined eligibility
596
- * @param determinations - Every determination in scope (program and line)
597
- * @param lines - The program's rated lines
598
- * @returns The derived status
599
- */
600
- function deriveStatus(eligibility, determinations, lines) {
601
- if (eligibility === "ineligible") return "ineligible";
602
- if (eligibility === "referral") return "referral";
603
- if (determinations.some((entry) => entry.applied && entry.effect === "condition")) return "conditional";
604
- if (lines.some((entry) => !(entry.worksheet?.success ?? false) || entry.amount === void 0)) return "unrated";
605
- return "eligible";
606
- }
607
- /**
608
323
  * Build a rated {@link LineResult} from a line's evaluated
609
324
  * {@link QuantitativeResult}.
610
325
  *
611
326
  * @param definition - The authored line definition
612
327
  * @param result - The evaluated quantitative result
613
- * @param determinations - The line-scoped determinations
614
328
  * @param labels - Optional field-to-label overrides, keyed by dot-joined field
615
329
  * @returns A fresh line result
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * import { createQuantitativeReasoner, createReason, quantitativeDefinition } from '@orkestrel/reason'
334
+ * import { lineDefinition, ratedLine } from '@orkestrel/rater'
335
+ *
336
+ * const line = lineDefinition('base', 'Base', quantitativeDefinition('base', 'Base', []))
337
+ * const result = createReason({ reasoners: [createQuantitativeReasoner()] }).reason({}, line.rate)
338
+ * if (result.reasoning === 'quantitative') ratedLine(line, result) // amount present only when success
339
+ * ```
616
340
  */
617
- function ratedLine(definition, result, determinations, labels) {
341
+ function ratedLine(definition, result, labels) {
618
342
  return {
619
343
  id: definition.id,
620
344
  name: definition.name,
621
- eligibility: deriveDeterminationEligibility(determinations),
622
345
  ...result.success ? { amount: result.value } : {},
623
346
  worksheet: resultsWorksheet(definition.rate, result, labels),
624
- determinations
347
+ success: result.success
625
348
  };
626
349
  }
627
350
  /**
@@ -629,6 +352,17 @@ function ratedLine(definition, result, determinations, labels) {
629
352
  *
630
353
  * @param lines - The rated lines
631
354
  * @returns The sum of every line's `amount`, or `undefined` when no line has one
355
+ *
356
+ * @example
357
+ * ```ts
358
+ * import { createQuantitativeReasoner, createReason, factorGroup, quantitativeDefinition, staticFactor } from '@orkestrel/reason'
359
+ * import { lineDefinition, ratedLine, sumAmounts } from '@orkestrel/rater'
360
+ *
361
+ * const rate = quantitativeDefinition('base', 'Base', [factorGroup('g', 'sum', [staticFactor('flat', 100)])])
362
+ * const line = lineDefinition('base', 'Base', rate)
363
+ * const result = createReason({ reasoners: [createQuantitativeReasoner()] }).reason({}, rate)
364
+ * if (result.reasoning === 'quantitative') sumAmounts([ratedLine(line, result)]) // 100
365
+ * ```
632
366
  */
633
367
  function sumAmounts(lines) {
634
368
  let total = 0;
@@ -639,703 +373,100 @@ function sumAmounts(lines) {
639
373
  }
640
374
  return count === 0 ? void 0 : total;
641
375
  }
376
+ //#endregion
377
+ //#region src/core/Rater.ts
642
378
  /**
643
- * Build an authority outcome projection from an assembled {@link ProgramResult}.
644
- *
645
- * @param result - The program result computed before authority runs
646
- * @returns A record shaped for the authority's `outcome` projection
647
- */
648
- function outcomeProjection(result) {
649
- const lines = {};
650
- for (const rated of result.lines) if (rated.amount !== void 0) lines[rated.id] = rated.amount;
651
- return {
652
- eligibility: result.eligibility,
653
- status: result.status,
654
- total: result.total,
655
- lines
656
- };
657
- }
658
- /**
659
- * Assemble a final {@link ProgramResult} from its rated parts.
660
- *
661
- * @param definition - The authored program definition
662
- * @param lines - The program's rated lines
663
- * @param determinations - The program-scoped determinations
664
- * @param derivations - The pass-level quantitative worksheets
665
- * @param total - The program's total amount, when the total handler produced one
666
- * @param decision - The authority-derived decision, when one was reached
667
- * @param trace - The accumulated reasoning trace
668
- * @param errors - The accumulated reasoning errors
669
- * @returns A fresh program result
670
- */
671
- function programResult(definition, lines, determinations, derivations, total, decision, trace, errors) {
672
- const eligibility = combineEligibilities([deriveDeterminationEligibility(determinations), ...lines.map((entry) => entry.eligibility)]);
673
- const allDeterminations = [...determinations, ...lines.flatMap((entry) => entry.determinations)];
674
- return {
675
- id: definition.id,
676
- name: definition.name,
677
- eligibility,
678
- status: deriveStatus(eligibility, allDeterminations, lines),
679
- ...decision === void 0 ? {} : { decision },
680
- lines,
681
- determinations,
682
- derivations,
683
- ...total === void 0 ? {} : { total },
684
- success: errors.length === 0 && lines.every((entry) => entry.worksheet?.success ?? true),
685
- trace,
686
- errors
687
- };
688
- }
689
- /**
690
- * Return authored line references (in passes, rulings, or notices) that name
691
- * no line on the program.
692
- *
693
- * @param definition - The program definition to check
694
- * @returns A fresh, deduped list of missing line ids
695
- */
696
- function findMissingLineReferences(definition) {
697
- const ids = new Set(definition.lines.map((entry) => entry.id));
698
- const missing = /* @__PURE__ */ new Set();
699
- for (const entry of definition.passes ?? []) if (entry.line !== void 0 && !ids.has(entry.line)) missing.add(entry.line);
700
- for (const entry of Object.values(definition.rulings ?? {})) if (entry.line !== void 0 && !ids.has(entry.line)) missing.add(entry.line);
701
- for (const entry of definition.notices ?? []) if (entry.line !== void 0 && !ids.has(entry.line)) missing.add(entry.line);
702
- return [...missing];
703
- }
704
- /** Determine whether a working record already carries a reserved rater key. */
705
- function hasReservedKey(subject) {
706
- return Object.hasOwn(subject, "aggregate") || Object.hasOwn(subject, "outcome");
707
- }
708
- /**
709
- * Locate a {@link ProgramDefinition}'s reserved-key collisions — a quantitative
710
- * pass whose id shadows {@link AGGREGATE_KEY} / {@link OUTCOME_KEY}, or a
711
- * logical pass's or the authority's fired rule conclusion whose asserted
712
- * field's FIRST path segment shadows one.
713
- *
714
- * @remarks
715
- * A quantitative pass writes its value onto the working subject under its own
716
- * definition id (`assignField`), silently clobbering the reserved aggregate or
717
- * outcome projection when the id collides. A fired rule's conclusion atoms
718
- * merge onto the working subject (`mergeSubjects`/`extractConclusions`) keyed
719
- * by `formatField(check.field)` — an atom whose field's first segment is a
720
- * reserved key is flagged here even when its FULL formatted key is a nested
721
- * form (`outcome.total`), since authoring a conclusion under a reserved
722
- * top-level segment is never intentional.
723
- *
724
- * @param definition - The program definition to scan
725
- * @returns A fresh, deduped list of offending pass or rule ids
726
- *
727
- * @example
728
- * ```ts
729
- * import { quantitativeDefinition } from '@orkestrel/reason'
730
- * import { findReservedCollisions, passDefinition, programDefinition } from '@orkestrel/rater'
731
- *
732
- * findReservedCollisions(
733
- * programDefinition('p1', 'P1', [], {
734
- * passes: [passDefinition(quantitativeDefinition('aggregate', 'Aggregate', []))],
735
- * }),
736
- * ) // ['aggregate']
737
- * ```
738
- */
739
- function findReservedCollisions(definition) {
740
- const offending = /* @__PURE__ */ new Set();
741
- const logicals = [];
742
- for (const pass of definition.passes ?? []) if (pass.definition.reasoning === "quantitative") {
743
- if (pass.definition.id === "aggregate" || pass.definition.id === "outcome") offending.add(pass.definition.id);
744
- } else if (pass.definition.reasoning === "logical") logicals.push(pass.definition);
745
- if (definition.authority !== void 0) logicals.push(definition.authority);
746
- for (const logical of logicals) for (const rule of logical.rules) for (const atom of (0, _orkestrel_reason.extractAtoms)(rule.conclusion)) {
747
- const head = Array.isArray(atom.check.field) ? atom.check.field[0] : atom.check.field;
748
- if (head === "aggregate" || head === "outcome") offending.add(rule.id);
749
- }
750
- return [...offending];
751
- }
752
- /**
753
- * Assert a value is a valid rater {@link Subject}, narrowing it in place.
754
- *
755
- * @param subject - The candidate subject to validate
756
- * @throws {@link RaterError} `'MISMATCH'` when the value is not a record, or
757
- * when it already carries a reserved rater working-subject key
758
- */
759
- function assertSubject(subject) {
760
- if (!(0, _orkestrel_contract.isRecord)(subject)) throw new RaterError("MISMATCH", "Subject must be a record");
761
- if (hasReservedKey(subject)) throw new RaterError("MISMATCH", "Subject uses a reserved rater key");
762
- }
763
- /**
764
- * Sum aggregate fields across a batch of subjects.
765
- *
766
- * @param subjects - The batch of subjects
767
- * @param fields - The fields to sum
768
- * @returns A fresh record of dot-joined field to summed finite value
769
- */
770
- function aggregateSums(subjects, fields) {
771
- const sums = {};
772
- for (const field of fields) sums[(0, _orkestrel_reason.formatField)(field)] = 0;
773
- for (const subject of subjects) for (const field of fields) {
774
- const key = (0, _orkestrel_reason.formatField)(field);
775
- const value = (0, _orkestrel_contract.resolveField)(subject, field);
776
- if ((0, _orkestrel_contract.isFiniteNumber)(value)) sums[key] = (sums[key] ?? 0) + value;
777
- }
778
- return sums;
779
- }
780
- /**
781
- * Partition a batch of subjects by a field, summing aggregate fields per
782
- * partition.
783
- *
784
- * @remarks
785
- * The partition key is the resolved `by` field, coerced with `String` —
786
- * `undefined` collapses to the empty string, so a subject missing the field
787
- * and a subject whose field is literally `''` land in the SAME partition,
788
- * and a numeric `1` collides with the string `'1'`.
789
- *
790
- * @param subjects - The batch of subjects
791
- * @param fields - The fields to sum within each partition
792
- * @param by - The partition key field; no partition is built when absent
793
- * @returns A fresh list of aggregate groups, or an empty list when `by` is absent
794
- */
795
- function aggregateGroups(subjects, fields, by) {
796
- if (by === void 0) return [];
797
- const groups = /* @__PURE__ */ new Map();
798
- for (const subject of subjects) {
799
- const key = String((0, _orkestrel_contract.resolveField)(subject, by) ?? "");
800
- const entries = groups.get(key) ?? [];
801
- entries.push(subject);
802
- groups.set(key, entries);
803
- }
804
- return [...groups.entries()].map(([key, entries]) => ({
805
- key,
806
- count: entries.length,
807
- sums: aggregateSums(entries, fields)
808
- }));
809
- }
810
- /**
811
- * Collect the deduped, ordered union of every program's aggregate fields.
812
- *
813
- * @param programs - The compiled programs to scan
814
- * @returns A fresh list of aggregate field paths, in first-seen order
815
- *
816
- * @example
817
- * ```ts
818
- * import { aggregateFields } from '@orkestrel/rater'
819
- *
820
- * aggregateFields(programs) // [['income'], ['assets']]
821
- * ```
822
- */
823
- function aggregateFields(programs) {
824
- const fields = [];
825
- const keys = /* @__PURE__ */ new Set();
826
- for (const program of programs) for (const field of program.definition.aggregate?.fields ?? []) {
827
- const key = (0, _orkestrel_reason.formatField)(field);
828
- if (!keys.has(key)) {
829
- keys.add(key);
830
- fields.push(field);
831
- }
832
- }
833
- return fields;
834
- }
835
- /**
836
- * Locate the {@link AggregateGroup} a subject belongs to.
379
+ * The rating orchestrator — owns (or receives) the shared quantitative
380
+ * reasoning engine and projects results into the rating domain vocabulary.
837
381
  *
838
382
  * @remarks
839
- * The lookup key is the resolved `by` field, coerced with `String` —
840
- * `undefined` collapses to the empty string, matching {@link aggregateGroups}'
841
- * partition-key coercion (a missing key and an empty-string key collide, and
842
- * a numeric `1` collides with the string `'1'`).
843
- *
844
- * @param subject - The subject to key
845
- * @param groups - The candidate groups to search
846
- * @param by - The partition key field; no group is found when absent
847
- * @returns The subject's matching group, or `undefined`
383
+ * When no `engine` is injected, `Rater` builds and OWNS its own
384
+ * quantitative-only engine (`bail: false`) — Rater performs NO evaluation
385
+ * arithmetic of its own; it only orchestrates and projects. The
386
+ * array-of-lines `rate` overload is declared FIRST (AGENTS §9.2). `destroy()`
387
+ * destroys an OWNED engine, then the emitter LAST (AGENTS §13); an INJECTED
388
+ * engine is never destroyed. Afterwards every other method throws
389
+ * {@link RaterError} `'DESTROYED'`.
848
390
  *
849
391
  * @example
850
392
  * ```ts
851
- * import { groupFor } from '@orkestrel/rater'
393
+ * import { Rater } from '@orkestrel/rater'
852
394
  *
853
- * groupFor({ region: 'east' }, [{ key: 'east', count: 1, sums: {} }], ['region'])
854
- * // { key: 'east', count: 1, sums: {} }
395
+ * const rater = new Rater()
396
+ * rater.rate([], { id: 'subject-1' }) // { lines: [], success: true }
397
+ * rater.destroy()
855
398
  * ```
856
399
  */
857
- function groupFor(subject, groups, by) {
858
- if (by === void 0) return void 0;
859
- const key = String((0, _orkestrel_contract.resolveField)(subject, by) ?? "");
860
- return groups.find((entry) => entry.key === key);
861
- }
862
- /**
863
- * Build the batch aggregate working projection written under
864
- * {@link AGGREGATE_KEY}'s value.
865
- *
866
- * @param count - The batch (or partition) subject count
867
- * @param sums - The summed aggregate fields
868
- * @param group - The subject's own partition, when the aggregate is partitioned
869
- * @returns A fresh aggregate projection record
870
- */
871
- function aggregateProjection(count, sums, group) {
872
- return {
873
- count,
874
- sums,
875
- ...group === void 0 ? {} : { group }
876
- };
877
- }
878
- /**
879
- * Build the reserved-key record an aggregate gate definition runs against.
880
- *
881
- * @param count - The batch (or partition) subject count
882
- * @param sums - The summed aggregate fields
883
- * @param group - The subject's own partition, when the aggregate is partitioned
884
- * @returns A fresh record carrying the aggregate projection under {@link AGGREGATE_KEY}
885
- */
886
- function aggregateRecord(count, sums, group) {
887
- return { [AGGREGATE_KEY]: aggregateProjection(count, sums, group) };
888
- }
889
- /**
890
- * Build zero sums for a set of fields.
891
- *
892
- * @param fields - The fields to zero
893
- * @returns A fresh record of dot-joined field to `0`
894
- */
895
- function emptySums(fields) {
896
- const sums = {};
897
- for (const field of fields) sums[(0, _orkestrel_reason.formatField)(field)] = 0;
898
- return sums;
899
- }
900
- /**
901
- * Complete a partial status tally record with zero entries for every missing
902
- * {@link Status}.
903
- *
904
- * @param entries - The partial tally entries to complete
905
- * @returns A record with all five statuses present
906
- */
907
- function completeTallies(entries) {
908
- return {
909
- ineligible: entries.ineligible ?? {
910
- count: 0,
911
- sums: {}
912
- },
913
- referral: entries.referral ?? {
914
- count: 0,
915
- sums: {}
916
- },
917
- conditional: entries.conditional ?? {
918
- count: 0,
919
- sums: {}
920
- },
921
- unrated: entries.unrated ?? {
922
- count: 0,
923
- sums: {}
924
- },
925
- eligible: entries.eligible ?? {
926
- count: 0,
927
- sums: {}
928
- }
929
- };
930
- }
931
- /**
932
- * Build empty status tallies in precedence order.
933
- *
934
- * @param fields - The fields each tally's sums are zeroed for
935
- * @returns A fresh, complete tally record
936
- */
937
- function emptyTallies(fields) {
938
- const entries = {};
939
- for (const status of STATUS_PRECEDENCE) entries[status] = {
940
- count: 0,
941
- sums: emptySums(fields)
942
- };
943
- return completeTallies(entries);
944
- }
945
- /**
946
- * Add one subject's aggregate contribution to a status tally record.
947
- *
948
- * @param tallies - The tallies to update
949
- * @param status - The subject's derived status
950
- * @param subject - The subject to fold in
951
- * @param fields - The fields to sum
952
- * @returns A fresh, complete tally record with the subject folded in
953
- */
954
- function tallySubject(tallies, status, subject, fields) {
955
- const updated = { ...tallies };
956
- const current = tallies[status];
957
- const sums = { ...current.sums };
958
- for (const field of fields) {
959
- const key = (0, _orkestrel_reason.formatField)(field);
960
- const value = (0, _orkestrel_contract.resolveField)(subject, field);
961
- if ((0, _orkestrel_contract.isFiniteNumber)(value)) sums[key] = (sums[key] ?? 0) + value;
962
- }
963
- updated[status] = {
964
- count: current.count + 1,
965
- sums
966
- };
967
- return completeTallies(updated);
968
- }
969
- //#endregion
970
- //#region src/core/programs/Program.ts
971
- /**
972
- * A compiled program — rates one subject at a time over an injected, shared
973
- * reasoning engine.
974
- *
975
- * @remarks
976
- * The engine is INJECTED and never owned, destroyed, or otherwise mutated by
977
- * the program: every actual evaluation (passes, lines, authority) delegates to
978
- * it. `rate` builds its working subject through copy-on-write overlays only —
979
- * the caller's `subject` is never mutated.
980
- */
981
- var Program = class {
982
- #definition;
983
- #engine;
984
- #evaluator;
985
- #total;
986
- #labels;
987
- constructor(definition, reason, options) {
988
- const missing = findMissingLineReferences(definition);
989
- if (missing.length > 0) throw new RaterError("MISSING", `Unknown line reference: ${missing.join(", ")}`, { program: definition.id });
990
- const collisions = findReservedCollisions(definition);
991
- if (collisions.length > 0) throw new RaterError("DEFINITION", `Reserved key collision: ${collisions.join(", ")}`, {
992
- program: definition.id,
993
- keys: collisions
994
- });
995
- this.#definition = definition;
996
- this.#engine = reason;
997
- this.#evaluator = (0, _orkestrel_reason.createEvaluator)();
998
- this.#total = options?.total;
999
- this.#labels = options?.labels;
1000
- }
1001
- get id() {
1002
- return this.#definition.id;
1003
- }
1004
- get name() {
1005
- return this.#definition.name;
1006
- }
1007
- get definition() {
1008
- return this.#definition;
1009
- }
1010
- rate(subject, aggregate) {
1011
- assertSubject(subject);
1012
- let working = aggregate === void 0 ? subject : (0, _orkestrel_reason.assignField)(subject, AGGREGATE_KEY, aggregate);
1013
- const determinations = [];
1014
- const derivations = [];
1015
- const trace = [];
1016
- const errors = [];
1017
- for (const pass of this.#definition.passes ?? []) if (pass.definition.reasoning === "quantitative") {
1018
- const result = this.#engine.reason(working, pass.definition);
1019
- trace.push(...result.trace);
1020
- errors.push(...result.errors);
1021
- if (result.reasoning === "quantitative") {
1022
- working = (0, _orkestrel_reason.assignField)(working, pass.definition.id, result.value);
1023
- derivations.push(resultsWorksheet(pass.definition, result, this.#labels));
1024
- }
1025
- } else {
1026
- const result = this.#engine.reason(working, pass.definition);
1027
- trace.push(...result.trace);
1028
- errors.push(...result.errors);
1029
- if (result.reasoning === "logical") {
1030
- determinations.push(...rulesToDeterminations(pass.definition, result, this.#definition.rulings, working, pass.line, this.#evaluator, this.#labels));
1031
- for (const entry of result.rules) {
1032
- if (!entry.applied) continue;
1033
- const rule = findRule(pass.definition, entry.id);
1034
- if (rule !== void 0) working = (0, _orkestrel_reason.mergeSubjects)(working, (0, _orkestrel_reason.extractConclusions)(rule.conclusion));
1035
- }
1036
- }
1037
- }
1038
- determinations.push(...noticesToDeterminations(this.#definition.notices, working));
1039
- const scoped = filterProgramDeterminations(determinations);
1040
- const lines = this.#rateLines(working, scoped, determinations);
1041
- const total = this.#total?.(lines);
1042
- const base = programResult(this.#definition, lines, scoped, derivations, total, void 0, trace, errors);
1043
- if (this.#definition.authority === void 0) return base;
1044
- const authorityWorking = (0, _orkestrel_reason.assignField)(working, OUTCOME_KEY, outcomeProjection(base));
1045
- const authorityResult = this.#engine.reason(authorityWorking, this.#definition.authority);
1046
- if (authorityResult.reasoning !== "logical") return base;
1047
- const limits = authorityToDeterminations(this.#definition.authority, authorityResult, this.#definition.rulings, authorityWorking, this.#evaluator, this.#labels);
1048
- const decision = limits.length === 0 && authorityResult.errors.length === 0 ? decideEligibility(base.eligibility) : void 0;
1049
- return programResult(this.#definition, lines, [...scoped, ...limits], derivations, total, decision, [...trace, ...authorityResult.trace], [...errors, ...authorityResult.errors]);
1050
- }
1051
- #rateLines(working, context, determinations) {
1052
- const lines = [];
1053
- for (const entry of this.#definition.lines) {
1054
- const own = filterLineDeterminations(determinations, entry.id);
1055
- const line = ratedLine(entry, this.#quantitative(working, entry.rate), own, this.#labels);
1056
- lines.push({
1057
- ...line,
1058
- eligibility: deriveDeterminationEligibility([...context, ...line.determinations])
1059
- });
1060
- }
1061
- return lines;
1062
- }
1063
- #quantitative(subject, definition) {
1064
- const result = this.#engine.reason(subject, definition);
1065
- if (result.reasoning === "quantitative") return result;
1066
- const failure = (0, _orkestrel_reason.buildErrorResult)(definition, `Expected quantitative result, got ${result.reasoning}`);
1067
- const errors = [...result.errors, ...failure.errors];
1068
- if (failure.reasoning === "quantitative") return {
1069
- ...failure,
1070
- errors
1071
- };
1072
- return {
1073
- reasoning: "quantitative",
1074
- value: 0,
1075
- groups: [],
1076
- count: 0,
1077
- success: false,
1078
- trace: [],
1079
- errors
1080
- };
1081
- }
1082
- };
1083
- //#endregion
1084
- //#region src/core/programs/ProgramManager.ts
1085
- /**
1086
- * An ordered manager over compiled {@link ProgramInterface}s (AGENTS §9), built
1087
- * over an injected, shared reasoning engine.
1088
- *
1089
- * @remarks
1090
- * OWNS its `#programs` collection and its own {@link Emitter} over
1091
- * {@link ProgramManagerEventMap}. `destroy()` is idempotent and tears the
1092
- * emitter down LAST; every other call afterwards throws
1093
- * {@link RaterError} `'DESTROYED'`.
1094
- */
1095
- var ProgramManager = class {
1096
- #programs = /* @__PURE__ */ new Map();
1097
- #engine;
400
+ var Rater = class {
1098
401
  #emitter;
402
+ #engine;
403
+ #owned;
1099
404
  #total;
1100
405
  #labels;
1101
- #validate;
1102
406
  #destroyed = false;
1103
- constructor(engine, options) {
1104
- this.#engine = engine;
407
+ constructor(options) {
1105
408
  this.#emitter = new _orkestrel_emitter.Emitter({
1106
409
  on: options?.on,
1107
410
  error: options?.error
1108
411
  });
412
+ if (options?.engine === void 0) {
413
+ this.#engine = (0, _orkestrel_reason.createReason)({
414
+ reasoners: [(0, _orkestrel_reason.createQuantitativeReasoner)()],
415
+ bail: false
416
+ });
417
+ this.#owned = true;
418
+ } else {
419
+ this.#engine = options.engine;
420
+ this.#owned = false;
421
+ }
1109
422
  this.#total = options?.total;
1110
423
  this.#labels = options?.labels;
1111
- this.#validate = options?.validate ?? true;
1112
424
  }
1113
425
  get emitter() {
1114
426
  return this.#emitter;
1115
427
  }
1116
- get size() {
1117
- this.#ensureAlive();
1118
- return this.#programs.size;
1119
- }
1120
- has(id) {
1121
- this.#ensureAlive();
1122
- return this.#programs.has(id);
1123
- }
1124
- program(id) {
1125
- this.#ensureAlive();
1126
- return this.#programs.get(id);
1127
- }
1128
- programs() {
428
+ rate(input, subject) {
1129
429
  this.#ensureAlive();
1130
- return [...this.#programs.values()];
1131
- }
1132
- add(definition, options) {
1133
- this.#ensureAlive();
1134
- if (this.#programs.has(definition.id)) throw new RaterError("DUPLICATE", `Program already exists: ${definition.id}`, { program: definition.id });
1135
- const id = definition.id;
1136
- if (this.#validate && !isProgramDefinition(definition)) throw new RaterError("DEFINITION", "Program definition failed validation", { program: id });
1137
- const program = new Program(definition, this.#engine, {
1138
- total: options?.total ?? this.#total,
1139
- labels: options?.labels ?? this.#labels
1140
- });
1141
- this.#programs.set(program.id, program);
1142
- this.#emitter.emit("add", program.id);
1143
- return program;
1144
- }
1145
- remove(target) {
1146
- this.#ensureAlive();
1147
- if (target === void 0) {
1148
- for (const id of this.#programs.keys()) this.#emitter.emit("remove", id);
1149
- this.#programs.clear();
1150
- return;
1151
- }
1152
- if (typeof target === "string") return this.#removeOne(target);
1153
- let removed = true;
1154
- for (const id of target) removed = this.#removeOne(id) && removed;
1155
- return removed;
1156
- }
1157
- destroy() {
1158
- if (this.#destroyed) return;
1159
- this.#programs.clear();
1160
- this.#destroyed = true;
1161
- this.#emitter.emit("destroy");
1162
- this.#emitter.destroy();
1163
- }
1164
- #removeOne(id) {
1165
- const removed = this.#programs.delete(id);
1166
- if (removed) this.#emitter.emit("remove", id);
1167
- return removed;
1168
- }
1169
- #ensureAlive() {
1170
- if (this.#destroyed) throw new RaterError("DESTROYED", "ProgramManager has been destroyed");
1171
- }
1172
- };
1173
- //#endregion
1174
- //#region src/core/Rater.ts
1175
- /**
1176
- * The rating orchestrator — owns the shared reasoning engine and an ordered
1177
- * {@link ProgramManager}, and projects results into the rating domain
1178
- * vocabulary.
1179
- *
1180
- * @remarks
1181
- * Constructs ONE shared reason engine (quantitative + logical reasoners,
1182
- * `bail: false`) and injects it into every compiled program — Rater performs
1183
- * NO evaluation arithmetic of its own; it only orchestrates and projects. The
1184
- * batch `rate` overload is declared FIRST (AGENTS §9.2) so an array subject
1185
- * resolves to the batch form. `destroy()` tears down the manager, then the
1186
- * engine, then the emitter LAST (AGENTS §13); afterwards every other method
1187
- * throws {@link RaterError} `'DESTROYED'`.
1188
- */
1189
- var Rater = class {
1190
- #emitter;
1191
- #manager;
1192
- #engine;
1193
- #evaluator;
1194
- #destroyed = false;
1195
- constructor(options) {
1196
- this.#emitter = new _orkestrel_emitter.Emitter({
1197
- on: options?.on,
1198
- error: options?.error
1199
- });
1200
- this.#engine = (0, _orkestrel_reason.createReason)({
1201
- reasoners: [(0, _orkestrel_reason.createQuantitativeReasoner)(), (0, _orkestrel_reason.createLogicalReasoner)()],
1202
- bail: false
1203
- });
1204
- this.#evaluator = (0, _orkestrel_reason.createEvaluator)();
1205
- this.#manager = new ProgramManager(this.#engine, {
1206
- total: options?.total,
1207
- labels: options?.labels,
1208
- validate: options?.validate
1209
- });
1210
- for (const definition of options?.programs ?? []) this.#manager.add(definition);
1211
- }
1212
- get emitter() {
1213
- return this.#emitter;
1214
- }
1215
- get programs() {
1216
- return this.#manager;
1217
- }
1218
- rate(subject) {
1219
- this.#ensureAlive();
1220
- if ((0, _orkestrel_contract.isRecord)(subject)) return this.#rateOne(subject);
1221
- return this.#rateBatch(subject);
430
+ const lines = this.#normalize(input);
431
+ if (!(0, _orkestrel_contract.isRecord)(subject)) throw new RaterError("MISMATCH", "Subject must be a record");
432
+ const results = lines.map((line) => this.#rateLine(line, subject));
433
+ const total = (this.#total ?? sumAmounts)(results);
434
+ const success = results.every((entry) => entry.success);
435
+ const result = {
436
+ lines: results,
437
+ ...total === void 0 ? {} : { total },
438
+ success
439
+ };
440
+ this.#emitter.emit("rate", subject, result);
441
+ return result;
1222
442
  }
1223
443
  destroy() {
1224
444
  if (this.#destroyed) return;
1225
- this.#manager.destroy();
1226
- this.#engine.destroy();
1227
445
  this.#destroyed = true;
446
+ if (this.#owned) this.#engine.destroy();
1228
447
  this.#emitter.destroy();
1229
448
  }
1230
- #rateOne(subject) {
1231
- assertSubject(subject);
1232
- const result = this.#subject(subject);
1233
- this.#emitSubject(result);
1234
- return result;
449
+ #normalize(input) {
450
+ if ((0, _orkestrel_contract.arrayOf)(isLineDefinition)(input)) return input;
451
+ if (isRatingDefinition(input)) return input.lines;
452
+ throw new RaterError("DEFINITION", "Rating input must be an array of line definitions or a rating definition");
1235
453
  }
1236
- #rateBatch(subjects) {
1237
- for (const subject of subjects) assertSubject(subject);
1238
- const programs = this.#manager.programs();
1239
- const sums = aggregateSums(subjects, aggregateFields(programs));
1240
- const programSums = /* @__PURE__ */ new Map();
1241
- const programGroups = /* @__PURE__ */ new Map();
1242
- for (const program of programs) {
1243
- const aggregate = program.definition.aggregate;
1244
- if (aggregate === void 0) continue;
1245
- programSums.set(program.id, aggregateSums(subjects, aggregate.fields));
1246
- programGroups.set(program.id, aggregateGroups(subjects, aggregate.fields, aggregate.by));
1247
- }
1248
- const groups = this.#groups(programs, programGroups);
1249
- const determinations = this.#aggregateDeterminations(programs, programSums, programGroups, subjects.length);
1250
- const subjectsResult = subjects.map((subject) => this.#subject(subject, this.#projections(subject, programs, programSums, programGroups, subjects.length)));
1251
- const result = {
1252
- subjects: subjectsResult,
1253
- determinations,
1254
- groups,
1255
- tallies: this.#tallies(subjects, subjectsResult, programs),
1256
- count: subjects.length,
1257
- sums
1258
- };
1259
- for (const rated of subjectsResult) this.#emitSubject(rated);
1260
- for (const determination of determinations) if (determination.applied) this.#emitter.emit("determine", determination);
1261
- this.#emitter.emit("aggregate", result);
1262
- return result;
1263
- }
1264
- #subject(subject, projections) {
1265
- return {
1266
- subject,
1267
- programs: this.#manager.programs().map((program) => {
1268
- const projection = projections?.[program.id];
1269
- return projection === void 0 ? program.rate(subject) : program.rate(subject, projection);
1270
- })
1271
- };
454
+ #rateLine(line, subject) {
455
+ return ratedLine(line, this.#reasonQuantitative(subject, line.rate), this.#labels);
1272
456
  }
1273
- #emitSubject(result) {
1274
- for (const program of result.programs) {
1275
- const determinations = [...program.determinations, ...program.lines.flatMap((rated) => rated.determinations)];
1276
- for (const determination of determinations) if (determination.applied) this.#emitter.emit("determine", determination);
1277
- if (program.decision !== void 0) this.#emitter.emit("decide", program.decision, program);
1278
- }
1279
- this.#emitter.emit("rate", result);
1280
- }
1281
- #groups(programs, programGroups) {
1282
- const output = [];
1283
- for (const program of programs) if (program.definition.aggregate?.by !== void 0) output.push(...programGroups.get(program.id) ?? []);
1284
- return output;
1285
- }
1286
- #projections(subject, programs, programSums, programGroups, count) {
1287
- const output = {};
1288
- for (const program of programs) {
1289
- const aggregate = program.definition.aggregate;
1290
- if (aggregate === void 0) continue;
1291
- const sums = programSums.get(program.id);
1292
- if (sums === void 0) continue;
1293
- const group = groupFor(subject, programGroups.get(program.id) ?? [], aggregate.by);
1294
- output[program.id] = aggregateProjection(count, sums, group);
1295
- }
1296
- return output;
1297
- }
1298
- #aggregateDeterminations(programs, programSums, programGroups, count) {
1299
- const determinations = [];
1300
- for (const program of programs) {
1301
- const aggregate = program.definition.aggregate;
1302
- if (aggregate?.gates === void 0) continue;
1303
- const sums = programSums.get(program.id);
1304
- if (sums === void 0) continue;
1305
- determinations.push(...this.#gate(aggregate.gates, aggregateRecord(count, sums), program.definition.rulings));
1306
- for (const group of programGroups.get(program.id) ?? []) determinations.push(...this.#gate(aggregate.gates, aggregateRecord(group.count, group.sums, group), program.definition.rulings));
1307
- }
1308
- return determinations;
1309
- }
1310
- #gate(definition, record, rulings) {
1311
- return rulesToDeterminations(definition, this.#reasonLogical(record, definition), rulings, record, void 0, this.#evaluator);
1312
- }
1313
- #tallies(subjects, results, programs) {
1314
- const output = {};
1315
- for (const program of programs) {
1316
- let tallies = emptyTallies(program.definition.aggregate?.fields ?? []);
1317
- for (let index = 0; index < results.length; index += 1) {
1318
- const subject = subjects[index];
1319
- const rated = results[index]?.programs.find((entry) => entry.id === program.id);
1320
- if (subject !== void 0 && rated !== void 0) tallies = tallySubject(tallies, rated.status, subject, program.definition.aggregate?.fields ?? []);
1321
- }
1322
- output[program.id] = tallies;
1323
- }
1324
- return output;
1325
- }
1326
- #reasonLogical(subject, definition) {
457
+ #reasonQuantitative(subject, definition) {
1327
458
  const result = this.#engine.reason(subject, definition);
1328
- if (result.reasoning === "logical") return result;
1329
- const failure = (0, _orkestrel_reason.buildErrorResult)(definition, `Expected logical result, got ${result.reasoning}`);
459
+ if (result.reasoning === "quantitative") return result;
460
+ const failure = (0, _orkestrel_reason.buildErrorResult)(definition, `Expected quantitative result, got ${result.reasoning}`);
1330
461
  const errors = [...result.errors, ...failure.errors];
1331
- if (failure.reasoning === "logical") return {
462
+ if (failure.reasoning === "quantitative") return {
1332
463
  ...failure,
1333
464
  errors
1334
465
  };
1335
466
  return {
1336
- reasoning: "logical",
1337
- conclusion: false,
1338
- rules: [],
467
+ reasoning: "quantitative",
468
+ value: 0,
469
+ groups: [],
1339
470
  count: 0,
1340
471
  success: false,
1341
472
  trace: [],
@@ -1351,7 +482,7 @@ var Rater = class {
1351
482
  /**
1352
483
  * Create a rating orchestrator.
1353
484
  *
1354
- * @param options - Optional total handler, seed programs, labels, validation policy, and emitter hooks
485
+ * @param options - Optional total handler, labels, injected engine, and emitter hooks
1355
486
  * @returns A {@link RaterInterface}
1356
487
  *
1357
488
  * @example
@@ -1366,75 +497,6 @@ function createRater(options) {
1366
497
  return new Rater(options);
1367
498
  }
1368
499
  /**
1369
- * Create and validate one compiled program over a shared reasoning engine.
1370
- *
1371
- * @param definition - The authored program definition
1372
- * @param reason - The shared reasoning engine, injected (never owned or destroyed by the program)
1373
- * @param options - Optional total handler and labels
1374
- * @returns A {@link ProgramInterface}
1375
- * @throws {@link RaterError} `'DEFINITION'` when the definition fails {@link isProgramDefinition}
1376
- *
1377
- * @example
1378
- * ```ts
1379
- * import { createLogicalReasoner, createQuantitativeReasoner, createReason } from '@orkestrel/reason'
1380
- * import { createProgram, programDefinition } from '@orkestrel/rater'
1381
- *
1382
- * const reason = createReason({ reasoners: [createQuantitativeReasoner(), createLogicalReasoner()], bail: false })
1383
- * const program = createProgram(programDefinition('p1', 'Program', []), reason)
1384
- * ```
1385
- */
1386
- function createProgram(definition, reason, options) {
1387
- const id = (0, _orkestrel_contract.isRecord)(definition) ? definition.id : void 0;
1388
- if (!isProgramDefinition(definition)) throw new RaterError("DEFINITION", "Program definition failed validation", { program: id });
1389
- return new Program(definition, reason, options);
1390
- }
1391
- /**
1392
- * Create an ordered manager over compiled programs (AGENTS §9), built over an
1393
- * injected, shared reasoning engine.
1394
- *
1395
- * @param reason - The shared reasoning engine, injected into every compiled program
1396
- * @param options - Optional total handler, labels, validation policy, and emitter hooks
1397
- * @returns A {@link ProgramManagerInterface}
1398
- *
1399
- * @example
1400
- * ```ts
1401
- * import { createLogicalReasoner, createQuantitativeReasoner, createReason } from '@orkestrel/reason'
1402
- * import { createProgramManager, programDefinition } from '@orkestrel/rater'
1403
- *
1404
- * const reason = createReason({ reasoners: [createQuantitativeReasoner(), createLogicalReasoner()], bail: false })
1405
- * const manager = createProgramManager(reason)
1406
- * manager.add(programDefinition('p1', 'Program', []))
1407
- * manager.destroy()
1408
- * ```
1409
- */
1410
- function createProgramManager(reason, options) {
1411
- return new ProgramManager(reason, options);
1412
- }
1413
- /**
1414
- * Build a {@link ProgramDefinition}.
1415
- *
1416
- * @param id - The program id
1417
- * @param name - The display name
1418
- * @param lines - The program's rateable lines
1419
- * @param overrides - Optional {@link ProgramDefinition} fields merged over the defaults
1420
- * @returns A fresh program definition
1421
- *
1422
- * @example
1423
- * ```ts
1424
- * import { programDefinition } from '@orkestrel/rater'
1425
- *
1426
- * programDefinition('p1', 'Program', [])
1427
- * ```
1428
- */
1429
- function programDefinition(id, name, lines, overrides) {
1430
- return {
1431
- id,
1432
- name,
1433
- lines,
1434
- ...overrides
1435
- };
1436
- }
1437
- /**
1438
500
  * Build a {@link LineDefinition}.
1439
501
  *
1440
502
  * @param id - The line id
@@ -1460,163 +522,47 @@ function lineDefinition(id, name, rate, overrides) {
1460
522
  };
1461
523
  }
1462
524
  /**
1463
- * Build a {@link PassDefinition}.
525
+ * Build a {@link RatingDefinition}.
1464
526
  *
1465
- * @param definition - The pass's logical or quantitative definition
1466
- * @param line - The line id this pass is scoped to, when it is line-scoped
1467
- * @returns A fresh pass definition
527
+ * @param id - The rating id
528
+ * @param name - The display name
529
+ * @param lines - The rating's ordered lines
530
+ * @param overrides - Optional {@link RatingDefinition} fields merged over the defaults
531
+ * @returns A fresh rating definition
1468
532
  *
1469
533
  * @example
1470
534
  * ```ts
535
+ * import { lineDefinition, ratingDefinition } from '@orkestrel/rater'
1471
536
  * import { quantitativeDefinition } from '@orkestrel/reason'
1472
- * import { passDefinition } from '@orkestrel/rater'
1473
537
  *
1474
- * passDefinition(quantitativeDefinition('surcharge', 'Surcharge', []))
538
+ * ratingDefinition('r1', 'Rating', [
539
+ * lineDefinition('base', 'Base Amount', quantitativeDefinition('base', 'Base', [])),
540
+ * ])
1475
541
  * ```
1476
542
  */
1477
- function passDefinition(definition, line) {
1478
- return {
1479
- definition,
1480
- ...line === void 0 ? {} : { line }
1481
- };
1482
- }
1483
- /**
1484
- * Build a {@link Ruling}.
1485
- *
1486
- * @param effect - The determination effect a fired rule routes to
1487
- * @param line - The line id this ruling is scoped to, when it is line-scoped
1488
- * @param message - An interpolated message template, when the ruling carries one
1489
- * @returns A fresh ruling
1490
- *
1491
- * @example
1492
- * ```ts
1493
- * import { rulingDefinition } from '@orkestrel/rater'
1494
- *
1495
- * rulingDefinition('restriction', 'base', 'Amount exceeds {{limit}}')
1496
- * ```
1497
- */
1498
- function rulingDefinition(effect, line, message) {
1499
- return {
1500
- effect,
1501
- ...line === void 0 ? {} : { line },
1502
- ...message === void 0 ? {} : { message }
1503
- };
1504
- }
1505
- /**
1506
- * Build a {@link Notice}.
1507
- *
1508
- * @param id - The notice id
1509
- * @param message - An interpolated message template
1510
- * @param line - The line id this notice is scoped to, when it is line-scoped
1511
- * @returns A fresh notice
1512
- *
1513
- * @example
1514
- * ```ts
1515
- * import { noticeDefinition } from '@orkestrel/rater'
1516
- *
1517
- * noticeDefinition('n1', 'Rated on {{date}}')
1518
- * ```
1519
- */
1520
- function noticeDefinition(id, message, line) {
543
+ function ratingDefinition(id, name, lines, overrides) {
1521
544
  return {
1522
545
  id,
1523
- message,
1524
- ...line === void 0 ? {} : { line }
1525
- };
1526
- }
1527
- /**
1528
- * Build an {@link AggregateDefinition}.
1529
- *
1530
- * @param fields - The batch fields to sum
1531
- * @param by - The partition key field, when the aggregate is partitioned
1532
- * @param gates - The logical definition gating aggregate determinations, when present
1533
- * @returns A fresh aggregate definition
1534
- *
1535
- * @example
1536
- * ```ts
1537
- * import { aggregateDefinition } from '@orkestrel/rater'
1538
- *
1539
- * aggregateDefinition(['amount'])
1540
- * ```
1541
- */
1542
- function aggregateDefinition(fields, by, gates) {
1543
- return {
1544
- fields,
1545
- ...by === void 0 ? {} : { by },
1546
- ...gates === void 0 ? {} : { gates }
546
+ name,
547
+ lines,
548
+ ...overrides
1547
549
  };
1548
550
  }
1549
551
  //#endregion
1550
- exports.AGGREGATE_KEY = AGGREGATE_KEY;
1551
- exports.DEFAULT_RATER_VALIDATE = DEFAULT_RATER_VALIDATE;
1552
- exports.EFFECT_ELIGIBILITIES = EFFECT_ELIGIBILITIES;
1553
- exports.ELIGIBILITY_DECISIONS = ELIGIBILITY_DECISIONS;
1554
- exports.ELIGIBILITY_PRECEDENCE = ELIGIBILITY_PRECEDENCE;
1555
- exports.OUTCOME_KEY = OUTCOME_KEY;
1556
- exports.Program = Program;
1557
- exports.ProgramManager = ProgramManager;
1558
552
  exports.Rater = Rater;
1559
553
  exports.RaterError = RaterError;
1560
- exports.STATUS_PRECEDENCE = STATUS_PRECEDENCE;
1561
- exports.aggregateDefinition = aggregateDefinition;
1562
- exports.aggregateFields = aggregateFields;
1563
- exports.aggregateGroups = aggregateGroups;
1564
- exports.aggregateProjection = aggregateProjection;
1565
- exports.aggregateRecord = aggregateRecord;
1566
- exports.aggregateSums = aggregateSums;
1567
- exports.assertSubject = assertSubject;
1568
- exports.authorityToDeterminations = authorityToDeterminations;
1569
554
  exports.checkPremises = checkPremises;
1570
- exports.combineEligibilities = combineEligibilities;
1571
- exports.completeTallies = completeTallies;
1572
- exports.createProgram = createProgram;
1573
- exports.createProgramManager = createProgramManager;
1574
555
  exports.createRater = createRater;
1575
- exports.decideEligibility = decideEligibility;
1576
- exports.deriveDeterminationEligibility = deriveDeterminationEligibility;
1577
- exports.deriveStatus = deriveStatus;
1578
- exports.describeComparison = describeComparison;
1579
- exports.describeExpression = describeExpression;
1580
- exports.describePremise = describePremise;
1581
- exports.describeValue = describeValue;
1582
- exports.emptySums = emptySums;
1583
- exports.emptyTallies = emptyTallies;
1584
- exports.filterLineDeterminations = filterLineDeterminations;
1585
- exports.filterProgramDeterminations = filterProgramDeterminations;
1586
- exports.findMissingLineReferences = findMissingLineReferences;
1587
- exports.findReservedCollisions = findReservedCollisions;
1588
- exports.findRule = findRule;
1589
- exports.groupFor = groupFor;
1590
- exports.hasReservedKey = hasReservedKey;
1591
- exports.interpolateMessage = interpolateMessage;
1592
- exports.isAggregateDefinition = isAggregateDefinition;
1593
- exports.isDecision = isDecision;
1594
- exports.isEffect = isEffect;
1595
- exports.isEligibility = isEligibility;
1596
556
  exports.isLineDefinition = isLineDefinition;
1597
- exports.isNotice = isNotice;
1598
- exports.isPassDefinition = isPassDefinition;
1599
- exports.isProgramDefinition = isProgramDefinition;
1600
557
  exports.isRaterError = isRaterError;
1601
- exports.isRuling = isRuling;
1602
- exports.isRulings = isRulings;
558
+ exports.isRatingDefinition = isRatingDefinition;
1603
559
  exports.isStage = isStage;
1604
- exports.isStatus = isStatus;
1605
560
  exports.lineDefinition = lineDefinition;
1606
- exports.logicalPremises = logicalPremises;
1607
- exports.noticeDefinition = noticeDefinition;
1608
- exports.noticesToDeterminations = noticesToDeterminations;
1609
- exports.outcomeProjection = outcomeProjection;
1610
- exports.passDefinition = passDefinition;
1611
561
  exports.premiseCheck = premiseCheck;
1612
- exports.programDefinition = programDefinition;
1613
- exports.programResult = programResult;
1614
562
  exports.ratedLine = ratedLine;
563
+ exports.ratingDefinition = ratingDefinition;
1615
564
  exports.resultsWorksheet = resultsWorksheet;
1616
- exports.rulesToDeterminations = rulesToDeterminations;
1617
- exports.rulingDefinition = rulingDefinition;
1618
565
  exports.sumAmounts = sumAmounts;
1619
- exports.tallySubject = tallySubject;
1620
566
  exports.worksheetFactor = worksheetFactor;
1621
567
  exports.worksheetGroup = worksheetGroup;
1622
568
  exports.worksheetStep = worksheetStep;