@larc-iu/plaid-client 0.2.0-alpha.29 → 0.2.0-alpha.30
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/package.json +3 -2
- package/src/apiAnnotations.d.ts +508 -0
- package/src/apiAnnotations.js +543 -0
- package/src/apiDocuments.d.ts +569 -0
- package/src/apiDocuments.js +551 -0
- package/src/apiLayers.d.ts +369 -0
- package/src/apiLayers.js +222 -0
- package/src/apiMessages.d.ts +293 -0
- package/src/apiMessages.js +265 -0
- package/src/apiPeople.d.ts +626 -0
- package/src/apiPeople.js +698 -0
- package/src/apiProject.d.ts +761 -0
- package/src/apiProject.js +774 -0
- package/src/apiServer.d.ts +455 -0
- package/src/apiServer.js +422 -0
- package/src/apiVocab.d.ts +456 -0
- package/src/apiVocab.js +425 -0
- package/src/batch.d.ts +54 -0
- package/src/batch.js +323 -0
- package/src/client.d.ts +1321 -0
- package/src/client.js +743 -0
- package/src/constraints.d.ts +8 -0
- package/src/constraints.js +14 -0
- package/src/documentLock.d.ts +18 -4
- package/src/documentLock.js +43 -9
- package/src/entryForms.d.ts +63 -0
- package/src/entryForms.js +152 -0
- package/src/events.js +2 -1
- package/src/families.d.ts +349 -0
- package/src/families.js +396 -0
- package/src/http.d.ts +39 -18
- package/src/http.js +127 -87
- package/src/index.d.ts +90 -4557
- package/src/index.js +113 -5172
- package/src/operations.d.ts +36 -0
- package/src/operations.js +164 -0
- package/src/provenance.d.ts +47 -4
- package/src/provenance.js +168 -12
- package/src/roles.d.ts +39 -0
- package/src/roles.js +44 -0
- package/src/serviceSchema.d.ts +19 -13
- package/src/serviceSchema.js +30 -15
- package/src/services.d.ts +20 -3
- package/src/services.js +45 -8
- package/src/sse.js +10 -1
- package/src/testing.d.ts +128 -0
- package/src/testing.js +379 -0
- package/src/types.d.ts +867 -0
- package/src/types.js +663 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End the key frame `frame` of `group`, wherever it is among the frames open:
|
|
3
|
+
* the frame under it, or the one open over it, numbers from then on. A plain
|
|
4
|
+
* function, so a caller that borrows withOperation onto another object
|
|
5
|
+
* (test doubles do) keeps it.
|
|
6
|
+
*/
|
|
7
|
+
export function dropFrame(group: any, frame: any): void;
|
|
8
|
+
/**
|
|
9
|
+
* The key for the next keyed request, and the strict-mode stamp it must
|
|
10
|
+
* claim when an earlier run of its operation already sent it. `joins` says
|
|
11
|
+
* whether the request joins the open logical operation.
|
|
12
|
+
* Returns `{ key, pin, record }`: `pin` is undefined when nothing is pinned,
|
|
13
|
+
* and `record(version)` pins what this request claimed, the first time.
|
|
14
|
+
*/
|
|
15
|
+
export function nextIdempotencyKey(client: any, joins: any): {
|
|
16
|
+
key: string;
|
|
17
|
+
pin: any;
|
|
18
|
+
record: (version: any) => void;
|
|
19
|
+
};
|
|
20
|
+
/** See PlaidClient.beginOperation. */
|
|
21
|
+
export function beginOperation(client: any, message: any, { groupId, kind, ref, keys, minted }?: {}): any;
|
|
22
|
+
/** See PlaidClient.keySeed. */
|
|
23
|
+
export function keySeed(): {
|
|
24
|
+
seed: string;
|
|
25
|
+
stamps: Map<any, any>;
|
|
26
|
+
};
|
|
27
|
+
/** See PlaidClient.endOperation. */
|
|
28
|
+
export function endOperation(client: any, message: any): Promise<void>;
|
|
29
|
+
/** See PlaidClient.withOperation. */
|
|
30
|
+
export function withOperation(client: any, message: any, fn: any, { kind, ref, groupId, keys, minted }?: {}): Promise<any>;
|
|
31
|
+
/**
|
|
32
|
+
* The kinds an operation may say it is (`?group-kind=`), the server's closed
|
|
33
|
+
* list (plaid.sql.operation-group/kinds). An operation of any other kind is
|
|
34
|
+
* refused when it begins, before it writes anything.
|
|
35
|
+
*/
|
|
36
|
+
export const OPERATION_KINDS: string[];
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logical operations: the audit-log group a run of writes is labelled with,
|
|
3
|
+
* its kind and reference, and the Idempotency-Keys that make a rerun send the
|
|
4
|
+
* same requests. The client's beginOperation, keySeed, endOperation and
|
|
5
|
+
* withOperation (whose notes say what each does) call these, with the client
|
|
6
|
+
* as `client`. The twin of plaid-client-py's operations.py.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { uuidv4, uuidv7 } from "./ids.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The kinds an operation may say it is (`?group-kind=`), the server's closed
|
|
13
|
+
* list (plaid.sql.operation-group/kinds). An operation of any other kind is
|
|
14
|
+
* refused when it begins, before it writes anything.
|
|
15
|
+
*/
|
|
16
|
+
export const OPERATION_KINDS = [
|
|
17
|
+
"assistant-plan",
|
|
18
|
+
"service-run",
|
|
19
|
+
"import",
|
|
20
|
+
"bulk-edit",
|
|
21
|
+
"guess-adoption",
|
|
22
|
+
"repair",
|
|
23
|
+
"review",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* End the key frame `frame` of `group`, wherever it is among the frames open:
|
|
28
|
+
* the frame under it, or the one open over it, numbers from then on. A plain
|
|
29
|
+
* function, so a caller that borrows withOperation onto another object
|
|
30
|
+
* (test doubles do) keeps it.
|
|
31
|
+
*/
|
|
32
|
+
export function dropFrame(group, frame) {
|
|
33
|
+
if (!group || !frame || group.frames[0] === frame) return;
|
|
34
|
+
group.frames = group.frames.filter((f) => f !== frame);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The key for the next keyed request, and the strict-mode stamp it must
|
|
39
|
+
* claim when an earlier run of its operation already sent it. `joins` says
|
|
40
|
+
* whether the request joins the open logical operation.
|
|
41
|
+
* Returns `{ key, pin, record }`: `pin` is undefined when nothing is pinned,
|
|
42
|
+
* and `record(version)` pins what this request claimed, the first time.
|
|
43
|
+
*/
|
|
44
|
+
export function nextIdempotencyKey(client, joins) {
|
|
45
|
+
const frame = joins ? client.operationGroup?.frames.at(-1) : null;
|
|
46
|
+
const keys = frame?.keys;
|
|
47
|
+
if (!keys) return { key: uuidv7(), pin: undefined, record: () => {} };
|
|
48
|
+
const n = frame.count;
|
|
49
|
+
frame.count += 1;
|
|
50
|
+
const pinned = keys.stamps.has(n);
|
|
51
|
+
return {
|
|
52
|
+
key: `${keys.seed}.${n}`,
|
|
53
|
+
pin: pinned ? keys.stamps.get(n) : undefined,
|
|
54
|
+
record: (version) => {
|
|
55
|
+
if (!pinned) keys.stamps.set(n, version ?? null);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** See PlaidClient.beginOperation. */
|
|
61
|
+
export function beginOperation(client, message, { groupId, kind, ref, keys, minted } = {}) {
|
|
62
|
+
if (kind != null && !OPERATION_KINDS.includes(String(kind))) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Unknown operation kind "${kind}". It is one of: ${OPERATION_KINDS.join(", ")}.`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const frame = {
|
|
68
|
+
keys: keys || null,
|
|
69
|
+
count: 0,
|
|
70
|
+
// Any iterable of ids (a Set, an array).
|
|
71
|
+
minted: minted ? new Set(minted) : null,
|
|
72
|
+
depth: 1,
|
|
73
|
+
};
|
|
74
|
+
const open = client.operationGroup;
|
|
75
|
+
client._openedFrame = null;
|
|
76
|
+
if (open) {
|
|
77
|
+
// A nested operation joins the outer one. When it brings its own key
|
|
78
|
+
// seed or ids (a queued edit run inside a longer operation), it numbers
|
|
79
|
+
// the keys in a frame of its own until it ends, then the frame under it
|
|
80
|
+
// numbers again.
|
|
81
|
+
open.depth += 1;
|
|
82
|
+
if (keys || minted) {
|
|
83
|
+
frame.depth = open.depth;
|
|
84
|
+
open.frames.push(frame);
|
|
85
|
+
client._openedFrame = frame;
|
|
86
|
+
}
|
|
87
|
+
return open.id;
|
|
88
|
+
}
|
|
89
|
+
client.operationGroup = {
|
|
90
|
+
id: groupId ? String(groupId) : uuidv4(),
|
|
91
|
+
message: message == null ? null : String(message),
|
|
92
|
+
kind: kind == null ? null : String(kind),
|
|
93
|
+
ref: ref == null ? null : String(ref),
|
|
94
|
+
depth: 1,
|
|
95
|
+
written: false,
|
|
96
|
+
refined: undefined,
|
|
97
|
+
frames: [frame],
|
|
98
|
+
};
|
|
99
|
+
client._openedFrame = frame;
|
|
100
|
+
return client.operationGroup.id;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** See PlaidClient.keySeed. */
|
|
104
|
+
export function keySeed() {
|
|
105
|
+
return { seed: uuidv7(), stamps: new Map() };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** See PlaidClient.endOperation. */
|
|
109
|
+
export async function endOperation(client, message) {
|
|
110
|
+
const group = client.operationGroup;
|
|
111
|
+
if (!group) return;
|
|
112
|
+
if (group.depth > 1) {
|
|
113
|
+
// A frame begun at client depth by hand (beginOperation) ends here. One
|
|
114
|
+
// begun by withOperation is ended by that call itself (_dropFrame).
|
|
115
|
+
const top = group.frames.at(-1);
|
|
116
|
+
if (group.frames.length > 1 && !top.owned && top.depth === group.depth) {
|
|
117
|
+
group.frames.pop();
|
|
118
|
+
}
|
|
119
|
+
group.depth -= 1;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
client.operationGroup = null;
|
|
123
|
+
const refined = message !== undefined ? message : group.refined;
|
|
124
|
+
if (refined !== undefined && group.written) {
|
|
125
|
+
try {
|
|
126
|
+
await client.operationGroups.update(group.id, refined);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
// 404: the group never materialized server-side (every tagged write
|
|
129
|
+
// failed or a batch was aborted). Nothing to relabel — not an error.
|
|
130
|
+
if (e.status !== 404) throw e;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** See PlaidClient.withOperation. */
|
|
136
|
+
export async function withOperation(client, message, fn, { kind, ref, groupId, keys, minted } = {}) {
|
|
137
|
+
client.beginOperation(message, { kind, ref, groupId, keys, minted });
|
|
138
|
+
const group = client.operationGroup;
|
|
139
|
+
// The key frame client call opened is ended by client call, whatever else
|
|
140
|
+
// began or ended meanwhile, not by whichever end comes at its depth.
|
|
141
|
+
const frame = client._openedFrame;
|
|
142
|
+
if (frame) frame.owned = true;
|
|
143
|
+
const setMessage = (msg) => {
|
|
144
|
+
if (group.depth === 1) group.refined = msg;
|
|
145
|
+
};
|
|
146
|
+
let result;
|
|
147
|
+
try {
|
|
148
|
+
result = await fn(setMessage);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
// A relabel the server refuses (a token scoped to projects may not
|
|
151
|
+
// relabel a group, which names none) must not take the place of the
|
|
152
|
+
// error `fn` threw: the caller answers for that one.
|
|
153
|
+
dropFrame(group, frame);
|
|
154
|
+
try {
|
|
155
|
+
await client.endOperation();
|
|
156
|
+
} catch (relabel) {
|
|
157
|
+
console.warn('The operation was not relabelled:', relabel);
|
|
158
|
+
}
|
|
159
|
+
throw e;
|
|
160
|
+
}
|
|
161
|
+
dropFrame(group, frame);
|
|
162
|
+
await client.endOperation();
|
|
163
|
+
return result;
|
|
164
|
+
}
|
package/src/provenance.d.ts
CHANGED
|
@@ -60,13 +60,21 @@
|
|
|
60
60
|
* the value it chose. Flat scalars are what the query engine filters
|
|
61
61
|
* and orders on, so "review the least-confident machine output first"
|
|
62
62
|
* is an ordinary query. Omit it unless you can honestly produce a
|
|
63
|
-
* probability (a raw logprob is NOT one — put it in provDetail)
|
|
63
|
+
* probability (a raw logprob is NOT one — put it in provDetail), and
|
|
64
|
+
* calibrate it on held-out data, so that values stated at 70% are
|
|
65
|
+
* right about 70% of the time. Apps mark a value below the project's
|
|
66
|
+
* threshold (readConfidenceThreshold), one number for every producer.
|
|
64
67
|
* - provDetail: ONE open map for everything else — top-k alternatives or
|
|
65
68
|
* distributions, model name/version, raw scores. Deliberately nested
|
|
66
69
|
* (not queryable); keep it small (top-k, not whole-vocabulary dumps).
|
|
67
|
-
*
|
|
68
|
-
* human edits (history is valuable)
|
|
69
|
-
*
|
|
70
|
+
* provDetail describes the machine's ORIGINAL prediction and is kept after
|
|
71
|
+
* human edits (history is valuable). provProb is the producer's probability
|
|
72
|
+
* for the value the entity holds, however a person entered it: a value the
|
|
73
|
+
* producer weighed (one in provDetail.valueProbs, or formProbs for a
|
|
74
|
+
* morpheme's form) carries that probability, and a value it never weighed
|
|
75
|
+
* carries none (writerPolicy's valueEditStamp is the one implementation,
|
|
76
|
+
* and it reads the distribution itself). A consumer still must not present
|
|
77
|
+
* provProb as the machine's confidence once the entity is verified
|
|
70
78
|
* (provConfirmed is exactly the flag to check).
|
|
71
79
|
*
|
|
72
80
|
* Recommended provDetail keys, so consumers (review dashboards, ranked
|
|
@@ -78,6 +86,12 @@
|
|
|
78
86
|
* - valueProbs / formProbs: a top-k {label: probability} map over the
|
|
79
87
|
* alternatives the producer considered, the chosen label included
|
|
80
88
|
* (provProb, when given, is that label's entry duplicated flat).
|
|
89
|
+
* - valueLinks: on a gloss, a list of {value, item, prob}, one per
|
|
90
|
+
* alternative that stands for a lexicon entry: its label, the vocab
|
|
91
|
+
* item id and its probability (two senses may share a label, so this
|
|
92
|
+
* is a list, not a map). igt offers each alternative together with
|
|
93
|
+
* its entry as one choice, under Alt+Down on the gloss and in the
|
|
94
|
+
* lexicon popover.
|
|
81
95
|
* - model: what made the prediction (a model name, or a tool and its
|
|
82
96
|
* release such as 'stanza==1.11.0'), and version: which version of the
|
|
83
97
|
* writer ran ('<release>+<8 hex>', see the manual, "Provenance"). Every
|
|
@@ -175,6 +189,7 @@ export function readReview(config?: object | null): {
|
|
|
175
189
|
users: string[];
|
|
176
190
|
roles: string[];
|
|
177
191
|
};
|
|
192
|
+
export function reviewsAnyone(config?: object | null): boolean;
|
|
178
193
|
export function projectRole(project: object, userId: string, { isAdmin }?: {
|
|
179
194
|
isAdmin?: boolean;
|
|
180
195
|
}): "maintainer" | "writer" | "reader";
|
|
@@ -188,12 +203,40 @@ export function withReviewedUser(review: {
|
|
|
188
203
|
users: string[];
|
|
189
204
|
roles: string[];
|
|
190
205
|
};
|
|
206
|
+
/**
|
|
207
|
+
* Key of the project's confidence setting under `config.plaid`:
|
|
208
|
+
* `{ threshold: number in [0, 1] }`. A machine value whose provProb is below
|
|
209
|
+
* the threshold is DOUBTFUL, and both apps give it a heavier mark. Producers
|
|
210
|
+
* write provProb calibrated on held-out data (manual, "Provenance"), so one
|
|
211
|
+
* number serves every service on the project.
|
|
212
|
+
*/
|
|
213
|
+
export const CONFIDENCE_KEY: "confidence";
|
|
214
|
+
/** The threshold when a project sets none: below it, a calibrated value is more likely wrong than right. */
|
|
215
|
+
export const DEFAULT_CONFIDENCE_THRESHOLD: 0.5;
|
|
216
|
+
export function readConfidenceThreshold(config?: object | null): number;
|
|
217
|
+
export function machineProb(metadata: object | null | undefined): number | null;
|
|
218
|
+
export function isDoubtful(metadata: object | null | undefined, threshold?: number): boolean;
|
|
219
|
+
/** The provDetail key of a producer's distribution over a span's or relation's value. */
|
|
220
|
+
export const VALUE_PROBS_KEY: "valueProbs";
|
|
221
|
+
/** The provDetail key of a producer's distribution over a morpheme's form. */
|
|
222
|
+
export const FORM_PROBS_KEY: "formProbs";
|
|
223
|
+
export function readValueProbs(metadata: object | null | undefined, key?: string): {
|
|
224
|
+
[x: string]: number;
|
|
225
|
+
} | null;
|
|
191
226
|
export function writerPolicy(contributorId?: string | null): {
|
|
192
227
|
contributorId: string | null;
|
|
193
228
|
isContributor: boolean;
|
|
194
229
|
createStamp: any | null;
|
|
195
230
|
editStamp: (metadata: any) => any | null;
|
|
231
|
+
valueEditStamp: (metadata: any, opts?: {
|
|
232
|
+
changes?: boolean;
|
|
233
|
+
value?: string;
|
|
234
|
+
probsKey?: string;
|
|
235
|
+
prob?: number | null;
|
|
236
|
+
}) => any | null;
|
|
237
|
+
repointStamp: (metadata: any) => any | null;
|
|
196
238
|
confirmStamp: (metadata: any) => any | null;
|
|
239
|
+
confirmFragment: () => any;
|
|
197
240
|
adoptStamp: (source: string, detail?: any) => any;
|
|
198
241
|
reviewable: (metadata: any) => boolean;
|
|
199
242
|
reviewableState: (state: string) => boolean;
|
package/src/provenance.js
CHANGED
|
@@ -60,13 +60,21 @@
|
|
|
60
60
|
* the value it chose. Flat scalars are what the query engine filters
|
|
61
61
|
* and orders on, so "review the least-confident machine output first"
|
|
62
62
|
* is an ordinary query. Omit it unless you can honestly produce a
|
|
63
|
-
* probability (a raw logprob is NOT one — put it in provDetail)
|
|
63
|
+
* probability (a raw logprob is NOT one — put it in provDetail), and
|
|
64
|
+
* calibrate it on held-out data, so that values stated at 70% are
|
|
65
|
+
* right about 70% of the time. Apps mark a value below the project's
|
|
66
|
+
* threshold (readConfidenceThreshold), one number for every producer.
|
|
64
67
|
* - provDetail: ONE open map for everything else — top-k alternatives or
|
|
65
68
|
* distributions, model name/version, raw scores. Deliberately nested
|
|
66
69
|
* (not queryable); keep it small (top-k, not whole-vocabulary dumps).
|
|
67
|
-
*
|
|
68
|
-
* human edits (history is valuable)
|
|
69
|
-
*
|
|
70
|
+
* provDetail describes the machine's ORIGINAL prediction and is kept after
|
|
71
|
+
* human edits (history is valuable). provProb is the producer's probability
|
|
72
|
+
* for the value the entity holds, however a person entered it: a value the
|
|
73
|
+
* producer weighed (one in provDetail.valueProbs, or formProbs for a
|
|
74
|
+
* morpheme's form) carries that probability, and a value it never weighed
|
|
75
|
+
* carries none (writerPolicy's valueEditStamp is the one implementation,
|
|
76
|
+
* and it reads the distribution itself). A consumer still must not present
|
|
77
|
+
* provProb as the machine's confidence once the entity is verified
|
|
70
78
|
* (provConfirmed is exactly the flag to check).
|
|
71
79
|
*
|
|
72
80
|
* Recommended provDetail keys, so consumers (review dashboards, ranked
|
|
@@ -78,6 +86,12 @@
|
|
|
78
86
|
* - valueProbs / formProbs: a top-k {label: probability} map over the
|
|
79
87
|
* alternatives the producer considered, the chosen label included
|
|
80
88
|
* (provProb, when given, is that label's entry duplicated flat).
|
|
89
|
+
* - valueLinks: on a gloss, a list of {value, item, prob}, one per
|
|
90
|
+
* alternative that stands for a lexicon entry: its label, the vocab
|
|
91
|
+
* item id and its probability (two senses may share a label, so this
|
|
92
|
+
* is a list, not a map). igt offers each alternative together with
|
|
93
|
+
* its entry as one choice, under Alt+Down on the gloss and in the
|
|
94
|
+
* lexicon popover.
|
|
81
95
|
* - model: what made the prediction (a model name, or a tool and its
|
|
82
96
|
* release such as 'stanza==1.11.0'), and version: which version of the
|
|
83
97
|
* writer ran ('<release>+<8 hex>', see the manual, "Provenance"). Every
|
|
@@ -130,20 +144,32 @@ export const PROV_STATES = Object.freeze({
|
|
|
130
144
|
VERIFIED: 'verified',
|
|
131
145
|
});
|
|
132
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Whether `p` is a probability, a finite number in [0, 1]: the only thing
|
|
149
|
+
* written as provProb. A stamp given anything else writes no provProb.
|
|
150
|
+
* @param {*} p
|
|
151
|
+
* @returns {boolean}
|
|
152
|
+
*/
|
|
153
|
+
const isProbability = (p) => typeof p === 'number' && p >= 0 && p <= 1;
|
|
154
|
+
|
|
133
155
|
/**
|
|
134
156
|
* The metadata fragment a machine writer merges into everything it creates.
|
|
135
157
|
* @param {string} source - producer id, e.g. serviceSource(serviceId)
|
|
136
158
|
* @param {Object} [extras] - optional prediction extras:
|
|
137
159
|
* @param {number} [extras.prob] - probability in [0,1] for the chosen value
|
|
138
|
-
* (omit unless it honestly is one — raw scores go in detail)
|
|
160
|
+
* (omit unless it honestly is one — raw scores go in detail). Anything
|
|
161
|
+
* else is left out.
|
|
139
162
|
* @param {Object} [extras.detail] - open map of producer extras (top-k
|
|
140
163
|
* alternatives, model version, raw scores; keep it small)
|
|
141
164
|
*/
|
|
142
165
|
export const stampInferred = (source, { prob, detail } = {}) => ({
|
|
143
166
|
[PROV.key]: PROV.INFERRED,
|
|
144
167
|
[PROV.sourceKey]: source,
|
|
145
|
-
...(prob
|
|
146
|
-
|
|
168
|
+
...(isProbability(prob) ? { [PROV.probKey]: prob } : {}),
|
|
169
|
+
// An empty detail is no detail, as in the Python client.
|
|
170
|
+
...(detail && !(typeof detail === 'object' && Object.keys(detail).length === 0)
|
|
171
|
+
? { [PROV.detailKey]: detail }
|
|
172
|
+
: {}),
|
|
147
173
|
});
|
|
148
174
|
|
|
149
175
|
/**
|
|
@@ -287,6 +313,18 @@ export const readReview = (config) => {
|
|
|
287
313
|
return { users: list(raw?.users), roles: list(raw?.roles) };
|
|
288
314
|
};
|
|
289
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Whether the project reviews anybody's work: a person or a role is named in
|
|
318
|
+
* its review lists. Where it reviews nobody, nothing is contributed, and an
|
|
319
|
+
* editor's legend has no contributed mark to explain.
|
|
320
|
+
* @param {object|null} [config] - a project's `config`
|
|
321
|
+
* @returns {boolean}
|
|
322
|
+
*/
|
|
323
|
+
export const reviewsAnyone = (config) => {
|
|
324
|
+
const { users, roles } = readReview(config);
|
|
325
|
+
return users.length > 0 || roles.length > 0;
|
|
326
|
+
};
|
|
327
|
+
|
|
290
328
|
/**
|
|
291
329
|
* A person's role in a project from its ACL lists: 'maintainer' | 'writer' |
|
|
292
330
|
* 'reader' | null. An admin with no explicit entry is a maintainer, as the
|
|
@@ -336,6 +374,76 @@ export const withReviewedUser = (review, userId, reviewed) => {
|
|
|
336
374
|
return { users: next, roles };
|
|
337
375
|
};
|
|
338
376
|
|
|
377
|
+
// ---- confidence: which machine values are doubtful ----
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Key of the project's confidence setting under `config.plaid`:
|
|
381
|
+
* `{ threshold: number in [0, 1] }`. A machine value whose provProb is below
|
|
382
|
+
* the threshold is DOUBTFUL, and both apps give it a heavier mark. Producers
|
|
383
|
+
* write provProb calibrated on held-out data (manual, "Provenance"), so one
|
|
384
|
+
* number serves every service on the project.
|
|
385
|
+
*/
|
|
386
|
+
export const CONFIDENCE_KEY = 'confidence';
|
|
387
|
+
|
|
388
|
+
/** The threshold when a project sets none: below it, a calibrated value is more likely wrong than right. */
|
|
389
|
+
export const DEFAULT_CONFIDENCE_THRESHOLD = 0.5;
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* The project's confidence threshold, or the default when it sets none or
|
|
393
|
+
* stores something that is not a number in [0, 1].
|
|
394
|
+
* @param {object|null} [config] - a project's `config`
|
|
395
|
+
* @returns {number}
|
|
396
|
+
*/
|
|
397
|
+
export const readConfidenceThreshold = (config) => {
|
|
398
|
+
const t = config?.plaid?.[CONFIDENCE_KEY]?.threshold;
|
|
399
|
+
return typeof t === 'number' && t >= 0 && t <= 1 ? t : DEFAULT_CONFIDENCE_THRESHOLD;
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The producer's probability for a machine value nobody has reviewed, or
|
|
404
|
+
* null. Null once a person has confirmed or contributed it, since provProb
|
|
405
|
+
* describes the original prediction and not the value as it stands.
|
|
406
|
+
* @param {object|null|undefined} metadata
|
|
407
|
+
* @returns {number|null}
|
|
408
|
+
*/
|
|
409
|
+
export const machineProb = (metadata) => {
|
|
410
|
+
if (provState(metadata) !== PROV_STATES.MACHINE) return null;
|
|
411
|
+
const p = metadata[PROV.probKey];
|
|
412
|
+
return typeof p === 'number' && Number.isFinite(p) ? p : null;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Whether an unreviewed machine value is below the project's threshold.
|
|
417
|
+
* @param {object|null|undefined} metadata
|
|
418
|
+
* @param {number} [threshold] - readConfidenceThreshold(project.config)
|
|
419
|
+
* @returns {boolean}
|
|
420
|
+
*/
|
|
421
|
+
export const isDoubtful = (metadata, threshold = DEFAULT_CONFIDENCE_THRESHOLD) => {
|
|
422
|
+
const p = machineProb(metadata);
|
|
423
|
+
return p != null && p < threshold;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
/** The provDetail key of a producer's distribution over a span's or relation's value. */
|
|
427
|
+
export const VALUE_PROBS_KEY = 'valueProbs';
|
|
428
|
+
/** The provDetail key of a producer's distribution over a morpheme's form. */
|
|
429
|
+
export const FORM_PROBS_KEY = 'formProbs';
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* The distribution a producer recorded under provDetail[key] (valueProbs by
|
|
433
|
+
* default, formProbs for a morpheme's form), as a {label: probability} map
|
|
434
|
+
* holding only the entries whose number is a probability, or null when there
|
|
435
|
+
* is none.
|
|
436
|
+
* @param {object|null|undefined} metadata
|
|
437
|
+
* @param {string} [key]
|
|
438
|
+
* @returns {Object<string, number>|null}
|
|
439
|
+
*/
|
|
440
|
+
export const readValueProbs = (metadata, key = VALUE_PROBS_KEY) => {
|
|
441
|
+
const raw = metadata?.[PROV.detailKey]?.[key];
|
|
442
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
443
|
+
const entries = Object.entries(raw).filter(([v, p]) => v !== '' && isProbability(p));
|
|
444
|
+
return entries.length ? Object.fromEntries(entries) : null;
|
|
445
|
+
};
|
|
446
|
+
|
|
339
447
|
// ---- the writer's policy: what a person's writes carry ----
|
|
340
448
|
|
|
341
449
|
/**
|
|
@@ -351,7 +459,10 @@ export const withReviewedUser = (review, userId, reviewed) => {
|
|
|
351
459
|
* isContributor: boolean,
|
|
352
460
|
* createStamp: Object|null,
|
|
353
461
|
* editStamp: (metadata: Object) => Object|null,
|
|
462
|
+
* valueEditStamp: (metadata: Object, opts?: {changes?: boolean, value?: string, probsKey?: string, prob?: number|null}) => Object|null,
|
|
463
|
+
* repointStamp: (metadata: Object) => Object|null,
|
|
354
464
|
* confirmStamp: (metadata: Object) => Object|null,
|
|
465
|
+
* confirmFragment: () => Object,
|
|
355
466
|
* adoptStamp: (source: string, detail?: Object) => Object,
|
|
356
467
|
* reviewable: (metadata: Object) => boolean,
|
|
357
468
|
* reviewableState: (state: string) => boolean,
|
|
@@ -368,6 +479,9 @@ export const writerPolicy = (contributorId = null) => {
|
|
|
368
479
|
contributor
|
|
369
480
|
? state === PROV_STATES.MACHINE
|
|
370
481
|
: state === PROV_STATES.MACHINE || state === PROV_STATES.CONTRIBUTED;
|
|
482
|
+
const editStamp = (metadata) =>
|
|
483
|
+
contributor ? contributeOnEdit(metadata, id) : verifyOnEdit(metadata);
|
|
484
|
+
const confirmFragment = () => (contributor ? contributeOnEdit(null, id) : PROV_CONFIRMED);
|
|
371
485
|
return Object.freeze({
|
|
372
486
|
contributorId: id,
|
|
373
487
|
isContributor: contributor,
|
|
@@ -379,16 +493,58 @@ export const writerPolicy = (contributorId = null) => {
|
|
|
379
493
|
* contributor's edit marks the entity contributed, dropping any earlier
|
|
380
494
|
* confirmation.
|
|
381
495
|
*/
|
|
382
|
-
editStamp
|
|
496
|
+
editStamp,
|
|
497
|
+
/**
|
|
498
|
+
* The fragment a person's edit of a VALUE merges: editStamp, plus what
|
|
499
|
+
* the edit means for provProb, the producer's probability for the value
|
|
500
|
+
* the entity holds, however it was entered. A new `value` the producer
|
|
501
|
+
* weighed, found in the entity's own provDetail[probsKey] (valueProbs,
|
|
502
|
+
* or formProbs for a morpheme's form), carries that probability. A
|
|
503
|
+
* `prob` in [0, 1] is used instead when the caller knows the number
|
|
504
|
+
* from elsewhere (a gloss alternative's own valueLinks entry). A new
|
|
505
|
+
* value the producer never weighed removes provProb. A value that
|
|
506
|
+
* `changes` nothing keeps it. provDetail stays, as history. Null when
|
|
507
|
+
* there is nothing to merge.
|
|
508
|
+
*/
|
|
509
|
+
valueEditStamp: (
|
|
510
|
+
metadata,
|
|
511
|
+
{ changes = true, value, probsKey = VALUE_PROBS_KEY, prob = null } = {},
|
|
512
|
+
) => {
|
|
513
|
+
const stamp = editStamp(metadata);
|
|
514
|
+
if (!changes) return stamp;
|
|
515
|
+
const probs = readValueProbs(metadata, probsKey);
|
|
516
|
+
const weighed =
|
|
517
|
+
probs && typeof value === 'string' && Object.hasOwn(probs, value) ? probs[value] : null;
|
|
518
|
+
const p = isProbability(prob) ? prob : weighed;
|
|
519
|
+
if (p != null) return { ...(stamp || {}), [PROV.probKey]: p };
|
|
520
|
+
if (metadata?.[PROV.probKey] == null) return stamp;
|
|
521
|
+
return { ...(stamp || {}), [PROV.probKey]: null };
|
|
522
|
+
},
|
|
523
|
+
/**
|
|
524
|
+
* The fragment that makes a relation whose head a person moved theirs:
|
|
525
|
+
* every provenance key it held removed, and the create stamp merged, so
|
|
526
|
+
* it reads as a relation this writer drew (the convention's "a head
|
|
527
|
+
* re-point yields a person's relation"). Null when there is nothing to
|
|
528
|
+
* merge.
|
|
529
|
+
*/
|
|
530
|
+
repointStamp: (metadata) => {
|
|
531
|
+
const cleared = {};
|
|
532
|
+
for (const k of PROVENANCE_KEYS) if (metadata?.[k] != null) cleared[k] = null;
|
|
533
|
+
const fragment = { ...cleared, ...(contributor ? stampContributed(id) : {}) };
|
|
534
|
+
return Object.keys(fragment).length ? fragment : null;
|
|
535
|
+
},
|
|
383
536
|
/**
|
|
384
537
|
* The fragment an explicit confirm gesture merges, or null when there is
|
|
385
538
|
* nothing for this writer to confirm: PROV_CONFIRMED for a verifier, the
|
|
386
539
|
* contributed stamp for a contributor accepting a machine proposal.
|
|
387
540
|
*/
|
|
388
|
-
confirmStamp: (metadata) =>
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
541
|
+
confirmStamp: (metadata) => (reviewable(metadata) ? confirmFragment() : null),
|
|
542
|
+
/**
|
|
543
|
+
* The fragment this writer's confirm merges over whatever it confirms,
|
|
544
|
+
* read without an entity: confirmStamp of anything reviewable. For a
|
|
545
|
+
* gesture that confirms many entities with one stamp.
|
|
546
|
+
*/
|
|
547
|
+
confirmFragment,
|
|
392
548
|
/**
|
|
393
549
|
* What an adopted suggestion is written with (a guess or a picked value
|
|
394
550
|
* into an empty cell): born-verified with the suggestion's producer as
|
package/src/roles.d.ts
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a project records research telemetry: `config.plaid.research.telemetry`
|
|
3
|
+
* is exactly true.
|
|
4
|
+
* @param {object} [config] a project's `config` object
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
export function readResearchTelemetry(config?: object): boolean;
|
|
1
8
|
/**
|
|
2
9
|
* The role recorded on a layer's `config`, or null if none.
|
|
3
10
|
* @param {object} [config] a layer's `config` object
|
|
4
11
|
* @returns {string|null}
|
|
5
12
|
*/
|
|
6
13
|
export function readRole(config?: object): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* The ignored-tokens rule on a word layer's `config` (see
|
|
16
|
+
* `IGNORED_TOKENS_KEY`), or null if none. The one reader of the key in JS.
|
|
17
|
+
* @param {object} [config] a token layer's `config` object
|
|
18
|
+
* @returns {{type: string, whitelist?: string[], blacklist?: string[]}|null}
|
|
19
|
+
*/
|
|
20
|
+
export function readIgnoredTokens(config?: object): {
|
|
21
|
+
type: string;
|
|
22
|
+
whitelist?: string[];
|
|
23
|
+
blacklist?: string[];
|
|
24
|
+
} | null;
|
|
7
25
|
/**
|
|
8
26
|
* The first layer in `layers` carrying the given role, or null. The single
|
|
9
27
|
* "find a layer by its role" primitive — apps build their named finders
|
|
@@ -55,6 +73,27 @@ export const PRESERVE_ON_SPLIT_KEY: "preserveOnSplit";
|
|
|
55
73
|
* See the manual's `Changing a text's body`.
|
|
56
74
|
*/
|
|
57
75
|
export const SPLIT_ON_SPACE_KEY: "splitOnSpace";
|
|
76
|
+
/**
|
|
77
|
+
* Word layer config key holding the ignored-tokens rule,
|
|
78
|
+
* `{type, whitelist, blacklist}`: under `type: 'unicodePunctuation'` a word made
|
|
79
|
+
* only of punctuation is ignored and each one-character entry of `whitelist` is
|
|
80
|
+
* a letter, under `type: 'blacklist'` the entries of `blacklist` are the words
|
|
81
|
+
* ignored and its one-character entries the only characters that part words.
|
|
82
|
+
* Plaid's text rules and the built-in tokenizer read it. See the manual's
|
|
83
|
+
* `Changing a text's body`.
|
|
84
|
+
*/
|
|
85
|
+
export const IGNORED_TOKENS_KEY: "ignoredTokens";
|
|
86
|
+
/**
|
|
87
|
+
* Project config key (false to hide): whether the project's tartan shows beside
|
|
88
|
+
* its name. Absent means shown.
|
|
89
|
+
*/
|
|
90
|
+
export const TARTAN_KEY: "tartan";
|
|
91
|
+
/**
|
|
92
|
+
* Project config key holding the research settings, `{telemetry}`. Client
|
|
93
|
+
* events are recorded only while `telemetry` is true. See the manual's
|
|
94
|
+
* `Research telemetry`.
|
|
95
|
+
*/
|
|
96
|
+
export const RESEARCH_KEY: "research";
|
|
58
97
|
export const ROLES: Readonly<{
|
|
59
98
|
BASELINE: "baseline";
|
|
60
99
|
SENTENCE: "sentence";
|
package/src/roles.js
CHANGED
|
@@ -42,6 +42,40 @@ export const PRESERVE_ON_SPLIT_KEY = 'preserveOnSplit';
|
|
|
42
42
|
*/
|
|
43
43
|
export const SPLIT_ON_SPACE_KEY = 'splitOnSpace';
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Word layer config key holding the ignored-tokens rule,
|
|
47
|
+
* `{type, whitelist, blacklist}`: under `type: 'unicodePunctuation'` a word made
|
|
48
|
+
* only of punctuation is ignored and each one-character entry of `whitelist` is
|
|
49
|
+
* a letter, under `type: 'blacklist'` the entries of `blacklist` are the words
|
|
50
|
+
* ignored and its one-character entries the only characters that part words.
|
|
51
|
+
* Plaid's text rules and the built-in tokenizer read it. See the manual's
|
|
52
|
+
* `Changing a text's body`.
|
|
53
|
+
*/
|
|
54
|
+
export const IGNORED_TOKENS_KEY = 'ignoredTokens';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Project config key (false to hide): whether the project's tartan shows beside
|
|
58
|
+
* its name. Absent means shown.
|
|
59
|
+
*/
|
|
60
|
+
export const TARTAN_KEY = 'tartan';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Project config key holding the research settings, `{telemetry}`. Client
|
|
64
|
+
* events are recorded only while `telemetry` is true. See the manual's
|
|
65
|
+
* `Research telemetry`.
|
|
66
|
+
*/
|
|
67
|
+
export const RESEARCH_KEY = 'research';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether a project records research telemetry: `config.plaid.research.telemetry`
|
|
71
|
+
* is exactly true.
|
|
72
|
+
* @param {object} [config] a project's `config` object
|
|
73
|
+
* @returns {boolean}
|
|
74
|
+
*/
|
|
75
|
+
export function readResearchTelemetry(config) {
|
|
76
|
+
return config?.[PLAID_NAMESPACE]?.[RESEARCH_KEY]?.telemetry === true;
|
|
77
|
+
}
|
|
78
|
+
|
|
45
79
|
export const ROLES = Object.freeze({
|
|
46
80
|
BASELINE: 'baseline',
|
|
47
81
|
SENTENCE: 'sentence',
|
|
@@ -61,6 +95,16 @@ export function readRole(config) {
|
|
|
61
95
|
return v == null ? null : v;
|
|
62
96
|
}
|
|
63
97
|
|
|
98
|
+
/**
|
|
99
|
+
* The ignored-tokens rule on a word layer's `config` (see
|
|
100
|
+
* `IGNORED_TOKENS_KEY`), or null if none. The one reader of the key in JS.
|
|
101
|
+
* @param {object} [config] a token layer's `config` object
|
|
102
|
+
* @returns {{type: string, whitelist?: string[], blacklist?: string[]}|null}
|
|
103
|
+
*/
|
|
104
|
+
export function readIgnoredTokens(config) {
|
|
105
|
+
return config?.[PLAID_NAMESPACE]?.[IGNORED_TOKENS_KEY] ?? null;
|
|
106
|
+
}
|
|
107
|
+
|
|
64
108
|
/**
|
|
65
109
|
* The first layer in `layers` carrying the given role, or null. The single
|
|
66
110
|
* "find a layer by its role" primitive — apps build their named finders
|