@factbag/jev 0.0.0-alpha.5

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/docs/README.md ADDED
@@ -0,0 +1,317 @@
1
+ # Fix one fact, not the whole answer
2
+
3
+ You're reviewing a new supplier. Its name doesn't appear on the sanctions list.
4
+ Its ownership records are less reassuring: most of its shares are held by a
5
+ nominee company. You need to know who those shares really belong to.
6
+
7
+ You have the register, an ownership agreement, a legal confirmation, and an old
8
+ due-diligence memo. The documents disagree.
9
+
10
+ You could give everything to an AI and ask, “Is this company under sanctions?”
11
+ It might produce a good answer. But suppose you notice that it relied on the
12
+ old memo rather than the executed agreement. You explain the mistake and ask
13
+ it to try again.
14
+
15
+ Now you have another answer to check. Did it fix that fact? Did it change its
16
+ reading of the agreement? Is it still applying the same rule?
17
+
18
+ The problem isn't just that the model got something wrong. It's that correcting
19
+ one fact has reopened the whole decision.
20
+
21
+ We already avoid this in other kinds of software. In a spreadsheet, we correct
22
+ one cell and let the formulas recalculate. In a program, we fix one function
23
+ while tests protect the behavior we've checked. The work has a structure. We
24
+ can keep the good parts.
25
+
26
+ **Factbag brings that idea to decisions made from evidence.** We write down the
27
+ rules, keep the facts that feed them, and calculate what follows. Some facts
28
+ come straight from structured data. AI helps interpret the parts that require
29
+ reading. A person can correct a particular interpretation without asking the
30
+ model to reconsider everything.
31
+
32
+ ## A company can be covered without appearing on a list
33
+
34
+ Consider a fictional supplier, Harbor Components. We're screening it as of
35
+ September 1, 2026. Three records give us a starting point:
36
+
37
+ | Record | What we know |
38
+ | ---------------------- | ------------------------------------------------------------------ |
39
+ | Blocking-list snapshot | Harbor Components is not directly listed. |
40
+ | The same snapshot | Oriole Holdings is listed as blocked. |
41
+ | Ownership register | Alder Nominees is the registered holder of 60% of Harbor's shares. |
42
+
43
+ The question is whether Alder holds those shares for Oriole.
44
+
45
+ This is a real kind of screening problem. Under
46
+ [OFAC's 50 Percent Rule](https://ofac.treasury.gov/faqs/401), entities owned
47
+ 50% or more in the aggregate, directly or indirectly, by blocked persons are
48
+ themselves considered blocked. A company need not have its own entry on the list.
49
+
50
+ For our example, we'll examine one route through that rule: a single majority
51
+ stake held by a nominee for a blocked owner. The companies and records are
52
+ invented. A complete screening system would also need to handle multiple owners,
53
+ indirect chains, and other sanctions restrictions.
54
+
55
+ The particular argument we're trying to establish looks like this:
56
+
57
+ ```text
58
+ Harbor is covered through this ownership arrangement if:
59
+ • Oriole is blocked.
60
+ • Alder holds at least 50% of Harbor.
61
+ • Alder holds that stake for Oriole's beneficial ownership.
62
+ • The arrangement is in force at the screening date.
63
+ ```
64
+
65
+ Writing the conditions down matters. A reviewer can inspect the rule before
66
+ looking at any individual model answer. The policy doesn't have to be
67
+ rediscovered each time we read a document.
68
+
69
+ ## Don't ask a model to read what the computer already knows
70
+
71
+ The first two conditions don't need AI.
72
+
73
+ We have a structured list snapshot and a structured ownership record. In this
74
+ example, the entities have already been matched to stable identifiers. Ordinary
75
+ code checks those identifiers against the list, reads the registered holding,
76
+ and compares 60% with the 50% threshold.
77
+
78
+ Those results enter the knowledge base directly, together with references to
79
+ the records and their dates. We don't ask Jev to estimate the percentage,
80
+ perform the comparison, or decide whether an ID is in an array.
81
+
82
+ The remaining conditions are different. An executed agreement may describe
83
+ whose benefit the shares are held for. A legal letter may say whether that
84
+ arrangement remains effective. Those are statements we need to interpret.
85
+
86
+ In Factbag, each condition is represented by a **predicate**: a named kind of
87
+ fact with fields identifying what it concerns. “A nominee holds shares for an
88
+ owner” is a predicate. “Alder holds its stake in Harbor for Oriole” is a specific
89
+ claim we can investigate.
90
+
91
+ The same knowledge base accepts facts from both paths. The source might be an
92
+ exact lookup in a structured record or a reading of a paragraph. The rules can
93
+ use either, and a reviewer can see where each came from.
94
+
95
+ At this stage, our ownership conclusion is **unknown**. The supplier's absence
96
+ from the list hasn't made the ownership question disappear.
97
+
98
+ ## Ask Jev a question the documents can answer
99
+
100
+ [Jev](https://typesafe.ai/) is TypeSafe AI's model for making structured judgments.
101
+ Here, we use it to ask a smaller question than “is Harbor sanctioned?”:
102
+
103
+ > Does this passage establish that Alder holds its registered stake in Harbor
104
+ > for Oriole's beneficial ownership?
105
+
106
+ We ask it to distinguish three possibilities:
107
+
108
+ - **Assert:** the passage supports the claim.
109
+ - **Deny:** the passage explicitly contradicts the claim.
110
+ - **Irrelevant:** it establishes neither.
111
+
112
+ The predicate includes guidance about what counts. A shared address or a right
113
+ to appoint directors doesn't, by itself, establish ownership. That distinction
114
+ is important here: [OFAC's ownership rule is not simply a control test](https://ofac.treasury.gov/faqs/398).
115
+
116
+ We separately ask whether the arrangement is in force at the screening date.
117
+ An old document's existence is not enough to settle that question.
118
+
119
+ Factbag's corpus library keeps each document and divides it into smaller
120
+ passages. Each passage has an ID tied to its position in a particular document
121
+ revision. When Jev's reading becomes evidence, that ID stays attached. We can
122
+ return to the exact words, with the surrounding text available when needed.
123
+
124
+ Jev supplies probabilities for assert, deny, and irrelevant. The application
125
+ chooses which readings to admit. When assert and deny are close, it can keep
126
+ both rather than force a premature choice.
127
+
128
+ ## Let the documents disagree
129
+
130
+ Suppose the documentary evidence comes back like this. These scores are invented
131
+ for the runnable example, not measured Jev results.
132
+
133
+ | Document | Reading | Support for that reading |
134
+ | -------------------------- | ------------------------------------------------------ | ------------------------ |
135
+ | Executed nominee deed | Alder holds the stake for Oriole. | 0.96 |
136
+ | Current legal confirmation | The arrangement is in force on the screening date. | 0.87 |
137
+ | Earlier onboarding memo | Alder holds the stake for its own account, not Oriole. | 0.98 |
138
+
139
+ A fourth document announces that Oriole appointed directors to Harbor's board.
140
+ It doesn't settle either of our ownership questions, so it adds no evidence for
141
+ them. It could matter to a different inquiry; it is not a shortcut to this one.
142
+
143
+ The earlier memo has the highest score. Should it win?
144
+
145
+ No. The score concerns Jev's interpretation of the memo. The model can be very
146
+ sure that a document says “held for Alder's own account” while the document
147
+ itself is outdated.
148
+
149
+ Factbag retains the positive and negative evidence. The ownership relationship
150
+ is now **conflicted**, and so is this argument for treating Harbor as blocked.
151
+ We have found a specific question to review.
152
+
153
+ This gives us four useful states: **true**, **false**, **unknown**, and
154
+ **conflicted**. Missing evidence calls for investigation. Opposing evidence
155
+ calls for review. Neither has to be disguised as a confident yes or no.
156
+
157
+ The result includes the facts and rules used to reach it. We can follow the
158
+ disputed relationship back to the deed and the memo. This is the calculation
159
+ the program performed, rather than an explanation a model composed afterward.
160
+
161
+ ## Correct the interpretation, not the whole case
162
+
163
+ The analyst checks that the executed deed supersedes the onboarding memo for
164
+ this same shareholding. The legal confirmation establishes that the arrangement
165
+ covers the screening date. They exclude the memo's denial from the active
166
+ evidence and record why.
167
+
168
+ The rules run again. The required facts now hold: the blocked owner beneficially
169
+ owns the 60% stake through the nominee, and the arrangement is effective. Harbor
170
+ is therefore treated as blocked under the ownership rule being modeled, even
171
+ though it is not directly listed.
172
+
173
+ We haven't asked Jev to check the sanctions list again. We haven't reimported
174
+ the register or changed the percentage. We haven't regenerated our reading of
175
+ the legal confirmation. Those accepted facts are still there.
176
+
177
+ The demo keeps the excluded evidence, its source, and the analyst's reason. A
178
+ durable application would save that decision and respect it on later ingestions.
179
+ Removing a fact from the current calculation needn't mean forgetting why it
180
+ was once included.
181
+
182
+ This is useful human involvement: a person can inspect one disagreement, change
183
+ the part that needs judgment, and see what follows.
184
+
185
+ ## Follow the uncertainty to the conclusion
186
+
187
+ We can now ask how well this conclusion is supported.
188
+
189
+ For the demonstration, we take the imported list and register as given inputs.
190
+ We assign them a support weight of one for this calculation. That is an explicit
191
+ assumption, not a claim that structured data is infallible and not a model
192
+ probability.
193
+
194
+ The documentary readings remain uncertain. One scores `0.96`, the other `0.87`.
195
+ We use a simple policy: an argument requiring all its facts is only as strong
196
+ as the weakest one.
197
+
198
+ ```text
199
+ Ownership argument 0.87
200
+ Oriole is blocked given
201
+ Alder's registered stake is at least 50% given
202
+ Alder holds the stake for Oriole 0.96
203
+ The arrangement is in force 0.87
204
+
205
+ min(1, 1, 0.96, 0.87) = 0.87
206
+ ```
207
+
208
+ The number tells us where to look next. Improving the reading of the deed to
209
+ `0.99` would not strengthen this argument. The weaker part is the evidence that
210
+ the arrangement remains in force.
211
+
212
+ Suppose we add a signed supplemental confirmation. Its support for that same
213
+ fact is `0.92`. Our demonstration policy takes the stronger of alternative
214
+ pieces of positive evidence:
215
+
216
+ ```text
217
+ Arrangement in force: max(0.87, 0.92) = 0.92
218
+ Ownership argument: min(1, 1, 0.96, 0.92) = 0.92
219
+ ```
220
+
221
+ We call this a **support score**. It summarizes the argument under an explicit
222
+ policy. It does not establish a 92% probability that a legal conclusion is
223
+ correct. The runnable example contains a small helper for this calculation;
224
+ it leaves conflicted results unscored rather than hide the disagreement behind
225
+ one number.
226
+
227
+ ## The same idea can explain reported causes
228
+
229
+ Consider another question: why did a customer leave?
230
+
231
+ Our customer database can tell us that Cobalt canceled on September 1. That is
232
+ another fact to import directly. An exit interview is different: it says that
233
+ outages, failed onboarding, and billing disputes contributed to the decision,
234
+ but price did not.
235
+
236
+ We ask Jev whether each candidate factor is reported to have contributed to
237
+ that particular cancellation:
238
+
239
+ | Factor | Reading of the interview | Support |
240
+ | ----------- | ----------------------------- | ------- |
241
+ | Reliability | Contributed | 0.94 |
242
+ | Onboarding | Contributed | 0.82 |
243
+ | Billing | Contributed | 0.76 |
244
+ | Price | Explicitly did not contribute | 0.91 |
245
+ | Competition | Not addressed | — |
246
+
247
+ Price and competition are different. There is evidence against one; we simply
248
+ don't know about the other. Several factors can contribute, so their scores
249
+ need not add up to one.
250
+
251
+ We can write rules that group the facts into explanations. If our definition
252
+ of “product breakdown” requires both reliability and onboarding to have
253
+ contributed, the same calculation gives it `min(0.94, 0.82) = 0.82`. A separate
254
+ explanation based on billing has support of `0.76`.
255
+
256
+ Set aside the onboarding evidence in a temporary copy of the case, and the
257
+ product explanation lacks a required fact. The billing explanation still
258
+ holds. The strongest remaining argument scores `0.76`.
259
+
260
+ That shows how the explanation depends on its evidence. It does not show that
261
+ fixing onboarding would reduce churn by six percentage points. We have
262
+ analyzed reported contributions, not estimated the effect of an intervention.
263
+
264
+ ## Learn whether the numbers deserve our trust
265
+
266
+ Eventually, reviewed cases and actual outcomes give us a way to evaluate the
267
+ scores. There are two different things we might want to learn.
268
+
269
+ First, **does Jev read the documents correctly?** Reviewers can label whether a
270
+ passage asserts, denies, or says nothing about a claim. Those labels let us
271
+ evaluate and adjust the model's three-way probabilities. We should retain the
272
+ original assessment alongside any adjusted score.
273
+
274
+ Second, **can these signals predict an outcome?** We might use support
275
+ conversations, onboarding notes, and billing records to predict cancellation
276
+ within 30 days. A small statistical model could learn from Jev's assessments,
277
+ structured customer data, and actual cancellation outcomes.
278
+
279
+ These experiments need different labels. Knowing that a customer canceled
280
+ doesn't establish that every proposed reason was correct. And an exit interview
281
+ collected after cancellation cannot predict that same cancellation.
282
+
283
+ For a forward-looking prediction, we need the information available at the time
284
+ and a complete period in which to observe the outcome. We fit on some customers
285
+ and test on other, later cases. If predictions near `0.80` repeatedly come true
286
+ only 60% of the time, they are overconfident. A useful calibration improves
287
+ that correspondence on cases it hasn't seen.
288
+
289
+ This layer is still a design idea. Calibrating individual readings would not
290
+ automatically calibrate the final min/max score, either. We would check that
291
+ separately. The aim is to earn the meaning of a probability through observation,
292
+ rather than obtain it by putting a percent sign next to a number.
293
+
294
+ ## Keep the work you've already done
295
+
296
+ The common thread is a place for progress to accumulate.
297
+
298
+ The rule is written down. Structured records enter through ordinary code.
299
+ Documentary interpretations point back to passages. A disagreement has a
300
+ location. A correction has a reason. A score has a calculation.
301
+
302
+ The AI still does valuable work: interpreting material that doesn't arrive as
303
+ neat fields. But its contribution becomes something we can inspect and revise
304
+ in pieces. Each correction improves the case instead of starting another round
305
+ of answer generation.
306
+
307
+ That's the experience we're trying to build with Factbag: an answer you can
308
+ understand well enough to fix, without losing the parts you've already checked.
309
+
310
+ ---
311
+
312
+ **Try the examples:** run `pnpm --filter @factbag/jev demo` from the repository
313
+ root. The examples use fictional structured records and scripted Jev responses;
314
+ they perform no live sanctions screening or model calls. Evidence review,
315
+ support scoring, and factor analysis are runnable; calibration is a proposed
316
+ next step. See the [example notes](https://github.com/factbag/factbag/tree/main/providers/jev/examples#readme) for the code and its
317
+ scope, or the [package reference](https://github.com/factbag/factbag/blob/main/providers/jev/README.md) for the current API.
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "@factbag/jev",
3
+ "version": "0.0.0-alpha.5",
4
+ "description": "Lazy AI-assisted classification and evidence resolution for Factbag.",
5
+ "keywords": [
6
+ "classification",
7
+ "evidence",
8
+ "ai",
9
+ "rules-engine",
10
+ "typescript"
11
+ ],
12
+ "homepage": "https://github.com/factbag/factbag/tree/main/providers/jev#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/factbag/factbag/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/factbag/factbag.git",
19
+ "directory": "providers/jev"
20
+ },
21
+ "license": "Apache-2.0",
22
+ "sideEffects": false,
23
+ "type": "module",
24
+ "files": [
25
+ "dist/public",
26
+ "docs/README.md",
27
+ "CHANGELOG.md",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/public/index.d.ts",
34
+ "import": "./dist/public/index.js"
35
+ }
36
+ },
37
+ "engines": {
38
+ "node": "^22.18.0 || ^24.0.0 || ^26.0.0"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "tsdown": [
44
+ {
45
+ "entry": [
46
+ "src/index.ts"
47
+ ],
48
+ "dts": false,
49
+ "sourcemap": true,
50
+ "fixedExtension": false,
51
+ "deps": {
52
+ "neverBundle": true
53
+ }
54
+ },
55
+ {
56
+ "entry": {
57
+ "index": "src/index.ts"
58
+ },
59
+ "outDir": "dist/public",
60
+ "clean": false,
61
+ "dts": true,
62
+ "fixedExtension": false,
63
+ "tsconfig": "tsconfig.public.json",
64
+ "deps": {
65
+ "alwaysBundle": [
66
+ "@factbag/system-one"
67
+ ],
68
+ "neverBundle": true,
69
+ "onlyImport": [
70
+ "factbag",
71
+ "@typesafe-ai/sdk"
72
+ ]
73
+ },
74
+ "publint": true,
75
+ "attw": {
76
+ "profile": "esm-only",
77
+ "level": "error"
78
+ }
79
+ }
80
+ ],
81
+ "devDependencies": {
82
+ "@factbag/system-one": "0.1.0-next.0",
83
+ "@factbag/tsconfig": "0.0.0",
84
+ "@types/node": "22.20.0",
85
+ "@typesafe-ai/sdk": "0.7.0",
86
+ "msw": "2.15.0",
87
+ "tsx": "4.23.15",
88
+ "typescript": "7.0.2",
89
+ "vitest": "5.0.5"
90
+ },
91
+ "peerDependencies": {
92
+ "factbag": "0.0.0-alpha.5",
93
+ "@typesafe-ai/sdk": "^0.7.0"
94
+ },
95
+ "scripts": {
96
+ "api-report": "api-extractor run --local",
97
+ "api-report:check": "api-extractor run",
98
+ "build": "tsdown && tsc -p tsconfig.build.json --emitDeclarationOnly",
99
+ "demo": "tsx examples/run.ts",
100
+ "demo:staged": "tsx examples/staged-comparison.ts",
101
+ "test": "vitest run",
102
+ "typecheck": "tsc --noEmit"
103
+ }
104
+ }