@larc-iu/plaid-client 0.1.0-alpha.9 → 0.2.0-alpha.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.
- package/index.d.ts +182 -111
- package/package.json +1 -1
- package/src/http.js +36 -4
- package/src/index.js +382 -225
- package/src/provenance.js +123 -0
- package/src/serviceSchema.js +10 -4
- package/src/services.js +21 -5
- package/src/sse.js +7 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provenance: the cross-app convention for distinguishing machine-provided
|
|
3
|
+
* from human-labeled information, expressed as flat metadata keys on
|
|
4
|
+
* annotation entities (spans, relations, vocab links, optionally tokens).
|
|
5
|
+
* Flat scalar keys — the query engine matches flat metadata well, nested
|
|
6
|
+
* objects poorly.
|
|
7
|
+
*
|
|
8
|
+
* Three states (the PRESENCE of the `prov` key, not its value, is the
|
|
9
|
+
* discriminator; absent keys mean "human"):
|
|
10
|
+
*
|
|
11
|
+
* human — no prov keys; a person made it.
|
|
12
|
+
* machine — { prov: 'inferred', provSource: '<producer>' };
|
|
13
|
+
* an algorithm/service made it, no human has vouched.
|
|
14
|
+
* verified — same + { provConfirmed: true }; machine-made and a
|
|
15
|
+
* human confirmed (or edited) it. provSource stays so
|
|
16
|
+
* machine origin remains traceable.
|
|
17
|
+
*
|
|
18
|
+
* The write contract every machine writer must follow:
|
|
19
|
+
* 1. Machine writers may freely replace MACHINE (unverified) material.
|
|
20
|
+
* 2. Machine writers must never modify or delete human or verified
|
|
21
|
+
* material unless explicitly told to overwrite (an explicit, per-run,
|
|
22
|
+
* user-facing opt-in — for services, a declared boolean `overwrite`
|
|
23
|
+
* parameter).
|
|
24
|
+
* 3. Any human edit of a machine annotation verifies it: the edit also
|
|
25
|
+
* stamps { provConfirmed: true } (see verifyOnEdit).
|
|
26
|
+
*
|
|
27
|
+
* Producer naming: 'service:<serviceId>' for services (use serviceSource),
|
|
28
|
+
* 'rule:<name>' for built-in rule algorithms, app-specific ids like
|
|
29
|
+
* 'gloss:doc-frequency' or 'flex-import' otherwise.
|
|
30
|
+
*
|
|
31
|
+
* PREDICTION EXTRAS. A producer may also record how confident it was and
|
|
32
|
+
* what else it considered, in two reserved slots split along the
|
|
33
|
+
* queryability line:
|
|
34
|
+
* - provProb: ONE flat number in [0, 1] — the producer's probability for
|
|
35
|
+
* the value it chose. Flat scalars are what the query engine filters
|
|
36
|
+
* and orders on, so "review the least-confident machine output first"
|
|
37
|
+
* is an ordinary query. Omit it unless you can honestly produce a
|
|
38
|
+
* probability (a raw logprob is NOT one — put it in provDetail).
|
|
39
|
+
* - provDetail: ONE open map for everything else — top-k alternatives or
|
|
40
|
+
* distributions, model name/version, raw scores. Deliberately nested
|
|
41
|
+
* (not queryable); keep it small (top-k, not whole-vocabulary dumps).
|
|
42
|
+
* Both describe the machine's ORIGINAL prediction. They are kept after
|
|
43
|
+
* human edits (history is valuable), so a consumer must not present
|
|
44
|
+
* provProb as confidence in the CURRENT value once the entity is verified
|
|
45
|
+
* (provConfirmed is exactly the flag to check).
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/** The flat metadata keys of the provenance convention. */
|
|
49
|
+
export const PROV = Object.freeze({
|
|
50
|
+
/** 'inferred' when produced by an algorithm/service; absent = human. */
|
|
51
|
+
key: 'prov',
|
|
52
|
+
/** Which producer, e.g. 'service:<id>', 'rule:precedent-or-unique'. */
|
|
53
|
+
sourceKey: 'provSource',
|
|
54
|
+
/** true once a human confirmed (or edited) the inferred value. */
|
|
55
|
+
confirmedKey: 'provConfirmed',
|
|
56
|
+
/** Producer's probability ([0,1]) for the chosen value. Flat = queryable. */
|
|
57
|
+
probKey: 'provProb',
|
|
58
|
+
/** Open map of producer extras (alternatives, model version, raw scores). */
|
|
59
|
+
detailKey: 'provDetail',
|
|
60
|
+
/** The (currently only) value of `prov`. Presence, not value, decides. */
|
|
61
|
+
INFERRED: 'inferred',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/** The three provenance states returned by provState. */
|
|
65
|
+
export const PROV_STATES = Object.freeze({
|
|
66
|
+
HUMAN: 'human',
|
|
67
|
+
MACHINE: 'machine',
|
|
68
|
+
VERIFIED: 'verified',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The metadata fragment a machine writer merges into everything it creates.
|
|
73
|
+
* @param {string} source - producer id, e.g. serviceSource(serviceId)
|
|
74
|
+
* @param {Object} [extras] - optional prediction extras:
|
|
75
|
+
* @param {number} [extras.prob] - probability in [0,1] for the chosen value
|
|
76
|
+
* (omit unless it honestly is one — raw scores go in detail)
|
|
77
|
+
* @param {Object} [extras.detail] - open map of producer extras (top-k
|
|
78
|
+
* alternatives, model version, raw scores; keep it small)
|
|
79
|
+
*/
|
|
80
|
+
export const stampInferred = (source, { prob, detail } = {}) => ({
|
|
81
|
+
[PROV.key]: PROV.INFERRED,
|
|
82
|
+
[PROV.sourceKey]: source,
|
|
83
|
+
...(prob !== undefined && prob !== null ? { [PROV.probKey]: prob } : {}),
|
|
84
|
+
...(detail ? { [PROV.detailKey]: detail } : {}),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The fragment for machine-made material that is born verified (e.g. an
|
|
89
|
+
* import carrying upstream human approval, or a guess written only on
|
|
90
|
+
* explicit user confirmation). Takes the same optional prediction extras
|
|
91
|
+
* as stampInferred.
|
|
92
|
+
*/
|
|
93
|
+
export const confirmedInferred = (source, extras = {}) => ({
|
|
94
|
+
...stampInferred(source, extras),
|
|
95
|
+
[PROV.confirmedKey]: true,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Classify an entity's metadata into one of PROV_STATES.
|
|
100
|
+
* @param {Object|null|undefined} metadata
|
|
101
|
+
* @returns {'human'|'machine'|'verified'}
|
|
102
|
+
*/
|
|
103
|
+
export const provState = (metadata) => {
|
|
104
|
+
if (!metadata || metadata[PROV.key] == null) return PROV_STATES.HUMAN;
|
|
105
|
+
return metadata[PROV.confirmedKey] ? PROV_STATES.VERIFIED : PROV_STATES.MACHINE;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Whether a machine writer must leave this entity alone (write-contract
|
|
110
|
+
* rule 2): true for human-made and human-verified material.
|
|
111
|
+
*/
|
|
112
|
+
export const isProtected = (metadata) => provState(metadata) !== PROV_STATES.MACHINE;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The metadata fragment a HUMAN edit of this entity should merge in
|
|
116
|
+
* (write-contract rule 3): { provConfirmed: true } when the entity is
|
|
117
|
+
* machine-made and unverified, else null (nothing to do).
|
|
118
|
+
*/
|
|
119
|
+
export const verifyOnEdit = (metadata) =>
|
|
120
|
+
(provState(metadata) === PROV_STATES.MACHINE ? { [PROV.confirmedKey]: true } : null);
|
|
121
|
+
|
|
122
|
+
/** Canonical provSource for a service: 'service:<serviceId>'. */
|
|
123
|
+
export const serviceSource = (serviceId) => `service:${serviceId}`;
|
package/src/serviceSchema.js
CHANGED
|
@@ -33,14 +33,20 @@
|
|
|
33
33
|
* snake_case key round-trips unchanged to a JS UI and back.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* The controlled task vocabulary — the fixed integration-point goals.
|
|
38
|
+
*
|
|
39
|
+
* Whatever the task, a service should stamp everything it creates with the
|
|
40
|
+
* provenance convention (see ./provenance.js: stampInferred(serviceSource(id)))
|
|
41
|
+
* so UIs can distinguish machine output until a human verifies it, and should
|
|
42
|
+
* honor the write contract: never destroy human-made or verified material
|
|
43
|
+
* without an explicit `overwrite` parameter. See the manual, "Provenance".
|
|
44
|
+
*/
|
|
37
45
|
export const TASKS = Object.freeze({
|
|
38
46
|
TOKENIZE: 'tokenize',
|
|
39
47
|
PARSE: 'parse',
|
|
40
48
|
TRANSCRIBE: 'transcribe',
|
|
41
|
-
/** Create vocab links for unlinked tokens.
|
|
42
|
-
* metadata with the provenance convention ({ prov: 'inferred', provSource:
|
|
43
|
-
* 'service:<id>' }) so UIs can distinguish machine links until confirmed. */
|
|
49
|
+
/** Create vocab links for unlinked tokens. */
|
|
44
50
|
LINK_VOCAB: 'link-vocab',
|
|
45
51
|
});
|
|
46
52
|
|
package/src/services.js
CHANGED
|
@@ -11,18 +11,34 @@
|
|
|
11
11
|
import { transformRequest, transformResponse } from './transforms.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Discover the services
|
|
15
|
-
*
|
|
14
|
+
* Discover the services seen on a project — a synchronous GET. Returns every
|
|
15
|
+
* service ever registered on the project: currently connected ones carry
|
|
16
|
+
* `online: true`; previously-seen offline ones carry `online: false` plus a
|
|
17
|
+
* `lastSeenAt` stamp. Callers that need a service they can actually submit
|
|
18
|
+
* work to should filter on `online`.
|
|
16
19
|
*
|
|
17
20
|
* @param {Object} client - PlaidClient instance
|
|
18
21
|
* @param {string} projectId - Project UUID
|
|
19
|
-
* @
|
|
20
|
-
* @returns {Promise<Array>} [{serviceId, serviceName, description, extras}]
|
|
22
|
+
* @returns {Promise<Array>} [{serviceId, serviceName, description, extras, online, lastSeenAt}]
|
|
21
23
|
*/
|
|
22
|
-
export function discoverServices(client, projectId
|
|
24
|
+
export function discoverServices(client, projectId) {
|
|
23
25
|
return client._request('GET', `/api/v1/projects/${projectId}/services`);
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Forget a previously-seen (offline) service: removes its row from the
|
|
30
|
+
* project's persistent registry. Maintainer-only; 409 if the service is
|
|
31
|
+
* currently connected (it would just re-register).
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} client - PlaidClient instance
|
|
34
|
+
* @param {string} projectId - Project UUID
|
|
35
|
+
* @param {string} serviceId - Service ID to forget
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
export function discardService(client, projectId, serviceId) {
|
|
39
|
+
return client._request('DELETE', `/api/v1/projects/${projectId}/services/${encodeURIComponent(serviceId)}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
26
42
|
/**
|
|
27
43
|
* Register a service and handle incoming work requests.
|
|
28
44
|
*
|
package/src/sse.js
CHANGED
|
@@ -75,6 +75,13 @@ export function createSSEConnection(client, projectId, onEvent, path) {
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
if (!response.ok) {
|
|
78
|
+
if (response.status === 409 && streamPath.includes('/services/')) {
|
|
79
|
+
// Service channel rejected: another live instance holds this
|
|
80
|
+
// service-id. The serve() reconnect timer will retry — once the
|
|
81
|
+
// other instance is gone (or its dead channel is reaped) the retry
|
|
82
|
+
// takes over.
|
|
83
|
+
console.warn('Service registration rejected (409): another instance of this service is already connected; will retry');
|
|
84
|
+
}
|
|
78
85
|
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
79
86
|
}
|
|
80
87
|
|