factbag 0.0.0-alpha.5 → 0.0.0-alpha.6
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/CHANGELOG.md +17 -0
- package/README.md +17 -25
- package/dist/dsl.d.ts +26 -4
- package/dist/dsl.js +34 -3
- package/dist/exceptions.d.ts +1 -1
- package/dist/exceptions.js +1 -1
- package/dist/explain.d.ts +50 -21
- package/dist/explain.js +266 -50
- package/dist/{goal-BP745uKh.d.ts → goal-BBoSfPUN.d.ts} +6 -4
- package/dist/{judgment-Cr0JcUt5.d.ts → judgment-v8i3ks5N.d.ts} +1 -1
- package/dist/kb.d.ts +3 -3
- package/dist/kb.js +2 -2
- package/dist/{knowledge-base-Da5HLCsY.d.ts → knowledge-base-CkU_xzmB.d.ts} +1 -1
- package/dist/{programmer-errors-DDfy03Y0.js → programmer-errors-DLE1xWJ_.js} +7 -1
- package/dist/{query-Dxm7EpxZ.js → query-CN6EOPwp.js} +21 -10
- package/dist/{types-0ZvnLf2N.js → types-DeYyybXd.js} +10 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# factbag
|
|
2
2
|
|
|
3
|
+
## 0.0.0-alpha.6
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7225a51: Add `firstOfMonth(year, month)`, a date-valued arithmetic term for the first day of a month at UTC midnight, and `yearOf(date)`, a number-valued term for a date's UTC calendar year. Rules can now derive a year's bounds, such as `firstOfMonth(Y, 1)` and `sub(firstOfMonth(add(Y, 1), 1), 86_400_000)`, instead of looking them up in asserted calendar facts. A month outside 1 to 12, or a year that is not a whole number, throws `PrimitiveArithmeticProgrammerError` rather than rolling over. The query API adds `FirstOfMonthGoal` and the `"firstOfMonth"` arithmetic kind.
|
|
8
|
+
- 5114b2f: Show the `citation` and `justification` recorded with a piece of evidence in its explanation, beside its `source`. An evidence reason no longer repeats the claim of the step it supports; it names its own claim only when that differs, as in a conflict, or when the evidence has no provenance. `EvidenceReason.quote`, which was never filled, is replaced by `citation`.
|
|
9
|
+
- eab3679: Explain a rule application with the rule alone, `eligible(P) iff not(active_sanction(P))`, without the trailing `, with P = alice`. The values a rule was applied to are already written into the claim it supports and the claims it rests on.
|
|
10
|
+
- e4b343e: Add a `budget` option to `explainJudgment`: the most claims to explain. The conclusion is always explained, then the claims that decided it before those that were evaluated and changed nothing, and those near the conclusion before those far from it. A claim that rests on claims left out names them, with their values, under `notShown`. Without a budget the whole proof is explained, as before; written as JSON a deep proof repeats each shared claim on every path to it, so give a budget when explaining a judgment in text.
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 7f26b45: Write explanation claims with the values their variables hold: `lte(1000, 36900) = true` instead of `lte(I, 36900) = true`, and `spouse(alice, S) = false` instead of `spouse(P, S) = false`. A variable with no value keeps its name. A term that computes a value states it, such as `H = max(1000, 500) = 1000`, instead of ending in `= true`.
|
|
15
|
+
- b7c85ff: Explain a comparison, arithmetic term or aggregate as `computed` instead of `no supporting evidence or rule was found`. When one could not be evaluated because a variable has no value, the explanation names the variable, such as `X has no value`.
|
|
16
|
+
- ed6ec84: Write a date at UTC midnight as a day in explanations, `2015-12-31` instead of `2015-12-31T00:00:00.000Z`. A date with a time of day keeps its full timestamp.
|
|
17
|
+
- 15391f2: Rest a rule application directly on the subgoals of its body in explanations. When a rule's body is an `and` or an `or`, the explanation no longer has a node that restates the body between the rule and its subgoals: the rule's `given` lists the subgoals themselves. A compound nested inside a body is still its own node, and so is a body that is a single atom or a negation.
|
|
18
|
+
- ecbd8d9: Stop naming the solver's temporaries, such as `$tmp0`, in explanations. A nested arithmetic term is written as the term and its value, `mul(1000, 0.15) = 150`; a term that could not be computed stands in for its result wherever that is used, `gte(add(X, 1), 18) = unknown`; and a rule's `with ...` list gives only the rule's own variables.
|
|
19
|
+
|
|
3
20
|
## 0.0.0-alpha.5
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -221,33 +221,25 @@ An abbreviated example with supporting evidence for all three claims:
|
|
|
221
221
|
```json
|
|
222
222
|
{
|
|
223
223
|
"claim": "is_qualified_investor(alice) = true",
|
|
224
|
-
"because": "is_qualified_investor(person) if and(...)
|
|
224
|
+
"because": "is_qualified_investor(person) if and(...)",
|
|
225
225
|
"given": [
|
|
226
226
|
{
|
|
227
|
-
"claim": "
|
|
228
|
-
"
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
{
|
|
244
|
-
"claim": "expects_same_income(alice) = true",
|
|
245
|
-
"because": {
|
|
246
|
-
"claim": "expects_same_income(alice) = true",
|
|
247
|
-
"source": "alice-2026-employment-confirmation-letter#0-1"
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
]
|
|
227
|
+
"claim": "has_income_over_200k(alice, 2024) = true",
|
|
228
|
+
"because": {
|
|
229
|
+
"source": "alice-2024-federal-tax-return#0-1"
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"claim": "has_income_over_200k(alice, 2025) = true",
|
|
234
|
+
"because": {
|
|
235
|
+
"source": "alice-2025-federal-tax-return#0-1"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"claim": "expects_same_income(alice) = true",
|
|
240
|
+
"because": {
|
|
241
|
+
"source": "alice-2026-employment-confirmation-letter#0-1"
|
|
242
|
+
}
|
|
251
243
|
}
|
|
252
244
|
]
|
|
253
245
|
}
|
package/dist/dsl.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { n as KnowledgeBase } from "./knowledge-base-
|
|
3
|
-
import { C as Bindings, S as Binding, _ as QueryAllJudgment, a as ErasedEvidenceSupport, b as SubgoalsSupport, c as Judgment, d as MissingBinding, f as NumericRange, g as PrimitiveUncertainty, h as PrimitiveSupportFor, i as CountPrimitiveSupport, l as JudgmentBase, m as PrimitiveSupport, n as ClosedWorldAbsenceSupport, o as ErasedRuleSupport, p as PrimitiveEvaluation, r as ComparisonPrimitiveSupport, s as EvidenceSupport, t as ArithmeticPrimitiveSupport, u as JudgmentSupportFor, v as QueryJudgment, w as GoalValue, x as SumPrimitiveSupport, y as RuleSupport } from "./judgment-
|
|
1
|
+
import { A as OrGoal, At as RuntimeType, B as ExistsArgs, C as LtGoal, Ct as Annotations, D as MulGoal, Dt as InferRuntime, E as MinGoal, Et as EnumType, F as YearsBetweenGoal, G as NumericTerm, H as FormulaArgumentInput, I as AggregateBuilder, K as RuleBodyArgs, L as ComparableTerm, M as RoundToGoal, N as SubGoal, O as NeqGoal, Ot as PredicateSchema, P as SumGoal, R as DateTerm, S as LetGoal, St as AnnotationValue, T as MaxGoal, Tt as Entity, V as Formula, X as Term, Y as Scalar, _ as Goal, _t as Predicate, a as ArithmeticKind, b as GtGoal, bt as PredicateMetadata, c as ComparisonGoal, ct as GroundAtom, d as DaysBetweenGoal, dt as PatternFields, f as DivGoal, ft as assertAtomLike, g as FloorToGoal, gt as OpenWorldPredicate, h as FirstOfMonthGoal, ht as ClosedWorldPredicate, i as ArithmeticGoal, j as PrimitiveGoal, k as NotGoal, kt as RuntimeConstructor, l as CompoundGoal, lt as GroundFields, m as ExistsGoal, mt as fieldEquals, n as AndGoal, o as AtomGoal, ot as AnyAtom, p as EqGoal, pt as assertGroundAtom, q as RuleBodyInput, r as AnyAtomGoal, rt as NegativeLiteral, s as CeilToGoal, st as Atom, t as AddGoal, u as CountGoal, ut as NonGroundAtom, v as GoalExpression, vt as PredicateBase, w as LteGoal, wt as Var, x as GteGoal, xt as PredicateWorld, y as GroundAtomGoal, yt as PredicateDeclaration, z as EqualityTerm } from "./goal-BBoSfPUN.js";
|
|
2
|
+
import { n as KnowledgeBase } from "./knowledge-base-CkU_xzmB.js";
|
|
3
|
+
import { C as Bindings, S as Binding, _ as QueryAllJudgment, a as ErasedEvidenceSupport, b as SubgoalsSupport, c as Judgment, d as MissingBinding, f as NumericRange, g as PrimitiveUncertainty, h as PrimitiveSupportFor, i as CountPrimitiveSupport, l as JudgmentBase, m as PrimitiveSupport, n as ClosedWorldAbsenceSupport, o as ErasedRuleSupport, p as PrimitiveEvaluation, r as ComparisonPrimitiveSupport, s as EvidenceSupport, t as ArithmeticPrimitiveSupport, u as JudgmentSupportFor, v as QueryJudgment, w as GoalValue, x as SumPrimitiveSupport, y as RuleSupport } from "./judgment-v8i3ks5N.js";
|
|
4
4
|
/**
|
|
5
5
|
* Negate an atom in a rule body. Open-world predicates require explicit
|
|
6
6
|
* negative support; closed-world predicates use negation-as-failure.
|
|
@@ -124,6 +124,28 @@ export declare function daysBetween(later: DateTerm, earlier: DateTerm): Formula
|
|
|
124
124
|
* A person's age on a day is `yearsBetween(day, birthday)`.
|
|
125
125
|
*/
|
|
126
126
|
export declare function yearsBetween(later: DateTerm, earlier: DateTerm): Formula<number, readonly [DateTerm, DateTerm]>;
|
|
127
|
+
/**
|
|
128
|
+
* The first day of a month at UTC midnight, from a whole year and a whole month from 1
|
|
129
|
+
* (January) to 12. Other days of the year follow by date arithmetic.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* // The tax year Y runs from its first day to the day before the next year's.
|
|
134
|
+
* letIn(First, firstOfMonth(Y, 1), letIn(Last, sub(firstOfMonth(add(Y, 1), 1), dayMs), ...))
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export declare function firstOfMonth(year: NumericTerm, month: NumericTerm): Formula<Date, readonly [NumericTerm, NumericTerm]>;
|
|
138
|
+
/**
|
|
139
|
+
* The UTC calendar year of a date, for dates from the year 0 on: the whole years from
|
|
140
|
+
* January 1 of the year 0, so it explains as that `yearsBetween`.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* // A death in an earlier year than Y.
|
|
145
|
+
* and(died({ person: P, day: D }), gt(Y, yearOf(D)))
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export declare function yearOf(date: DateTerm): Formula<number, readonly [DateTerm, DateTerm]>;
|
|
127
149
|
/**
|
|
128
150
|
* The lesser of two numeric terms.
|
|
129
151
|
*
|
|
@@ -284,4 +306,4 @@ export declare function query<Schema extends PredicateSchema>(kb: KnowledgeBase,
|
|
|
284
306
|
* ```
|
|
285
307
|
*/
|
|
286
308
|
export declare function queryAll<Schema extends PredicateSchema>(kb: KnowledgeBase, pattern: Atom<Schema>): QueryAllJudgment<Schema>;
|
|
287
|
-
export { type AddGoal, type AggregateBuilder, type AndGoal, type AnnotationValue, type Annotations, type AnyAtom, type AnyAtomGoal, type ArithmeticGoal, type ArithmeticKind, type ArithmeticPrimitiveSupport, type Atom, type AtomGoal, type Binding, type Bindings, type CeilToGoal, type ClosedWorldAbsenceSupport, type ClosedWorldPredicate, type ComparableTerm, type ComparisonGoal, type ComparisonPrimitiveSupport, type CompoundGoal, type CountGoal, type CountPrimitiveSupport, type DateTerm, type DaysBetweenGoal, type DivGoal, type Entity, type EnumType, type EqGoal, type EqualityTerm, type ErasedEvidenceSupport, type ErasedRuleSupport, type EvidenceSupport, type ExistsGoal, type FloorToGoal, type Formula, type Goal, type GoalExpression, type GoalValue, type GroundAtom, type GroundAtomGoal, type GroundFields, type GtGoal, type GteGoal, type InferRuntime, type Judgment, type JudgmentBase, type JudgmentSupportFor, type LetGoal, type LtGoal, type LteGoal, type MaxGoal, type MinGoal, type MissingBinding, type MulGoal, type NeqGoal, type NonGroundAtom, type NotGoal, type NumericRange, type NumericTerm, type OpenWorldPredicate, type OrGoal, type PatternFields, type Predicate, type PredicateBase, type PredicateDeclaration, type PredicateMetadata, type PredicateSchema, type PredicateWorld, type PrimitiveEvaluation, type PrimitiveGoal, type PrimitiveSupport, type PrimitiveSupportFor, type PrimitiveUncertainty, type QueryAllJudgment, type QueryJudgment, type RoundToGoal, type RuleSupport, type RuntimeConstructor, type RuntimeType, type Scalar, type SubGoal, type SubgoalsSupport, type SumGoal, type SumPrimitiveSupport, type Term, Var, type YearsBetweenGoal, assertAtomLike, assertGroundAtom, fieldEquals };
|
|
309
|
+
export { type AddGoal, type AggregateBuilder, type AndGoal, type AnnotationValue, type Annotations, type AnyAtom, type AnyAtomGoal, type ArithmeticGoal, type ArithmeticKind, type ArithmeticPrimitiveSupport, type Atom, type AtomGoal, type Binding, type Bindings, type CeilToGoal, type ClosedWorldAbsenceSupport, type ClosedWorldPredicate, type ComparableTerm, type ComparisonGoal, type ComparisonPrimitiveSupport, type CompoundGoal, type CountGoal, type CountPrimitiveSupport, type DateTerm, type DaysBetweenGoal, type DivGoal, type Entity, type EnumType, type EqGoal, type EqualityTerm, type ErasedEvidenceSupport, type ErasedRuleSupport, type EvidenceSupport, type ExistsGoal, type FirstOfMonthGoal, type FloorToGoal, type Formula, type Goal, type GoalExpression, type GoalValue, type GroundAtom, type GroundAtomGoal, type GroundFields, type GtGoal, type GteGoal, type InferRuntime, type Judgment, type JudgmentBase, type JudgmentSupportFor, type LetGoal, type LtGoal, type LteGoal, type MaxGoal, type MinGoal, type MissingBinding, type MulGoal, type NeqGoal, type NonGroundAtom, type NotGoal, type NumericRange, type NumericTerm, type OpenWorldPredicate, type OrGoal, type PatternFields, type Predicate, type PredicateBase, type PredicateDeclaration, type PredicateMetadata, type PredicateSchema, type PredicateWorld, type PrimitiveEvaluation, type PrimitiveGoal, type PrimitiveSupport, type PrimitiveSupportFor, type PrimitiveUncertainty, type QueryAllJudgment, type QueryJudgment, type RoundToGoal, type RuleSupport, type RuntimeConstructor, type RuntimeType, type Scalar, type SubGoal, type SubgoalsSupport, type SumGoal, type SumPrimitiveSupport, type Term, Var, type YearsBetweenGoal, assertAtomLike, assertGroundAtom, fieldEquals };
|
package/dist/dsl.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as fieldEquals, i as assertGroundAtom, n as queryAll, r as assertAtomLike, t as query } from "./query-
|
|
1
|
+
import { a as Var } from "./types-DeYyybXd.js";
|
|
2
|
+
import { a as fieldEquals, i as assertGroundAtom, n as queryAll, r as assertAtomLike, t as query } from "./query-CN6EOPwp.js";
|
|
3
3
|
// ../core/src/dsl/assert-supported-value.ts
|
|
4
4
|
/** Reject removed scalar types before building rules or atoms. */
|
|
5
5
|
function assertSupportedValue(value) {
|
|
@@ -206,6 +206,37 @@ function yearsBetween(later, earlier) {
|
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
208
|
/**
|
|
209
|
+
* The first day of a month at UTC midnight, from a whole year and a whole month from 1
|
|
210
|
+
* (January) to 12. Other days of the year follow by date arithmetic.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* // The tax year Y runs from its first day to the day before the next year's.
|
|
215
|
+
* letIn(First, firstOfMonth(Y, 1), letIn(Last, sub(firstOfMonth(add(Y, 1), 1), dayMs), ...))
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
function firstOfMonth(year, month) {
|
|
219
|
+
assertSupportedValue(year);
|
|
220
|
+
assertSupportedValue(month);
|
|
221
|
+
return {
|
|
222
|
+
kind: "firstOfMonth",
|
|
223
|
+
args: [year, month]
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The UTC calendar year of a date, for dates from the year 0 on: the whole years from
|
|
228
|
+
* January 1 of the year 0, so it explains as that `yearsBetween`.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* // A death in an earlier year than Y.
|
|
233
|
+
* and(died({ person: P, day: D }), gt(Y, yearOf(D)))
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
function yearOf(date) {
|
|
237
|
+
return yearsBetween(date, firstOfMonth(0, 1));
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
209
240
|
* The lesser of two numeric terms.
|
|
210
241
|
*
|
|
211
242
|
* @example
|
|
@@ -442,4 +473,4 @@ function enumeration(first, ...rest) {
|
|
|
442
473
|
function variable(name) {
|
|
443
474
|
return new Var(name);
|
|
444
475
|
}
|
|
445
|
-
export { Var, add, and, assertAtomLike, assertGroundAtom, ceilTo, closedPredicate, count, daysBetween, div, enumeration, eq, exists, fieldEquals, floorTo, gt, gte, letIn, lte, max, min, mul, neq, not, or, predicate, query, queryAll, roundTo, sub, sum, variable, yearsBetween };
|
|
476
|
+
export { Var, add, and, assertAtomLike, assertGroundAtom, ceilTo, closedPredicate, count, daysBetween, div, enumeration, eq, exists, fieldEquals, firstOfMonth, floorTo, gt, gte, letIn, lte, max, min, mul, neq, not, or, predicate, query, queryAll, roundTo, sub, sum, variable, yearOf, yearsBetween };
|
package/dist/exceptions.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Z as AnyRule, a as ArithmeticKind, i as ArithmeticGoal, wt as Var, yt as PredicateDeclaration } from "./goal-BBoSfPUN.js";
|
|
2
2
|
/** Base class for errors that mean the caller supplied invalid program logic. */
|
|
3
3
|
export declare class ProgrammerError extends Error {
|
|
4
4
|
constructor(message: string);
|
package/dist/exceptions.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as ExclusiveDefinitionProgrammerError, n as PrimitiveArithmeticProgrammerError, r as ProgrammerError, i as UnconstructiveRecursionProgrammerError, a as UnsafeUnboundVariableProgrammerError, o as UnsupportedPredicateError } from "./programmer-errors-
|
|
1
|
+
export { t as ExclusiveDefinitionProgrammerError, n as PrimitiveArithmeticProgrammerError, r as ProgrammerError, i as UnconstructiveRecursionProgrammerError, a as UnsafeUnboundVariableProgrammerError, o as UnsupportedPredicateError } from "./programmer-errors-DLE1xWJ_.js";
|
package/dist/explain.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { c as Judgment } from "./judgment-
|
|
1
|
+
import { c as Judgment } from "./judgment-v8i3ks5N.js";
|
|
2
2
|
/** Shared shape for every compact, LLM-readable explanation node. */
|
|
3
3
|
type ExplanationBase = Readonly<{
|
|
4
4
|
/** The evaluated claim and its value, e.g. `eligible(alice) = true`. */
|
|
5
5
|
claim: string;
|
|
6
|
+
/**
|
|
7
|
+
* Claims this one also rests on that a budget left unexplained, each with its value,
|
|
8
|
+
* e.g. `resident(alice) = true`. What decided a claim is explained before what did
|
|
9
|
+
* not, so these are most often claims that were evaluated and changed nothing.
|
|
10
|
+
*/
|
|
11
|
+
notShown?: readonly string[];
|
|
6
12
|
}>;
|
|
7
13
|
/**
|
|
8
14
|
* A compact, LLM-readable explanation node.
|
|
@@ -12,7 +18,7 @@ type ExplanationBase = Readonly<{
|
|
|
12
18
|
* ```json
|
|
13
19
|
* {
|
|
14
20
|
* "claim": "eligible(alice) = true",
|
|
15
|
-
* "because": "eligible(P) iff not(active_sanction(P))
|
|
21
|
+
* "because": "eligible(P) iff not(active_sanction(P))",
|
|
16
22
|
* "given": [
|
|
17
23
|
* {
|
|
18
24
|
* "claim": "not(active_sanction(alice)) = true",
|
|
@@ -33,7 +39,7 @@ type Explanation = EvidenceExplanation | DerivedExplanation | CompoundExplanatio
|
|
|
33
39
|
* "claim": "active_sanction(bob) = true",
|
|
34
40
|
* "because": {
|
|
35
41
|
* "source": "sanctions_registry",
|
|
36
|
-
* "
|
|
42
|
+
* "citation": "Bob appears on the active sanctions list."
|
|
37
43
|
* }
|
|
38
44
|
* }
|
|
39
45
|
* ```
|
|
@@ -47,12 +53,12 @@ type Explanation = EvidenceExplanation | DerivedExplanation | CompoundExplanatio
|
|
|
47
53
|
* {
|
|
48
54
|
* "claim": "active_sanction(drew) = true",
|
|
49
55
|
* "source": "sanctions_registry",
|
|
50
|
-
* "
|
|
56
|
+
* "citation": "Drew appears on the active sanctions list."
|
|
51
57
|
* },
|
|
52
58
|
* {
|
|
53
59
|
* "claim": "active_sanction(drew) = false",
|
|
54
60
|
* "source": "appeals_board",
|
|
55
|
-
* "
|
|
61
|
+
* "citation": "Drew's sanction was overturned on appeal."
|
|
56
62
|
* }
|
|
57
63
|
* ]
|
|
58
64
|
* }
|
|
@@ -69,7 +75,7 @@ type EvidenceExplanation = ExplanationBase & Readonly<{
|
|
|
69
75
|
* ```json
|
|
70
76
|
* {
|
|
71
77
|
* "claim": "eligible(bob) = false",
|
|
72
|
-
* "because": "eligible(P) iff not(active_sanction(P))
|
|
78
|
+
* "because": "eligible(P) iff not(active_sanction(P))",
|
|
73
79
|
* "given": [
|
|
74
80
|
* {
|
|
75
81
|
* "claim": "not(active_sanction(bob)) = false",
|
|
@@ -78,7 +84,7 @@ type EvidenceExplanation = ExplanationBase & Readonly<{
|
|
|
78
84
|
* "claim": "active_sanction(bob) = true",
|
|
79
85
|
* "because": {
|
|
80
86
|
* "source": "sanctions_registry",
|
|
81
|
-
* "
|
|
87
|
+
* "citation": "Bob appears on the active sanctions list."
|
|
82
88
|
* }
|
|
83
89
|
* }
|
|
84
90
|
* ]
|
|
@@ -106,12 +112,12 @@ type DerivedExplanation = ExplanationBase & Readonly<{
|
|
|
106
112
|
* {
|
|
107
113
|
* "claim": "active_sanction(drew) = true",
|
|
108
114
|
* "source": "sanctions_registry",
|
|
109
|
-
* "
|
|
115
|
+
* "citation": "Drew appears on the active sanctions list."
|
|
110
116
|
* },
|
|
111
117
|
* {
|
|
112
118
|
* "claim": "active_sanction(drew) = false",
|
|
113
119
|
* "source": "appeals_board",
|
|
114
|
-
* "
|
|
120
|
+
* "citation": "Drew's sanction was overturned on appeal."
|
|
115
121
|
* }
|
|
116
122
|
* ]
|
|
117
123
|
* }
|
|
@@ -146,7 +152,7 @@ type TerminalExplanation = ExplanationBase & Readonly<{
|
|
|
146
152
|
* ```json
|
|
147
153
|
* {
|
|
148
154
|
* "source": "sanctions_registry",
|
|
149
|
-
* "
|
|
155
|
+
* "citation": "Bob appears on the active sanctions list."
|
|
150
156
|
* }
|
|
151
157
|
* ```
|
|
152
158
|
*
|
|
@@ -156,28 +162,48 @@ type TerminalExplanation = ExplanationBase & Readonly<{
|
|
|
156
162
|
* {
|
|
157
163
|
* "claim": "active_sanction(drew) = false",
|
|
158
164
|
* "source": "appeals_board",
|
|
159
|
-
* "
|
|
165
|
+
* "citation": "Drew's sanction was overturned on appeal.",
|
|
160
166
|
* "metadata": { "reviewed_by": "appeals_panel" }
|
|
161
167
|
* }
|
|
162
168
|
* ```
|
|
163
169
|
*/
|
|
164
170
|
type EvidenceReason = Readonly<{
|
|
165
|
-
/**
|
|
171
|
+
/**
|
|
172
|
+
* What this evidence says, e.g. `active_sanction(drew) = false`. Left out when it only
|
|
173
|
+
* repeats the claim of the explanation it sits under and the evidence has provenance.
|
|
174
|
+
*/
|
|
166
175
|
claim?: string;
|
|
167
176
|
/** Where the evidence came from, e.g. `sanctions_registry`. */
|
|
168
177
|
source?: string;
|
|
169
|
-
/** The
|
|
170
|
-
|
|
178
|
+
/** The words of the source that support the claim, as recorded with the evidence. */
|
|
179
|
+
citation?: string;
|
|
180
|
+
/** Why the citation supports the claim, as stated by whoever recorded the evidence. */
|
|
181
|
+
justification?: string;
|
|
171
182
|
/** Raw source-specific details that should not be flattened into prose. */
|
|
172
183
|
metadata?: Readonly<Record<string, unknown>>;
|
|
173
184
|
}>;
|
|
185
|
+
/**
|
|
186
|
+
* How much of a judgment's proof to explain.
|
|
187
|
+
*/
|
|
188
|
+
type ExplanationOptions = Readonly<{
|
|
189
|
+
/**
|
|
190
|
+
* The most claims to explain. Without it, every claim of the proof is explained.
|
|
191
|
+
*
|
|
192
|
+
* Most of a proof is conditions that held and changed nothing, and a deep rule set has
|
|
193
|
+
* hundreds of them. Within a budget, the conclusion is always explained, then what
|
|
194
|
+
* decided it before what did not, and what is near it before what is far. A claim that
|
|
195
|
+
* rests on claims left out names them, with their values, in `notShown`.
|
|
196
|
+
*/
|
|
197
|
+
budget?: number;
|
|
198
|
+
}>;
|
|
174
199
|
/**
|
|
175
200
|
* Convert a judgment's proof-support graph into a compact, LLM-readable
|
|
176
201
|
* {@link Explanation} of what was concluded and why.
|
|
177
202
|
*
|
|
178
|
-
*
|
|
179
|
-
* explanation nodes within this call. Serializing
|
|
180
|
-
*
|
|
203
|
+
* Without a budget, the result is a directed acyclic graph (DAG): reused judgment
|
|
204
|
+
* objects share explanation nodes within this call. Serializing it as JSON expands a
|
|
205
|
+
* shared node at every path to it, so the text of a deep proof grows with its paths, to
|
|
206
|
+
* megabytes. Give a `budget` to explain a judgment in text.
|
|
181
207
|
*
|
|
182
208
|
* @example
|
|
183
209
|
* ```ts
|
|
@@ -187,10 +213,13 @@ type EvidenceReason = Readonly<{
|
|
|
187
213
|
* ```json
|
|
188
214
|
* {
|
|
189
215
|
* "claim": "doctor(alice) = true",
|
|
190
|
-
* "because": "doctor(P) iff and(passed_medical_school(P), has_active_license(P))
|
|
191
|
-
* "given": [
|
|
216
|
+
* "because": "doctor(P) iff and(passed_medical_school(P), has_active_license(P))",
|
|
217
|
+
* "given": [
|
|
218
|
+
* { "claim": "passed_medical_school(alice) = true" },
|
|
219
|
+
* { "claim": "has_active_license(alice) = true" }
|
|
220
|
+
* ]
|
|
192
221
|
* }
|
|
193
222
|
* ```
|
|
194
223
|
*/
|
|
195
|
-
export declare function explainJudgment(judgment: Judgment): Explanation;
|
|
196
|
-
export type { CompoundExplanation, DerivedExplanation, EvidenceExplanation, EvidenceReason, Explanation, ExplanationBase, TerminalExplanation };
|
|
224
|
+
export declare function explainJudgment(judgment: Judgment, options?: ExplanationOptions): Explanation;
|
|
225
|
+
export type { CompoundExplanation, DerivedExplanation, EvidenceExplanation, EvidenceReason, Explanation, ExplanationBase, ExplanationOptions, TerminalExplanation };
|
package/dist/explain.js
CHANGED
|
@@ -1,12 +1,96 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as AssertionError, o as isTemporary, s as isVar, t as isFormula } from "./types-DeYyybXd.js";
|
|
2
|
+
// ../core/src/explanation/relevance.ts
|
|
3
|
+
/**
|
|
4
|
+
* Which of the claims an explanation rests on did not decide its value.
|
|
5
|
+
*
|
|
6
|
+
* A false conjunction is false because of its false conditions; the true ones were
|
|
7
|
+
* evaluated and changed nothing. That is not part of what an explanation says, so it is
|
|
8
|
+
* kept beside the explanation rather than in it, for writing an explanation out within a
|
|
9
|
+
* budget: what decided a claim is worth a reader's attention before what did not.
|
|
10
|
+
*/
|
|
11
|
+
const undecisive = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
/** Record the claims under `explanation` that did not decide its value. */
|
|
13
|
+
function noteUndecisive(explanation, claims) {
|
|
14
|
+
const noted = new Set(claims);
|
|
15
|
+
if (noted.size > 0) undecisive.set(explanation, noted);
|
|
16
|
+
}
|
|
17
|
+
/** The claims under `explanation` that did not decide its value. None, if none were recorded. */
|
|
18
|
+
function undecisiveIn(explanation) {
|
|
19
|
+
return undecisive.get(explanation) ?? NONE;
|
|
20
|
+
}
|
|
21
|
+
const NONE = /* @__PURE__ */ new Set();
|
|
22
|
+
// ../core/src/explanation/budget.ts
|
|
23
|
+
/**
|
|
24
|
+
* The explanation cut down to its `budget` claims most worth a reader's attention, the
|
|
25
|
+
* conclusion first. A claim that rests on claims left out names them in `notShown`.
|
|
26
|
+
*
|
|
27
|
+
* A claim is as far from the conclusion as the fewest undecisive claims on the way to
|
|
28
|
+
* it, then as the number of claims on the way. So what decided the conclusion is kept
|
|
29
|
+
* before anything that did not, the conditions of the conclusion's own rule before those
|
|
30
|
+
* of a rule three levels down, and no claim is kept without the claim that rests on it.
|
|
31
|
+
*
|
|
32
|
+
* The budget counts claims as they are written out, so a claim that two others rest on
|
|
33
|
+
* counts under each: the result written as JSON has at most `budget` nodes.
|
|
34
|
+
*/
|
|
35
|
+
function withinBudget(explanation, budget) {
|
|
36
|
+
const root = {
|
|
37
|
+
source: explanation,
|
|
38
|
+
children: /* @__PURE__ */ new Map()
|
|
39
|
+
};
|
|
40
|
+
const frontier = candidates(root, [0, 0]);
|
|
41
|
+
for (let kept = 1; kept < budget; kept++) {
|
|
42
|
+
const nearest = frontier.reduce((best, candidate) => best === void 0 || nearer(candidate.distance, best.distance) ? candidate : best, void 0);
|
|
43
|
+
if (nearest === void 0) break;
|
|
44
|
+
frontier.splice(frontier.indexOf(nearest), 1);
|
|
45
|
+
const child = {
|
|
46
|
+
source: nearest.source,
|
|
47
|
+
children: /* @__PURE__ */ new Map()
|
|
48
|
+
};
|
|
49
|
+
nearest.under.children.set(nearest.source, child);
|
|
50
|
+
frontier.push(...candidates(child, nearest.distance));
|
|
51
|
+
}
|
|
52
|
+
return write(root);
|
|
53
|
+
}
|
|
54
|
+
function nearer(a, b) {
|
|
55
|
+
return a[0] < b[0] || a[0] === b[0] && a[1] < b[1];
|
|
56
|
+
}
|
|
57
|
+
function candidates(kept, distance) {
|
|
58
|
+
const { source } = kept;
|
|
59
|
+
const undecisive = undecisiveIn(source);
|
|
60
|
+
return ("given" in source ? source.given : []).map((child) => ({
|
|
61
|
+
source: child,
|
|
62
|
+
under: kept,
|
|
63
|
+
distance: [distance[0] + (undecisive.has(child) ? 1 : 0), distance[1] + 1]
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
function write(kept) {
|
|
67
|
+
const { source } = kept;
|
|
68
|
+
if (!("given" in source)) return source;
|
|
69
|
+
const given = [];
|
|
70
|
+
const notShown = [];
|
|
71
|
+
for (const child of source.given) {
|
|
72
|
+
const keptChild = kept.children.get(child);
|
|
73
|
+
if (keptChild === void 0) notShown.push(child.claim);
|
|
74
|
+
else given.push(write(keptChild));
|
|
75
|
+
}
|
|
76
|
+
return notShown.length === 0 ? {
|
|
77
|
+
...source,
|
|
78
|
+
given
|
|
79
|
+
} : {
|
|
80
|
+
...source,
|
|
81
|
+
given,
|
|
82
|
+
notShown: [...new Set(notShown)]
|
|
83
|
+
};
|
|
84
|
+
}
|
|
2
85
|
// ../core/src/explanation/explain.ts
|
|
3
86
|
/**
|
|
4
87
|
* Convert a judgment's proof-support graph into a compact, LLM-readable
|
|
5
88
|
* {@link Explanation} of what was concluded and why.
|
|
6
89
|
*
|
|
7
|
-
*
|
|
8
|
-
* explanation nodes within this call. Serializing
|
|
9
|
-
*
|
|
90
|
+
* Without a budget, the result is a directed acyclic graph (DAG): reused judgment
|
|
91
|
+
* objects share explanation nodes within this call. Serializing it as JSON expands a
|
|
92
|
+
* shared node at every path to it, so the text of a deep proof grows with its paths, to
|
|
93
|
+
* megabytes. Give a `budget` to explain a judgment in text.
|
|
10
94
|
*
|
|
11
95
|
* @example
|
|
12
96
|
* ```ts
|
|
@@ -16,17 +100,22 @@ import { a as isVar, n as AssertionError, t as isFormula } from "./types-0ZvnLf2
|
|
|
16
100
|
* ```json
|
|
17
101
|
* {
|
|
18
102
|
* "claim": "doctor(alice) = true",
|
|
19
|
-
* "because": "doctor(P) iff and(passed_medical_school(P), has_active_license(P))
|
|
20
|
-
* "given": [
|
|
103
|
+
* "because": "doctor(P) iff and(passed_medical_school(P), has_active_license(P))",
|
|
104
|
+
* "given": [
|
|
105
|
+
* { "claim": "passed_medical_school(alice) = true" },
|
|
106
|
+
* { "claim": "has_active_license(alice) = true" }
|
|
107
|
+
* ]
|
|
21
108
|
* }
|
|
22
109
|
* ```
|
|
23
110
|
*/
|
|
24
|
-
function explainJudgment(judgment) {
|
|
25
|
-
|
|
111
|
+
function explainJudgment(judgment, options = {}) {
|
|
112
|
+
const explanation = explainWithContext(judgment, {
|
|
113
|
+
definitions: /* @__PURE__ */ new Map(),
|
|
26
114
|
explanations: /* @__PURE__ */ new WeakMap(),
|
|
27
115
|
contentKeys: /* @__PURE__ */ new WeakMap(),
|
|
28
116
|
keyIds: /* @__PURE__ */ new Map()
|
|
29
117
|
});
|
|
118
|
+
return options.budget === void 0 ? explanation : withinBudget(explanation, options.budget);
|
|
30
119
|
}
|
|
31
120
|
function explainWithContext(judgment, context) {
|
|
32
121
|
let explanation = context.explanations.get(judgment);
|
|
@@ -40,40 +129,94 @@ function explainOnce(judgment, context) {
|
|
|
40
129
|
if (isClosedWorldAbsence(judgment)) {
|
|
41
130
|
const atom = judgment.goal.goal.atom;
|
|
42
131
|
return {
|
|
43
|
-
claim: claimForJudgment(judgment),
|
|
44
|
-
because: `${formatAtom(atom)} is closed-world, and no evidence is known`
|
|
132
|
+
claim: claimForJudgment(judgment, context),
|
|
133
|
+
because: `${formatAtom(atom, scopeOf(judgment, context))} is closed-world, and no evidence is known`
|
|
45
134
|
};
|
|
46
135
|
}
|
|
47
136
|
const supports = supportsForValue(judgment);
|
|
48
137
|
const ruleSupport = supports.find((support) => support.source === "rule");
|
|
49
|
-
if (ruleSupport !== void 0)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
138
|
+
if (ruleSupport !== void 0) {
|
|
139
|
+
const body = explainWithContext(ruleSupport.body, context);
|
|
140
|
+
const explanation = {
|
|
141
|
+
claim: claimForJudgment(judgment, context),
|
|
142
|
+
because: formatRule(ruleSupport.rule),
|
|
143
|
+
given: ruleBodyExplanations(ruleSupport.body, body)
|
|
144
|
+
};
|
|
145
|
+
noteUndecisive(explanation, undecisiveIn(body));
|
|
146
|
+
return explanation;
|
|
147
|
+
}
|
|
54
148
|
const evidence = supports.flatMap((support) => support.source === "evidence" ? support.literals : []);
|
|
55
149
|
if (evidence.length > 0) {
|
|
56
|
-
const
|
|
150
|
+
const claim = claimForJudgment(judgment, context);
|
|
151
|
+
const reasons = evidence.map((literal) => evidenceReason(literal, claim));
|
|
57
152
|
const firstReason = reasons[0];
|
|
58
153
|
return {
|
|
59
|
-
claim
|
|
154
|
+
claim,
|
|
60
155
|
because: reasons.length === 1 && firstReason !== void 0 ? firstReason : reasons
|
|
61
156
|
};
|
|
62
157
|
}
|
|
63
158
|
const subgoals = supports.flatMap((support) => support.source === "subgoals" ? support.judgments : []);
|
|
64
|
-
if (subgoals.length > 0)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
159
|
+
if (subgoals.length > 0) {
|
|
160
|
+
const explanation = {
|
|
161
|
+
claim: claimForJudgment(judgment, context),
|
|
162
|
+
given: uniqueExplanations(subgoals.map((subgoal) => explainWithContext(subgoal, context)), context)
|
|
163
|
+
};
|
|
164
|
+
const deciding = new Set(decidingSubgoals(judgment, subgoals).map((subgoal) => explainWithContext(subgoal, context)));
|
|
165
|
+
noteUndecisive(explanation, explanation.given.filter((given) => !deciding.has(given)));
|
|
166
|
+
return explanation;
|
|
167
|
+
}
|
|
68
168
|
if (supports.find((support) => support.source === "closed-world-absence") !== void 0 && judgment.goal.kind === "atom") return {
|
|
69
|
-
claim: claimForJudgment(judgment),
|
|
70
|
-
because: `${formatAtom(judgment.goal.atom)} is closed-world, and no matching evidence is known`
|
|
169
|
+
claim: claimForJudgment(judgment, context),
|
|
170
|
+
because: `${formatAtom(judgment.goal.atom, scopeOf(judgment, context))} is closed-world, and no matching evidence is known`
|
|
171
|
+
};
|
|
172
|
+
const primitive = supports.find((support) => support.source === "primitive");
|
|
173
|
+
if (primitive !== void 0) return {
|
|
174
|
+
claim: claimForJudgment(judgment, context),
|
|
175
|
+
because: primitiveReason(primitive, scopeOf(judgment, context))
|
|
71
176
|
};
|
|
72
177
|
return {
|
|
73
|
-
claim: claimForJudgment(judgment),
|
|
178
|
+
claim: claimForJudgment(judgment, context),
|
|
74
179
|
because: "no supporting evidence or rule was found"
|
|
75
180
|
};
|
|
76
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Why a comparison, arithmetic term or aggregate has its value. It is computed rather than
|
|
184
|
+
* supported by evidence or a rule, unless a variable it needs was never given a value.
|
|
185
|
+
*/
|
|
186
|
+
function primitiveReason(support, scope) {
|
|
187
|
+
const uncertainty = support.source === "primitive" && "evaluation" in support ? support.evaluation.uncertainty ?? [] : [];
|
|
188
|
+
const missing = uncertainty.flatMap((reason) => reason.kind === "missing-binding" ? [formatValue(reason.variable, scope)] : []);
|
|
189
|
+
if (missing.length > 0) return `${[...new Set(missing)].join(", ")} ${missing.length === 1 ? "has" : "have"} no value`;
|
|
190
|
+
return uncertainty.length > 0 ? "computed from values that are not settled" : "computed";
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* What a rule application rests on. A body that is an `and` or an `or` is not a step of
|
|
194
|
+
* its own: the rule already states it, so the application rests directly on the body's
|
|
195
|
+
* subgoals. Any other body, such as a single atom or a negation, is the one thing it
|
|
196
|
+
* rests on.
|
|
197
|
+
*/
|
|
198
|
+
function ruleBodyExplanations(body, explanation) {
|
|
199
|
+
return (body.goal.kind === "and" || body.goal.kind === "or") && "given" in explanation && !("because" in explanation) ? explanation.given : [explanation];
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The subgoals that give a conjunction or disjunction its value. The rest were evaluated
|
|
203
|
+
* and changed nothing.
|
|
204
|
+
*
|
|
205
|
+
* A false `and` is decided by its false subgoals, all of them: each is something that
|
|
206
|
+
* would have to change. A true `or` is decided by one true subgoal, and the first will
|
|
207
|
+
* do. A true `and` and a false `or` need every subgoal. When the value is not settled,
|
|
208
|
+
* the subgoals that are not settled either way are what it waits on.
|
|
209
|
+
*/
|
|
210
|
+
function decidingSubgoals(judgment, subgoals) {
|
|
211
|
+
const { kind } = judgment.goal;
|
|
212
|
+
if (kind !== "and" && kind !== "or") return subgoals;
|
|
213
|
+
const deciding = kind === "and" ? "false" : "true";
|
|
214
|
+
const neutral = kind === "and" ? "true" : "false";
|
|
215
|
+
if (judgment.value === neutral) return subgoals;
|
|
216
|
+
const decided = judgment.value === deciding ? subgoals.filter((subgoal) => subgoal.value === deciding) : subgoals.filter((subgoal) => subgoal.value !== neutral);
|
|
217
|
+
const chosen = kind === "or" && judgment.value === "true" ? decided.slice(0, 1) : decided;
|
|
218
|
+
return chosen.length > 0 ? chosen : subgoals;
|
|
219
|
+
}
|
|
77
220
|
function uniqueExplanations(explanations, context) {
|
|
78
221
|
const seen = /* @__PURE__ */ new Set();
|
|
79
222
|
const unique = [];
|
|
@@ -125,21 +268,86 @@ function isClosedWorldAbsence(judgment) {
|
|
|
125
268
|
if (child.value === "unknown") return true;
|
|
126
269
|
return child.value === "false" && child.negative.some((childSupport) => childSupport.source === "closed-world-absence");
|
|
127
270
|
}
|
|
128
|
-
|
|
129
|
-
|
|
271
|
+
const NO_SCOPE = {
|
|
272
|
+
values: /* @__PURE__ */ new Map(),
|
|
273
|
+
definitions: /* @__PURE__ */ new Map()
|
|
274
|
+
};
|
|
275
|
+
function scopeOf(judgment, context) {
|
|
276
|
+
noteDefinitions(judgment.goal, context.definitions);
|
|
277
|
+
return {
|
|
278
|
+
values: new Map(judgment.bindings),
|
|
279
|
+
definitions: context.definitions
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/** Record the term that computes each temporary in the goal. A parent is always noted before its subgoals are explained. */
|
|
283
|
+
function noteDefinitions(goal, definitions) {
|
|
284
|
+
if ("bind" in goal && isTemporary(goal.bind)) definitions.set(goal.bind, goal);
|
|
285
|
+
if (goal.kind === "and" || goal.kind === "or") for (const subgoal of goal.goals) noteDefinitions(subgoal, definitions);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* The goal as it was evaluated, and what it came to. Variables are written as the values
|
|
289
|
+
* they hold, and a variable without one keeps its name. A term that computes a value says
|
|
290
|
+
* the value it computed, such as `A = mul(1000, 0.15) = 150`; every other claim ends in
|
|
291
|
+
* its truth value. A temporary is never named: the term that computes it stands for it.
|
|
292
|
+
*/
|
|
293
|
+
function claimForJudgment(judgment, context) {
|
|
294
|
+
const scope = scopeOf(judgment, context);
|
|
295
|
+
const { goal } = judgment;
|
|
296
|
+
if ("bind" in goal) {
|
|
297
|
+
const result = scope.values.has(goal.bind) && judgment.value === "true" ? formatValue(scope.values.get(goal.bind)) : judgment.value;
|
|
298
|
+
return `${formatComputation(goal, scope)} = ${result}`;
|
|
299
|
+
}
|
|
300
|
+
return `${formatGoal(goal, scope)} = ${judgment.value}`;
|
|
130
301
|
}
|
|
131
|
-
|
|
302
|
+
/**
|
|
303
|
+
* A computing goal with the variable it binds left as a name, `A = mul(1000, 0.15)`, or
|
|
304
|
+
* with no name when it binds a temporary, `mul(1000, 0.15)`.
|
|
305
|
+
*/
|
|
306
|
+
function formatComputation(goal, scope) {
|
|
307
|
+
const values = new Map(scope.values);
|
|
308
|
+
values.delete(goal.bind);
|
|
309
|
+
const term = formatTerm(goal, {
|
|
310
|
+
...scope,
|
|
311
|
+
values
|
|
312
|
+
});
|
|
313
|
+
return isTemporary(goal.bind) ? term : `${goal.bind.name} = ${term}`;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* A computing goal inside a larger one: the term and its value, `mul(1000, 0.15) = 150`,
|
|
317
|
+
* or with no value the variable it would bind, `A = mul(I, 0.15)`. A temporary with no
|
|
318
|
+
* value is written as its term wherever it is used, so here it is the term alone.
|
|
319
|
+
*/
|
|
320
|
+
function formatComputing(goal, scope) {
|
|
321
|
+
const term = formatTerm(goal, scope);
|
|
322
|
+
if (scope.values.has(goal.bind)) return `${term} = ${formatValue(scope.values.get(goal.bind))}`;
|
|
323
|
+
return isTemporary(goal.bind) ? term : `${goal.bind.name} = ${term}`;
|
|
324
|
+
}
|
|
325
|
+
/** The computation itself, without the variable it binds: `mul(1000, 0.15)`. */
|
|
326
|
+
function formatTerm(goal, scope) {
|
|
327
|
+
if (goal.kind === "sum") return `sum(${formatValue(goal.value, scope)})`;
|
|
328
|
+
if (goal.kind === "count") return `count(${formatGoal(goal.where, scope)})`;
|
|
329
|
+
return `${goal.kind}(${formatValue(goal.left, scope)}, ${formatValue(goal.right, scope)})`;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* What one piece of evidence says and where it comes from. Its claim is left out when it
|
|
333
|
+
* only repeats `stepClaim` beside provenance, so a step states its claim once.
|
|
334
|
+
*/
|
|
335
|
+
function evidenceReason(literal, stepClaim) {
|
|
132
336
|
const claim = `${formatAtom(literal.atom)} = ${literal.sign === "+" ? "true" : "false"}`;
|
|
133
|
-
const source = literal.metadata
|
|
134
|
-
|
|
337
|
+
const { source, citation, justification } = literal.metadata ?? {};
|
|
338
|
+
const provenance = {
|
|
339
|
+
...source === void 0 ? {} : { source },
|
|
340
|
+
...citation === void 0 ? {} : { citation },
|
|
341
|
+
...justification === void 0 ? {} : { justification }
|
|
342
|
+
};
|
|
343
|
+
return claim === stepClaim && Object.keys(provenance).length > 0 ? provenance : {
|
|
135
344
|
claim,
|
|
136
|
-
|
|
345
|
+
...provenance
|
|
137
346
|
};
|
|
138
347
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return `${base}, with ${bindings.map(([variable, value]) => `${variable.name} = ${formatValue(value)}`).join(", ")}`;
|
|
348
|
+
/** A rule as its author wrote it. The values it was applied to are in the claims it supports. */
|
|
349
|
+
function formatRule(rule) {
|
|
350
|
+
return `${formatLiteral(rule.head)} ${rule.kind} ${formatRuleBody(rule.body)}`;
|
|
143
351
|
}
|
|
144
352
|
function formatRuleBody(body) {
|
|
145
353
|
if (isLiteral(body)) return formatLiteral(body);
|
|
@@ -150,44 +358,52 @@ function formatRuleBody(body) {
|
|
|
150
358
|
function formatLiteral(literal) {
|
|
151
359
|
return literal.sign === "-" ? `not(${formatAtom(literal.atom)})` : formatAtom(literal.atom);
|
|
152
360
|
}
|
|
153
|
-
function formatGoal(goal) {
|
|
361
|
+
function formatGoal(goal, scope = NO_SCOPE) {
|
|
154
362
|
switch (goal.kind) {
|
|
155
|
-
case "atom": return formatAtom(goal.atom);
|
|
156
|
-
case "not": return `not(${formatGoal(goal.goal)})`;
|
|
363
|
+
case "atom": return formatAtom(goal.atom, scope);
|
|
364
|
+
case "not": return `not(${formatGoal(goal.goal, scope)})`;
|
|
157
365
|
case "and":
|
|
158
|
-
case "or": return `${goal.kind}(${goal.goals.map(formatGoal).join(", ")})`;
|
|
159
|
-
case "exists": return `exists(${goal.variables.map((variable) => variable.name).join(", ")}: ${formatGoal(goal.goal)})`;
|
|
160
|
-
case "let": return `let(${goal.variable.name} = ${formatValue(goal.value)}: ${formatGoal(goal.goal)})`;
|
|
366
|
+
case "or": return `${goal.kind}(${goal.goals.map((subgoal) => formatGoal(subgoal, scope)).join(", ")})`;
|
|
367
|
+
case "exists": return `exists(${goal.variables.map((variable) => variable.name).join(", ")}: ${formatGoal(goal.goal, scope)})`;
|
|
368
|
+
case "let": return `let(${goal.variable.name} = ${formatValue(goal.value, scope)}: ${formatGoal(goal.goal, scope)})`;
|
|
161
369
|
case "add":
|
|
162
370
|
case "sub":
|
|
163
371
|
case "mul":
|
|
164
372
|
case "div":
|
|
165
373
|
case "daysBetween":
|
|
166
374
|
case "yearsBetween":
|
|
375
|
+
case "firstOfMonth":
|
|
167
376
|
case "min":
|
|
168
377
|
case "max":
|
|
169
378
|
case "roundTo":
|
|
170
379
|
case "floorTo":
|
|
171
|
-
case "ceilTo":
|
|
380
|
+
case "ceilTo":
|
|
381
|
+
case "sum":
|
|
382
|
+
case "count": return formatComputing(goal, scope);
|
|
172
383
|
case "eq":
|
|
173
384
|
case "neq":
|
|
174
385
|
case "lt":
|
|
175
386
|
case "lte":
|
|
176
387
|
case "gt":
|
|
177
|
-
case "gte": return `${goal.kind}(${formatValue(goal.left)}, ${formatValue(goal.right)})`;
|
|
178
|
-
case "sum": return `${goal.bind.name} = sum(${formatValue(goal.value)})`;
|
|
179
|
-
case "count": return `${goal.bind.name} = count(${formatGoal(goal.where)})`;
|
|
388
|
+
case "gte": return `${goal.kind}(${formatValue(goal.left, scope)}, ${formatValue(goal.right, scope)})`;
|
|
180
389
|
default: throw new AssertionError("Unhandled goal kind");
|
|
181
390
|
}
|
|
182
391
|
}
|
|
183
|
-
function formatAtom(atom) {
|
|
184
|
-
const
|
|
185
|
-
return `${atom.predicate.name}(${
|
|
392
|
+
function formatAtom(atom, scope = NO_SCOPE) {
|
|
393
|
+
const fields = atom.predicate.paramNames.map((name) => formatValue(atom.fields[name], scope));
|
|
394
|
+
return `${atom.predicate.name}(${fields.join(", ")})`;
|
|
186
395
|
}
|
|
187
|
-
function formatValue(value) {
|
|
188
|
-
if (isVar(value))
|
|
396
|
+
function formatValue(value, scope = NO_SCOPE) {
|
|
397
|
+
if (isVar(value)) {
|
|
398
|
+
if (scope.values.has(value)) return formatValue(scope.values.get(value));
|
|
399
|
+
const definition = scope.definitions.get(value);
|
|
400
|
+
return definition === void 0 ? value.name : formatTerm(definition, scope);
|
|
401
|
+
}
|
|
189
402
|
if (typeof value === "object" && value !== null && "id" in value && typeof value.id === "string") return value.id;
|
|
190
|
-
if (value instanceof Date)
|
|
403
|
+
if (value instanceof Date) {
|
|
404
|
+
const text = value.toISOString();
|
|
405
|
+
return text.endsWith("T00:00:00.000Z") ? text.slice(0, 10) : text;
|
|
406
|
+
}
|
|
191
407
|
return String(value);
|
|
192
408
|
}
|
|
193
409
|
function isLiteral(value) {
|
|
@@ -270,6 +270,8 @@ type DivGoal = ArithmeticGoal<"div">;
|
|
|
270
270
|
type DaysBetweenGoal = ArithmeticGoal<"daysBetween">;
|
|
271
271
|
/** Arithmetic goal binding whole anniversary years from the right date to the left. */
|
|
272
272
|
type YearsBetweenGoal = ArithmeticGoal<"yearsBetween">;
|
|
273
|
+
/** Arithmetic goal binding the first day of the right month of the left year. */
|
|
274
|
+
type FirstOfMonthGoal = ArithmeticGoal<"firstOfMonth">;
|
|
273
275
|
/** Arithmetic goal binding the lesser of two numeric expressions. */
|
|
274
276
|
type MinGoal = ArithmeticGoal<"min">;
|
|
275
277
|
/** Arithmetic goal binding the greater of two numeric expressions. */
|
|
@@ -281,7 +283,7 @@ type FloorToGoal = ArithmeticGoal<"floorTo">;
|
|
|
281
283
|
/** Arithmetic goal rounding toward positive infinity at the right operand's decimal places. */
|
|
282
284
|
type CeilToGoal = ArithmeticGoal<"ceilTo">;
|
|
283
285
|
/** The primitive arithmetic operations. */
|
|
284
|
-
type ArithmeticKind = "add" | "sub" | "mul" | "div" | "daysBetween" | "yearsBetween" | "min" | "max" | "roundTo" | "floorTo" | "ceilTo";
|
|
286
|
+
type ArithmeticKind = "add" | "sub" | "mul" | "div" | "daysBetween" | "yearsBetween" | "firstOfMonth" | "min" | "max" | "roundTo" | "floorTo" | "ceilTo";
|
|
285
287
|
/** Bind the result of a primitive arithmetic operation. */
|
|
286
288
|
type ArithmeticGoal<Kind extends ArithmeticKind> = Readonly<{
|
|
287
289
|
readonly kind: Kind;
|
|
@@ -323,9 +325,9 @@ type CountGoal = Readonly<{
|
|
|
323
325
|
readonly completeWhen?: Goal;
|
|
324
326
|
}>;
|
|
325
327
|
/** A normalized rule-body target that the solver/proof system can evaluate. */
|
|
326
|
-
type Goal = AnyAtomGoal | NotGoal | AndGoal | OrGoal | ExistsGoal | LetGoal | AddGoal | SubGoal | MulGoal | DivGoal | DaysBetweenGoal | YearsBetweenGoal | MinGoal | MaxGoal | RoundToGoal | FloorToGoal | CeilToGoal | EqGoal | NeqGoal | LtGoal | LteGoal | GtGoal | GteGoal | SumGoal | CountGoal;
|
|
328
|
+
type Goal = AnyAtomGoal | NotGoal | AndGoal | OrGoal | ExistsGoal | LetGoal | AddGoal | SubGoal | MulGoal | DivGoal | DaysBetweenGoal | YearsBetweenGoal | FirstOfMonthGoal | MinGoal | MaxGoal | RoundToGoal | FloorToGoal | CeilToGoal | EqGoal | NeqGoal | LtGoal | LteGoal | GtGoal | GteGoal | SumGoal | CountGoal;
|
|
327
329
|
/** A goal evaluated by recursively evaluating one or more child goals. */
|
|
328
330
|
type CompoundGoal = NotGoal | AndGoal | OrGoal | ExistsGoal | LetGoal;
|
|
329
331
|
/** A goal evaluated directly by a primitive operator. */
|
|
330
|
-
type PrimitiveGoal = AddGoal | SubGoal | MulGoal | DivGoal | DaysBetweenGoal | YearsBetweenGoal | MinGoal | MaxGoal | RoundToGoal | FloorToGoal | CeilToGoal | EqGoal | NeqGoal | LtGoal | LteGoal | GtGoal | GteGoal | SumGoal | CountGoal;
|
|
331
|
-
export {
|
|
332
|
+
type PrimitiveGoal = AddGoal | SubGoal | MulGoal | DivGoal | DaysBetweenGoal | YearsBetweenGoal | FirstOfMonthGoal | MinGoal | MaxGoal | RoundToGoal | FloorToGoal | CeilToGoal | EqGoal | NeqGoal | LtGoal | LteGoal | GtGoal | GteGoal | SumGoal | CountGoal;
|
|
333
|
+
export { RuleKind as $, OrGoal as A, RuntimeType as At, ExistsArgs as B, LtGoal as C, Annotations as Ct, MulGoal as D, InferRuntime as Dt, MinGoal as E, EnumType as Et, YearsBetweenGoal as F, NumericTerm as G, FormulaArgumentInput as H, AggregateBuilder as I, RuleBuilder as J, RuleBodyArgs as K, ComparableTerm as L, RoundToGoal as M, SubGoal as N, NeqGoal as O, PredicateSchema as Ot, SumGoal as P, Rule as Q, DateTerm as R, LetGoal as S, AnnotationValue as St, MaxGoal as T, Entity as Tt, LiteralArgs as U, Formula as V, LiteralFilter as W, Term as X, Scalar as Y, AnyRule as Z, Goal as _, Predicate as _t, ArithmeticKind as a, EvidenceMetadata as at, GtGoal as b, PredicateMetadata as bt, ComparisonGoal as c, GroundAtom as ct, DaysBetweenGoal as d, PatternFields as dt, AnyLiteral as et, DivGoal as f, assertAtomLike as ft, FloorToGoal as g, OpenWorldPredicate as gt, FirstOfMonthGoal as h, ClosedWorldPredicate as ht, ArithmeticGoal as i, PositiveLiteral as it, PrimitiveGoal as j, NotGoal as k, RuntimeConstructor as kt, CompoundGoal as l, GroundFields as lt, ExistsGoal as m, fieldEquals as mt, AndGoal as n, LiteralSign as nt, AtomGoal as o, AnyAtom as ot, EqGoal as p, assertGroundAtom as pt, RuleBodyInput as q, AnyAtomGoal as r, NegativeLiteral as rt, CeilToGoal as s, Atom as st, AddGoal as t, Literal as tt, CountGoal as u, NonGroundAtom as ut, GoalExpression as v, PredicateBase as vt, LteGoal as w, Var as wt, GteGoal as x, PredicateWorld as xt, GroundAtomGoal as y, PredicateDeclaration as yt, EqualityTerm as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ot as PredicateSchema, Q as Rule, Z as AnyRule, _ as Goal, a as ArithmeticKind, et as AnyLiteral, j as PrimitiveGoal, l as CompoundGoal, o as AtomGoal, r as AnyAtomGoal, tt as Literal, wt as Var, y as GroundAtomGoal } from "./goal-BBoSfPUN.js";
|
|
2
2
|
/** Four-valued result for an evaluated proposition or goal. */
|
|
3
3
|
type GoalValue = "false" | "unknown" | "conflicted" | "true";
|
|
4
4
|
/** One variable assignment in scope for an evaluated goal. */
|
package/dist/kb.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { n as KnowledgeBase, t as EntityRegistry } from "./knowledge-base-
|
|
3
|
-
import { _ as QueryAllJudgment, v as QueryJudgment } from "./judgment-
|
|
1
|
+
import { $ as RuleKind, Ct as Annotations, J as RuleBuilder, Ot as PredicateSchema, Q as Rule, St as AnnotationValue, W as LiteralFilter, Z as AnyRule, at as EvidenceMetadata, ct as GroundAtom, et as AnyLiteral, it as PositiveLiteral, nt as LiteralSign, rt as NegativeLiteral, st as Atom, tt as Literal, yt as PredicateDeclaration } from "./goal-BBoSfPUN.js";
|
|
2
|
+
import { n as KnowledgeBase, t as EntityRegistry } from "./knowledge-base-CkU_xzmB.js";
|
|
3
|
+
import { _ as QueryAllJudgment, v as QueryJudgment } from "./judgment-v8i3ks5N.js";
|
|
4
4
|
/** Guarded knowledge-base operations that can be permitted independently. */
|
|
5
5
|
type Operation = "assert" | "deny" | "query";
|
|
6
6
|
/** An effective grant after blocks and duplicates have been removed. */
|
package/dist/kb.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as ExclusiveDefinitionProgrammerError } from "./programmer-errors-
|
|
2
|
-
import { n as queryAll, o as isAtomLike, t as query } from "./query-
|
|
1
|
+
import { t as ExclusiveDefinitionProgrammerError } from "./programmer-errors-DLE1xWJ_.js";
|
|
2
|
+
import { n as queryAll, o as isAtomLike, t as query } from "./query-CN6EOPwp.js";
|
|
3
3
|
// ../core/src/knowledge-base/assertions/exclusive-definitions.ts
|
|
4
4
|
/** Assert that each predicate has either iff definition semantics or if rule-disjunction semantics, not both. */
|
|
5
5
|
function assertExclusiveDefinitions(rules) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { At as RuntimeType, J as RuleBuilder, Ot as PredicateSchema, Tt as Entity, U as LiteralArgs, W as LiteralFilter, Z as AnyRule, at as EvidenceMetadata, et as AnyLiteral, it as PositiveLiteral, rt as NegativeLiteral, st as Atom, yt as PredicateDeclaration } from "./goal-BBoSfPUN.js";
|
|
2
2
|
/** Registry of known domain entities, keyed by constructor and id. */
|
|
3
3
|
declare class EntityRegistry {
|
|
4
4
|
private readonly entitiesByType;
|
|
@@ -15,13 +15,19 @@ var PrimitiveArithmeticProgrammerError = class extends ProgrammerError {
|
|
|
15
15
|
/** The arithmetic goal being evaluated when the failure occurred. */
|
|
16
16
|
goal;
|
|
17
17
|
constructor(operation, operands, goal) {
|
|
18
|
-
super(`${operation} expected ${operation
|
|
18
|
+
super(`${operation} expected ${expectedOperands(operation)}`);
|
|
19
19
|
this.name = "PrimitiveArithmeticProgrammerError";
|
|
20
20
|
this.operation = operation;
|
|
21
21
|
this.operands = operands;
|
|
22
22
|
this.goal = goal;
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
|
+
function expectedOperands(operation) {
|
|
26
|
+
if (operation === "daysBetween" || operation === "yearsBetween") return "date operands";
|
|
27
|
+
if (operation === "firstOfMonth") return "a whole year and a whole month from 1 to 12";
|
|
28
|
+
if (operation === "roundTo" || operation === "floorTo" || operation === "ceilTo") return "numeric operands and a whole number of decimal places";
|
|
29
|
+
return "numeric operands";
|
|
30
|
+
}
|
|
25
31
|
/** Rule body uses variables before any constructive source can bind them. */
|
|
26
32
|
var UnsafeUnboundVariableProgrammerError = class extends ProgrammerError {
|
|
27
33
|
/** The variables used before any constructive source could bind them. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as UnsafeUnboundVariableProgrammerError, i as UnconstructiveRecursionProgrammerError, n as PrimitiveArithmeticProgrammerError } from "./programmer-errors-
|
|
1
|
+
import { a as Var, i as TEMPORARY_PREFIX, n as AssertionError, o as isTemporary, r as assert, s as isVar, t as isFormula } from "./types-DeYyybXd.js";
|
|
2
|
+
import { a as UnsafeUnboundVariableProgrammerError, i as UnconstructiveRecursionProgrammerError, n as PrimitiveArithmeticProgrammerError } from "./programmer-errors-DLE1xWJ_.js";
|
|
3
3
|
// ../core/src/types/atom.ts
|
|
4
4
|
/** Return whether an atom has no logic variables in its fields. */
|
|
5
5
|
function isGroundAtom(atom) {
|
|
@@ -85,6 +85,16 @@ function yearsBetween(later, earlier) {
|
|
|
85
85
|
const years = later.getUTCFullYear() - earlier.getUTCFullYear();
|
|
86
86
|
return later.getUTCMonth() * 100 + later.getUTCDate() < earlier.getUTCMonth() * 100 + earlier.getUTCDate() ? years - 1 : years;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* The first day of a month at UTC midnight, or undefined unless the year is a whole
|
|
90
|
+
* number a date can hold and the month a whole number from 1 to 12.
|
|
91
|
+
*/
|
|
92
|
+
function firstOfMonth(year, month) {
|
|
93
|
+
if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) return;
|
|
94
|
+
const day = /* @__PURE__ */ new Date(0);
|
|
95
|
+
day.setUTCFullYear(year, month - 1, 1);
|
|
96
|
+
return Number.isNaN(day.getTime()) ? void 0 : day;
|
|
97
|
+
}
|
|
88
98
|
// ../core/src/types/goal.ts
|
|
89
99
|
/** Shared runtime classification for arithmetic formulas. */
|
|
90
100
|
const ARITHMETIC_KINDS = [
|
|
@@ -94,6 +104,7 @@ const ARITHMETIC_KINDS = [
|
|
|
94
104
|
"div",
|
|
95
105
|
"daysBetween",
|
|
96
106
|
"yearsBetween",
|
|
107
|
+
"firstOfMonth",
|
|
97
108
|
"min",
|
|
98
109
|
"max",
|
|
99
110
|
"roundTo",
|
|
@@ -1359,7 +1370,7 @@ function evaluateAggregateInto(literals, rules, variable, aggregate, bindings, a
|
|
|
1359
1370
|
}
|
|
1360
1371
|
}
|
|
1361
1372
|
const existing = valueForVariable(bindings, variable);
|
|
1362
|
-
const target = existing === UNBOUND ? variable : new Var(`$
|
|
1373
|
+
const target = existing === UNBOUND ? variable : new Var(`${TEMPORARY_PREFIX}${context.nextTemp++}`);
|
|
1363
1374
|
const nextBindings = new Map(bindings);
|
|
1364
1375
|
const values = rows.map((row) => resolveAggregateValue(valueExpression, row.bindings));
|
|
1365
1376
|
let value;
|
|
@@ -1650,6 +1661,11 @@ function calculateValue(kind, left, right, goal) {
|
|
|
1650
1661
|
case "div": return left / right;
|
|
1651
1662
|
case "daysBetween":
|
|
1652
1663
|
case "yearsBetween": break;
|
|
1664
|
+
case "firstOfMonth": {
|
|
1665
|
+
const day = firstOfMonth(left, right);
|
|
1666
|
+
if (day === void 0) throw new PrimitiveArithmeticProgrammerError(kind, [left, right], goal);
|
|
1667
|
+
return day;
|
|
1668
|
+
}
|
|
1653
1669
|
case "min": return Math.min(left, right);
|
|
1654
1670
|
case "max": return Math.max(left, right);
|
|
1655
1671
|
case "roundTo":
|
|
@@ -1883,16 +1899,11 @@ function bindingsArray(bindings) {
|
|
|
1883
1899
|
return [...bindings.entries()];
|
|
1884
1900
|
}
|
|
1885
1901
|
/**
|
|
1886
|
-
*
|
|
1887
|
-
* computation in the proof tree but are not rule variables, so two environments that
|
|
1902
|
+
* Whether the result binds a rule variable the environment does not. Temporaries record
|
|
1903
|
+
* the computation in the proof tree but are not rule variables, so two environments that
|
|
1888
1904
|
* differ only in temporaries are the same environment, and a result that adds only
|
|
1889
1905
|
* temporaries binds nothing beyond the environment it was evaluated in.
|
|
1890
1906
|
*/
|
|
1891
|
-
const TEMPORARY_PREFIX = "$tmp";
|
|
1892
|
-
function isTemporary(variable) {
|
|
1893
|
-
return variable.name.startsWith(TEMPORARY_PREFIX);
|
|
1894
|
-
}
|
|
1895
|
-
/** Whether the result binds a rule variable the environment does not. */
|
|
1896
1907
|
function bindsBeyond(result, environment) {
|
|
1897
1908
|
for (const variable of result.keys()) if (!isTemporary(variable) && !environment.has(variable)) return true;
|
|
1898
1909
|
return false;
|
|
@@ -18,6 +18,15 @@ var Var = class {
|
|
|
18
18
|
function isVar(value) {
|
|
19
19
|
return value instanceof Var;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* The prefix of the variables the solver introduces for the intermediate results of
|
|
23
|
+
* arithmetic. No rule names them, so they are not written into explanations.
|
|
24
|
+
*/
|
|
25
|
+
const TEMPORARY_PREFIX = "$tmp";
|
|
26
|
+
/** Return whether the solver introduced the variable for an intermediate result. */
|
|
27
|
+
function isTemporary(variable) {
|
|
28
|
+
return variable.name.startsWith(TEMPORARY_PREFIX);
|
|
29
|
+
}
|
|
21
30
|
// ../core/src/utils/assert.ts
|
|
22
31
|
var AssertionError = class extends Error {
|
|
23
32
|
constructor(message = "Assertion failed") {
|
|
@@ -34,4 +43,4 @@ function assert(condition, message = "Assertion failed") {
|
|
|
34
43
|
function isFormula(value, kind) {
|
|
35
44
|
return typeof value === "object" && value !== null && "kind" in value && "args" in value && (kind === void 0 || value.kind === kind) && Array.isArray(value.args);
|
|
36
45
|
}
|
|
37
|
-
export {
|
|
46
|
+
export { Var as a, TEMPORARY_PREFIX as i, AssertionError as n, isTemporary as o, assert as r, isVar as s, isFormula as t };
|