factbag 0.0.0-alpha.1 → 0.0.0-alpha.3
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 +34 -0
- package/README.md +282 -20
- package/dist/core/{chunk-X3LSDJL4.js → chunk-QOMFY3XB.js} +4 -1
- package/dist/core/{chunk-4IP7CXNV.js → chunk-RMQ3YX7V.js} +3 -2
- package/dist/core/dsl/assert-supported-value.d.ts +2 -0
- package/dist/core/dsl/index.d.ts +3 -3
- package/dist/core/dsl/index.js +81 -2
- package/dist/core/dsl/operators/aggregate.d.ts +1 -1
- package/dist/core/dsl/operators/arithmetic.d.ts +72 -5
- package/dist/core/dsl/operators/comparison.d.ts +11 -1
- package/dist/core/dsl/types.d.ts +5 -3
- package/dist/core/exceptions/programmer-errors.d.ts +4 -4
- package/dist/core/explanation/explain.d.ts +5 -1
- package/dist/core/explanation/index.js +45 -5
- package/dist/core/knowledge-base/index.js +2 -2
- package/dist/core/query/index.d.ts +1 -1
- package/dist/core/query/index.js +416 -94
- package/dist/core/solver/calendar.d.ts +4 -0
- package/dist/core/solver/candidate-search.d.ts +3 -1
- package/dist/core/solver/program-index.d.ts +17 -0
- package/dist/core/solver/query-validation/utils.d.ts +2 -0
- package/dist/core/types/atom.d.ts +6 -1
- package/dist/core/types/goal.d.ts +24 -6
- package/dist/core/types/judgment.d.ts +4 -4
- package/dist/core/types/schema.d.ts +3 -3
- package/dist/jev/{chunk-2UXHML67.js → chunk-PISXBJOT.js} +2 -2
- package/dist/jev/classifier.js +2 -2
- package/dist/jev/resolve.js +1 -1
- package/package.json +2 -2
- /package/dist/core/{chunk-YF4PP42K.js → chunk-NDUIQAH4.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# factbag
|
|
2
2
|
|
|
3
|
+
## 0.0.0-alpha.3
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f8f2712: Add `eq` and `neq` rule-body comparisons for numbers, dates, strings, booleans, and entities. Entities compare by type and id rather than instance; dates compare by instant.
|
|
8
|
+
- 900b104: Add min, max, roundTo, floorTo, and ceilTo numeric arithmetic terms for rule bodies. Rounding supports whole-number decimal places (including negative places), with halves rounded away from zero. Decimal exponent shifting avoids multiplication artifacts at decimal rounding boundaries.
|
|
9
|
+
- 4f040e3: Add `daysBetween(later, earlier)` and `yearsBetween(later, earlier)` as number-valued arithmetic terms over dates. They count signed UTC calendar days and whole anniversary years, ignoring time of day; February 29 anniversaries fall on March 1 in common years. Numeric subtraction on variables now composes with other numeric operators.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 01cc338: Speed up queries over large rulesets with linear-time recursive predicate grouping,
|
|
14
|
+
per-query rule and evidence indexes, and judgment reuse during candidate discovery.
|
|
15
|
+
Preserve candidate pruning and distinguish same-named predicate declarations when
|
|
16
|
+
tracking active atoms, avoiding incorrect recursion cutoffs.
|
|
17
|
+
- 80d1e75: Share explanation nodes for reused proof judgments so explanation construction no longer grows with the number of paths through a shared proof. Identical sub-explanations are still listed once, and rendered content is unchanged.
|
|
18
|
+
- a074dab: Judge evidence-free, closed-world recursive components together as a least fixed
|
|
19
|
+
point instead of enumerating recursive proof paths. Doubly recursive transitive
|
|
20
|
+
closures and symmetric compositions no longer require exponential proof search.
|
|
21
|
+
Other components retain the existing search semantics. Variable queries recognize
|
|
22
|
+
a non-recursive disjunctive base case within an exclusive definition.
|
|
23
|
+
|
|
24
|
+
## 0.0.0-alpha.2
|
|
25
|
+
|
|
26
|
+
### Major Changes
|
|
27
|
+
|
|
28
|
+
- e212a94: Remove bigint support from predicate schemas, numeric expressions, aggregates, and numeric proof ranges. Numeric values now use JavaScript number exclusively, and an empty sum returns 0. The DSL rejects BigInt schemas and bigint operands rather than coercing them.
|
|
29
|
+
|
|
30
|
+
Migrate BigInt fields to Number and bigint literals to numbers. Division now uses number semantics (fractional results rather than bigint truncation); values outside the safe integer range can lose precision. Date support is unchanged.
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- bc114ed: Add npm alpha version, CI status, and license badges to the README.
|
|
35
|
+
- a3583ab: Publish the full project README on npm, including the document-to-conclusion walkthrough.
|
|
36
|
+
|
|
3
37
|
## 0.0.0-alpha.1
|
|
4
38
|
|
|
5
39
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,44 +1,306 @@
|
|
|
1
1
|
# Factbag
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/factbag)
|
|
4
|
+
[](https://github.com/factbag/factbag/actions/workflows/ci.yml?query=branch%3Amain)
|
|
5
|
+
[](https://github.com/factbag/factbag/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
TypeSafe AI’s Jev model.
|
|
7
|
+
<img src="https://raw.githubusercontent.com/factbag/factbag/main/docs/assets/factbag.png" alt="An open canvas bag" align="right" width="180" />
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
Turn documents into evidence and conclusions with [Jev](https://typesafe.ai/) and TypeScript.
|
|
10
|
+
|
|
11
|
+
Factbag uses TypeSafe AI’s Jev model to read passages and assess specific claims.
|
|
12
|
+
Those assessments become evidence in a knowledge base, alongside facts from your
|
|
13
|
+
application. You write the rules that connect them and query what follows.
|
|
14
|
+
|
|
15
|
+
Each piece of documentary evidence points back to its source passage. Conclusions
|
|
16
|
+
can be **true**, **false**, **conflicted**, or **unknown**. When documents disagree,
|
|
17
|
+
you can inspect the conflicting evidence, correct a particular fact, and query
|
|
18
|
+
the rules again.
|
|
19
|
+
|
|
20
|
+
> **Important:** Factbag is an early alpha release and still pretty rough - APIs will definitely change.
|
|
21
|
+
|
|
22
|
+
## How it works
|
|
23
|
+
|
|
24
|
+
1. **Define the claims** you want Jev to assess, with descriptions of what counts
|
|
25
|
+
as evidence.
|
|
26
|
+
2. **Add documents** to a corpus and choose which predicates Jev may assert or deny.
|
|
27
|
+
3. **Execute classification jobs.** Jev assesses whether each passage supports,
|
|
28
|
+
contradicts, or is irrelevant to each candidate claim.
|
|
29
|
+
4. **Query the result.** Factbag combines admitted evidence with your existing
|
|
30
|
+
facts and rules, retaining the sources behind the conclusion.
|
|
13
31
|
|
|
14
32
|
## Install
|
|
15
33
|
|
|
16
34
|
```sh
|
|
17
|
-
npm install factbag
|
|
35
|
+
npm install factbag@alpha @typesafe-ai/sdk@^0.6.0
|
|
18
36
|
```
|
|
19
37
|
|
|
20
38
|
ESM only. Requires Node 22.18+ on the 22.x line, or Node 24 or 26.
|
|
21
|
-
This is an early prerelease; APIs may change.
|
|
22
39
|
|
|
23
|
-
|
|
40
|
+
Set your TypeSafe API key in the environment:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
export TYPESAFE_API_KEY="your-api-key"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## From a document to a conclusion
|
|
47
|
+
|
|
48
|
+
### 1. Decide on your ruleset
|
|
49
|
+
|
|
50
|
+
Start with the criteria your application needs to apply. These might be your own
|
|
51
|
+
business rules or requirements set out in legislation or regulations.
|
|
52
|
+
|
|
53
|
+
Here, we're translating the individual-income route in the SEC's
|
|
54
|
+
[accredited-investor definition, 17 CFR § 230.501(a)(6)](<https://www.ecfr.gov/current/title-17/chapter-II/part-230/section-230.501#p-230.501(a)(6)>),
|
|
55
|
+
into three conditions:
|
|
56
|
+
|
|
57
|
+
- Individual income exceeded $200,000 in the first of the two most recent years.
|
|
58
|
+
- Individual income exceeded $200,000 in the second year.
|
|
59
|
+
- The person reasonably expects to reach the same income level in the current year.
|
|
60
|
+
|
|
61
|
+
### 2. Define the claims
|
|
62
|
+
|
|
63
|
+
Declare the facts we'll combine, and describe the claims Jev will assess.
|
|
24
64
|
|
|
25
65
|
```ts
|
|
26
|
-
import { predicate, variable } from "factbag/dsl";
|
|
66
|
+
import { and, enumeration, predicate, variable } from "factbag/dsl";
|
|
27
67
|
import { KnowledgeBase } from "factbag/kb";
|
|
28
68
|
import { query } from "factbag/query";
|
|
69
|
+
import { Corpus } from "factbag/corpus";
|
|
70
|
+
import { guard } from "factbag/guards";
|
|
71
|
+
import { classifier } from "factbag/jev/classifier";
|
|
72
|
+
|
|
73
|
+
class Person {
|
|
74
|
+
constructor(
|
|
75
|
+
readonly id: string,
|
|
76
|
+
readonly name: string,
|
|
77
|
+
) {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const hasIncomeOver200k = predicate(
|
|
81
|
+
"has_income_over_200k",
|
|
82
|
+
{ person: Person, year: enumeration("2024", "2025") },
|
|
83
|
+
{
|
|
84
|
+
description:
|
|
85
|
+
"The person's total individual annual income exceeded USD 200,000 in the " +
|
|
86
|
+
"specified year. Joint household income and forecasts do not count.",
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const expectsSameIncome = predicate(
|
|
91
|
+
"expects_same_income",
|
|
92
|
+
{ person: Person },
|
|
93
|
+
{
|
|
94
|
+
description:
|
|
95
|
+
"The person reasonably expects their individual income in 2026 to remain " +
|
|
96
|
+
"at least at the level earned in each of 2024 and 2025.",
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const isQualifiedInvestor = predicate("is_qualified_investor", {
|
|
101
|
+
person: Person,
|
|
102
|
+
});
|
|
103
|
+
const hasVerifiedIdentity = predicate("has_verified_identity", {
|
|
104
|
+
person: Person,
|
|
105
|
+
});
|
|
106
|
+
const isReadyForReview = predicate("is_ready_for_review", { person: Person });
|
|
107
|
+
```
|
|
29
108
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
109
|
+
### 3. Encode the ruleset
|
|
110
|
+
|
|
111
|
+
All three income conditions must hold for this route to qualification. Our
|
|
112
|
+
application also requires verified identity before sending an investor for review.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const person = variable("person");
|
|
33
116
|
const kb = new KnowledgeBase();
|
|
34
117
|
|
|
35
|
-
kb.rule(
|
|
36
|
-
|
|
118
|
+
kb.rule(isQualifiedInvestor({ person })).if(
|
|
119
|
+
and(
|
|
120
|
+
hasIncomeOver200k({ person, year: "2024" }),
|
|
121
|
+
hasIncomeOver200k({ person, year: "2025" }),
|
|
122
|
+
expectsSameIncome({ person }),
|
|
123
|
+
),
|
|
124
|
+
);
|
|
37
125
|
|
|
38
|
-
|
|
126
|
+
kb.rule(isReadyForReview({ person })).if(
|
|
127
|
+
and(isQualifiedInvestor({ person }), hasVerifiedIdentity({ person })),
|
|
128
|
+
);
|
|
39
129
|
```
|
|
40
130
|
|
|
41
|
-
|
|
42
|
-
|
|
131
|
+
### 4. Import a known fact and add documents
|
|
132
|
+
|
|
133
|
+
Identity verification comes directly from our onboarding system. Income evidence
|
|
134
|
+
goes into the corpus for Jev to assess.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const alice = new Person("alice", "Alice");
|
|
138
|
+
kb.entities.add(alice);
|
|
139
|
+
|
|
140
|
+
kb.asserts(hasVerifiedIdentity({ person: alice }), {
|
|
141
|
+
source: "onboarding:alice",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const corpus = new Corpus();
|
|
145
|
+
|
|
146
|
+
// Tax returns provide evidence of past annual income.
|
|
147
|
+
corpus.add({ documentId: "alice-2024-federal-tax-return", text: "…" });
|
|
148
|
+
corpus.add({ documentId: "alice-2025-federal-tax-return", text: "…" });
|
|
149
|
+
|
|
150
|
+
// An employer letter supports the expectation of continued income.
|
|
151
|
+
corpus.add({
|
|
152
|
+
documentId: "alice-2026-employment-confirmation-letter",
|
|
153
|
+
text: "…",
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 5. Set Jev's permissions
|
|
158
|
+
|
|
159
|
+
The guard starts with no permissions. Permit Jev to assert or deny the two income
|
|
160
|
+
predicates.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const incomeEvidence = guard(kb)
|
|
164
|
+
.permit("assert", hasIncomeOver200k)
|
|
165
|
+
.permit("deny", hasIncomeOver200k)
|
|
166
|
+
.permit("assert", expectsSameIncome)
|
|
167
|
+
.permit("deny", expectsSameIncome);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 6. Ask Jev to assess the income evidence
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const run = classifier(corpus, incomeEvidence);
|
|
174
|
+
|
|
175
|
+
for (const job of run) {
|
|
176
|
+
await job.execute(); // Makes a live Jev request.
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 7. Query the conclusion
|
|
181
|
+
|
|
182
|
+
The output knowledge base combines the imported fact and rules with the admitted
|
|
183
|
+
evidence.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const qualification = query(run.kb, isQualifiedInvestor({ person: alice }));
|
|
187
|
+
const reviewReadiness = query(run.kb, isReadyForReview({ person: alice }));
|
|
188
|
+
|
|
189
|
+
// Both are "true" if Jev admits supporting evidence for all three income claims.
|
|
190
|
+
qualification.value; // "true"
|
|
191
|
+
reviewReadiness.value; // "true"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Results can be `true`, `false`, `conflicted`, or
|
|
195
|
+
`unknown`, depending on the evidence admitted.
|
|
196
|
+
|
|
197
|
+
### 8. Inspect the proof tree
|
|
198
|
+
|
|
199
|
+
Use `explainJudgment()` to follow a conclusion through its rule to the supporting
|
|
200
|
+
evidence and source passages.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { explainJudgment } from "factbag/explain";
|
|
204
|
+
|
|
205
|
+
const proof = explainJudgment(qualification);
|
|
206
|
+
JSON.stringify(proof, null, 2); // JSON shown below.
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
An abbreviated example with supporting evidence for all three claims:
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"claim": "is_qualified_investor(alice) = true",
|
|
214
|
+
"because": "is_qualified_investor(person) if and(...), with person = alice",
|
|
215
|
+
"given": [
|
|
216
|
+
{
|
|
217
|
+
"claim": "and(...) = true",
|
|
218
|
+
"given": [
|
|
219
|
+
{
|
|
220
|
+
"claim": "has_income_over_200k(alice, 2024) = true",
|
|
221
|
+
"because": {
|
|
222
|
+
"claim": "has_income_over_200k(alice, 2024) = true",
|
|
223
|
+
"source": "alice-2024-federal-tax-return#0-1"
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"claim": "has_income_over_200k(alice, 2025) = true",
|
|
228
|
+
"because": {
|
|
229
|
+
"claim": "has_income_over_200k(alice, 2025) = true",
|
|
230
|
+
"source": "alice-2025-federal-tax-return#0-1"
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"claim": "expects_same_income(alice) = true",
|
|
235
|
+
"because": {
|
|
236
|
+
"claim": "expects_same_income(alice) = true",
|
|
237
|
+
"source": "alice-2026-employment-confirmation-letter#0-1"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Source IDs identify document passages; their ranges depend on the document text.
|
|
247
|
+
Use `corpus.resolve(source)` to retrieve the passage behind a piece of evidence.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const source = "alice-2024-federal-tax-return#0-1";
|
|
251
|
+
corpus.resolve(source); // Object shown below.
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The result includes the exact passage and its containing document:
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"chunk": {
|
|
259
|
+
"id": "alice-2024-federal-tax-return#0-1",
|
|
260
|
+
"documentId": "alice-2024-federal-tax-return",
|
|
261
|
+
"range": { "start": 0, "end": 1 },
|
|
262
|
+
"text": "…"
|
|
263
|
+
},
|
|
264
|
+
"document": {
|
|
265
|
+
"documentId": "alice-2024-federal-tax-return",
|
|
266
|
+
"text": "…",
|
|
267
|
+
"metadata": {}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 9. Review conflicting evidence (optional)
|
|
273
|
+
|
|
274
|
+
Suppose an original tax return puts Alice below the income threshold, but an
|
|
275
|
+
amended return puts her above it. Jev admits both accounts, leaving qualification
|
|
276
|
+
`conflicted`. In this example, the other income conditions have supporting evidence.
|
|
277
|
+
|
|
278
|
+
You could pass the conflicting evidence to a human for review, or ask Jev to
|
|
279
|
+
compare the sources using the `resolver`.
|
|
280
|
+
|
|
281
|
+
Evidence judged superseded is retracted from the resolver's output
|
|
282
|
+
knowledge base.
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { resolver } from "factbag/jev/resolve";
|
|
286
|
+
|
|
287
|
+
query(run.kb, isQualifiedInvestor({ person: alice })).value; // "conflicted"
|
|
288
|
+
|
|
289
|
+
const review = resolver(corpus, run.kb);
|
|
290
|
+
|
|
291
|
+
for (const job of review) {
|
|
292
|
+
await job.execute();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// If the resolver retracts the superseded return's denial:
|
|
296
|
+
query(review.kb, isQualifiedInvestor({ person: alice })).value; // "true"
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Go further
|
|
300
|
+
|
|
301
|
+
- [Introducing Factbag](https://daniel.du-pless.is/introducing-factbag): how and why
|
|
302
|
+
to use it.
|
|
303
|
+
- [Jev and the Art of Truth Maintenance](https://daniel.du-pless.is/jev-and-the-art-of-truth-maintenance):
|
|
304
|
+
the thinking behind the project.
|
|
43
305
|
|
|
44
306
|
Apache-2.0 licensed.
|
|
@@ -13,7 +13,10 @@ var PrimitiveArithmeticProgrammerError = class extends ProgrammerError {
|
|
|
13
13
|
/** The arithmetic goal being evaluated when the failure occurred. */
|
|
14
14
|
goal;
|
|
15
15
|
constructor(operation, operands, goal) {
|
|
16
|
-
|
|
16
|
+
const rounding = operation === "roundTo" || operation === "floorTo" || operation === "ceilTo";
|
|
17
|
+
super(
|
|
18
|
+
`${operation} expected ${operation === "daysBetween" || operation === "yearsBetween" ? "date" : "numeric"} operands${rounding ? " and a whole number of decimal places" : ""}`
|
|
19
|
+
);
|
|
17
20
|
this.name = "PrimitiveArithmeticProgrammerError";
|
|
18
21
|
this.operation = operation;
|
|
19
22
|
this.operands = operands;
|
|
@@ -50,7 +50,7 @@ function fieldIdentityToken(value) {
|
|
|
50
50
|
return ["entity", value.constructor.name, value.id];
|
|
51
51
|
}
|
|
52
52
|
if (typeof value === "bigint") {
|
|
53
|
-
|
|
53
|
+
throw new TypeError("bigint is not supported; use number values");
|
|
54
54
|
}
|
|
55
55
|
return value;
|
|
56
56
|
}
|
|
@@ -82,5 +82,6 @@ export {
|
|
|
82
82
|
isAtomForPredicate,
|
|
83
83
|
atomEquals,
|
|
84
84
|
fieldEquals,
|
|
85
|
-
fieldIdentityToken
|
|
85
|
+
fieldIdentityToken,
|
|
86
|
+
isEntityValue
|
|
86
87
|
};
|
package/dist/core/dsl/index.d.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { and, exists, letIn, not, or } from "./formula.js";
|
|
8
8
|
export { count, sum } from "./operators/aggregate.js";
|
|
9
|
-
export type { AggregateBuilder, ComparableTerm, DateTerm, NumericTerm } from "./types.js";
|
|
10
|
-
export { add, div, mul, sub } from "./operators/arithmetic.js";
|
|
11
|
-
export { gt, gte, lte } from "./operators/comparison.js";
|
|
9
|
+
export type { AggregateBuilder, ComparableTerm, DateTerm, EqualityTerm, NumericTerm, } from "./types.js";
|
|
10
|
+
export { add, ceilTo, daysBetween, div, floorTo, max, min, mul, roundTo, sub, yearsBetween, } from "./operators/arithmetic.js";
|
|
11
|
+
export { eq, gt, gte, lte, neq } from "./operators/comparison.js";
|
|
12
12
|
export { closedPredicate, predicate } from "./predicate.js";
|
|
13
13
|
export { enumeration } from "./enumeration.js";
|
|
14
14
|
export type { ClosedWorldPredicate, OpenWorldPredicate, Predicate, PredicateBase, PredicateDeclaration, PredicateMetadata, PredicateWorld, } from "../types/predicate.js";
|
package/dist/core/dsl/index.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertAtomLike,
|
|
3
3
|
assertGroundAtom
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-RMQ3YX7V.js";
|
|
5
5
|
import {
|
|
6
6
|
Var
|
|
7
7
|
} from "../chunk-UVOCOH2I.js";
|
|
8
8
|
|
|
9
|
+
// src/dsl/assert-supported-value.ts
|
|
10
|
+
function assertSupportedValue(value) {
|
|
11
|
+
if (typeof value === "bigint" || value === BigInt) {
|
|
12
|
+
throw new TypeError("bigint is not supported; use number values and Number fields");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
// src/dsl/formula.ts
|
|
10
17
|
function not(atom) {
|
|
11
18
|
return { sign: "-", atom };
|
|
@@ -20,6 +27,7 @@ function exists(...args) {
|
|
|
20
27
|
return { kind: "exists", args };
|
|
21
28
|
}
|
|
22
29
|
function letIn(variable2, value, body) {
|
|
30
|
+
assertSupportedValue(value);
|
|
23
31
|
return { kind: "letIn", args: [variable2, value, body] };
|
|
24
32
|
}
|
|
25
33
|
|
|
@@ -31,6 +39,7 @@ function count(value) {
|
|
|
31
39
|
return aggregateBuilder("count", value);
|
|
32
40
|
}
|
|
33
41
|
function aggregateBuilder(kind, value) {
|
|
42
|
+
assertSupportedValue(value);
|
|
34
43
|
const where = (body, options) => ({
|
|
35
44
|
kind,
|
|
36
45
|
args: options === void 0 ? [value, body] : [value, body, options]
|
|
@@ -40,37 +49,98 @@ function aggregateBuilder(kind, value) {
|
|
|
40
49
|
|
|
41
50
|
// src/dsl/operators/arithmetic.ts
|
|
42
51
|
function add(left, right) {
|
|
52
|
+
assertSupportedValue(left);
|
|
53
|
+
assertSupportedValue(right);
|
|
43
54
|
return { kind: "add", args: [left, right] };
|
|
44
55
|
}
|
|
45
56
|
function sub(left, right) {
|
|
57
|
+
assertSupportedValue(left);
|
|
58
|
+
assertSupportedValue(right);
|
|
46
59
|
return { kind: "sub", args: [left, right] };
|
|
47
60
|
}
|
|
48
61
|
function mul(left, right) {
|
|
62
|
+
assertSupportedValue(left);
|
|
63
|
+
assertSupportedValue(right);
|
|
49
64
|
return { kind: "mul", args: [left, right] };
|
|
50
65
|
}
|
|
51
66
|
function div(left, right) {
|
|
67
|
+
assertSupportedValue(left);
|
|
68
|
+
assertSupportedValue(right);
|
|
52
69
|
return { kind: "div", args: [left, right] };
|
|
53
70
|
}
|
|
71
|
+
function daysBetween(later, earlier) {
|
|
72
|
+
assertSupportedValue(later);
|
|
73
|
+
assertSupportedValue(earlier);
|
|
74
|
+
return { kind: "daysBetween", args: [later, earlier] };
|
|
75
|
+
}
|
|
76
|
+
function yearsBetween(later, earlier) {
|
|
77
|
+
assertSupportedValue(later);
|
|
78
|
+
assertSupportedValue(earlier);
|
|
79
|
+
return { kind: "yearsBetween", args: [later, earlier] };
|
|
80
|
+
}
|
|
81
|
+
function min(left, right) {
|
|
82
|
+
assertSupportedValue(left);
|
|
83
|
+
assertSupportedValue(right);
|
|
84
|
+
return { kind: "min", args: [left, right] };
|
|
85
|
+
}
|
|
86
|
+
function max(left, right) {
|
|
87
|
+
assertSupportedValue(left);
|
|
88
|
+
assertSupportedValue(right);
|
|
89
|
+
return { kind: "max", args: [left, right] };
|
|
90
|
+
}
|
|
91
|
+
function roundTo(value, places) {
|
|
92
|
+
assertSupportedValue(value);
|
|
93
|
+
assertSupportedValue(places);
|
|
94
|
+
return { kind: "roundTo", args: [value, places] };
|
|
95
|
+
}
|
|
96
|
+
function floorTo(value, places) {
|
|
97
|
+
assertSupportedValue(value);
|
|
98
|
+
assertSupportedValue(places);
|
|
99
|
+
return { kind: "floorTo", args: [value, places] };
|
|
100
|
+
}
|
|
101
|
+
function ceilTo(value, places) {
|
|
102
|
+
assertSupportedValue(value);
|
|
103
|
+
assertSupportedValue(places);
|
|
104
|
+
return { kind: "ceilTo", args: [value, places] };
|
|
105
|
+
}
|
|
54
106
|
|
|
55
107
|
// src/dsl/operators/comparison.ts
|
|
108
|
+
function eq(left, right) {
|
|
109
|
+
assertSupportedValue(left);
|
|
110
|
+
assertSupportedValue(right);
|
|
111
|
+
return { kind: "eq", args: [left, right] };
|
|
112
|
+
}
|
|
113
|
+
function neq(left, right) {
|
|
114
|
+
assertSupportedValue(left);
|
|
115
|
+
assertSupportedValue(right);
|
|
116
|
+
return { kind: "neq", args: [left, right] };
|
|
117
|
+
}
|
|
56
118
|
function gt(left, right) {
|
|
119
|
+
assertSupportedValue(left);
|
|
120
|
+
assertSupportedValue(right);
|
|
57
121
|
return { kind: "gt", args: [left, right] };
|
|
58
122
|
}
|
|
59
123
|
function gte(left, right) {
|
|
124
|
+
assertSupportedValue(left);
|
|
125
|
+
assertSupportedValue(right);
|
|
60
126
|
return { kind: "gte", args: [left, right] };
|
|
61
127
|
}
|
|
62
128
|
function lte(left, right) {
|
|
129
|
+
assertSupportedValue(left);
|
|
130
|
+
assertSupportedValue(right);
|
|
63
131
|
return { kind: "lte", args: [left, right] };
|
|
64
132
|
}
|
|
65
133
|
|
|
66
134
|
// src/dsl/predicate.ts
|
|
67
135
|
function makePredicate(name, params, world, metadata) {
|
|
136
|
+
Object.values(params).forEach(assertSupportedValue);
|
|
68
137
|
const holder = {};
|
|
69
138
|
const construct = (fields) => {
|
|
70
139
|
const current = holder.current;
|
|
71
140
|
if (current === void 0) {
|
|
72
141
|
throw new Error(`Predicate ${name} was called before initialization`);
|
|
73
142
|
}
|
|
143
|
+
Object.values(fields).forEach(assertSupportedValue);
|
|
74
144
|
return { predicate: current, fields };
|
|
75
145
|
};
|
|
76
146
|
const declared = construct;
|
|
@@ -113,20 +183,29 @@ export {
|
|
|
113
183
|
and,
|
|
114
184
|
assertAtomLike,
|
|
115
185
|
assertGroundAtom,
|
|
186
|
+
ceilTo,
|
|
116
187
|
closedPredicate,
|
|
117
188
|
count,
|
|
189
|
+
daysBetween,
|
|
118
190
|
div,
|
|
119
191
|
enumeration,
|
|
192
|
+
eq,
|
|
120
193
|
exists,
|
|
194
|
+
floorTo,
|
|
121
195
|
gt,
|
|
122
196
|
gte,
|
|
123
197
|
letIn,
|
|
124
198
|
lte,
|
|
199
|
+
max,
|
|
200
|
+
min,
|
|
125
201
|
mul,
|
|
202
|
+
neq,
|
|
126
203
|
not,
|
|
127
204
|
or,
|
|
128
205
|
predicate,
|
|
206
|
+
roundTo,
|
|
129
207
|
sub,
|
|
130
208
|
sum,
|
|
131
|
-
variable
|
|
209
|
+
variable,
|
|
210
|
+
yearsBetween
|
|
132
211
|
};
|
|
@@ -21,7 +21,7 @@ import type { AggregateBuilder, NumericTerm, Term } from "../types.js";
|
|
|
21
21
|
* );
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
export declare function sum(value: NumericTerm): AggregateBuilder
|
|
24
|
+
export declare function sum(value: NumericTerm): AggregateBuilder;
|
|
25
25
|
/**
|
|
26
26
|
* Count the rows matched by the aggregate body. Runs through the same
|
|
27
27
|
* aggregate machinery as {@link sum}, but thresholds the number of matching
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { DateTerm, Formula, NumericTerm } from "../types.js";
|
|
2
2
|
/** Add two numeric terms, producing a numeric rule-body expression. */
|
|
3
|
-
export declare function add(left: NumericTerm, right: NumericTerm): Formula<number
|
|
3
|
+
export declare function add(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
4
|
+
/** Subtract one numeric term from another. */
|
|
5
|
+
export declare function sub(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
4
6
|
/**
|
|
5
7
|
* Subtract a numeric duration in milliseconds from a date term, producing a
|
|
6
8
|
* date expression.
|
|
@@ -19,9 +21,74 @@ export declare function add(left: NumericTerm, right: NumericTerm): Formula<numb
|
|
|
19
21
|
* ```
|
|
20
22
|
*/
|
|
21
23
|
export declare function sub(left: DateTerm, right: NumericTerm): Formula<Date, readonly [DateTerm, NumericTerm]>;
|
|
22
|
-
/** Subtract one numeric term from another. */
|
|
23
|
-
export declare function sub(left: NumericTerm, right: NumericTerm): Formula<number | bigint, readonly [NumericTerm, NumericTerm]>;
|
|
24
24
|
/** Multiply two numeric terms, producing a numeric rule-body expression. */
|
|
25
|
-
export declare function mul(left: NumericTerm, right: NumericTerm): Formula<number
|
|
25
|
+
export declare function mul(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
26
26
|
/** Divide one numeric term by another, producing a numeric rule-body expression. */
|
|
27
|
-
export declare function div(left: NumericTerm, right: NumericTerm): Formula<number
|
|
27
|
+
export declare function div(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
28
|
+
/**
|
|
29
|
+
* Count signed UTC calendar days from `earlier` to `later`, ignoring time of day.
|
|
30
|
+
* Add one to count both the first and last day of a stay.
|
|
31
|
+
*/
|
|
32
|
+
export declare function daysBetween(later: DateTerm, earlier: DateTerm): Formula<number, readonly [DateTerm, DateTerm]>;
|
|
33
|
+
/**
|
|
34
|
+
* Count signed whole years from `earlier` to `later` by UTC calendar anniversaries,
|
|
35
|
+
* ignoring time of day. February 29 anniversaries fall on March 1 in common years.
|
|
36
|
+
* A person's age on a day is `yearsBetween(day, birthday)`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function yearsBetween(later: DateTerm, earlier: DateTerm): Formula<number, readonly [DateTerm, DateTerm]>;
|
|
39
|
+
/**
|
|
40
|
+
* The lesser of two numeric terms.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* // Deduction reduction cannot exceed 80% of the deductions.
|
|
45
|
+
* letIn(R, min(mul(0.03, Excess), mul(0.8, Deductions)), gte(R, 0))
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function min(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
49
|
+
/**
|
|
50
|
+
* The greater of two numeric terms.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* // The remaining allowance cannot be negative.
|
|
55
|
+
* letIn(A, max(sub(2000, Reduction), 0), gte(A, 0))
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function max(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
59
|
+
/**
|
|
60
|
+
* Round to a whole number of decimal places, with halves away from zero.
|
|
61
|
+
* Negative places round to tens, hundreds, and so on. Decimal exponent shifting
|
|
62
|
+
* avoids multiplication artifacts such as `1.005 * 100`, so `roundTo(1.005, 2)`
|
|
63
|
+
* yields `1.01`. Inputs and results still have JavaScript number precision;
|
|
64
|
+
* this does not recover precision already lost in an earlier expression.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* // Tax is 15% of taxable income, in whole dollars.
|
|
69
|
+
* letIn(Tax, roundTo(mul(Income, 0.15), 0), gte(Tax, 0))
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function roundTo(value: NumericTerm, places: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
73
|
+
/**
|
|
74
|
+
* Round toward negative infinity at a whole number of decimal places.
|
|
75
|
+
* Negative places and decimal exponent shifting work as in {@link roundTo}.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* // Only complete thousands of eligible expenses count.
|
|
80
|
+
* letIn(Eligible, floorTo(Expenses, -3), gte(Eligible, 0))
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function floorTo(value: NumericTerm, places: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
84
|
+
/**
|
|
85
|
+
* Round toward positive infinity at a whole number of decimal places.
|
|
86
|
+
* Negative places and decimal exponent shifting work as in {@link roundTo}.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // Two percentage points for each $2,500, or fraction thereof, of the excess.
|
|
91
|
+
* letIn(Points, mul(2, ceilTo(div(Excess, 2500), 0)), lte(Points, 100))
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function ceilTo(value: NumericTerm, places: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|