@ovok/core 0.4.4 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -247,6 +247,45 @@ item count: access filtering can produce a short or empty page with another curs
|
|
|
247
247
|
and `reload` start again at the first page. The hook exposes `reload` so a screen can refresh its
|
|
248
248
|
alert list after `useAcknowledgeSignalsAlert` succeeds.
|
|
249
249
|
|
|
250
|
+
### Raw Signals alerts
|
|
251
|
+
|
|
252
|
+
`listSignalsAlerts` reads episodes and needs the project's `episodicAlerts` (409
|
|
253
|
+
`signals_episodes_disabled` otherwise). To work with individual alerts instead, whether or not
|
|
254
|
+
`episodicAlerts` is on, use the raw alert methods:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
const open = await client.listSignalsRawAlerts({
|
|
258
|
+
acknowledged: false,
|
|
259
|
+
codes: ["8867-4", "9279-1"],
|
|
260
|
+
since: "2026-10-01T00:00:00Z",
|
|
261
|
+
limit: 50,
|
|
262
|
+
});
|
|
263
|
+
const alert = await client.getSignalsRawAlert(open.items[0].id);
|
|
264
|
+
const acked = await client.acknowledgeSignalsRawAlert(alert.id);
|
|
265
|
+
// acked.acknowledgedAt and acked.acknowledgedActor record when and by whom.
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Each `SignalsRawAlert` carries Signals' own `id`, `createdAt` and `resolvedAt`. `status` is Signals'
|
|
269
|
+
state of the reading: `ACTIVE` while it breaches, `RESOLVED` once it recovered or the alert was
|
|
270
|
+
acknowledged. An alert stays open for the practitioner until it is acknowledged, so list the
|
|
271
|
+
worklist with `acknowledged: false` rather than `status: "ACTIVE"`. Acknowledging resolves the
|
|
272
|
+
alert, and the next breaching reading opens a new one. It is idempotent: an acknowledged alert keeps
|
|
273
|
+
its first `acknowledgedAt` and `acknowledgedActor`.
|
|
274
|
+
|
|
275
|
+
Filters: `status`, `acknowledged`, `patientId` (a Medplum Patient id), `codes` (LOINC codes), and
|
|
276
|
+
`since` (inclusive) and `until` (exclusive) on `createdAt`, as date-times with `Z` or an offset.
|
|
277
|
+
Pages hold at most 50 alerts. As with episodes, a page can be short or empty and still have a
|
|
278
|
+
`nextCursor`, because alerts of residents the caller can't read are left out; page until
|
|
279
|
+
`nextCursor` is null, and pass it back unchanged: it is sealed to the project.
|
|
280
|
+
|
|
281
|
+
Alerts raised before a project starts acknowledging are unacknowledged too, so a new worklist
|
|
282
|
+
should bound its history with `since`. Each row also carries the breaching reading (`valueNumber`
|
|
283
|
+
or `valueString`), the rule's `message` and the alert's `reason`. Acknowledging a raw alert does not
|
|
284
|
+
acknowledge an episode: with `episodicAlerts` on, keep a screen to one of the two. Every request is scoped to the
|
|
285
|
+
caller's project and the residents their access policy allows: an alert outside that scope answers
|
|
286
|
+
404, and acknowledging needs `signals_alerts:update` and update access to the resident (403 when the
|
|
287
|
+
caller can only read them).
|
|
288
|
+
|
|
250
289
|
## CareHub APIs
|
|
251
290
|
|
|
252
291
|
CareHub methods cover the verified `/v1/slim/*` and `/v2/slim/*` application routes for
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OvokClient } from "../ovok-client";
|
|
2
|
-
import { EnableSignalsResponse, SetSignalsThresholdBody, SignalsAlert, SignalsAlertsPage, SignalsAlertsQuery, SignalsSettingsPatch, SignalsSettingsResponse, SignalsThreshold, SignalsThresholds } from "./types";
|
|
2
|
+
import { EnableSignalsResponse, SetSignalsThresholdBody, SignalsAlert, SignalsAlertsPage, SignalsAlertsQuery, SignalsRawAlert, SignalsRawAlertsPage, SignalsRawAlertsQuery, SignalsSettingsPatch, SignalsSettingsResponse, SignalsThreshold, SignalsThresholds } from "./types";
|
|
3
3
|
/**
|
|
4
4
|
* Enables Signals for an existing patient. The operation is idempotent; retry after 409 or 503.
|
|
5
5
|
* Observations saved through Ovok are forwarded by the backend, so callers should not submit
|
|
@@ -9,9 +9,21 @@ export declare function enableSignalsForPatient(this: OvokClient, patientId: str
|
|
|
9
9
|
export declare function getSignalsThresholds(this: OvokClient, patientId: string): Promise<SignalsThresholds>;
|
|
10
10
|
export declare function setSignalsThreshold(this: OvokClient, patientId: string, code: string, body: SetSignalsThresholdBody): Promise<SignalsThreshold>;
|
|
11
11
|
export declare function removeSignalsThreshold(this: OvokClient, patientId: string, code: string, reason: string): Promise<void>;
|
|
12
|
-
export declare function serializeSignalsAlertsQuery(query?: SignalsAlertsQuery): string;
|
|
12
|
+
export declare function serializeSignalsAlertsQuery(query?: SignalsAlertsQuery | SignalsRawAlertsQuery, keys?: readonly string[]): string;
|
|
13
13
|
export declare function listSignalsAlerts(this: OvokClient, query?: SignalsAlertsQuery): Promise<SignalsAlertsPage>;
|
|
14
14
|
export declare function getSignalsAlert(this: OvokClient, episodeId: string): Promise<SignalsAlert>;
|
|
15
15
|
export declare function acknowledgeSignalsAlert(this: OvokClient, episodeId: string): Promise<SignalsAlert>;
|
|
16
|
+
/**
|
|
17
|
+
* The project's individual Signals alerts, not episodes: works whether or not the project's
|
|
18
|
+
* `episodicAlerts` is on. `acknowledged: false` lists what is still open for the practitioner.
|
|
19
|
+
*/
|
|
20
|
+
export declare function listSignalsRawAlerts(this: OvokClient, query?: SignalsRawAlertsQuery): Promise<SignalsRawAlertsPage>;
|
|
21
|
+
export declare function getSignalsRawAlert(this: OvokClient, alertId: string): Promise<SignalsRawAlert>;
|
|
22
|
+
/**
|
|
23
|
+
* Acknowledges one alert: it resolves, and `acknowledgedAt` and `acknowledgedActor` record when
|
|
24
|
+
* and by whom. Idempotent: an acknowledged alert keeps its first acknowledgement. Needs
|
|
25
|
+
* `signals_alerts:update` and update access to the resident.
|
|
26
|
+
*/
|
|
27
|
+
export declare function acknowledgeSignalsRawAlert(this: OvokClient, alertId: string): Promise<SignalsRawAlert>;
|
|
16
28
|
export declare function getSignalsSettings(this: OvokClient): Promise<SignalsSettingsResponse>;
|
|
17
29
|
export declare function updateSignalsSettings(this: OvokClient, patch: SignalsSettingsPatch, reason?: string | null): Promise<SignalsSettingsResponse>;
|
|
@@ -8,6 +8,9 @@ exports.serializeSignalsAlertsQuery = serializeSignalsAlertsQuery;
|
|
|
8
8
|
exports.listSignalsAlerts = listSignalsAlerts;
|
|
9
9
|
exports.getSignalsAlert = getSignalsAlert;
|
|
10
10
|
exports.acknowledgeSignalsAlert = acknowledgeSignalsAlert;
|
|
11
|
+
exports.listSignalsRawAlerts = listSignalsRawAlerts;
|
|
12
|
+
exports.getSignalsRawAlert = getSignalsRawAlert;
|
|
13
|
+
exports.acknowledgeSignalsRawAlert = acknowledgeSignalsRawAlert;
|
|
11
14
|
exports.getSignalsSettings = getSignalsSettings;
|
|
12
15
|
exports.updateSignalsSettings = updateSignalsSettings;
|
|
13
16
|
const segment = (value) => encodeURIComponent(value);
|
|
@@ -28,28 +31,28 @@ function setSignalsThreshold(patientId, code, body) {
|
|
|
28
31
|
function removeSignalsThreshold(patientId, code, reason) {
|
|
29
32
|
return this.delete(`/v1/signals/patients/${segment(patientId)}/thresholds/${segment(code)}?reason=${segment(reason)}`);
|
|
30
33
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
// Each route's own filters, in the order they are sent: a key outside the list is never sent, so
|
|
35
|
+
// a wider object passed as an episode query can't turn into a refused request.
|
|
36
|
+
const EPISODE_QUERY_KEYS = ["status", "since", "acknowledged", "codes", "limit", "cursor"];
|
|
37
|
+
const RAW_ALERT_QUERY_KEYS = [
|
|
38
|
+
"status",
|
|
39
|
+
"patientId",
|
|
40
|
+
"since",
|
|
41
|
+
"until",
|
|
42
|
+
"acknowledged",
|
|
43
|
+
"codes",
|
|
44
|
+
"limit",
|
|
45
|
+
"cursor",
|
|
46
|
+
];
|
|
47
|
+
function serializeSignalsAlertsQuery(query = {}, keys = EPISODE_QUERY_KEYS) {
|
|
48
|
+
const values = query;
|
|
49
|
+
return keys
|
|
50
|
+
.filter((key) => values[key] !== undefined)
|
|
51
|
+
.map((key) => {
|
|
52
|
+
const value = values[key];
|
|
53
|
+
const text = Array.isArray(value) ? value.join(",") : String(value);
|
|
54
|
+
return `${segment(key)}=${segment(text)}`;
|
|
55
|
+
})
|
|
53
56
|
.join("&");
|
|
54
57
|
}
|
|
55
58
|
function listSignalsAlerts(query = {}) {
|
|
@@ -62,6 +65,25 @@ function getSignalsAlert(episodeId) {
|
|
|
62
65
|
function acknowledgeSignalsAlert(episodeId) {
|
|
63
66
|
return this.post(`/v1/signals/alerts/${segment(episodeId)}/ack`);
|
|
64
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* The project's individual Signals alerts, not episodes: works whether or not the project's
|
|
70
|
+
* `episodicAlerts` is on. `acknowledged: false` lists what is still open for the practitioner.
|
|
71
|
+
*/
|
|
72
|
+
function listSignalsRawAlerts(query = {}) {
|
|
73
|
+
const serialized = serializeSignalsAlertsQuery(query, RAW_ALERT_QUERY_KEYS);
|
|
74
|
+
return this.get(`/v1/signals/raw-alerts${serialized ? `?${serialized}` : ""}`);
|
|
75
|
+
}
|
|
76
|
+
function getSignalsRawAlert(alertId) {
|
|
77
|
+
return this.get(`/v1/signals/raw-alerts/${segment(alertId)}`);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Acknowledges one alert: it resolves, and `acknowledgedAt` and `acknowledgedActor` record when
|
|
81
|
+
* and by whom. Idempotent: an acknowledged alert keeps its first acknowledgement. Needs
|
|
82
|
+
* `signals_alerts:update` and update access to the resident.
|
|
83
|
+
*/
|
|
84
|
+
function acknowledgeSignalsRawAlert(alertId) {
|
|
85
|
+
return this.post(`/v1/signals/raw-alerts/${segment(alertId)}/ack`);
|
|
86
|
+
}
|
|
65
87
|
function getSignalsSettings() {
|
|
66
88
|
return this.get("/v1/signals/settings");
|
|
67
89
|
}
|
|
@@ -39,6 +39,79 @@ export type SignalsAlertsPage = {
|
|
|
39
39
|
items: SignalsAlert[];
|
|
40
40
|
nextCursor: string | null;
|
|
41
41
|
};
|
|
42
|
+
/** Signals' own state of a raw alert: ACTIVE while breaching, RESOLVED once recovered or acknowledged. */
|
|
43
|
+
export type SignalsRawAlertStatus = "ACTIVE" | "RESOLVED";
|
|
44
|
+
/** The person who acknowledged an alert. */
|
|
45
|
+
export type SignalsAcknowledgedActor = {
|
|
46
|
+
/** A FHIR reference, e.g. `Practitioner/<id>`. */
|
|
47
|
+
reference: string;
|
|
48
|
+
display: string | null;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* One individual Signals alert, not an episode: available whether or not the project's
|
|
52
|
+
* `episodicAlerts` is on. `id`, `createdAt` and `resolvedAt` are Signals' own.
|
|
53
|
+
*
|
|
54
|
+
* An alert stays open for the practitioner until it is acknowledged: `acknowledgedAt` is null until
|
|
55
|
+
* then, even when `status` is already RESOLVED because the reading recovered. Acknowledging
|
|
56
|
+
* resolves it, and the next breaching reading opens a new alert.
|
|
57
|
+
*/
|
|
58
|
+
export type SignalsRawAlert = {
|
|
59
|
+
id: string;
|
|
60
|
+
/** The resident's Medplum Patient id. */
|
|
61
|
+
patientId: string;
|
|
62
|
+
signalsPatientId: string;
|
|
63
|
+
/** The LOINC code that breached; null when no reading's code raised it. */
|
|
64
|
+
code: string | null;
|
|
65
|
+
/**
|
|
66
|
+
* TRIGGER is a threshold breach; EMPTY a reading with no value; NOTE a note; INACTIVE a resident
|
|
67
|
+
* who stopped reporting.
|
|
68
|
+
*/
|
|
69
|
+
reason: "TRIGGER" | "EMPTY" | "NOTE" | "INACTIVE";
|
|
70
|
+
/** The latest breaching reading, as a number or as text. */
|
|
71
|
+
valueNumber: number | null;
|
|
72
|
+
valueString: string | null;
|
|
73
|
+
/** The rule's own payload; it names the bound that was crossed. */
|
|
74
|
+
message: string | null;
|
|
75
|
+
observationId: string | null;
|
|
76
|
+
triggerCode: string | null;
|
|
77
|
+
triggerVersion: number | null;
|
|
78
|
+
status: SignalsRawAlertStatus;
|
|
79
|
+
createdAt: string;
|
|
80
|
+
resolvedAt: string | null;
|
|
81
|
+
acknowledgedAt: string | null;
|
|
82
|
+
/** The Signals client that acknowledged it (Ovok's), not a person: use `acknowledgedActor`. */
|
|
83
|
+
acknowledgedBy: string | null;
|
|
84
|
+
acknowledgedActor: SignalsAcknowledgedActor | null;
|
|
85
|
+
[key: string]: unknown;
|
|
86
|
+
};
|
|
87
|
+
export type SignalsRawAlertsQuery = {
|
|
88
|
+
status?: SignalsRawAlertStatus;
|
|
89
|
+
/** `false` for the alerts still open for the practitioner, whatever their `status`. */
|
|
90
|
+
acknowledged?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* One resident's alerts, by Medplum Patient id. 404 when the caller can't read the resident,
|
|
93
|
+
* 409 `signals_patient_not_enrolled` when they aren't in Signals yet.
|
|
94
|
+
*/
|
|
95
|
+
patientId?: string;
|
|
96
|
+
/** LOINC codes, e.g. `["8867-4", "9279-1"]`. Omitted or empty, every alert. */
|
|
97
|
+
codes?: readonly string[];
|
|
98
|
+
/** Alerts created at or after this date-time, with `Z` or an offset. */
|
|
99
|
+
since?: string;
|
|
100
|
+
/** Alerts created before this date-time, with `Z` or an offset. */
|
|
101
|
+
until?: string;
|
|
102
|
+
/** 1 to 50; 50 when omitted. */
|
|
103
|
+
limit?: number;
|
|
104
|
+
/** The previous page's `nextCursor`, passed back unchanged. */
|
|
105
|
+
cursor?: string;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Newest first. A page can be shorter than `limit`, even empty, and still have a next one: alerts
|
|
109
|
+
* of residents the caller can't read are left out. Page until `nextCursor` is null.
|
|
110
|
+
*/
|
|
111
|
+
export type SignalsRawAlertsPage = {
|
|
112
|
+
items: SignalsRawAlert[];
|
|
113
|
+
nextCursor: string | null;
|
|
114
|
+
};
|
|
42
115
|
export type SignalsSettings = {
|
|
43
116
|
episodicAlerts: boolean;
|
|
44
117
|
synchronousMode: boolean;
|
|
@@ -48,7 +48,7 @@ exports.OVOK_CORE_REQUIREMENTS = {
|
|
|
48
48
|
description: "The FHIR surface a server must provide for the @ovok/core client library's own methods to function.",
|
|
49
49
|
software: {
|
|
50
50
|
name: "@ovok/core",
|
|
51
|
-
version: "0.4.
|
|
51
|
+
version: "0.4.5",
|
|
52
52
|
},
|
|
53
53
|
fhirVersion: "4.0.1",
|
|
54
54
|
format: ["json"],
|