@littlebigbrain/create 0.1.0 → 0.2.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 (30) hide show
  1. package/README.md +3 -1
  2. package/lib/create.mjs +7 -1
  3. package/package.json +1 -1
  4. package/templates/_shared/testing.ts +36 -5
  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/ontology-fit/workflow.test.ts +7 -1
  20. package/templates/ontology-fit/workflow.ts +5 -2
  21. package/templates/review/README.md +158 -0
  22. package/templates/review/decision.ts +483 -0
  23. package/templates/review/samples/expected.json +37 -0
  24. package/templates/review/samples/proposals.json +35 -0
  25. package/templates/review/samples.ts +154 -0
  26. package/templates/review/workflow.test.ts +438 -0
  27. package/templates/review/workflow.ts +459 -0
  28. package/templates/values-extract/workflow.test.ts +24 -1
  29. package/templates/values-extract/workflow.ts +11 -3
  30. package/templates/versions.json +1 -1
@@ -0,0 +1,483 @@
1
+ // The parts of review that need no I/O: what a proposal's statement is in the graph's records
2
+ // ontology, the decision (write, a person, or refused), and the facts that write it. Adapted
3
+ // from the LBB-run `review@v1` (database/crates/lbb-graph-query/src/review.rs).
4
+ import { ENTITY_ID_KEY_SENTINEL, ENTITY_IRI_NAMESPACE, type Schemas } from "@littlebigbrain/client";
5
+
6
+ export const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
7
+ export const RDFS_LABEL = "http://www.w3.org/2000/01/rdf-schema#label";
8
+ export const CLASS_NS = "https://littlebigbrain.com/class/";
9
+ export const PROPERTY_NS = "https://littlebigbrain.com/p/";
10
+ export const RELATION_NS = "https://littlebigbrain.com/r/";
11
+ const segment = (name: string) =>
12
+ encodeURIComponent(name.trim().toLowerCase().split(/\s+/).join(" "));
13
+ /** The IRIs a records graph gives a class, a property and a relation. */
14
+ export const classIri = (name: string) => CLASS_NS + segment(name);
15
+ export const propertyIri = (name: string) => PROPERTY_NS + segment(name);
16
+ export const relationIri = (name: string) => RELATION_NS + segment(name);
17
+
18
+ /** The properties a proposer writes on a proposal. */
19
+ export const PROPOSAL_FIELDS: Record<string, string> = {
20
+ fact_subject: "keyword",
21
+ fact_predicate: "keyword",
22
+ fact_object: "text",
23
+ fact_datatype: "keyword",
24
+ fact_confidence: "f64",
25
+ fact_evidence: "text",
26
+ proposed_by: "keyword",
27
+ };
28
+ /** The properties the review writes: a proposal's status and why, and on a new instance the proposal that made it. */
29
+ export const REVIEW_FIELDS: Record<string, string> = {
30
+ fact_status: "keyword",
31
+ review_note: "text",
32
+ proposed_in: "keyword",
33
+ };
34
+ /** The rule that sends every statement about a new instance to a person. */
35
+ export const NEW_INSTANCE = "new_instance";
36
+
37
+ /** One proposed statement: IRIs, or a literal object with its datatype. */
38
+ export interface Statement {
39
+ subject: string;
40
+ predicate: string;
41
+ object: string;
42
+ datatype?: string;
43
+ }
44
+
45
+ /** What the review knows of one entity a statement names. */
46
+ export interface Known {
47
+ iri: string;
48
+ /** The class name; absent for an instance the graph does not have and no proposal types. */
49
+ class?: string;
50
+ label?: string;
51
+ /** The graph has the instance. */
52
+ exists: boolean;
53
+ }
54
+
55
+ /** The graph's records ontology as the review reads it. */
56
+ export interface Ontology {
57
+ /** Class name by class IRI. */
58
+ classes: Map<string, string>;
59
+ /** Each class name with its superclasses (names), itself included. */
60
+ ancestors: Map<string, Set<string>>;
61
+ /** Relation by predicate IRI: its name, domain and range (class names; empty: any). */
62
+ relations: Map<string, { name: string; domain: string[]; range: string[] }>;
63
+ /** Property by predicate IRI: its name and value type. */
64
+ properties: Map<string, { name: string; type: string }>;
65
+ }
66
+
67
+ /** The ontology of `GET /v1/ontology`. */
68
+ export function readOntology(view: Schemas["OntologyView"]): Ontology {
69
+ const types = (view.entity_type_defs ?? []).filter((t) => t.removed_version == null);
70
+ const nameOf = new Map<string, string>();
71
+ for (const t of types) {
72
+ nameOf.set(t.name.toLowerCase(), t.name);
73
+ if (t.stable_id) nameOf.set(t.stable_id.toLowerCase(), t.name);
74
+ }
75
+ const ancestors = new Map<string, Set<string>>();
76
+ for (const t of types) {
77
+ const out = new Set<string>();
78
+ const queue = [t.name];
79
+ while (queue.length) {
80
+ const next = queue.pop()!;
81
+ if (out.has(next)) continue;
82
+ out.add(next);
83
+ const def = types.find((d) => d.name === next);
84
+ queue.push(...(def?.super_types ?? []).flatMap((s) => nameOf.get(s.toLowerCase()) ?? []));
85
+ }
86
+ ancestors.set(t.name, out);
87
+ }
88
+ const names = (list: string[]) => list.map((n) => nameOf.get(n.toLowerCase()) ?? n);
89
+ return {
90
+ classes: new Map(types.map((t) => [t.iri || classIri(t.name), t.name])),
91
+ ancestors,
92
+ relations: new Map(
93
+ (view.relation_defs ?? [])
94
+ .filter((r) => r.removed_version == null)
95
+ .map((r) => [
96
+ relationIri(r.name),
97
+ { name: r.name, domain: names(r.src_types), range: names(r.dst_types) },
98
+ ]),
99
+ ),
100
+ properties: new Map(
101
+ (view.property_defs ?? []).map((p) => [
102
+ propertyIri(p.name),
103
+ { name: p.name, type: p.value_type },
104
+ ]),
105
+ ),
106
+ };
107
+ }
108
+
109
+ /** What a statement is in this graph: a relation, a property, a type, a label, or refused. */
110
+ export type Terms =
111
+ | { kind: "relation"; relation: string; subject: Known; object: Known }
112
+ | {
113
+ kind: "property";
114
+ field: string;
115
+ type: string;
116
+ subject: Known;
117
+ value: string | number | boolean;
118
+ current: string[];
119
+ }
120
+ | { kind: "type"; class: string; subject: Known; holds: boolean }
121
+ | { kind: "label"; label: string; subject: Known }
122
+ | { kind: "refused"; why: string };
123
+
124
+ const ENTITY = /^https:\/\/littlebigbrain\.com\/e\/[0-9a-f]{32}$/;
125
+ /** Whether an IRI names an instance of a records graph. */
126
+ export const isEntity = (iri: string) => ENTITY.test(iri.trim());
127
+
128
+ /** A value in its property's type, or null when it does not take the type. */
129
+ export function coerce(raw: string, type: string): string | number | boolean | null {
130
+ const text = raw.trim();
131
+ if (!text) return null;
132
+ const number = Number(text);
133
+ switch (type) {
134
+ case "i64":
135
+ return Number.isInteger(number) ? number : null;
136
+ case "f64":
137
+ return Number.isFinite(number) ? number : null;
138
+ case "bool":
139
+ return text === "true" ? true : text === "false" ? false : null;
140
+ case "date_time":
141
+ return Number.isNaN(Date.parse(text)) ? null : text;
142
+ default:
143
+ return text;
144
+ }
145
+ }
146
+
147
+ /** Everything `classify` reads about a statement's entities and values. */
148
+ export interface Seen {
149
+ /** The entities the statement names, by IRI. */
150
+ entities: Map<string, Known>;
151
+ /** The values the subject has now for a property statement. */
152
+ current?: string[];
153
+ }
154
+
155
+ /**
156
+ * The terms of a statement in a records graph, or why it cannot hold it:
157
+ * `rdf:type` of a class, `rdfs:label`, a relation between two instances, a
158
+ * property value. `proposalClass` is the class of the proposals: the review
159
+ * writes no statement about a proposal.
160
+ */
161
+ export function classify(
162
+ s: Statement,
163
+ ontology: Ontology,
164
+ seen: Seen,
165
+ proposalClass: string,
166
+ ): Terms {
167
+ const refused = (why: string): Terms => ({ kind: "refused", why });
168
+ if (!isEntity(s.subject))
169
+ return refused(`The subject <${s.subject}> is not an instance IRI of this graph.`);
170
+ const known = (iri: string): Known => seen.entities.get(iri) ?? { iri, exists: false };
171
+ const subject = known(s.subject);
172
+ if (subject.class === proposalClass)
173
+ return refused("The review writes no statement about a proposal.");
174
+ const untyped = (e: Known) =>
175
+ `<${e.iri}> is not an instance of the graph, and no proposal of its rdf:type gives its class.`;
176
+ const fits = (cls: string | undefined, allowed: string[]) =>
177
+ !allowed.length || allowed.some((a) => ontology.ancestors.get(cls ?? "")?.has(a));
178
+ if (s.predicate === RDF_TYPE) {
179
+ const cls = ontology.classes.get(s.object.trim());
180
+ if (!cls) return refused(`<${s.object}> is not a class of the graph's ontology.`);
181
+ if (!subject.exists) return { kind: "type", class: cls, subject, holds: false };
182
+ const holds = ontology.ancestors.get(subject.class ?? "")?.has(cls) ?? false;
183
+ if (!holds)
184
+ return refused(
185
+ `<${s.subject}> is an instance of ${subject.class} already; a records graph gives an instance one class.`,
186
+ );
187
+ return { kind: "type", class: cls, subject, holds };
188
+ }
189
+ if (s.predicate === RDFS_LABEL) {
190
+ if (!s.datatype || !s.object.trim())
191
+ return refused("A label is a text; the object is an IRI or empty.");
192
+ if (!subject.class) return refused(untyped(subject));
193
+ return { kind: "label", label: s.object.trim(), subject };
194
+ }
195
+ const relation = ontology.relations.get(s.predicate);
196
+ if (relation) {
197
+ if (s.datatype || !isEntity(s.object))
198
+ return refused(
199
+ `The relation ${relation.name} links two instances; the object is not an instance IRI.`,
200
+ );
201
+ const object = known(s.object.trim());
202
+ for (const end of [subject, object]) if (!end.class) return refused(untyped(end));
203
+ if (!fits(subject.class, relation.domain))
204
+ return refused(
205
+ `The relation ${relation.name} starts at ${relation.domain.join(", ")}; <${s.subject}> is a ${subject.class}.`,
206
+ );
207
+ if (!fits(object.class, relation.range))
208
+ return refused(
209
+ `The relation ${relation.name} points to ${relation.range.join(", ")}; <${s.object}> is a ${object.class}.`,
210
+ );
211
+ return { kind: "relation", relation: relation.name, subject, object };
212
+ }
213
+ const property = ontology.properties.get(s.predicate);
214
+ if (property) {
215
+ if (!subject.class) return refused(untyped(subject));
216
+ const value = coerce(s.object, property.type);
217
+ if (value === null)
218
+ return refused(`"${s.object}" is not a value of ${property.name} (${property.type}).`);
219
+ return {
220
+ kind: "property",
221
+ field: property.name,
222
+ type: property.type,
223
+ subject,
224
+ value,
225
+ current: seen.current ?? [],
226
+ };
227
+ }
228
+ return refused(
229
+ `<${s.predicate}> is not a relation or a property of the graph's records ontology (nor rdf:type or rdfs:label). The template writes with POST /v1/graph/commit, which takes those only.`,
230
+ );
231
+ }
232
+
233
+ /** The settings of the decision. */
234
+ export interface Rules {
235
+ gate: number;
236
+ /** `new_instance`, and predicate IRIs whose statements always go to a person. */
237
+ alwaysReview: string[];
238
+ }
239
+
240
+ /** The decision about one proposal: write it, give it to a person (and why), or refuse it (why). */
241
+ export type Decision =
242
+ | { status: "written"; holds: boolean }
243
+ | { status: "in_review"; why: string }
244
+ | { status: "refused"; why: string };
245
+
246
+ /** Whether a statement names a new instance: its subject or object is not in the graph yet. */
247
+ export function namesNewInstance(t: Terms): boolean {
248
+ switch (t.kind) {
249
+ case "relation":
250
+ return !t.subject.exists || !t.object.exists;
251
+ case "type":
252
+ return !t.subject.exists || !t.holds;
253
+ case "property":
254
+ case "label":
255
+ return !t.subject.exists;
256
+ default:
257
+ return false;
258
+ }
259
+ }
260
+
261
+ /** Whether the graph holds the statement already. */
262
+ export function holds(t: Terms, edgeHolds: boolean): boolean {
263
+ switch (t.kind) {
264
+ case "relation":
265
+ return edgeHolds;
266
+ case "type":
267
+ return t.holds;
268
+ case "property":
269
+ return t.current.includes(String(t.value));
270
+ case "label":
271
+ return t.subject.exists && t.subject.label === t.label;
272
+ default:
273
+ return false;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Refused when the graph cannot hold the statement; to a person when a rule
279
+ * names it, when it would replace another writer's value or name, or when its
280
+ * confidence is below the gate; else written.
281
+ */
282
+ export function decide(
283
+ t: Terms,
284
+ predicate: string,
285
+ confidence: number,
286
+ rules: Rules,
287
+ edgeHolds = false,
288
+ ): Decision {
289
+ if (t.kind === "refused") return { status: "refused", why: t.why };
290
+ if (rules.alwaysReview.includes(NEW_INSTANCE) && namesNewInstance(t))
291
+ return {
292
+ status: "in_review",
293
+ why: "A new instance: every statement about a new instance goes to a person (rule new_instance).",
294
+ };
295
+ if (rules.alwaysReview.includes(predicate))
296
+ return {
297
+ status: "in_review",
298
+ why: `Every statement of <${predicate}> goes to a person (rule).`,
299
+ };
300
+ if (t.kind === "property" && t.current.length && !t.current.includes(String(t.value)))
301
+ return {
302
+ status: "in_review",
303
+ why: `The subject's ${t.field} is ${t.current.join(", ")} already: writing ${t.value} would replace it.`,
304
+ };
305
+ if (t.kind === "label" && t.subject.exists && t.subject.label && t.subject.label !== t.label)
306
+ return {
307
+ status: "in_review",
308
+ why: `The subject is named ${t.subject.label} already: writing ${t.label} would rename it (a records entity has one name).`,
309
+ };
310
+ if (!Number.isFinite(confidence) || confidence < rules.gate)
311
+ return {
312
+ status: "in_review",
313
+ why: `The confidence ${(Number.isFinite(confidence) ? confidence : 0).toFixed(2)} is below the gate ${rules.gate}.`,
314
+ };
315
+ return { status: "written", holds: holds(t, edgeHolds) };
316
+ }
317
+
318
+ /** The commit reference of an entity the review writes: by its id, with its class and name. */
319
+ export function ref(e: Known): Schemas["NamedEntityInput"] {
320
+ const id = e.iri.slice(ENTITY_IRI_NAMESPACE.length);
321
+ return { type: e.class!, name: e.label?.trim() || id, key: `${ENTITY_ID_KEY_SENTINEL}${id}` };
322
+ }
323
+
324
+ /** The facts that write a statement: an edge whose evidence names the proposal, or an entity row. */
325
+ export interface Facts {
326
+ triplets: Schemas["TripletInput"][];
327
+ entity_properties: Schemas["EntityPropertiesInput"][];
328
+ }
329
+
330
+ export function factsOf(
331
+ t: Exclude<Terms, { kind: "refused" }>,
332
+ proposal: string,
333
+ confidence: number,
334
+ evidence: string | undefined,
335
+ ): Facts {
336
+ switch (t.kind) {
337
+ case "relation":
338
+ return {
339
+ triplets: [
340
+ {
341
+ source: ref(t.subject),
342
+ relation: t.relation,
343
+ target: ref(t.object),
344
+ confidence: Math.min(1, Math.max(0, confidence)),
345
+ evidence: {
346
+ text: clip(evidence || "A fact proposal the review workflow wrote", 500),
347
+ source_id: proposal,
348
+ },
349
+ },
350
+ ],
351
+ entity_properties: [],
352
+ };
353
+ case "property":
354
+ return {
355
+ triplets: [],
356
+ entity_properties: [{ ...ref(t.subject), properties: { [t.field]: t.value } }],
357
+ };
358
+ case "type":
359
+ // A records graph holds an instance through its facts: the proposal that made it.
360
+ return {
361
+ triplets: [],
362
+ entity_properties: [
363
+ { ...ref({ ...t.subject, class: t.class }), properties: { proposed_in: proposal } },
364
+ ],
365
+ };
366
+ case "label":
367
+ return {
368
+ triplets: [],
369
+ entity_properties: [{ ...ref({ ...t.subject, label: t.label }), properties: {} }],
370
+ };
371
+ }
372
+ }
373
+
374
+ /** The statement in words, for a review item: `R 0.12 · HAS_EQUIPMENT · T 1`. */
375
+ export function shown(t: Terms, s: Statement): string {
376
+ const name = (e: Known) => e.label?.trim() || e.iri.slice(e.iri.lastIndexOf("/") + 1);
377
+ switch (t.kind) {
378
+ case "relation":
379
+ return `${name(t.subject)} · ${t.relation} · ${name(t.object)}`;
380
+ case "property":
381
+ return `${name(t.subject)} · ${t.field} · ${t.value}`;
382
+ case "type":
383
+ return `${name(t.subject)} · is a · ${t.class}`;
384
+ case "label":
385
+ return `${name(t.subject)} · label · ${t.label}`;
386
+ default:
387
+ return `${s.subject} · ${s.predicate} · ${s.object}`;
388
+ }
389
+ }
390
+
391
+ /** The trace from a document entry to its instance: `lbb:denotes`, or the records relation `DENOTES`. */
392
+ export const isTrace = (predicate: string) =>
393
+ predicate.trim() === "https://littlebigbrain.com/ns#denotes" ||
394
+ predicate.trim() === relationIri("DENOTES");
395
+
396
+ /**
397
+ * The instance a proposal's review item is about: the object of the trace, the object of a link
398
+ * whose subject the graph has and whose object is new, else the subject. Null when refused.
399
+ */
400
+ export function groupOf(t: Terms, s: Statement): string | null {
401
+ switch (t.kind) {
402
+ case "refused":
403
+ return null;
404
+ case "relation":
405
+ return isTrace(s.predicate) || (t.subject.exists && !t.object.exists)
406
+ ? t.object.iri
407
+ : t.subject.iri;
408
+ default:
409
+ return t.subject.iri;
410
+ }
411
+ }
412
+
413
+ /** One fact of a review item, as the item shows it. */
414
+ export interface Shown {
415
+ /** `type`, `label`, `value`, `link` or `source` (the trace). */
416
+ kind: string;
417
+ /** The fact in words, seen from its instance: `is a Room`, `HAS_EQUIPMENT → T 1`. */
418
+ text: string;
419
+ }
420
+
421
+ /** What an item shows of a fact about the instance `group`. */
422
+ export function shownOf(
423
+ t: Exclude<Terms, { kind: "refused" }>,
424
+ s: Statement,
425
+ group: string,
426
+ ): Shown {
427
+ const name = (e: Known) => e.label?.trim() || e.iri.slice(e.iri.lastIndexOf("/") + 1);
428
+ switch (t.kind) {
429
+ case "type":
430
+ return { kind: "type", text: `is a ${t.class}` };
431
+ case "label":
432
+ return { kind: "label", text: `label · ${t.label}` };
433
+ case "property":
434
+ return { kind: "value", text: `${t.field} · ${t.value}` };
435
+ case "relation":
436
+ if (isTrace(s.predicate)) return { kind: "source", text: name(t.subject) };
437
+ return t.object.iri === group && t.subject.iri !== group
438
+ ? { kind: "link", text: `${t.relation} ← ${name(t.subject)}` }
439
+ : { kind: "link", text: `${t.relation} → ${name(t.object)}` };
440
+ }
441
+ }
442
+
443
+ /** The order of the kinds in an item: the type, the label, values, links, the trace. */
444
+ export const KIND_ORDER = ["type", "label", "value", "link", "source"];
445
+
446
+ /**
447
+ * Where a proposal's evidence comes from, for a person: `document`, `page`, `line` and `box` of
448
+ * its first source (the JSON of documents-map, or any object with those fields), else its text as
449
+ * the line. Null without evidence.
450
+ */
451
+ export function place(evidence: string | undefined): Record<string, unknown> | null {
452
+ const raw = evidence?.trim();
453
+ if (!raw) return null;
454
+ let value: unknown;
455
+ try {
456
+ value = JSON.parse(raw);
457
+ } catch {
458
+ return { line: clip(raw, 160) };
459
+ }
460
+ const object = (v: unknown): v is Record<string, unknown> =>
461
+ typeof v === "object" && v !== null && !Array.isArray(v);
462
+ const sources = object(value) && Array.isArray(value.sources) ? value.sources : null;
463
+ const source = sources?.[0] ?? value;
464
+ if (!object(source)) return { line: clip(raw, 160) };
465
+ const text = (k: string) => (typeof source[k] === "string" ? (source[k] as string).trim() : "");
466
+ const line =
467
+ text("line") ||
468
+ [text("identifier"), text("description")].filter(Boolean).join(" ") ||
469
+ text("context");
470
+ const out: Record<string, unknown> = {};
471
+ if (text("document")) out.document = clip(text("document"), 160);
472
+ if (Number.isSafeInteger(source.page)) out.page = source.page;
473
+ if (line) out.line = clip(line, 160);
474
+ if (Array.isArray(source.box) && source.box.length === 4 && source.box.every(Number.isFinite))
475
+ out.box = source.box;
476
+ return Object.keys(out).length ? out : null;
477
+ }
478
+
479
+ /** Whitespace kept, cut at `chars` with an ellipsis. */
480
+ export function clip(text: string, chars: number): string {
481
+ const all = [...text];
482
+ return all.length <= chars ? text : `${all.slice(0, chars - 1).join("")}…`;
483
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "about": [
3
+ "The answer key for the review samples: the status each proposal of proposals.json gets with the settings of the samples (gate 0.8, alwaysReview [\"new_instance\"]; the default alwaysReview is []), and why.",
4
+ "written: the review writes the statement (or finds it held already). in_review: the proposal goes into the review item of its instance; accepting the item writes the statement. refused: the graph cannot hold the statement.",
5
+ "facts: the statements the graph holds after the run, beside the edge it held before (room-014 HAS_EQUIPMENT s11).",
6
+ "items: one review item per instance, with the proposals it holds. The instance is the subject, the new object of a link from an instance the graph has (p9), and always the object of the trace."
7
+ ],
8
+ "statuses": {
9
+ "p1": { "status": "written", "why": "At the gate or above, about two instances the graph has." },
10
+ "p2": { "status": "in_review", "why": "The confidence 0.62 is below the gate." },
11
+ "p3": { "status": "written", "why": "At the gate or above." },
12
+ "p4": { "status": "written", "held": true, "why": "The graph holds the edge already: nothing is written again." },
13
+ "p5": { "status": "written", "why": "A value of a property of the ontology, on a room with no area yet." },
14
+ "p6": { "status": "refused", "why": "vierzehn is not a value of area_m2 (f64)." },
15
+ "p7": { "status": "in_review", "why": "rdf:type of a new instance: the rule new_instance." },
16
+ "p8": { "status": "in_review", "why": "The label of a new instance: the rule new_instance." },
17
+ "p9": { "status": "in_review", "why": "Its object is a new instance: the rule new_instance." },
18
+ "p10": { "status": "refused", "why": "Not a relation or a property of the graph's records ontology." },
19
+ "p11": { "status": "refused", "why": "HAS_EQUIPMENT points to Equipment; room-014 is a Room." },
20
+ "p12": { "status": "in_review", "why": "s21 has another name already: a records entity has one name." },
21
+ "p13": { "status": "refused", "why": "owl:sameAs is not a relation of the records ontology." },
22
+ "p14": { "status": "written", "why": "At the gate or above." }
23
+ },
24
+ "facts": [
25
+ { "subject": "room-012", "predicate": "HAS_EQUIPMENT", "object": "t1" },
26
+ { "subject": "room-014", "predicate": "HAS_EQUIPMENT", "object": "s12" },
27
+ { "subject": "room-014", "predicate": "HAS_EQUIPMENT", "object": "s11" },
28
+ { "subject": "room-014", "predicate": "HAS_EQUIPMENT", "object": "l1" },
29
+ { "subject": "room-012", "predicate": "area_m2", "object": 62.4 }
30
+ ],
31
+ "review_items": 3,
32
+ "items": [
33
+ { "instance": "room-012", "title": "Room Raum 0.12 Klassenraum: 1 fact to check", "facts": ["p2"] },
34
+ { "instance": "new:m7", "title": "New Equipment M 7 Medienwagen", "facts": ["p7", "p8", "p9"] },
35
+ { "instance": "s21", "title": "Equipment S 2.1 Spiegel: 1 fact to check", "facts": ["p12"] }
36
+ ]
37
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "about": [
3
+ "The input of the review samples: a small graph of the invented school building (two rooms and their equipment) and the fact proposals a proposer makes about it. All text is invented.",
4
+ "An instance is named by its id; samples.ts commits it with its class, name and identifier, and reads its IRI back. new names an instance the graph does not have yet: its IRI is minted from its id.",
5
+ "A predicate is a relation or a property of the ontology by name, rdf:type, rdfs:label, or an IRI as written. The object of rdf:type is a class name, of a relation an instance id, else a literal with its datatype.",
6
+ "expected.json gives the status each proposal gets with the settings of the samples: gate 0.8, alwaysReview [\"new_instance\"] (the default alwaysReview is [])."
7
+ ],
8
+ "instances": [
9
+ { "id": "room-012", "class": "Room", "name": "Raum 0.12 Klassenraum", "identifier": "0.12" },
10
+ { "id": "room-014", "class": "Room", "name": "Raum 0.14 WC Mädchen", "identifier": "0.14" },
11
+ { "id": "t1", "class": "Equipment", "name": "T 1 Einzeltisch", "identifier": "T 1" },
12
+ { "id": "s11", "class": "Equipment", "name": "S 1.1 Waschtisch", "identifier": "S 1.1" },
13
+ { "id": "s12", "class": "Equipment", "name": "S 1.2 WC", "identifier": "S 1.2" },
14
+ { "id": "s21", "class": "Equipment", "name": "S 2.1 Spiegel", "identifier": "S 2.1" },
15
+ { "id": "l1", "class": "Equipment", "name": "L 1 Wandleuchte", "identifier": "L 1" }
16
+ ],
17
+ "edges": [{ "source": "room-014", "relation": "HAS_EQUIPMENT", "target": "s11" }],
18
+ "new": ["m7"],
19
+ "proposals": [
20
+ { "id": "p1", "subject": "room-012", "predicate": "HAS_EQUIPMENT", "object": "t1", "confidence": 0.95, "line": "T1 Einzeltisch, 28 Stück" },
21
+ { "id": "p2", "subject": "room-012", "predicate": "HAS_EQUIPMENT", "object": "s21", "confidence": 0.62, "line": "S1.1 Waschtisch, 1 Stück, darüber Spiegel S 2.1" },
22
+ { "id": "p3", "subject": "room-014", "predicate": "HAS_EQUIPMENT", "object": "s12", "confidence": 0.91, "line": "S 1.2 WC, wandhängend, 3 Stück" },
23
+ { "id": "p4", "subject": "room-014", "predicate": "HAS_EQUIPMENT", "object": "s11", "confidence": 0.88, "line": "S1.1 Waschtisch, 2 Stück" },
24
+ { "id": "p5", "subject": "room-012", "predicate": "area_m2", "object": "62.4", "datatype": "http://www.w3.org/2001/XMLSchema#double", "confidence": 0.9, "line": "Fläche 62,40 m²" },
25
+ { "id": "p6", "subject": "room-014", "predicate": "area_m2", "object": "vierzehn", "datatype": "http://www.w3.org/2001/XMLSchema#string", "confidence": 0.9, "line": "Fläche 14,20 m²" },
26
+ { "id": "p7", "subject": "new:m7", "predicate": "rdf:type", "object": "Equipment", "confidence": 0.93, "line": "M 7 Medienwagen, 1 Stück" },
27
+ { "id": "p8", "subject": "new:m7", "predicate": "rdfs:label", "object": "M 7 Medienwagen", "datatype": "http://www.w3.org/2001/XMLSchema#string", "confidence": 0.95, "line": "M 7 Medienwagen, 1 Stück" },
28
+ { "id": "p9", "subject": "room-012", "predicate": "HAS_EQUIPMENT", "object": "new:m7", "confidence": 0.9, "line": "M 7 Medienwagen, 1 Stück" },
29
+ { "id": "p10", "subject": "room-012", "predicate": "https://example.org/onto#hasWindow", "object": "Fenster Süd", "datatype": "http://www.w3.org/2001/XMLSchema#string", "confidence": 0.9, "line": "Fenster nach Süden" },
30
+ { "id": "p11", "subject": "room-012", "predicate": "HAS_EQUIPMENT", "object": "room-014", "confidence": 0.85, "line": "Raum 0.14 WC Mädchen" },
31
+ { "id": "p12", "subject": "s21", "predicate": "rdfs:label", "object": "S 2.1 Spiegel, Kristallglas", "datatype": "http://www.w3.org/2001/XMLSchema#string", "confidence": 0.9, "line": "S 2.1 Spiegel, Kristallglas, 60x45 cm" },
32
+ { "id": "p13", "subject": "l1", "predicate": "owl:sameAs", "object": "s21", "confidence": 0.9, "line": "L1 Wandleuchte über dem Spiegel, 2 Stück" },
33
+ { "id": "p14", "subject": "room-014", "predicate": "HAS_EQUIPMENT", "object": "l1", "confidence": 0.97, "line": "L1 Wandleuchte über dem Spiegel, 2 Stück" }
34
+ ]
35
+ }