@littlebigbrain/create 0.1.0 → 0.3.0

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.
Files changed (56) hide show
  1. package/README.md +6 -2
  2. package/lib/create.mjs +33 -5
  3. package/package.json +1 -1
  4. package/templates/_shared/testing.ts +53 -9
  5. package/templates/documents-link/README.md +9 -7
  6. package/templates/documents-link/workflow.test.ts +28 -1
  7. package/templates/documents-link/workflow.ts +24 -14
  8. package/templates/documents-map/README.md +212 -0
  9. package/templates/documents-map/map.ts +1135 -0
  10. package/templates/documents-map/samples/ausstattungsliste.pdf +76 -0
  11. package/templates/documents-map/samples/baubeschreibung.pdf +54 -0
  12. package/templates/documents-map/samples/expected.json +50 -0
  13. package/templates/documents-map/samples/pages/raumblaetter.json +1580 -0
  14. package/templates/documents-map/samples/raumblaetter.pdf +109 -0
  15. package/templates/documents-map/samples/trace.json +613 -0
  16. package/templates/documents-map/samples.ts +182 -0
  17. package/templates/documents-map/workflow.test.ts +770 -0
  18. package/templates/documents-map/workflow.ts +1199 -0
  19. package/templates/link/README.md +203 -0
  20. package/templates/link/candidates.ts +432 -0
  21. package/templates/link/decide.test.ts +368 -0
  22. package/templates/link/decide.ts +340 -0
  23. package/templates/link/fixture.ts +727 -0
  24. package/templates/link/grade-run.ts +53 -0
  25. package/templates/link/grade.test.ts +113 -0
  26. package/templates/link/grade.ts +335 -0
  27. package/templates/link/items.ts +333 -0
  28. package/templates/link/names.test.ts +260 -0
  29. package/templates/link/names.ts +590 -0
  30. package/templates/link/reads.ts +302 -0
  31. package/templates/link/samples/gold.json +108 -0
  32. package/templates/link/samples/records.json +48 -0
  33. package/templates/link/samples.ts +64 -0
  34. package/templates/link/settings.ts +162 -0
  35. package/templates/link/setup.ts +18 -0
  36. package/templates/link/source-turn.ts +894 -0
  37. package/templates/link/sources/records.ts +429 -0
  38. package/templates/link/things-turn.ts +984 -0
  39. package/templates/link/things.test.ts +204 -0
  40. package/templates/link/things.ts +426 -0
  41. package/templates/link/try.ts +28 -0
  42. package/templates/link/worker.ts +24 -0
  43. package/templates/link/workflow.test.ts +484 -0
  44. package/templates/link/workflow.ts +202 -0
  45. package/templates/ontology-fit/workflow.test.ts +7 -1
  46. package/templates/ontology-fit/workflow.ts +5 -2
  47. package/templates/review/README.md +158 -0
  48. package/templates/review/decision.ts +483 -0
  49. package/templates/review/samples/expected.json +37 -0
  50. package/templates/review/samples/proposals.json +35 -0
  51. package/templates/review/samples.ts +154 -0
  52. package/templates/review/workflow.test.ts +438 -0
  53. package/templates/review/workflow.ts +459 -0
  54. package/templates/values-extract/workflow.test.ts +24 -1
  55. package/templates/values-extract/workflow.ts +11 -3
  56. package/templates/versions.json +1 -1
@@ -13,6 +13,7 @@ import { ENTITY_IRI_NAMESPACE, type Schemas } from "@littlebigbrain/client";
13
13
  import {
14
14
  MockActivities,
15
15
  mockId,
16
+ phases,
16
17
  readJson,
17
18
  runMessage,
18
19
  samplesDir,
@@ -195,7 +196,7 @@ function rules(more: MockRules = {}): MockRules {
195
196
 
196
197
  test("the samples give the changes of expected.md, one review item per key", async () => {
197
198
  const lbb = new MockActivities(rules());
198
- const { state } = await runMessage(
199
+ const { state, turns } = await runMessage(
199
200
  createWorkflow(lbb),
200
201
  triggerMessage(
201
202
  "berichte",
@@ -203,6 +204,11 @@ test("the samples give the changes of expected.md, one review item per key", asy
203
204
  ),
204
205
  );
205
206
 
207
+ assert.deepEqual(phases(turns), ["reading texts", "filing review items"]);
208
+ assert.deepEqual(
209
+ turns[0]!.progress.filter((p) => p.phase === "filing review items").map((p) => p.done),
210
+ [0, 1, 2, 3, 4],
211
+ );
206
212
  assert.equal(EXPECTED_KEYS.length, 5);
207
213
  assert.deepEqual([...lbb.reviews.keys()].sort(), [...EXPECTED_KEYS].sort());
208
214
  assert.deepEqual(state, { texts: 5, proposals: 3 + 4 + 5 + 5 + 2, filed: 5 });
@@ -300,6 +300,7 @@ export function createWorkflow(lbb: LbbActivities, settings: Settings = defaults
300
300
  for (const [i, iri] of batch.entries()) {
301
301
  kept.push(
302
302
  await ctx.step(`propose-${i}`, async (s) => {
303
+ s.progress("reading texts", { done: i, total: batch.length, unit: "instances" });
303
304
  const record = (await texts())[iri];
304
305
  if (!record?.text.trim()) return [];
305
306
  const ontology = await (onto ??= lbb.ontology().then(describe));
@@ -346,7 +347,7 @@ export function createWorkflow(lbb: LbbActivities, settings: Settings = defaults
346
347
  }),
347
348
  );
348
349
  }
349
- const filed = await ctx.step("review", async () => {
350
+ const filed = await ctx.step("review", async (s) => {
350
351
  const records = await texts();
351
352
  const byKey = new Map<
352
353
  string,
@@ -362,7 +363,8 @@ export function createWorkflow(lbb: LbbActivities, settings: Settings = defaults
362
363
  });
363
364
  byKey.set(change.key, group);
364
365
  }
365
- for (const { change, samples } of byKey.values())
366
+ for (const [n, { change, samples }] of [...byKey.values()].entries()) {
367
+ s.progress("filing review items", { done: n, total: byKey.size, unit: "items" });
366
368
  await lbb.review({
367
369
  workflow: message.workflow,
368
370
  key: change.key,
@@ -372,6 +374,7 @@ export function createWorkflow(lbb: LbbActivities, settings: Settings = defaults
372
374
  change: change.change,
373
375
  evidence: { records: samples.length, samples: samples.slice(0, 20) },
374
376
  });
377
+ }
375
378
  return byKey.size;
376
379
  });
377
380
  const next = {
@@ -0,0 +1,158 @@
1
+ # review
2
+
3
+ Decides which fact proposals become facts. A proposer (the
4
+ [`documents-map`](../documents-map/README.md) template, LBB's
5
+ `documents.map`, or a workflow of yours) does not write the facts of the
6
+ graph's own ontology itself: it writes one `FactProposal` per statement,
7
+ with its confidence and its evidence. This workflow writes a proposal at or
8
+ above the gate as a fact, with the proposal as its evidence, and gives the
9
+ others to a person: one review item per instance.
10
+
11
+ The LBB-run version is the starter workflow `review@v1`
12
+ (`docs/reference/starter-workflows.md`, "Review"; design
13
+ `docs/architecture/document-output-mapping.md`). This template is the same
14
+ decision in TypeScript: the workflow in `workflow.ts`, the rules and the
15
+ facts in `decision.ts`. It calls no model.
16
+
17
+ ## How it runs
18
+
19
+ The triggered workflow watches the class `FactProposal` and the fields of
20
+ its statement (`fact_subject`, `fact_predicate`, `fact_object`), so a new
21
+ proposal, or one whose statement changed, sends a message. A turn decides at
22
+ most 20 proposals, two steps each, and files at most one review item per
23
+ instance; the rest of a message goes to a new turn (`ctx.continue`). Each
24
+ proposal is decided once: one that has a `fact_status` already is skipped.
25
+
26
+ 1. **Read** the proposal (`fact_subject`, `fact_predicate`, `fact_object`,
27
+ `fact_datatype`, `fact_confidence`, `fact_evidence`, `proposed_by`), the
28
+ entities its statement names, and the ontology (`GET /v1/ontology`). An
29
+ entity the graph does not have is a new instance: its class comes from an
30
+ `rdf:type` proposal of it, its name from an `rdfs:label` proposal.
31
+ 2. **What the statement is** (`classify`): an `rdf:type` of a class, an
32
+ `rdfs:label`, a relation between two instances (the entry's `DENOTES`
33
+ included), or a property value in its type. The ends of a relation must fit
34
+ its domain and range, superclasses included.
35
+ 3. **The decision** (`decide`), in this order:
36
+ - **refused** when the graph cannot hold the statement: a predicate that
37
+ is no relation or property of the records ontology (nor `rdf:type` or
38
+ `rdfs:label`), a value of another type, an object that is no instance,
39
+ an instance without a class, an end its relation does not take, a second
40
+ class, or a statement about a proposal;
41
+ - **to a person** when a rule of `alwaysReview` names it (`new_instance`:
42
+ a statement whose subject or object the graph does not have yet; or a
43
+ predicate IRI), when it would replace another writer's value (a records
44
+ graph holds one value per property) or name, or when its confidence is
45
+ below the gate. The proposal goes into the **review item of its
46
+ instance** (`groupOf`): its subject; the object of a link whose subject
47
+ the graph has and whose object is new; and always the object of the
48
+ trace (`DENOTES`). One item holds every proposal of its instance that
49
+ the turn sends to a person, in order: the type, the label, values,
50
+ links, the trace (`fileItem`). Its title names the instance (`New
51
+ Equipment M 7 Medienwagen`, or `Room Raum 0.12 Klassenraum: 1 fact to
52
+ check`), its rationale says why, and it has one evidence sample per
53
+ fact (what it states, its confidence, its line). Its facts are each
54
+ statement and its proposal's `fact_status: accepted`, and each sample
55
+ names the rows and triplets of its fact (`row`, `rows`, `triplets`), so
56
+ a person can leave facts out of the accept. The type of a new instance
57
+ is a required row: a person keeps it or dismisses the item;
58
+ - **written** otherwise: a relation as an edge whose evidence names the
59
+ proposal (`source_id` is the proposal's IRI, the text its evidence); a
60
+ property as its value; a new instance's `rdf:type` as the instance of
61
+ that class with `proposed_in` (a records graph holds an instance through
62
+ its facts); a label as the entity's name. A new instance takes the name
63
+ of its label proposal (the first in order) from its first write, so its
64
+ name does not depend on which statement is written first. A label is a
65
+ text: an IRI or an empty label is refused. A statement the graph holds
66
+ already is not written again. Nothing is taken back, a name included: a
67
+ records instance keeps one.
68
+ 4. **Write** (`writeOwned`): the proposal's `fact_status` (`written`,
69
+ `in_review` or `refused`) and `review_note`, owned by this workflow, and in
70
+ the same commit the facts of a written statement.
71
+
72
+ ## Settings
73
+
74
+ `defaults` in `workflow.ts`, those of LBB's `review`:
75
+
76
+ | Setting | Default | Meaning |
77
+ | --- | --- | --- |
78
+ | `className` | `FactProposal` | The class of the proposals |
79
+ | `gate` | 0.8 | A proposal below this confidence goes to a person |
80
+ | `alwaysReview` | `[]` | What goes to a person whatever its confidence: `new_instance`, and predicate IRIs (`https://littlebigbrain.com/r/has_equipment`). Empty: the gate decides everything, a new instance too |
81
+
82
+ With `["new_instance"]` every statement about a new instance goes to a
83
+ person, whatever its confidence. That was the default of LBB's `review`
84
+ until 2026-10-12.
85
+
86
+ `trigger(settings)` names the triggered workflow `review` and owns
87
+ `fact_status` and `review_note`. `ontology(settings)` adds the class and the
88
+ properties of a proposal and of the review (`proposed_in` too). Do not run it
89
+ beside an LBB-run `review` on the same graph: they own the same properties.
90
+
91
+ ## What the LBB-run version adds
92
+
93
+ - **It writes the accepted facts itself.** Its item carries the statuses
94
+ only; its sweep writes each kept fact, the type first, and dismisses the
95
+ facts a person left out. A link to another new instance waits until that
96
+ instance's item is accepted, and is dismissed with it. The template's item
97
+ commits its facts on accept, so such a link makes the other instance too.
98
+ - **One item per instance over time.** Its items follow the open proposals
99
+ of an instance across messages: when they change, it supersedes the open
100
+ item with a new one, and an accept of the old item fails (409). The
101
+ template files one item per instance and turn: the key carries the
102
+ proposals, so a later turn with other proposals of the instance files
103
+ another item.
104
+ - **Withdrawn proposals.** When a proposer withdraws a proposal, it takes
105
+ the proposal out of its item and retracts the fact it wrote, unless a
106
+ person accepted it or the fact held before; a record per proposal says
107
+ which fact. The template keeps both: dismiss the item, or retract the fact
108
+ yourself.
109
+ - **Dismissed items.** It checks the open review items and sets
110
+ `dismissed` on the proposals of a dismissed one. The template leaves
111
+ `in_review`.
112
+ - **Predicates another workflow owns.** It refuses a statement whose
113
+ predicate another workflow owns by predicate, since that workflow's owned
114
+ write would delete it.
115
+ - **RDF-native graphs by IRI.** On an RDF-native graph it writes each
116
+ statement as one owned triple: `lbb:denotes`, the asserted schema's
117
+ properties (the OWL object, symmetric, transitive, functional,
118
+ inverse-functional and asymmetric properties take an IRI) and classes
119
+ (the ends of `rdfs:subClassOf` count), the records ontology's terms and
120
+ `rdfs:label`. There a subject or object is new when it is the subject of
121
+ no statement. The template writes through `POST /v1/graph/commit`, on both
122
+ graph kinds: the records ontology's relations (the records `DENOTES`
123
+ included), properties and classes, and `rdfs:label`. An instance is new
124
+ when it has no class of the records ontology. It refuses any other
125
+ predicate with a note that says so (`owl:sameAs`, `lbb:denotes`).
126
+ - **Ownership by statement.** Its facts count as owned by statement, so it
127
+ conflicts with no other workflow on the predicates it writes. The template
128
+ owns `fact_status` and `review_note` only and writes the facts without
129
+ owning them.
130
+ - **The graph refuses.** It lets the commit refuse a statement (a domain, a
131
+ range) and marks the proposal `refused`. The template checks domain and
132
+ range before it writes; another refusal stops the turn.
133
+ - **Status and preview.** Counters per outcome, the open items it follows,
134
+ and a preview of what it would decide.
135
+
136
+ ## Samples and test
137
+
138
+ `samples/proposals.json` holds a small invented graph (two rooms of the
139
+ school building and five pieces of equipment, one `HAS_EQUIPMENT` edge) and
140
+ 14 proposals about it, one for each case; `samples/expected.json` gives the
141
+ status each one gets with `alwaysReview` `["new_instance"]`, the facts the
142
+ graph holds after the run, and the three review items with the proposals
143
+ each one holds
144
+ ([`../_samples/README.md`](../_samples/README.md)).
145
+
146
+ `npm run try` adds the sample ontology (`Room`, `Equipment`,
147
+ `HAS_EQUIPMENT`, `identifier`, `area_m2`), commits the instances, reads
148
+ their IRIs back, and commits the proposals as a proposer would
149
+ (`proposed_by` is `samples/review`). This is the simplest input: the review
150
+ calls no model and needs no proposer to run first. To review the proposals
151
+ of `documents-map`, run it (or LBB's `documents.map`) on the same graph.
152
+
153
+ `workflow.test.ts` runs the workflow on a graph in memory with the samples:
154
+ the statuses and facts of the answer key, the evidence of a written fact,
155
+ the notes, one item per instance (the order of its facts and its required
156
+ type), the facts of an accepted item, the default settings (the gate
157
+ decides a new instance), a predicate rule, a second message that changes
158
+ nothing, and a message larger than a turn.