@ovok/core 0.3.15 → 0.3.17
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 +5 -0
- package/dist/client/observation/methods/observationsToMeasurements.d.ts +8 -1
- package/dist/client/observation/methods/observationsToMeasurements.js +137 -28
- package/dist/client/observation/observation-bundle-utils.js +58 -7
- package/dist/client/patient/methods.d.ts +2 -1
- package/dist/client/patient/methods.js +21 -0
- package/dist/client/patient/types.d.ts +11 -0
- package/dist/conformance/capability-requirements.js +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +2 -1
- package/dist/hooks/observation-hooks.d.ts +4 -1
- package/dist/hooks/observation-hooks.js +2 -2
- package/dist/hooks/patient-hooks.d.ts +2 -1
- package/dist/hooks/patient-hooks.js +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,6 +83,11 @@ CommunicationRequest-backed notifications.
|
|
|
83
83
|
|
|
84
84
|
```typescript
|
|
85
85
|
const me = await client.getCurrentPatientProfile();
|
|
86
|
+
const dashboard = await client.getPatientDashboard({
|
|
87
|
+
latestObservations: { code: ["8867-4", "9279-1"], max: 5 },
|
|
88
|
+
measurementHistory: { _sort: "-date", _count: 50 },
|
|
89
|
+
carePlans: { status: "active" },
|
|
90
|
+
});
|
|
86
91
|
const latest = await client.getPatientLatestObservations({
|
|
87
92
|
patient: `Patient/${me.profile.id}`,
|
|
88
93
|
code: ["8867-4", "9279-1"],
|
|
@@ -2,9 +2,16 @@ import { Observation } from "@medplum/fhirtypes";
|
|
|
2
2
|
import { BloodGlucoseMeasurement, BloodPressureMeasurement, BodyTemperatureMeasurement, BodyWeightMeasurement, EcgMeasurement, HeartRateMeasurement, HeartRateVariabilityMeasurement, Measurement, MeasurementTypeKey, PulseOximeterMeasurement, RespiratoryRateMeasurement, RestingHeartRateMeasurement, StepCountMeasurement, UrineAnalyzeMeasurement, Vo2MaxMeasurement, WalkingHeartRateAverageMeasurement } from "../../../types";
|
|
3
3
|
export type SupportedMeasurement = BloodGlucoseMeasurement | BloodPressureMeasurement | BodyTemperatureMeasurement | BodyWeightMeasurement | EcgMeasurement | HeartRateMeasurement | HeartRateVariabilityMeasurement | PulseOximeterMeasurement | RespiratoryRateMeasurement | RestingHeartRateMeasurement | StepCountMeasurement | UrineAnalyzeMeasurement | Vo2MaxMeasurement | WalkingHeartRateAverageMeasurement | Measurement;
|
|
4
4
|
type SupportedType = Exclude<MeasurementTypeKey, MeasurementTypeKey.symptomQuestionnaire>;
|
|
5
|
+
export type ObservationGrouping = "hasMember" | "time" | ((observation: Observation, index: number, observations: readonly Observation[]) => string);
|
|
6
|
+
export interface ObservationsToMeasurementsOptions {
|
|
7
|
+
/** How related Observation resources should be reconstructed into measurements. */
|
|
8
|
+
groupBy?: ObservationGrouping;
|
|
9
|
+
/** Maximum timestamp difference used by the time-based fallback. */
|
|
10
|
+
timeToleranceMs?: number;
|
|
11
|
+
}
|
|
5
12
|
export declare const observationCodesForTypes: (types: readonly SupportedType[]) => string;
|
|
6
13
|
/** Converts FHIR observations back into SDK measurements without patient/app assumptions. */
|
|
7
|
-
export declare const observationsToMeasurements: <T extends readonly SupportedType[]>(observations: readonly Observation[], types: T) => Array<Extract<SupportedMeasurement, {
|
|
14
|
+
export declare const observationsToMeasurements: <T extends readonly SupportedType[]>(observations: readonly Observation[], types: T, options?: ObservationsToMeasurementsOptions) => Array<Extract<SupportedMeasurement, {
|
|
8
15
|
measurementTypeKey: T[number];
|
|
9
16
|
}>>;
|
|
10
17
|
export {};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.observationsToMeasurements = exports.observationCodesForTypes = void 0;
|
|
5
5
|
const types_1 = require("../../../types");
|
|
6
|
+
const DEFAULT_OBSERVATION_TIME_TOLERANCE_MS = 1000;
|
|
6
7
|
const codeOf = (observation) => {
|
|
7
8
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
8
9
|
return (_d = (_c = (_b = (_a = observation.code) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b.find((coding) => coding.system === "http://loinc.org")) === null || _c === void 0 ? void 0 : _c.code) !== null && _d !== void 0 ? _d : (_g = (_f = (_e = observation.code) === null || _e === void 0 ? void 0 : _e.coding) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.code;
|
|
@@ -50,28 +51,138 @@ const ecgGroupKey = (observation, observations) => {
|
|
|
50
51
|
});
|
|
51
52
|
return root ? groupKey(root) : groupKey(observation);
|
|
52
53
|
};
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
54
|
+
const referenceKeys = (reference) => {
|
|
55
|
+
const id = reference.replace(/^urn:uuid:/, "").replace(/^Observation\//, "");
|
|
56
|
+
return [reference, id, `Observation/${id}`, `urn:uuid:${id}`];
|
|
57
|
+
};
|
|
58
|
+
const observationGroups = (observations) => {
|
|
59
|
+
const byReference = new Map();
|
|
60
|
+
observations.forEach((observation, index) => {
|
|
61
|
+
if (observation.id) {
|
|
62
|
+
referenceKeys(observation.id).forEach((key) => byReference.set(key, index));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
const parent = observations.map((_, index) => index);
|
|
66
|
+
const find = (index) => {
|
|
67
|
+
if (parent[index] !== index) {
|
|
68
|
+
parent[index] = find(parent[index]);
|
|
69
|
+
}
|
|
70
|
+
return parent[index];
|
|
71
|
+
};
|
|
72
|
+
const union = (left, right) => {
|
|
73
|
+
const leftRoot = find(left);
|
|
74
|
+
const rightRoot = find(right);
|
|
75
|
+
if (leftRoot !== rightRoot) {
|
|
76
|
+
parent[rightRoot] = leftRoot;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const linked = new Set();
|
|
80
|
+
observations.forEach((observation, index) => {
|
|
59
81
|
var _a;
|
|
60
|
-
flattened.push(observation);
|
|
61
82
|
(_a = observation.hasMember) === null || _a === void 0 ? void 0 : _a.forEach((reference) => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
83
|
+
const referenced = reference.reference
|
|
84
|
+
? referenceKeys(reference.reference)
|
|
85
|
+
.map((key) => byReference.get(key))
|
|
86
|
+
.find((candidate) => candidate !== undefined)
|
|
87
|
+
: undefined;
|
|
88
|
+
if (referenced !== undefined) {
|
|
89
|
+
linked.add(index);
|
|
90
|
+
linked.add(referenced);
|
|
91
|
+
union(index, referenced);
|
|
65
92
|
}
|
|
66
93
|
});
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
componentOf: new Map(observations.map((_, index) => [index, find(index)])),
|
|
97
|
+
linked,
|
|
67
98
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
99
|
+
};
|
|
100
|
+
const addToGroup = (groups, key, observation, index) => {
|
|
101
|
+
const group = groups.get(key);
|
|
102
|
+
if (group) {
|
|
103
|
+
group.observations.push(observation);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
groups.set(key, { firstIndex: index, observations: [observation] });
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const groupByTime = (observations, timeToleranceMs) => {
|
|
110
|
+
const groups = [];
|
|
111
|
+
observations.forEach((observation, index) => {
|
|
112
|
+
var _a;
|
|
113
|
+
const recordedAt = (_a = recordedAtOf(observation)) === null || _a === void 0 ? void 0 : _a.getTime();
|
|
114
|
+
const group = recordedAt === undefined
|
|
115
|
+
? undefined
|
|
116
|
+
: groups.find((candidate) => candidate.anchor !== undefined &&
|
|
117
|
+
Math.abs(candidate.anchor - recordedAt) <= timeToleranceMs);
|
|
118
|
+
if (group) {
|
|
119
|
+
group.observations.push(observation);
|
|
120
|
+
return;
|
|
72
121
|
}
|
|
122
|
+
groups.push({
|
|
123
|
+
firstIndex: index,
|
|
124
|
+
...(recordedAt !== undefined ? { anchor: recordedAt } : {}),
|
|
125
|
+
observations: [observation],
|
|
126
|
+
});
|
|
73
127
|
});
|
|
74
|
-
return
|
|
128
|
+
return groups;
|
|
129
|
+
};
|
|
130
|
+
const groupObservations = (observations, options, legacyKey) => {
|
|
131
|
+
var _a, _b;
|
|
132
|
+
const grouping = (_a = options.groupBy) !== null && _a !== void 0 ? _a : "hasMember";
|
|
133
|
+
const tolerance = Math.max(0, (_b = options.timeToleranceMs) !== null && _b !== void 0 ? _b : DEFAULT_OBSERVATION_TIME_TOLERANCE_MS);
|
|
134
|
+
if (grouping === "time") {
|
|
135
|
+
return groupByTime(observations, tolerance);
|
|
136
|
+
}
|
|
137
|
+
if (typeof grouping === "function") {
|
|
138
|
+
const groups = new Map();
|
|
139
|
+
observations.forEach((observation, index) => {
|
|
140
|
+
addToGroup(groups, grouping(observation, index, observations), observation, index);
|
|
141
|
+
});
|
|
142
|
+
return [...groups.values()].sort((left, right) => left.firstIndex - right.firstIndex);
|
|
143
|
+
}
|
|
144
|
+
const { componentOf, linked } = observationGroups(observations);
|
|
145
|
+
const componentSizes = new Map();
|
|
146
|
+
componentOf.forEach((root) => {
|
|
147
|
+
var _a;
|
|
148
|
+
componentSizes.set(root, ((_a = componentSizes.get(root)) !== null && _a !== void 0 ? _a : 0) + 1);
|
|
149
|
+
});
|
|
150
|
+
const groups = new Map();
|
|
151
|
+
const fallback = observations.filter((_, index) => {
|
|
152
|
+
var _a;
|
|
153
|
+
const root = componentOf.get(index);
|
|
154
|
+
return ((_a = componentSizes.get(root)) !== null && _a !== void 0 ? _a : 0) === 1;
|
|
155
|
+
});
|
|
156
|
+
observations.forEach((observation, index) => {
|
|
157
|
+
var _a;
|
|
158
|
+
const root = componentOf.get(index);
|
|
159
|
+
if (((_a = componentSizes.get(root)) !== null && _a !== void 0 ? _a : 0) > 1) {
|
|
160
|
+
addToGroup(groups, `member:${root}`, observation, index);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
if (fallback.length > 0) {
|
|
164
|
+
const fallbackIndexes = new Set(fallback.map((observation) => observations.indexOf(observation)));
|
|
165
|
+
const unlinked = observations.filter((_, index) => fallbackIndexes.has(index) && !linked.has(index));
|
|
166
|
+
const unresolved = observations.filter((_, index) => fallbackIndexes.has(index) && linked.has(index));
|
|
167
|
+
groupByTime(unlinked, tolerance).forEach((group) => {
|
|
168
|
+
const first = group.observations[0];
|
|
169
|
+
const firstIndex = observations.indexOf(first);
|
|
170
|
+
const key = legacyKey
|
|
171
|
+
? `legacy:${legacyKey(first, firstIndex)}`
|
|
172
|
+
: `time:${firstIndex}`;
|
|
173
|
+
addToGroup(groups, key, first, firstIndex);
|
|
174
|
+
group.observations.slice(1).forEach((observation) => {
|
|
175
|
+
const index = observations.indexOf(observation);
|
|
176
|
+
addToGroup(groups, key, observation, index);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
unresolved.forEach((observation) => {
|
|
180
|
+
var _a;
|
|
181
|
+
const index = observations.indexOf(observation);
|
|
182
|
+
addToGroup(groups, `unresolved:${(_a = observation.id) !== null && _a !== void 0 ? _a : index}`, observation, index);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return [...groups.values()].sort((left, right) => left.firstIndex - right.firstIndex);
|
|
75
186
|
};
|
|
76
187
|
const firstValue = (observations, code) => observations.find((observation) => codeOf(observation) === code)
|
|
77
188
|
? valueOf(observations.find((observation) => codeOf(observation) === code))
|
|
@@ -82,7 +193,11 @@ const baseMeasurement = (measurementTypeKey, observation) => ({
|
|
|
82
193
|
});
|
|
83
194
|
const inverseGroup = (type, observations) => {
|
|
84
195
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
85
|
-
const first = observations
|
|
196
|
+
const first = [...observations].sort((left, right) => {
|
|
197
|
+
var _a, _b, _c, _d;
|
|
198
|
+
return ((_b = (_a = recordedAtOf(left)) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : Number.MAX_SAFE_INTEGER) -
|
|
199
|
+
((_d = (_c = recordedAtOf(right)) === null || _c === void 0 ? void 0 : _c.getTime()) !== null && _d !== void 0 ? _d : Number.MAX_SAFE_INTEGER);
|
|
200
|
+
})[0];
|
|
86
201
|
const measurement = baseMeasurement(type, first);
|
|
87
202
|
const value = (code) => firstValue(observations, code);
|
|
88
203
|
switch (type) {
|
|
@@ -225,22 +340,16 @@ const codesForType = {
|
|
|
225
340
|
const observationCodesForTypes = (types) => [...new Set(types.flatMap((type) => { var _a; return (_a = codesForType[type]) !== null && _a !== void 0 ? _a : []; }))].join(",");
|
|
226
341
|
exports.observationCodesForTypes = observationCodesForTypes;
|
|
227
342
|
/** Converts FHIR observations back into SDK measurements without patient/app assumptions. */
|
|
228
|
-
const observationsToMeasurements = (observations, types) => {
|
|
229
|
-
const source = flattenMembers(observations);
|
|
343
|
+
const observationsToMeasurements = (observations, types, options = {}) => {
|
|
230
344
|
const output = [];
|
|
231
345
|
types.forEach((type) => {
|
|
232
346
|
var _a;
|
|
233
347
|
const codes = (_a = codesForType[type]) !== null && _a !== void 0 ? _a : [];
|
|
234
|
-
const matching =
|
|
235
|
-
const groups =
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
? ecgGroupKey(observation, matching)
|
|
240
|
-
: (_b = (_a = recordedAtOf(observation)) === null || _a === void 0 ? void 0 : _a.toISOString()) !== null && _b !== void 0 ? _b : "unknown";
|
|
241
|
-
groups.set(key, [...((_c = groups.get(key)) !== null && _c !== void 0 ? _c : []), observation]);
|
|
242
|
-
});
|
|
243
|
-
groups.forEach((group) => output.push(inverseGroup(type, group)));
|
|
348
|
+
const matching = observations.filter((observation) => { var _a; return codes.includes((_a = codeOf(observation)) !== null && _a !== void 0 ? _a : ""); });
|
|
349
|
+
const groups = groupObservations(matching, options, type === types_1.MeasurementTypeKey.ecg
|
|
350
|
+
? (observation, index) => ecgGroupKey(observation, matching) || `ecg:${index}`
|
|
351
|
+
: undefined);
|
|
352
|
+
groups.forEach((group) => output.push(inverseGroup(type, group.observations)));
|
|
244
353
|
});
|
|
245
354
|
return output;
|
|
246
355
|
};
|
|
@@ -24,6 +24,34 @@ const linkObservationMembers = (observations) => {
|
|
|
24
24
|
});
|
|
25
25
|
};
|
|
26
26
|
exports.linkObservationMembers = linkObservationMembers;
|
|
27
|
+
const createdObservationReference = (response, index) => {
|
|
28
|
+
var _a, _b, _c, _d;
|
|
29
|
+
const entry = (_a = response.entry) === null || _a === void 0 ? void 0 : _a[index];
|
|
30
|
+
const id = ((_b = entry === null || entry === void 0 ? void 0 : entry.resource) === null || _b === void 0 ? void 0 : _b.resourceType) === "Observation"
|
|
31
|
+
? entry.resource.id
|
|
32
|
+
: undefined;
|
|
33
|
+
if (id) {
|
|
34
|
+
return `Observation/${id}`;
|
|
35
|
+
}
|
|
36
|
+
const location = (_d = (_c = entry === null || entry === void 0 ? void 0 : entry.response) === null || _c === void 0 ? void 0 : _c.location) === null || _d === void 0 ? void 0 : _d.split("?")[0];
|
|
37
|
+
const match = location === null || location === void 0 ? void 0 : location.match(/(?:^|\/)(Observation\/[^/]+)(?:\/_history\/[^/]+)?$/);
|
|
38
|
+
return match === null || match === void 0 ? void 0 : match[1];
|
|
39
|
+
};
|
|
40
|
+
const rewriteObservationReference = (entry, from, to) => {
|
|
41
|
+
var _a;
|
|
42
|
+
const resource = entry.resource;
|
|
43
|
+
if ((resource === null || resource === void 0 ? void 0 : resource.resourceType) !== "Observation" || !((_a = resource.hasMember) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
44
|
+
return entry;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
...entry,
|
|
48
|
+
resource: {
|
|
49
|
+
...resource,
|
|
50
|
+
hasMember: resource.hasMember.map((reference) => reference.reference === from ? { ...reference, reference: to } : reference),
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
const rewriteObservationReferences = (entries, from, to) => entries.map((entry) => rewriteObservationReference(entry, from, to));
|
|
27
55
|
const splitObservationBundleEntries = (bundle) => {
|
|
28
56
|
var _a;
|
|
29
57
|
const entries = (_a = bundle.entry) !== null && _a !== void 0 ? _a : [];
|
|
@@ -50,26 +78,49 @@ const splitObservationBundleEntries = (bundle) => {
|
|
|
50
78
|
};
|
|
51
79
|
exports.splitObservationBundleEntries = splitObservationBundleEntries;
|
|
52
80
|
const executeObservationBundle = async (bundle, executeBatch) => {
|
|
53
|
-
var _a, _b, _c, _d;
|
|
81
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
54
82
|
const entries = (_a = bundle.entry) !== null && _a !== void 0 ? _a : [];
|
|
55
83
|
if (bundle.type === "transaction" &&
|
|
56
84
|
entries.length > exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES) {
|
|
85
|
+
const rootReference = (_c = (_b = entries[0]) === null || _b === void 0 ? void 0 : _b.fullUrl) !== null && _c !== void 0 ? _c : (((_e = (_d = entries[0]) === null || _d === void 0 ? void 0 : _d.resource) === null || _e === void 0 ? void 0 : _e.resourceType) === "Observation"
|
|
86
|
+
? `urn:uuid:${(0, offline_1.stableMeasurementId)(entries[0].resource)}`
|
|
87
|
+
: undefined);
|
|
57
88
|
const transactionResponse = await (0, exports.executeObservationBundle)({ ...bundle, entry: entries.slice(0, 1) }, executeBatch);
|
|
58
|
-
const
|
|
89
|
+
const savedRootReference = rootReference
|
|
90
|
+
? createdObservationReference(transactionResponse, 0)
|
|
91
|
+
: undefined;
|
|
92
|
+
const batchResponse = await (0, exports.executeObservationBundle)({
|
|
93
|
+
...bundle,
|
|
94
|
+
type: "batch",
|
|
95
|
+
entry: savedRootReference
|
|
96
|
+
? rewriteObservationReferences(entries.slice(1), rootReference, savedRootReference)
|
|
97
|
+
: entries.slice(1),
|
|
98
|
+
}, executeBatch);
|
|
59
99
|
return {
|
|
60
100
|
resourceType: "Bundle",
|
|
61
101
|
type: "batch-response",
|
|
62
102
|
entry: [
|
|
63
|
-
...((
|
|
64
|
-
...((
|
|
103
|
+
...((_f = transactionResponse.entry) !== null && _f !== void 0 ? _f : []),
|
|
104
|
+
...((_g = batchResponse.entry) !== null && _g !== void 0 ? _g : []),
|
|
65
105
|
],
|
|
66
106
|
};
|
|
67
107
|
}
|
|
68
108
|
const chunks = (0, exports.splitObservationBundleEntries)(bundle);
|
|
69
109
|
if (chunks) {
|
|
70
110
|
const responses = [];
|
|
71
|
-
|
|
72
|
-
|
|
111
|
+
const rootReference = (_j = (_h = entries[0]) === null || _h === void 0 ? void 0 : _h.fullUrl) !== null && _j !== void 0 ? _j : (((_l = (_k = entries[0]) === null || _k === void 0 ? void 0 : _k.resource) === null || _l === void 0 ? void 0 : _l.resourceType) === "Observation"
|
|
112
|
+
? `urn:uuid:${(0, offline_1.stableMeasurementId)(entries[0].resource)}`
|
|
113
|
+
: undefined);
|
|
114
|
+
let savedRootReference;
|
|
115
|
+
for (const [index, entry] of chunks.entries()) {
|
|
116
|
+
const chunkEntries = index > 0 && rootReference && savedRootReference
|
|
117
|
+
? rewriteObservationReferences(entry, rootReference, savedRootReference)
|
|
118
|
+
: entry;
|
|
119
|
+
const response = await (0, exports.executeObservationBundle)({ ...bundle, entry: chunkEntries }, executeBatch);
|
|
120
|
+
if (index === 0 && rootReference) {
|
|
121
|
+
savedRootReference = createdObservationReference(response, 0);
|
|
122
|
+
}
|
|
123
|
+
responses.push(response);
|
|
73
124
|
}
|
|
74
125
|
return {
|
|
75
126
|
resourceType: "Bundle",
|
|
@@ -78,7 +129,7 @@ const executeObservationBundle = async (bundle, executeBatch) => {
|
|
|
78
129
|
};
|
|
79
130
|
}
|
|
80
131
|
const response = await executeBatch(bundle);
|
|
81
|
-
if ((
|
|
132
|
+
if ((_m = response.entry) === null || _m === void 0 ? void 0 : _m.some((entry) => {
|
|
82
133
|
var _a, _b;
|
|
83
134
|
const status = (_b = (_a = entry.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : "";
|
|
84
135
|
return status.startsWith("4") || status.startsWith("5");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OvokClient } from "../ovok-client";
|
|
2
|
-
import { OvokCurrentPatientProfile, OvokPatientLatestObservationsQuery, OvokPatientObservationBundle, OvokPatientAppointment, OvokPatientCarePlan, OvokPatientDevice, OvokPatientMeasurement, OvokPatientNotification, OvokPatientQuestionnaire, OvokPatientQuestionnaireResponse, OvokPatientResourceQuery, OvokPatientSchedule, OvokPatientSchedulingQuery, OvokPatientSlot, OvokPatientTelemetryHistory, OvokPatientTelemetryHistoryQuery } from "./types";
|
|
2
|
+
import { OvokCurrentPatientProfile, OvokPatientLatestObservationsQuery, OvokPatientObservationBundle, OvokPatientAppointment, OvokPatientCarePlan, OvokPatientDashboard, OvokPatientDashboardQuery, OvokPatientDevice, OvokPatientMeasurement, OvokPatientNotification, OvokPatientQuestionnaire, OvokPatientQuestionnaireResponse, OvokPatientResourceQuery, OvokPatientSchedule, OvokPatientSchedulingQuery, OvokPatientSlot, OvokPatientTelemetryHistory, OvokPatientTelemetryHistoryQuery } from "./types";
|
|
3
3
|
export declare function getCurrentPatientProfile(this: OvokClient): Promise<OvokCurrentPatientProfile>;
|
|
4
4
|
export declare function getPatientLatestObservations(this: OvokClient, params?: OvokPatientLatestObservationsQuery): Promise<OvokPatientObservationBundle>;
|
|
5
5
|
export declare function getPatientDeviceTelemetry(this: OvokClient, deviceId: string): Promise<OvokPatientObservationBundle>;
|
|
@@ -22,3 +22,4 @@ export declare function listPatientDevices(this: OvokClient, params?: OvokPatien
|
|
|
22
22
|
export declare function getPatientDevice(this: OvokClient, deviceId: string): Promise<OvokPatientDevice>;
|
|
23
23
|
export declare function listPatientNotifications(this: OvokClient, params?: OvokPatientResourceQuery): Promise<OvokPatientNotification[]>;
|
|
24
24
|
export declare function getPatientNotification(this: OvokClient, notificationId: string): Promise<OvokPatientNotification>;
|
|
25
|
+
export declare function getPatientDashboard(this: OvokClient, params?: OvokPatientDashboardQuery): Promise<OvokPatientDashboard>;
|
|
@@ -22,6 +22,7 @@ exports.listPatientDevices = listPatientDevices;
|
|
|
22
22
|
exports.getPatientDevice = getPatientDevice;
|
|
23
23
|
exports.listPatientNotifications = listPatientNotifications;
|
|
24
24
|
exports.getPatientNotification = getPatientNotification;
|
|
25
|
+
exports.getPatientDashboard = getPatientDashboard;
|
|
25
26
|
const segment = (value) => encodeURIComponent(value);
|
|
26
27
|
const query = (params) => {
|
|
27
28
|
const values = new URLSearchParams();
|
|
@@ -104,3 +105,23 @@ async function listPatientNotifications(params = {}) {
|
|
|
104
105
|
async function getPatientNotification(notificationId) {
|
|
105
106
|
return this.readResource("CommunicationRequest", notificationId);
|
|
106
107
|
}
|
|
108
|
+
async function getPatientDashboard(params = {}) {
|
|
109
|
+
var _a, _b, _c, _d, _e, _f;
|
|
110
|
+
const profile = await this.getCurrentPatientProfile();
|
|
111
|
+
const patient = `Patient/${profile.profile.id}`;
|
|
112
|
+
const latestObservations = await this.getPatientLatestObservations({
|
|
113
|
+
...params.latestObservations,
|
|
114
|
+
patient: (_b = (_a = params.latestObservations) === null || _a === void 0 ? void 0 : _a.patient) !== null && _b !== void 0 ? _b : patient,
|
|
115
|
+
});
|
|
116
|
+
const [measurementHistory, carePlans] = await Promise.all([
|
|
117
|
+
this.listPatientMeasurementHistory({
|
|
118
|
+
...params.measurementHistory,
|
|
119
|
+
patient: (_d = (_c = params.measurementHistory) === null || _c === void 0 ? void 0 : _c.patient) !== null && _d !== void 0 ? _d : patient,
|
|
120
|
+
}),
|
|
121
|
+
this.listPatientCarePlans({
|
|
122
|
+
...params.carePlans,
|
|
123
|
+
patient: (_f = (_e = params.carePlans) === null || _e === void 0 ? void 0 : _e.patient) !== null && _f !== void 0 ? _f : patient,
|
|
124
|
+
}),
|
|
125
|
+
]);
|
|
126
|
+
return { carePlans, latestObservations, measurementHistory, profile };
|
|
127
|
+
}
|
|
@@ -70,6 +70,11 @@ export type OvokPatientTelemetryHistoryQuery = {
|
|
|
70
70
|
};
|
|
71
71
|
export type OvokPatientSchedulingQuery = Record<string, string | number | boolean | undefined>;
|
|
72
72
|
export type OvokPatientResourceQuery = OvokPatientSchedulingQuery;
|
|
73
|
+
export type OvokPatientDashboardQuery = {
|
|
74
|
+
latestObservations?: OvokPatientLatestObservationsQuery;
|
|
75
|
+
measurementHistory?: OvokPatientResourceQuery;
|
|
76
|
+
carePlans?: OvokPatientResourceQuery;
|
|
77
|
+
};
|
|
73
78
|
export type OvokPatientAppointment = Appointment;
|
|
74
79
|
export type OvokPatientSchedule = Schedule;
|
|
75
80
|
export type OvokPatientSlot = Slot;
|
|
@@ -79,6 +84,12 @@ export type OvokPatientDevice = Device;
|
|
|
79
84
|
export type OvokPatientNotification = CommunicationRequest;
|
|
80
85
|
export type OvokPatientQuestionnaire = Questionnaire;
|
|
81
86
|
export type OvokPatientQuestionnaireResponse = QuestionnaireResponse;
|
|
87
|
+
export type OvokPatientDashboard = {
|
|
88
|
+
profile: OvokCurrentPatientProfile;
|
|
89
|
+
latestObservations: OvokPatientObservationBundle;
|
|
90
|
+
measurementHistory: OvokPatientMeasurement[];
|
|
91
|
+
carePlans: OvokPatientCarePlan[];
|
|
92
|
+
};
|
|
82
93
|
export type OvokPatientObservationBundle = Bundle<Observation>;
|
|
83
94
|
export type OvokPatientTelemetryHistory = {
|
|
84
95
|
deviceId: string;
|
|
@@ -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.3.
|
|
51
|
+
version: "0.3.17",
|
|
52
52
|
},
|
|
53
53
|
fhirVersion: "4.0.1",
|
|
54
54
|
format: ["json"],
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -9,4 +9,4 @@ export { observationsToMeasurements, useEcgRecording, useObservations, useSaveMe
|
|
|
9
9
|
export { useAdministeredProjects, useCarehubDeviceTelemetry, useCarehubDevices, useCarehubNotifications, useCarehubPatients, useCapabilityStatement, useOvokMutation, useOvokRequest, useProjectFeatures, useProjectSettings, usePublicApiEndpoints, useRecentCarehubNotifications, } from "./api-hooks";
|
|
10
10
|
export { useConsentSignature, useContent, useCreateVideoCallAppointment, useCreateVideoCallUserAccess, useDocuments, useVideoCallAccess, useVideoCallAppointments, } from "./experience-hooks";
|
|
11
11
|
export { useI18nextDocument, useLocales, useLocalizations, useUpdateI18nextDocument, useUpdateLocalization, } from "./platform-hooks";
|
|
12
|
-
export { useCurrentPatientProfile, useExtractQuestionnaireObservations, usePatientAppointments, usePatientCarePlans, usePatientDevices, usePatientDeviceTelemetry, usePatientDeviceTelemetryHistory, usePatientMeasurement, usePatientMeasurementHistory, usePatientLatestObservations, usePatientNotifications, usePatientQuestionnaireResponses, usePatientQuestionnaires, usePatientSchedules, usePatientSlots, usePopulateQuestionnaire, useSubmitQuestionnaireResponse, } from "./patient-hooks";
|
|
12
|
+
export { useCurrentPatientProfile, useExtractQuestionnaireObservations, usePatientAppointments, usePatientCarePlans, usePatientDevices, usePatientDeviceTelemetry, usePatientDeviceTelemetryHistory, usePatientMeasurement, usePatientMeasurementHistory, usePatientDashboard, usePatientLatestObservations, usePatientNotifications, usePatientQuestionnaireResponses, usePatientQuestionnaires, usePatientSchedules, usePatientSlots, usePopulateQuestionnaire, useSubmitQuestionnaireResponse, } from "./patient-hooks";
|
package/dist/hooks/index.js
CHANGED
|
@@ -35,7 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
exports.usePatientMeasurement = exports.usePatientDeviceTelemetryHistory = exports.usePatientDeviceTelemetry = exports.usePatientDevices = exports.usePatientCarePlans = exports.usePatientAppointments = exports.useExtractQuestionnaireObservations = exports.useCurrentPatientProfile = exports.useUpdateLocalization = exports.useUpdateI18nextDocument = exports.useLocalizations = exports.useLocales = exports.useI18nextDocument = exports.useVideoCallAppointments = exports.useVideoCallAccess = exports.useDocuments = exports.useCreateVideoCallUserAccess = exports.useCreateVideoCallAppointment = exports.useContent = exports.useConsentSignature = exports.useRecentCarehubNotifications = exports.usePublicApiEndpoints = exports.useProjectSettings = exports.useProjectFeatures = exports.useOvokRequest = exports.useOvokMutation = exports.useCapabilityStatement = exports.useCarehubPatients = exports.useCarehubNotifications = exports.useCarehubDevices = exports.useCarehubDeviceTelemetry = exports.useAdministeredProjects = exports.useUrineTests = exports.useSaveMeasurement = exports.useObservations = exports.useEcgRecording = exports.observationsToMeasurements = exports.OvokProvider = exports.useClient = exports.useCachedBinaryUrl = exports.reactContext = exports.useSubscription = exports.useSearchResources = exports.useSearchOne = exports.useSearch = exports.useResource = exports.usePrevious = exports.useMedplumProfile = exports.useMedplumNavigate = exports.useMedplumContext = void 0;
|
|
38
|
-
exports.useSubmitQuestionnaireResponse = exports.usePopulateQuestionnaire = exports.usePatientSlots = exports.usePatientSchedules = exports.usePatientQuestionnaires = exports.usePatientQuestionnaireResponses = exports.usePatientNotifications = exports.usePatientLatestObservations = exports.usePatientMeasurementHistory = void 0;
|
|
38
|
+
exports.useSubmitQuestionnaireResponse = exports.usePopulateQuestionnaire = exports.usePatientSlots = exports.usePatientSchedules = exports.usePatientQuestionnaires = exports.usePatientQuestionnaireResponses = exports.usePatientNotifications = exports.usePatientLatestObservations = exports.usePatientDashboard = exports.usePatientMeasurementHistory = void 0;
|
|
39
39
|
/*
|
|
40
40
|
* The only React-dependent module in the package.
|
|
41
41
|
*
|
|
@@ -114,6 +114,7 @@ Object.defineProperty(exports, "usePatientDeviceTelemetry", { enumerable: true,
|
|
|
114
114
|
Object.defineProperty(exports, "usePatientDeviceTelemetryHistory", { enumerable: true, get: function () { return patient_hooks_1.usePatientDeviceTelemetryHistory; } });
|
|
115
115
|
Object.defineProperty(exports, "usePatientMeasurement", { enumerable: true, get: function () { return patient_hooks_1.usePatientMeasurement; } });
|
|
116
116
|
Object.defineProperty(exports, "usePatientMeasurementHistory", { enumerable: true, get: function () { return patient_hooks_1.usePatientMeasurementHistory; } });
|
|
117
|
+
Object.defineProperty(exports, "usePatientDashboard", { enumerable: true, get: function () { return patient_hooks_1.usePatientDashboard; } });
|
|
117
118
|
Object.defineProperty(exports, "usePatientLatestObservations", { enumerable: true, get: function () { return patient_hooks_1.usePatientLatestObservations; } });
|
|
118
119
|
Object.defineProperty(exports, "usePatientNotifications", { enumerable: true, get: function () { return patient_hooks_1.usePatientNotifications; } });
|
|
119
120
|
Object.defineProperty(exports, "usePatientQuestionnaireResponses", { enumerable: true, get: function () { return patient_hooks_1.usePatientQuestionnaireResponses; } });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Observation } from "@medplum/fhirtypes";
|
|
2
|
+
import type { ObservationsToMeasurementsOptions } from "../client/observation/methods/observationsToMeasurements";
|
|
2
3
|
import { observationsToMeasurements, SupportedMeasurement } from "../client/observation/methods/observationsToMeasurements";
|
|
3
4
|
import { GenerateObservationBodyParams } from "../client/observation/types/generate-observation-body/GenerateObservationBodyParams";
|
|
4
5
|
import { OvokClient } from "../client/ovok-client";
|
|
@@ -12,10 +13,12 @@ export interface UseObservationsOptions<T extends readonly SupportedType[]> {
|
|
|
12
13
|
types: T;
|
|
13
14
|
period?: ObservationPeriod;
|
|
14
15
|
patientId?: string;
|
|
16
|
+
groupBy?: ObservationsToMeasurementsOptions["groupBy"];
|
|
17
|
+
timeToleranceMs?: number;
|
|
15
18
|
}
|
|
16
19
|
/** Plain inverse mapping for apps that already fetched their Observation resources. */
|
|
17
20
|
export { observationsToMeasurements };
|
|
18
|
-
export declare const useObservations: <T extends readonly SupportedType[]>({ types, period, patientId, }: UseObservationsOptions<T>) => {
|
|
21
|
+
export declare const useObservations: <T extends readonly SupportedType[]>({ types, period, patientId, groupBy, timeToleranceMs, }: UseObservationsOptions<T>) => {
|
|
19
22
|
measurements: Array<Extract<SupportedMeasurement, {
|
|
20
23
|
measurementTypeKey: T[number];
|
|
21
24
|
}>>;
|
|
@@ -41,7 +41,7 @@ const observationsToMeasurements_1 = require("../client/observation/methods/obse
|
|
|
41
41
|
Object.defineProperty(exports, "observationsToMeasurements", { enumerable: true, get: function () { return observationsToMeasurements_1.observationsToMeasurements; } });
|
|
42
42
|
const types_1 = require("../types");
|
|
43
43
|
const index_1 = require("./index");
|
|
44
|
-
const useObservations = ({ types, period, patientId, }) => {
|
|
44
|
+
const useObservations = ({ types, period, patientId, groupBy, timeToleranceMs, }) => {
|
|
45
45
|
var _a, _b, _c;
|
|
46
46
|
// A broad search is intentional: one measurement can expand into several
|
|
47
47
|
// observations, and the inverse mapper performs the type-specific grouping.
|
|
@@ -60,7 +60,7 @@ const useObservations = ({ types, period, patientId, }) => {
|
|
|
60
60
|
}), [codes, patientId, period === null || period === void 0 ? void 0 : period.end, period === null || period === void 0 ? void 0 : period.start]);
|
|
61
61
|
const [resources, loading, outcome] = (0, react_hooks_1.useSearchResources)("Observation", query);
|
|
62
62
|
const observations = resources !== null && resources !== void 0 ? resources : [];
|
|
63
|
-
const measurements = React.useMemo(() => (0, observationsToMeasurements_1.observationsToMeasurements)(observations, types), [observations, types]);
|
|
63
|
+
const measurements = React.useMemo(() => (0, observationsToMeasurements_1.observationsToMeasurements)(observations, types, { groupBy, timeToleranceMs }), [groupBy, observations, timeToleranceMs, types]);
|
|
64
64
|
return {
|
|
65
65
|
measurements,
|
|
66
66
|
loading,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Bundle, Observation, Parameters, QuestionnaireResponse } from "@medplum/fhirtypes";
|
|
2
|
-
import { OvokCurrentPatientProfile, OvokPatientAppointment, OvokPatientCarePlan, OvokPatientDevice, OvokPatientLatestObservationsQuery, OvokPatientMeasurement, OvokPatientNotification, OvokPatientObservationBundle, OvokPatientQuestionnaire, OvokPatientQuestionnaireResponse, OvokPatientResourceQuery, OvokPatientSchedule, OvokPatientSchedulingQuery, OvokPatientSlot, OvokPatientTelemetryHistory, OvokPatientTelemetryHistoryQuery } from "../client/patient/types";
|
|
2
|
+
import { OvokCurrentPatientProfile, OvokPatientAppointment, OvokPatientCarePlan, OvokPatientDashboard, OvokPatientDashboardQuery, OvokPatientDevice, OvokPatientLatestObservationsQuery, OvokPatientMeasurement, OvokPatientNotification, OvokPatientObservationBundle, OvokPatientQuestionnaire, OvokPatientQuestionnaireResponse, OvokPatientResourceQuery, OvokPatientSchedule, OvokPatientSchedulingQuery, OvokPatientSlot, OvokPatientTelemetryHistory, OvokPatientTelemetryHistoryQuery } from "../client/patient/types";
|
|
3
3
|
import { OvokQuestionnaireOperationResponse } from "../client/questionnaire-response/types";
|
|
4
4
|
import { OvokMutationState, OvokRequestState } from "./api-hooks";
|
|
5
5
|
export declare const useCurrentPatientProfile: () => OvokRequestState<OvokCurrentPatientProfile>;
|
|
6
|
+
export declare const usePatientDashboard: (params?: OvokPatientDashboardQuery) => OvokRequestState<OvokPatientDashboard>;
|
|
6
7
|
export declare const usePatientLatestObservations: (params?: OvokPatientLatestObservationsQuery) => OvokRequestState<OvokPatientObservationBundle>;
|
|
7
8
|
export declare const usePatientDeviceTelemetry: (deviceId?: string) => OvokRequestState<OvokPatientObservationBundle>;
|
|
8
9
|
export declare const usePatientDeviceTelemetryHistory: (deviceId: string | undefined, params?: OvokPatientTelemetryHistoryQuery) => OvokRequestState<OvokPatientTelemetryHistory>;
|
|
@@ -34,7 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.useExtractQuestionnaireObservations = exports.useSubmitQuestionnaireResponse = exports.usePopulateQuestionnaire = exports.usePatientNotifications = exports.usePatientDevices = exports.usePatientQuestionnaireResponses = exports.usePatientQuestionnaires = exports.usePatientCarePlans = exports.usePatientMeasurement = exports.usePatientMeasurementHistory = exports.usePatientSlots = exports.usePatientSchedules = exports.usePatientAppointments = exports.usePatientDeviceTelemetryHistory = exports.usePatientDeviceTelemetry = exports.usePatientLatestObservations = exports.useCurrentPatientProfile = void 0;
|
|
37
|
+
exports.useExtractQuestionnaireObservations = exports.useSubmitQuestionnaireResponse = exports.usePopulateQuestionnaire = exports.usePatientNotifications = exports.usePatientDevices = exports.usePatientQuestionnaireResponses = exports.usePatientQuestionnaires = exports.usePatientCarePlans = exports.usePatientMeasurement = exports.usePatientMeasurementHistory = exports.usePatientSlots = exports.usePatientSchedules = exports.usePatientAppointments = exports.usePatientDeviceTelemetryHistory = exports.usePatientDeviceTelemetry = exports.usePatientLatestObservations = exports.usePatientDashboard = exports.useCurrentPatientProfile = void 0;
|
|
38
38
|
const React = __importStar(require("react"));
|
|
39
39
|
const api_hooks_1 = require("./api-hooks");
|
|
40
40
|
const index_1 = require("./index");
|
|
@@ -44,6 +44,13 @@ const useCurrentPatientProfile = () => {
|
|
|
44
44
|
return (0, api_hooks_1.useOvokRequest)(load);
|
|
45
45
|
};
|
|
46
46
|
exports.useCurrentPatientProfile = useCurrentPatientProfile;
|
|
47
|
+
const usePatientDashboard = (params = {}) => {
|
|
48
|
+
const client = (0, index_1.useClient)();
|
|
49
|
+
const serialized = JSON.stringify(params);
|
|
50
|
+
const load = React.useCallback(() => client.getPatientDashboard(params), [client, serialized]);
|
|
51
|
+
return (0, api_hooks_1.useOvokRequest)(load);
|
|
52
|
+
};
|
|
53
|
+
exports.usePatientDashboard = usePatientDashboard;
|
|
47
54
|
const usePatientLatestObservations = (params = {}) => {
|
|
48
55
|
const client = (0, index_1.useClient)();
|
|
49
56
|
const serialized = JSON.stringify(params);
|