@healthcloudai/hc-safe-cdx 0.1.4 → 0.2.1
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 +1368 -320
- package/dist/index.cjs +159 -244
- package/dist/index.d.cts +372 -205
- package/dist/index.d.ts +372 -205
- package/dist/index.js +159 -244
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
# Healthcheck Safe CDX Connector
|
|
2
2
|
|
|
3
|
-
Safe CDX
|
|
3
|
+
Safe CDX supports the diagnostic test workflow performed by an authenticated patient. It provides the SDK methods used to retrieve available test profiles, resolve a selected test by GTIN, prepare and upload test images, process test results, and complete the test flow.
|
|
4
4
|
|
|
5
|
-
The
|
|
5
|
+
`HCSafeCDXClient` uses the authenticated patient context provided by `HCLoginClient`. The client derives its own Safe CDX service URL from the configured environment, sends authenticated requests to Safe CDX routes, and wraps request payloads internally where the API expects the standard `Data` envelope.
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- `HCLoginClient` provides the authenticated request headers.
|
|
9
|
-
- `HCSafeCDXClient` builds Safe CDX URLs, wraps request payloads, and exposes one method per Safe CDX route.
|
|
10
|
-
|
|
11
|
-
Safe CDX has its own API host. The connector uses `HCLoginClient` for authentication only; it does not use `loginClient.getBaseUrl()` for Safe CDX routes.
|
|
12
|
-
|
|
13
|
-
Image upload is handled separately because the upload target is a pre-signed S3 URL, not a Safe CDX API route.
|
|
7
|
+
Image upload is handled separately because the upload target is a pre-signed storage URL rather than a Safe CDX API route.
|
|
14
8
|
|
|
15
9
|
## Installation
|
|
16
10
|
|
|
@@ -26,588 +20,1642 @@ import { HCLoginClient } from "@healthcloudai/hc-login-connector";
|
|
|
26
20
|
import { HCSafeCDXClient } from "@healthcloudai/hc-safe-cdx";
|
|
27
21
|
```
|
|
28
22
|
|
|
29
|
-
##
|
|
23
|
+
## Setup
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
const http = new FetchClient();
|
|
25
|
+
Create the shared HTTP client, authenticate the patient through `HCLoginClient`, and then create the Safe CDX client with the authenticated login instance.
|
|
33
26
|
|
|
34
|
-
|
|
27
|
+
```ts
|
|
28
|
+
const httpClient = new FetchClient();
|
|
29
|
+
const loginClient = new HCLoginClient(httpClient);
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
loginClient.configure("healthcheck", "dev");
|
|
32
|
+
await loginClient.login(email, password);
|
|
38
33
|
|
|
39
|
-
const safeCdx = new HCSafeCDXClient(
|
|
40
|
-
environment: "dev",
|
|
41
|
-
defaultLanguage: "en",
|
|
42
|
-
defaultImageType: "jpg",
|
|
43
|
-
});
|
|
34
|
+
const safeCdx = new HCSafeCDXClient(httpClient, loginClient);
|
|
44
35
|
```
|
|
45
36
|
|
|
46
|
-
The connector does not perform login
|
|
37
|
+
The Safe CDX connector does not perform login and does not require a separate `setAccessToken(...)` step. Authenticated headers are read from the existing `HCLoginClient` instance.
|
|
47
38
|
|
|
48
|
-
|
|
39
|
+
## Client Configuration
|
|
49
40
|
|
|
50
|
-
|
|
41
|
+
### Service URL
|
|
51
42
|
|
|
52
|
-
|
|
43
|
+
Safe CDX uses its own service base URL and does not reuse `loginClient.getBaseUrl()`. The client reads the configured environment through `loginClient.getEnvironment()` and resolves the Safe CDX host internally.
|
|
53
44
|
|
|
54
|
-
Example package configuration:
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
46
|
+
### Optional API Key
|
|
47
|
+
|
|
48
|
+
When an environment requires an API key header, configure it on the Safe CDX client:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
safeCdx.setApiKey("x-api-key", apiKeyValue);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The configured API key header is added to Safe CDX API route requests together with the patient authentication headers.
|
|
55
|
+
|
|
56
|
+
## Request Contract
|
|
57
|
+
|
|
58
|
+
SDK methods receive only the values needed by the consuming application. The consumer does not manually provide the backend `Data` wrapper, tenant context, patient identity values, or authentication headers.
|
|
59
|
+
|
|
60
|
+
For POST requests, `HCSafeCDXClient` constructs the API request envelope internally:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
interface APIRequest<T> {
|
|
64
|
+
Data: T;
|
|
62
65
|
}
|
|
63
66
|
```
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
For example:
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
```ts
|
|
71
|
+
await safeCdx.submitAnswers({
|
|
72
|
+
UserTestResultId: userTestResultId,
|
|
73
|
+
Result: [
|
|
74
|
+
{
|
|
75
|
+
Analyte: "Purchase",
|
|
76
|
+
ReportedValue: "drug store",
|
|
77
|
+
StoredValue: "drug store",
|
|
78
|
+
Score: "0"
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The client sends:
|
|
68
85
|
|
|
69
86
|
```json
|
|
70
87
|
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
88
|
+
"Data": {
|
|
89
|
+
"UserTestResultId": "<USER_TEST_RESULT_ID>",
|
|
90
|
+
"Result": [
|
|
91
|
+
{
|
|
92
|
+
"Analyte": "Purchase",
|
|
93
|
+
"ReportedValue": "drug store",
|
|
94
|
+
"StoredValue": "drug store",
|
|
95
|
+
"Score": "0"
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
75
99
|
}
|
|
76
100
|
```
|
|
77
101
|
|
|
78
|
-
##
|
|
102
|
+
## Response Contract
|
|
79
103
|
|
|
80
|
-
|
|
104
|
+
Safe CDX methods return the response shape produced by their backend endpoint without unwrapping or transforming it.
|
|
81
105
|
|
|
82
|
-
|
|
83
|
-
const http = new FetchClient();
|
|
84
|
-
const loginClient = new HCLoginClient(http);
|
|
106
|
+
Most documented methods return the standard Health Cloud response envelope:
|
|
85
107
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
```ts
|
|
109
|
+
interface APIResponse<T> {
|
|
110
|
+
Data: T | null;
|
|
111
|
+
IsOK: boolean;
|
|
112
|
+
ErrorMessage: string | null;
|
|
113
|
+
}
|
|
89
114
|
```
|
|
90
115
|
|
|
91
|
-
|
|
116
|
+
Several Safe CDX operations return a service response inside `APIResponse.Data`, or return that service response directly:
|
|
92
117
|
|
|
93
|
-
|
|
118
|
+
```ts
|
|
119
|
+
interface SafeAPIResponse<T> {
|
|
120
|
+
success: boolean;
|
|
121
|
+
data: T | null;
|
|
122
|
+
message: string | null;
|
|
123
|
+
code: number;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
94
126
|
|
|
95
|
-
The
|
|
127
|
+
The following methods return `SafeAPIResponse<T>` directly, without the outer `APIResponse<T>` wrapper:
|
|
96
128
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
- exposing one SDK method per Safe CDX route
|
|
129
|
+
```ts
|
|
130
|
+
safeCdx.updateCvmlStatus(...)
|
|
131
|
+
safeCdx.getCvmlResults(...)
|
|
132
|
+
safeCdx.getResultDetails(...)
|
|
133
|
+
safeCdx.getImageCaptureUrl(...)
|
|
134
|
+
```
|
|
104
135
|
|
|
105
|
-
|
|
136
|
+
`HCSafeCDXClient` does not replace an API-defined response with a different return value. Local errors may still be thrown before or outside a Safe CDX API response, such as invalid API key configuration or a failed direct image upload. Transport-level handling for unsuccessful HTTP responses is determined by the supplied `HttpClient`.
|
|
106
137
|
|
|
107
|
-
##
|
|
138
|
+
## Parameter Design
|
|
108
139
|
|
|
109
|
-
|
|
140
|
+
Public methods follow the documented patient workflow request shapes.
|
|
110
141
|
|
|
111
142
|
```ts
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
143
|
+
safeCdx.getTestProfileByGTIN(gtin)
|
|
144
|
+
safeCdx.getTestProfilesByAccount(includeRegisterTestDetails?)
|
|
145
|
+
safeCdx.createUploadUrl(userTestResultId, gtin, imageType?)
|
|
146
|
+
safeCdx.updateCvmlStatus(imageOfCaptureId, cvmlStatus)
|
|
147
|
+
safeCdx.getCvmlResults(imageOfCaptureId)
|
|
148
|
+
safeCdx.getPendingResults(excludeStatus?)
|
|
149
|
+
safeCdx.getLastResults(excludeStatus?)
|
|
150
|
+
safeCdx.getTestHistory(excludeStatus?)
|
|
151
|
+
safeCdx.getResultDetails(userTestResultId)
|
|
152
|
+
safeCdx.getResultPdf(userTestResultId)
|
|
153
|
+
safeCdx.getImageCaptureUrl(userTestResultId)
|
|
154
|
+
safeCdx.resumeFlow(userTestResultId, resumed?)
|
|
155
|
+
safeCdx.finalizeTest(userTestResultId)
|
|
115
156
|
```
|
|
116
157
|
|
|
117
|
-
|
|
158
|
+
|
|
159
|
+
## Typical Patient Workflow
|
|
118
160
|
|
|
119
161
|
```ts
|
|
120
|
-
|
|
121
|
-
|
|
162
|
+
const profiles = await safeCdx.getTestProfilesByAccount();
|
|
163
|
+
|
|
164
|
+
const profile = await safeCdx.getTestProfileByGTIN("850024942325");
|
|
165
|
+
|
|
166
|
+
if (!profile.IsOK || !profile.Data) {
|
|
167
|
+
throw new Error(profile.ErrorMessage ?? "Unable to resolve the test profile.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const userTestResultId = profile.Data.ID;
|
|
171
|
+
const gtin = profile.Data.DiagnosticProfile?.TestInfo.gtin;
|
|
172
|
+
|
|
173
|
+
if (!userTestResultId || !gtin) {
|
|
174
|
+
throw new Error("The selected test profile does not include the required workflow identifiers.");
|
|
175
|
+
}
|
|
122
176
|
|
|
123
|
-
|
|
177
|
+
const upload = await safeCdx.createUploadUrl(userTestResultId, gtin);
|
|
178
|
+
|
|
179
|
+
if (!upload.IsOK || !upload.Data?.preSignedURL || !upload.Data.Metadata?.UploadId) {
|
|
180
|
+
throw new Error(upload.ErrorMessage ?? "Unable to prepare image upload.");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const preSignedURL = upload.Data.preSignedURL;
|
|
184
|
+
const imageOfCaptureId = upload.Data.Metadata.UploadId;
|
|
124
185
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
186
|
+
await safeCdx.uploadImage(preSignedURL, imageBody, "image/jpeg");
|
|
187
|
+
await safeCdx.updateCvmlStatus(imageOfCaptureId, "ImageProcessing");
|
|
188
|
+
|
|
189
|
+
const cvmlResults = await safeCdx.getCvmlResults(imageOfCaptureId);
|
|
190
|
+
|
|
191
|
+
await safeCdx.submitAnswers({
|
|
192
|
+
UserTestResultId: userTestResultId,
|
|
193
|
+
Result: [
|
|
194
|
+
{
|
|
195
|
+
Analyte: "Purchase",
|
|
196
|
+
ReportedValue: "drug store",
|
|
197
|
+
StoredValue: "drug store",
|
|
198
|
+
Score: "0"
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await safeCdx.finalizeTest(userTestResultId);
|
|
204
|
+
|
|
205
|
+
const details = await safeCdx.getResultDetails(userTestResultId);
|
|
206
|
+
const pdf = await safeCdx.getResultPdf(userTestResultId);
|
|
207
|
+
const imageCaptureUrl = await safeCdx.getImageCaptureUrl(userTestResultId);
|
|
129
208
|
```
|
|
130
209
|
|
|
131
|
-
|
|
210
|
+
The application is responsible for preserving workflow values returned between steps, including `userTestResultId`, `gtin`, `preSignedURL`, and `imageOfCaptureId`.
|
|
132
211
|
|
|
133
|
-
##
|
|
212
|
+
## Public Methods
|
|
213
|
+
|
|
214
|
+
### `getTestProfilesByAccount(...)`
|
|
134
215
|
|
|
135
|
-
|
|
216
|
+
Lists test profiles available to the authenticated patient account. The backend resolves the tenant from the authenticated patient context.
|
|
136
217
|
|
|
137
|
-
|
|
218
|
+
#### Signature
|
|
138
219
|
|
|
139
220
|
```ts
|
|
140
|
-
|
|
221
|
+
safeCdx.getTestProfilesByAccount(
|
|
222
|
+
includeRegisterTestDetails?: boolean
|
|
223
|
+
): Promise<APIResponse<GetTestProfilesByAccountData>>
|
|
141
224
|
```
|
|
142
225
|
|
|
143
|
-
|
|
226
|
+
`GetTestProfilesByAccountData` is:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
type GetTestProfilesByAccountData =
|
|
230
|
+
SafeAPIResponse<TestProfileByAccountItem[]>;
|
|
231
|
+
```
|
|
144
232
|
|
|
145
|
-
|
|
233
|
+
#### Parameters
|
|
234
|
+
|
|
235
|
+
| Parameter | Type | Required | Description |
|
|
236
|
+
|---|---|---:|---|
|
|
237
|
+
| `includeRegisterTestDetails` | `boolean` | No | Includes registration-specific test details when `true`. Defaults to `true`. |
|
|
238
|
+
|
|
239
|
+
#### Usage
|
|
146
240
|
|
|
147
241
|
```ts
|
|
148
|
-
const
|
|
242
|
+
const profiles = await safeCdx.getTestProfilesByAccount();
|
|
243
|
+
```
|
|
149
244
|
|
|
150
|
-
|
|
245
|
+
```ts
|
|
246
|
+
const profiles = await safeCdx.getTestProfilesByAccount(true);
|
|
247
|
+
```
|
|
151
248
|
|
|
152
|
-
|
|
153
|
-
// await loginClient.loginPatient(...)
|
|
249
|
+
#### API request sent internally
|
|
154
250
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"Data": {
|
|
254
|
+
"IncludeRegisterTestDetails": true
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### API response example
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"Data": {
|
|
264
|
+
"success": true,
|
|
265
|
+
"data": [
|
|
266
|
+
{
|
|
267
|
+
"gtin": "810172700093",
|
|
268
|
+
"productName": "Speedy Swab COVID-19 + Flu Self-Test",
|
|
269
|
+
"description": "",
|
|
270
|
+
"testKitImageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/4307c803740644ab8c4594f69783c1d8-638694157517533356.png",
|
|
271
|
+
"language": "en",
|
|
272
|
+
"registerTestDetail": {
|
|
273
|
+
"title": "Scan the barcode",
|
|
274
|
+
"description": "<p><span style=\"font-size: 12pt; font-family: Avenir, -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\">Hold your phone over the barcode on the box.</span></p>",
|
|
275
|
+
"buttonTitle": "Scan Code",
|
|
276
|
+
"imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/1ccc293f924a4c5bb2c5fc76f474c625-638769599179275771.png"
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"gtin": "860002060439",
|
|
281
|
+
"productName": "Winx UTI Test + Treat",
|
|
282
|
+
"description": "",
|
|
283
|
+
"testKitImageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/36649cb6377c435a9e4f83af0f2fea46-639058689126409862.png",
|
|
284
|
+
"language": "en",
|
|
285
|
+
"registerTestDetail": {
|
|
286
|
+
"title": "",
|
|
287
|
+
"description": "<p></p>",
|
|
288
|
+
"buttonTitle": "Scan Barcode on the Box",
|
|
289
|
+
"imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/ad72c9b8c3324c1cb7aa9dbf29e46d64-638905990491115782.png"
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"gtin": "850024942325",
|
|
294
|
+
"productName": "Vaginal Health pH Test + Treat",
|
|
295
|
+
"description": "",
|
|
296
|
+
"testKitImageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/3cebedbb69cb4455af33da5ab524ec93-639058688331122684.png",
|
|
297
|
+
"language": "en",
|
|
298
|
+
"registerTestDetail": {
|
|
299
|
+
"title": "",
|
|
300
|
+
"description": "<p></p>",
|
|
301
|
+
"buttonTitle": "Scan Barcode on the Box",
|
|
302
|
+
"imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/1475ae0004084d749ee2dfc9123af952-638905989776168388.png"
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
],
|
|
306
|
+
"message": "Success",
|
|
307
|
+
"code": 0
|
|
308
|
+
},
|
|
309
|
+
"ErrorMessage": null,
|
|
310
|
+
"IsOK": true
|
|
311
|
+
}
|
|
158
312
|
```
|
|
159
313
|
|
|
160
|
-
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
### `getTestProfileByGTIN(...)`
|
|
161
317
|
|
|
162
|
-
|
|
318
|
+
Resolves the Safe CDX test profile associated with a GTIN barcode.
|
|
163
319
|
|
|
164
|
-
|
|
320
|
+
#### Signature
|
|
165
321
|
|
|
166
322
|
```ts
|
|
167
|
-
safeCdx.
|
|
323
|
+
safeCdx.getTestProfileByGTIN(gtin: string): Promise<APIResponse<GetTestProfileByGTINData>>
|
|
168
324
|
```
|
|
169
325
|
|
|
170
|
-
|
|
326
|
+
#### Parameters
|
|
171
327
|
|
|
172
|
-
|
|
328
|
+
| Parameter | Type | Required | Description |
|
|
329
|
+
|---|---|---:|---|
|
|
330
|
+
| `gtin` | `string` | Yes | GTIN barcode value used to resolve the test profile. |
|
|
173
331
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
The SDK wraps the payload internally as:
|
|
332
|
+
#### Usage
|
|
177
333
|
|
|
178
334
|
```ts
|
|
335
|
+
const profile = await safeCdx.getTestProfileByGTIN("850024942325");
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
#### API response example
|
|
340
|
+
|
|
341
|
+
```json
|
|
179
342
|
{
|
|
180
|
-
Data:
|
|
343
|
+
"Data": {
|
|
344
|
+
"ID": "6a104bba6068dd9a213db810",
|
|
345
|
+
"FHIRID": null,
|
|
346
|
+
"CQLID": null,
|
|
347
|
+
"TestName": "UTI Test + Treat",
|
|
348
|
+
"CustomTestName": "UTI Test + Treat",
|
|
349
|
+
"IsDeactivated": false,
|
|
350
|
+
"IsOrderable": true,
|
|
351
|
+
"EnablePublicHealthReporting": true,
|
|
352
|
+
"IsSelfAssessmentMode": true,
|
|
353
|
+
"CaptureMethod": 0,
|
|
354
|
+
"IsMLDisabled": false,
|
|
355
|
+
"ShortName": "UTI Test + Treat",
|
|
356
|
+
"DescriptionHTML": null,
|
|
357
|
+
"OrderableName": "Winx Health",
|
|
358
|
+
"VendorName": null,
|
|
359
|
+
"ManufactureName": null,
|
|
360
|
+
"VendorTestID": "0123",
|
|
361
|
+
"Sku": "SH00131",
|
|
362
|
+
"LabTestType": null,
|
|
363
|
+
"TestClassification": "NOT_SPECIFIED",
|
|
364
|
+
"KitRegistrationRequired": false,
|
|
365
|
+
"RequiresProviderVerificationOfResults": false,
|
|
366
|
+
"DiagnosticProfile": {
|
|
367
|
+
"_id": "6780031a991814469e88237f",
|
|
368
|
+
"Created": "2025-01-09T17:10:50.8Z",
|
|
369
|
+
"Modified": "2026-05-06T12:39:08.732Z",
|
|
370
|
+
"TestInfo": {
|
|
371
|
+
"gtin": "860002060439",
|
|
372
|
+
"testInfoHeader": "Winx Health",
|
|
373
|
+
"productName": "Winx UTI Test + Treat",
|
|
374
|
+
"testCategory": "Rapid",
|
|
375
|
+
"fdaStatus": "EUA",
|
|
376
|
+
"cvmlTestName": "winx-uti",
|
|
377
|
+
"cvmlDuration": 20,
|
|
378
|
+
"isCVMLResult": true,
|
|
379
|
+
"accountIds": [
|
|
380
|
+
"winxhealth",
|
|
381
|
+
"safehealth",
|
|
382
|
+
"test-account",
|
|
383
|
+
"speedyswab",
|
|
384
|
+
"healthcheck"
|
|
385
|
+
],
|
|
386
|
+
"cvmlRetryLimit": 2,
|
|
387
|
+
"failoverEnabled": true,
|
|
388
|
+
"cvmlPollingPolicy": "FullResult"
|
|
389
|
+
},
|
|
390
|
+
"Analyte": [
|
|
391
|
+
{
|
|
392
|
+
"_id": "1",
|
|
393
|
+
"DisplayOrder": 1,
|
|
394
|
+
"Display": "CVML",
|
|
395
|
+
"Name": "Leukocyte",
|
|
396
|
+
"Question": "What is the Leukocyte reading?",
|
|
397
|
+
"Image": "/analyte/WinxLeuk.png",
|
|
398
|
+
"Responses": [
|
|
399
|
+
{
|
|
400
|
+
"value": "Negative -",
|
|
401
|
+
"displayOrder": 1,
|
|
402
|
+
"storedValue": "Neg",
|
|
403
|
+
"cvmlValue": "Neg",
|
|
404
|
+
"score": "0"
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
"value": "Positive 70+",
|
|
408
|
+
"displayOrder": 3,
|
|
409
|
+
"storedValue": "70+",
|
|
410
|
+
"cvmlValue": "70+",
|
|
411
|
+
"score": "4"
|
|
412
|
+
}
|
|
413
|
+
]
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
"_id": "2",
|
|
417
|
+
"DisplayOrder": 2,
|
|
418
|
+
"Display": "CVML",
|
|
419
|
+
"Name": "Nitrite",
|
|
420
|
+
"Question": "What is the Nitrite reading?",
|
|
421
|
+
"Image": "/analyte/WinxNitr.png",
|
|
422
|
+
"Responses": [
|
|
423
|
+
{
|
|
424
|
+
"value": "Negative",
|
|
425
|
+
"displayOrder": 1,
|
|
426
|
+
"storedValue": "Neg",
|
|
427
|
+
"cvmlValue": "Neg",
|
|
428
|
+
"score": "0"
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"value": "Positive High++",
|
|
432
|
+
"displayOrder": 3,
|
|
433
|
+
"storedValue": "High++",
|
|
434
|
+
"cvmlValue": "High++",
|
|
435
|
+
"score": "2"
|
|
436
|
+
}
|
|
437
|
+
]
|
|
438
|
+
}
|
|
439
|
+
],
|
|
440
|
+
"Cta": [
|
|
441
|
+
{
|
|
442
|
+
"id": "1",
|
|
443
|
+
"title": "No UTI is detected",
|
|
444
|
+
"instructions": "If you're still having symptoms, we recommend talking to your doctor about additional testing.",
|
|
445
|
+
"linkType": "url",
|
|
446
|
+
"linkText": "Find a Doctor",
|
|
447
|
+
"linkValue": "https://book.zocdoc.com/?utm_source=winx&utm_medium=app&utm_campaign=testandtreat"
|
|
448
|
+
}
|
|
449
|
+
],
|
|
450
|
+
"Indication": [
|
|
451
|
+
{
|
|
452
|
+
"id": "4",
|
|
453
|
+
"title": "Indication",
|
|
454
|
+
"text": "Results suggest you have a UTI",
|
|
455
|
+
"recommendTitle": "Recommendation",
|
|
456
|
+
"recommendText": "Drink lots of water and connect with a doctor to discuss treatment and next steps.",
|
|
457
|
+
"ctaId": "2"
|
|
458
|
+
}
|
|
459
|
+
],
|
|
460
|
+
"Decision": [
|
|
461
|
+
{
|
|
462
|
+
"calculation": "Total: analyte.responses.score",
|
|
463
|
+
"results": [
|
|
464
|
+
{
|
|
465
|
+
"indicationId": "4",
|
|
466
|
+
"value": "4"
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
}
|
|
470
|
+
]
|
|
471
|
+
},
|
|
472
|
+
"ProductAssetDetail": {
|
|
473
|
+
"imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/ProductAsset/Image/47197f0c75844ba3b11335f7bfe1959b-639062362985442065.png",
|
|
474
|
+
"body": "<p></p>"
|
|
475
|
+
},
|
|
476
|
+
"DisclaimerDetail": {
|
|
477
|
+
"body": "<p><span style=\"font-family: Avenir, -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 14pt;\">At this time please refer back to your paper Instructions For Use (IFU).</span></p>",
|
|
478
|
+
"title": "Record Results",
|
|
479
|
+
"continueButtonTitle": "Continue",
|
|
480
|
+
"showDisclaimer": false
|
|
481
|
+
},
|
|
482
|
+
"ScanImageDetail": {
|
|
483
|
+
"ImageUrl": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/ScanImage/Image/590172d7548e4b3eb09bfe42679fc6b1-638900206144635626.png"
|
|
484
|
+
},
|
|
485
|
+
"Instructions": [
|
|
486
|
+
{
|
|
487
|
+
"NavigationTitle": "Instructional Video",
|
|
488
|
+
"Type": "CollectionInstructionOnly",
|
|
489
|
+
"Title": "Instructional Video",
|
|
490
|
+
"ButtonTitle": "Continue",
|
|
491
|
+
"TimeInSeconds": 0,
|
|
492
|
+
"VideoUrl": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/CollectionInstruction/Video/3a74d7091d65455ab675f8ddd5f9d9d5-638918232791589391.mp4",
|
|
493
|
+
"SequenceOrder": 0
|
|
494
|
+
}
|
|
495
|
+
],
|
|
496
|
+
"BillingInfo": {
|
|
497
|
+
"Taxable": false
|
|
498
|
+
},
|
|
499
|
+
"Associations": null,
|
|
500
|
+
"TestReportAsset": null,
|
|
501
|
+
"Articles": [],
|
|
502
|
+
"TestResults": [
|
|
503
|
+
{
|
|
504
|
+
"PatientTestResult": 0,
|
|
505
|
+
"ResultTitle": "Positive",
|
|
506
|
+
"ResultDisplay": "Positive",
|
|
507
|
+
"TreatmentPlanDescription": "",
|
|
508
|
+
"IsReturnToDashboardAllowed": false,
|
|
509
|
+
"IsOrderTestAllowed": false,
|
|
510
|
+
"IsFindTestRetailerAllowed": false
|
|
511
|
+
}
|
|
512
|
+
],
|
|
513
|
+
"GS1GTINs": null,
|
|
514
|
+
"Included": null,
|
|
515
|
+
"LOINCCodes": null,
|
|
516
|
+
"TestLOINCCodes": null,
|
|
517
|
+
"TestSNOMEDCTCodes": null,
|
|
518
|
+
"Created": "2024-12-23T18:00:48.619Z",
|
|
519
|
+
"Modified": "2026-05-13T17:07:07.048Z",
|
|
520
|
+
"CreatedByID": null,
|
|
521
|
+
"ModifiedByID": null,
|
|
522
|
+
"TenantID": null
|
|
523
|
+
},
|
|
524
|
+
"ErrorMessage": null,
|
|
525
|
+
"IsOK": true
|
|
181
526
|
}
|
|
182
527
|
```
|
|
183
528
|
|
|
184
|
-
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
### `createUploadUrl(...)`
|
|
532
|
+
|
|
533
|
+
Creates a pre-signed URL for uploading a test image.
|
|
534
|
+
|
|
535
|
+
#### Signature
|
|
185
536
|
|
|
186
537
|
```ts
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
ReportedValue: "drug store",
|
|
193
|
-
StoredValue: "drug store",
|
|
194
|
-
Score: "0",
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
});
|
|
538
|
+
safeCdx.createUploadUrl(
|
|
539
|
+
userTestResultId: string,
|
|
540
|
+
gtin: string,
|
|
541
|
+
imageType?: string
|
|
542
|
+
): Promise<APIResponse<CreateUploadUrlData>>
|
|
198
543
|
```
|
|
199
544
|
|
|
200
|
-
|
|
545
|
+
#### Parameters
|
|
546
|
+
|
|
547
|
+
| Parameter | Type | Required | Description |
|
|
548
|
+
|---|---|---:|---|
|
|
549
|
+
| `userTestResultId` | `string` | Yes | Test result identifier reused through the workflow. |
|
|
550
|
+
| `gtin` | `string` | Yes | GTIN associated with the selected test. |
|
|
551
|
+
| `imageType` | `string` | No | Image type used in upload metadata. Defaults to `"jpg"`. |
|
|
552
|
+
|
|
553
|
+
#### Usage
|
|
201
554
|
|
|
202
555
|
```ts
|
|
203
|
-
await safeCdx.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
});
|
|
556
|
+
const upload = await safeCdx.createUploadUrl(
|
|
557
|
+
userTestResultId,
|
|
558
|
+
gtin,
|
|
559
|
+
"jpg"
|
|
560
|
+
);
|
|
209
561
|
```
|
|
210
562
|
|
|
211
|
-
|
|
563
|
+
#### API request sent internally
|
|
212
564
|
|
|
213
|
-
|
|
565
|
+
```json
|
|
566
|
+
{
|
|
567
|
+
"Data": {
|
|
568
|
+
"Gtin": "<GTIN>",
|
|
569
|
+
"UserTestResultId": "<USER_TEST_RESULT_ID>",
|
|
570
|
+
"Metadata": {
|
|
571
|
+
"ImageType": "jpg"
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
```
|
|
214
576
|
|
|
215
|
-
|
|
577
|
+
#### API response example
|
|
216
578
|
|
|
217
|
-
```
|
|
579
|
+
```json
|
|
218
580
|
{
|
|
219
|
-
Data:
|
|
220
|
-
|
|
221
|
-
|
|
581
|
+
"Data": {
|
|
582
|
+
"preSignedURL": "https://sf-us-west-2-lower-neural-cdx-img-uploads-quality.s3.us-west-2.amazonaws.com/healthcheck/winx-uti/860002060439_6a104bba6068dd9a213db810-1.jpg?X-Amz-Expires=1200&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=***&X-Amz-Date=20260522T123118Z&X-Amz-SignedHeaders=host&X-Amz-Signature=***",
|
|
583
|
+
"Metadata": {
|
|
584
|
+
"UploadId": "860002060439_6a104bba6068dd9a213db810-1",
|
|
585
|
+
"Type": "Rapid Test Kit Upload",
|
|
586
|
+
"File": "860002060439_6a104bba6068dd9a213db810-1.jpg"
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
"ErrorMessage": null,
|
|
590
|
+
"IsOK": true
|
|
222
591
|
}
|
|
223
592
|
```
|
|
224
593
|
|
|
225
|
-
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
### `uploadImage(...)`
|
|
597
|
+
|
|
598
|
+
Uploads an image directly to the pre-signed URL returned by `createUploadUrl(...)`. This method does not call a Safe CDX API route and does not send Health Cloud authentication headers.
|
|
599
|
+
|
|
600
|
+
#### Signature
|
|
226
601
|
|
|
227
|
-
|
|
602
|
+
```ts
|
|
603
|
+
safeCdx.uploadImage(
|
|
604
|
+
preSignedURL: string,
|
|
605
|
+
image: BodyInit,
|
|
606
|
+
contentType?: string
|
|
607
|
+
): Promise<void>
|
|
608
|
+
```
|
|
228
609
|
|
|
229
|
-
|
|
610
|
+
#### Parameters
|
|
230
611
|
|
|
231
|
-
|
|
612
|
+
| Parameter | Type | Required | Description |
|
|
613
|
+
|---|---|---:|---|
|
|
614
|
+
| `preSignedURL` | `string` | Yes | Temporary storage upload URL returned by `createUploadUrl(...)`. |
|
|
615
|
+
| `image` | `BodyInit` | Yes | Raw image body, such as a `File`, `Blob` or compatible upload body. |
|
|
616
|
+
| `contentType` | `string` | No | MIME type of the uploaded file. Defaults to `"image/jpeg"`. |
|
|
232
617
|
|
|
233
|
-
|
|
618
|
+
#### Usage
|
|
234
619
|
|
|
235
620
|
```ts
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
621
|
+
await safeCdx.uploadImage(
|
|
622
|
+
preSignedURL,
|
|
623
|
+
imageBody,
|
|
624
|
+
"image/jpeg"
|
|
625
|
+
);
|
|
240
626
|
```
|
|
241
627
|
|
|
242
|
-
|
|
628
|
+
A failed direct upload throws `SafeCDXError`.
|
|
243
629
|
|
|
244
|
-
|
|
630
|
+
---
|
|
245
631
|
|
|
246
|
-
|
|
632
|
+
### `updateCvmlStatus(...)`
|
|
633
|
+
|
|
634
|
+
Updates the CVML processing status for a captured image. This endpoint returns `SafeAPIResponse<T>` directly.
|
|
635
|
+
|
|
636
|
+
#### Signature
|
|
247
637
|
|
|
248
638
|
```ts
|
|
249
|
-
|
|
639
|
+
safeCdx.updateCvmlStatus(
|
|
640
|
+
imageOfCaptureId: string,
|
|
641
|
+
cvmlStatus: string
|
|
642
|
+
): Promise<UpdateCvmlStatusResponse>
|
|
250
643
|
```
|
|
251
644
|
|
|
252
|
-
|
|
645
|
+
`UpdateCvmlStatusResponse` is:
|
|
253
646
|
|
|
254
|
-
|
|
647
|
+
```ts
|
|
648
|
+
type UpdateCvmlStatusResponse =
|
|
649
|
+
SafeAPIResponse<EntityReferenceData>;
|
|
650
|
+
```
|
|
255
651
|
|
|
256
|
-
|
|
652
|
+
#### Parameters
|
|
257
653
|
|
|
258
|
-
|
|
654
|
+
| Parameter | Type | Required | Description |
|
|
655
|
+
|---|---|---:|---|
|
|
656
|
+
| `imageOfCaptureId` | `string` | Yes | Identifier returned in the image upload metadata. |
|
|
657
|
+
| `cvmlStatus` | `string` | Yes | CVML processing status to apply, such as `"ImageProcessing"`. |
|
|
259
658
|
|
|
260
|
-
|
|
659
|
+
#### Usage
|
|
261
660
|
|
|
262
661
|
```ts
|
|
263
|
-
const
|
|
662
|
+
const response = await safeCdx.updateCvmlStatus(
|
|
663
|
+
imageOfCaptureId,
|
|
664
|
+
"ImageProcessing"
|
|
665
|
+
);
|
|
666
|
+
```
|
|
264
667
|
|
|
265
|
-
|
|
668
|
+
#### API request sent internally
|
|
266
669
|
|
|
267
|
-
|
|
670
|
+
```json
|
|
671
|
+
{
|
|
672
|
+
"Data": {
|
|
673
|
+
"ImageOfCaptureId": "<IMAGE_OF_CAPTURE_ID>",
|
|
674
|
+
"CvmlStatus": "ImageProcessing"
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
```
|
|
268
678
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
679
|
+
#### API response example
|
|
680
|
+
|
|
681
|
+
```json
|
|
682
|
+
{
|
|
683
|
+
"success": true,
|
|
684
|
+
"data": {
|
|
685
|
+
"_id": "6a104bba6068dd9a213db810"
|
|
686
|
+
},
|
|
687
|
+
"message": "Success",
|
|
688
|
+
"code": 0
|
|
689
|
+
}
|
|
274
690
|
```
|
|
275
691
|
|
|
276
|
-
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
### `getCvmlResults(...)`
|
|
695
|
+
|
|
696
|
+
Polls CVML results for a captured image. This endpoint returns `SafeAPIResponse<T>` directly.
|
|
697
|
+
|
|
698
|
+
#### Signature
|
|
277
699
|
|
|
278
700
|
```ts
|
|
279
|
-
|
|
701
|
+
safeCdx.getCvmlResults(
|
|
702
|
+
imageOfCaptureId: string
|
|
703
|
+
): Promise<GetCvmlResultsResponse>
|
|
280
704
|
```
|
|
281
705
|
|
|
282
|
-
|
|
706
|
+
`GetCvmlResultsResponse` is:
|
|
283
707
|
|
|
284
708
|
```ts
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const userTestResultId = profile.Data.ID;
|
|
288
|
-
const gtin = profile.Data.DiagnosticProfile.TestInfo.gtin;
|
|
709
|
+
type GetCvmlResultsResponse =
|
|
710
|
+
SafeAPIResponse<CvmlResultsData>;
|
|
289
711
|
```
|
|
290
712
|
|
|
291
|
-
|
|
713
|
+
#### Parameters
|
|
714
|
+
|
|
715
|
+
| Parameter | Type | Required | Description |
|
|
716
|
+
|---|---|---:|---|
|
|
717
|
+
| `imageOfCaptureId` | `string` | Yes | Identifier of the captured image being processed. |
|
|
718
|
+
|
|
719
|
+
#### Usage
|
|
292
720
|
|
|
293
721
|
```ts
|
|
294
|
-
const
|
|
722
|
+
const response = await safeCdx.getCvmlResults(imageOfCaptureId);
|
|
723
|
+
```
|
|
295
724
|
|
|
296
|
-
|
|
297
|
-
|
|
725
|
+
#### API response example
|
|
726
|
+
|
|
727
|
+
```json
|
|
728
|
+
{
|
|
729
|
+
"success": true,
|
|
730
|
+
"data": {
|
|
731
|
+
"userID": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
|
|
732
|
+
"status": "Started",
|
|
733
|
+
"cvmlStatus": "Retake",
|
|
734
|
+
"gtin": "860002060439",
|
|
735
|
+
"isCVMLResult": true,
|
|
736
|
+
"isSelfReportingPostTest": false,
|
|
737
|
+
"schemaVersion": 1,
|
|
738
|
+
"capture": {
|
|
739
|
+
"retryCount": 0,
|
|
740
|
+
"retryLimit": 0,
|
|
741
|
+
"failoverEnabled": true,
|
|
742
|
+
"lastCvmlStatus": "Retake",
|
|
743
|
+
"failoverTriggered": false
|
|
744
|
+
},
|
|
745
|
+
"questionnaireSnapshot": {
|
|
746
|
+
"isValid": []
|
|
747
|
+
},
|
|
748
|
+
"resumed": false,
|
|
749
|
+
"imageOfCaptureResult": {
|
|
750
|
+
"parsed": {
|
|
751
|
+
"outcome": "Retake",
|
|
752
|
+
"responseCode": "Err4",
|
|
753
|
+
"responseMessage": "ml_result_code_error_04_msg",
|
|
754
|
+
"responseTitle": "ml_result_code_error_04_title"
|
|
755
|
+
}
|
|
756
|
+
},
|
|
757
|
+
"failoverEnabled": true,
|
|
758
|
+
"failoverTriggered": false,
|
|
759
|
+
"resumable": false,
|
|
760
|
+
"showValidity": false,
|
|
761
|
+
"showError": true,
|
|
762
|
+
"_id": "6a104bba6068dd9a213db810"
|
|
763
|
+
},
|
|
764
|
+
"message": "Success",
|
|
765
|
+
"code": 0
|
|
766
|
+
}
|
|
298
767
|
```
|
|
299
768
|
|
|
300
|
-
|
|
769
|
+
---
|
|
770
|
+
|
|
771
|
+
### `getPendingResults(...)`
|
|
772
|
+
|
|
773
|
+
Returns pending or incomplete test results for the authenticated patient.
|
|
774
|
+
|
|
775
|
+
#### Signature
|
|
301
776
|
|
|
302
777
|
```ts
|
|
303
|
-
|
|
778
|
+
safeCdx.getPendingResults(
|
|
779
|
+
excludeStatus?: string
|
|
780
|
+
): Promise<APIResponse<GetPendingResultsData>>
|
|
304
781
|
```
|
|
305
782
|
|
|
306
|
-
|
|
783
|
+
`GetPendingResultsData` is:
|
|
307
784
|
|
|
308
785
|
```ts
|
|
309
|
-
|
|
786
|
+
type GetPendingResultsData =
|
|
787
|
+
SafeAPIResponse<TestResultSummary[]>;
|
|
310
788
|
```
|
|
311
789
|
|
|
312
|
-
|
|
790
|
+
#### Parameters
|
|
791
|
+
|
|
792
|
+
| Parameter | Type | Required | Description |
|
|
793
|
+
|---|---|---:|---|
|
|
794
|
+
| `excludeStatus` | `string` | No | Comma-separated statuses excluded from the returned results. Defaults to `"Invalid,Canceled"`. |
|
|
795
|
+
|
|
796
|
+
#### Usage
|
|
313
797
|
|
|
314
798
|
```ts
|
|
315
|
-
const
|
|
799
|
+
const pending = await safeCdx.getPendingResults();
|
|
316
800
|
```
|
|
317
801
|
|
|
318
|
-
### 7. Submit answers
|
|
319
|
-
|
|
320
802
|
```ts
|
|
321
|
-
await safeCdx.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
{
|
|
325
|
-
Analyte: "Purchase",
|
|
326
|
-
ReportedValue: "drug store",
|
|
327
|
-
StoredValue: "drug store",
|
|
328
|
-
Score: "0",
|
|
329
|
-
},
|
|
330
|
-
],
|
|
331
|
-
});
|
|
803
|
+
const pending = await safeCdx.getPendingResults(
|
|
804
|
+
"Invalid,Canceled"
|
|
805
|
+
);
|
|
332
806
|
```
|
|
333
807
|
|
|
334
|
-
|
|
808
|
+
#### API request sent internally
|
|
335
809
|
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
recommendText: "Review the result with a healthcare provider.",
|
|
352
|
-
ctaId: "2",
|
|
353
|
-
},
|
|
354
|
-
decisionResult: {
|
|
355
|
-
calculation: "Total: analyte.responses.score",
|
|
356
|
-
results: [
|
|
810
|
+
```json
|
|
811
|
+
{
|
|
812
|
+
"Data": {
|
|
813
|
+
"ExcludeStatus": "Invalid,Canceled"
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
#### API response example
|
|
819
|
+
|
|
820
|
+
```json
|
|
821
|
+
{
|
|
822
|
+
"Data": {
|
|
823
|
+
"success": true,
|
|
824
|
+
"data": [
|
|
357
825
|
{
|
|
358
|
-
|
|
359
|
-
|
|
826
|
+
"status": "Started",
|
|
827
|
+
"cvmlStatus": "Retake",
|
|
828
|
+
"gtin": "860002060439",
|
|
829
|
+
"testName": "Winx UTI Test + Treat",
|
|
830
|
+
"recordedDate": "2026-05-22T12:49:26.047Z",
|
|
831
|
+
"isCVMLBackground": false,
|
|
832
|
+
"isCVMLResult": true,
|
|
833
|
+
"isSelfReportingPostTest": false,
|
|
834
|
+
"isSelfReportingPreTest": false,
|
|
835
|
+
"viewed": false,
|
|
836
|
+
"schemaVersion": 1,
|
|
837
|
+
"capture": {
|
|
838
|
+
"retryCount": 0,
|
|
839
|
+
"retryLimit": 0,
|
|
840
|
+
"failoverEnabled": true,
|
|
841
|
+
"lastCvmlStatus": "Retake",
|
|
842
|
+
"failoverTriggered": false
|
|
843
|
+
},
|
|
844
|
+
"questionnaireSnapshot": {
|
|
845
|
+
"isValid": []
|
|
846
|
+
},
|
|
847
|
+
"resumed": false,
|
|
848
|
+
"failoverEnabled": true,
|
|
849
|
+
"failoverTriggered": false,
|
|
850
|
+
"resumable": true,
|
|
851
|
+
"resumeNext": "SelfReporting",
|
|
852
|
+
"showValidity": false,
|
|
853
|
+
"showError": true,
|
|
854
|
+
"_id": "6a104bba6068dd9a213db810"
|
|
360
855
|
},
|
|
856
|
+
{
|
|
857
|
+
"status": "Started",
|
|
858
|
+
"gtin": "810172700093",
|
|
859
|
+
"testName": "Speedy Swab COVID-19 + Flu Self-Test",
|
|
860
|
+
"recordedDate": "2026-05-22T10:11:10.181Z",
|
|
861
|
+
"isCVMLBackground": true,
|
|
862
|
+
"isCVMLResult": false,
|
|
863
|
+
"isSelfReportingPostTest": true,
|
|
864
|
+
"isSelfReportingPreTest": false,
|
|
865
|
+
"viewed": false,
|
|
866
|
+
"schemaVersion": 1,
|
|
867
|
+
"capture": {
|
|
868
|
+
"retryCount": 0,
|
|
869
|
+
"retryLimit": 0,
|
|
870
|
+
"failoverEnabled": true,
|
|
871
|
+
"failoverTriggered": false
|
|
872
|
+
},
|
|
873
|
+
"questionnaireSnapshot": {
|
|
874
|
+
"isValid": [
|
|
875
|
+
{
|
|
876
|
+
"display": "post-test",
|
|
877
|
+
"question": "Do you see a control line (C)?"
|
|
878
|
+
}
|
|
879
|
+
]
|
|
880
|
+
},
|
|
881
|
+
"resumed": false,
|
|
882
|
+
"failoverEnabled": true,
|
|
883
|
+
"failoverTriggered": false,
|
|
884
|
+
"resumable": false,
|
|
885
|
+
"showValidity": false,
|
|
886
|
+
"showError": true,
|
|
887
|
+
"_id": "6a102bbe6068dd9a213db80f"
|
|
888
|
+
}
|
|
361
889
|
],
|
|
890
|
+
"message": "Success",
|
|
891
|
+
"code": 0
|
|
362
892
|
},
|
|
363
|
-
|
|
893
|
+
"ErrorMessage": null,
|
|
894
|
+
"IsOK": true
|
|
895
|
+
}
|
|
364
896
|
```
|
|
365
897
|
|
|
366
|
-
|
|
898
|
+
---
|
|
899
|
+
|
|
900
|
+
### `getLastResults(...)`
|
|
901
|
+
|
|
902
|
+
Returns the most recent test result for the authenticated patient.
|
|
903
|
+
|
|
904
|
+
#### Signature
|
|
367
905
|
|
|
368
906
|
```ts
|
|
369
|
-
|
|
907
|
+
safeCdx.getLastResults(
|
|
908
|
+
excludeStatus?: string
|
|
909
|
+
): Promise<APIResponse<GetLastResultsData>>
|
|
910
|
+
```
|
|
370
911
|
|
|
371
|
-
|
|
372
|
-
UserTestResultId: userTestResultId,
|
|
373
|
-
});
|
|
912
|
+
`GetLastResultsData` is:
|
|
374
913
|
|
|
375
|
-
|
|
914
|
+
```ts
|
|
915
|
+
type GetLastResultsData =
|
|
916
|
+
SafeAPIResponse<TestResultDetails>;
|
|
376
917
|
```
|
|
377
918
|
|
|
378
|
-
|
|
919
|
+
#### Parameters
|
|
920
|
+
|
|
921
|
+
| Parameter | Type | Required | Description |
|
|
922
|
+
|---|---|---:|---|
|
|
923
|
+
| `excludeStatus` | `string` | No | Status excluded from the returned result. Defaults to `"Invalid"`. |
|
|
379
924
|
|
|
380
|
-
|
|
925
|
+
#### Usage
|
|
381
926
|
|
|
382
|
-
|
|
927
|
+
```ts
|
|
928
|
+
const lastResult = await safeCdx.getLastResults();
|
|
929
|
+
```
|
|
383
930
|
|
|
384
931
|
```ts
|
|
385
|
-
const
|
|
386
|
-
gtin: undefined,
|
|
387
|
-
userTestResultId: undefined,
|
|
388
|
-
preSignedURL: undefined,
|
|
389
|
-
imageOfCaptureId: undefined,
|
|
390
|
-
};
|
|
932
|
+
const lastResult = await safeCdx.getLastResults("Invalid");
|
|
391
933
|
```
|
|
392
934
|
|
|
393
|
-
|
|
935
|
+
#### API request sent internally
|
|
394
936
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
937
|
+
```json
|
|
938
|
+
{
|
|
939
|
+
"Data": {
|
|
940
|
+
"ExcludeStatus": "Invalid"
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
```
|
|
399
944
|
|
|
400
|
-
|
|
945
|
+
#### API response example
|
|
401
946
|
|
|
402
|
-
|
|
947
|
+
```json
|
|
948
|
+
{
|
|
949
|
+
"Data": {
|
|
950
|
+
"success": true,
|
|
951
|
+
"data": {
|
|
952
|
+
"cvmlUploadId": "6a104bba6068dd9a213db810",
|
|
953
|
+
"userID": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
|
|
954
|
+
"tenantID": "healthcheck",
|
|
955
|
+
"isSelfAssessment": false,
|
|
956
|
+
"status": "Started",
|
|
957
|
+
"cvmlStatus": "Retake",
|
|
958
|
+
"gtin": "860002060439",
|
|
959
|
+
"testInfoHeader": "Winx Health",
|
|
960
|
+
"testName": "Winx UTI Test + Treat",
|
|
961
|
+
"imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
|
|
962
|
+
"imageOfCaptureUploadAt": "2026-05-22T12:31:18.333Z",
|
|
963
|
+
"imageOfCaptureResults": [
|
|
964
|
+
{
|
|
965
|
+
"imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
|
|
966
|
+
"uploadAt": "2026-05-22T12:31:18.333Z",
|
|
967
|
+
"cvmlStatus": "Retake",
|
|
968
|
+
"parsed": {
|
|
969
|
+
"outcome": "Retake",
|
|
970
|
+
"responseCode": "Err4",
|
|
971
|
+
"responseMessage": "ml_result_code_error_04_msg",
|
|
972
|
+
"responseTitle": "ml_result_code_error_04_title"
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
],
|
|
976
|
+
"resultPDF": "archive/2026/05/22/12/48/7ecfd730-c4c3-4cd6-8b12-4210b9a2db28.pdf",
|
|
977
|
+
"result": [
|
|
978
|
+
{
|
|
979
|
+
"analyte": "Purchase",
|
|
980
|
+
"reportedValue": "drug store",
|
|
981
|
+
"storedValue": "drug store",
|
|
982
|
+
"score": "0"
|
|
983
|
+
}
|
|
984
|
+
],
|
|
985
|
+
"recordedDate": "2026-05-22T12:49:26.047Z",
|
|
986
|
+
"cvmlTestName": "winx-uti",
|
|
987
|
+
"isCVMLBackground": false,
|
|
988
|
+
"isCVMLResult": true,
|
|
989
|
+
"isSelfReportingPostTest": false,
|
|
990
|
+
"isSelfReportingPreTest": false,
|
|
991
|
+
"viewed": false,
|
|
992
|
+
"finalized": false,
|
|
993
|
+
"schemaVersion": 1,
|
|
994
|
+
"capture": {
|
|
995
|
+
"retryCount": 1,
|
|
996
|
+
"retryLimit": 2,
|
|
997
|
+
"failoverEnabled": true,
|
|
998
|
+
"lastCvmlStatus": "Retake",
|
|
999
|
+
"failoverTriggered": false
|
|
1000
|
+
},
|
|
1001
|
+
"questionnaireSnapshot": {
|
|
1002
|
+
"isValid": [],
|
|
1003
|
+
"analyte": [
|
|
1004
|
+
{
|
|
1005
|
+
"_id": "1",
|
|
1006
|
+
"displayOrder": 1,
|
|
1007
|
+
"display": "CVML",
|
|
1008
|
+
"name": "Leukocyte",
|
|
1009
|
+
"question": "What is the Leukocyte reading?",
|
|
1010
|
+
"image": "WinxLeuk.png",
|
|
1011
|
+
"cvmlOrder": 1,
|
|
1012
|
+
"responses": [
|
|
1013
|
+
{
|
|
1014
|
+
"value": "Negative -",
|
|
1015
|
+
"displayOrder": 1,
|
|
1016
|
+
"storedValue": "Neg",
|
|
1017
|
+
"cvmlValue": "Neg",
|
|
1018
|
+
"score": "0"
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
"value": "Trace 15+-",
|
|
1022
|
+
"displayOrder": 2,
|
|
1023
|
+
"storedValue": "15+-",
|
|
1024
|
+
"cvmlValue": "15+-",
|
|
1025
|
+
"score": "3"
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
"value": "Positive 70+",
|
|
1029
|
+
"displayOrder": 3,
|
|
1030
|
+
"storedValue": "70+",
|
|
1031
|
+
"cvmlValue": "70+",
|
|
1032
|
+
"score": "4"
|
|
1033
|
+
}
|
|
1034
|
+
]
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
"_id": "2",
|
|
1038
|
+
"displayOrder": 2,
|
|
1039
|
+
"display": "CVML",
|
|
1040
|
+
"name": "Nitrite",
|
|
1041
|
+
"question": "What is the Nitrite reading?",
|
|
1042
|
+
"image": "WinxNitr.png",
|
|
1043
|
+
"cvmlOrder": 2,
|
|
1044
|
+
"responses": [
|
|
1045
|
+
{
|
|
1046
|
+
"value": "Negative",
|
|
1047
|
+
"displayOrder": 1,
|
|
1048
|
+
"storedValue": "Neg",
|
|
1049
|
+
"cvmlValue": "Neg",
|
|
1050
|
+
"score": "0"
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
"value": "Positive Low+",
|
|
1054
|
+
"displayOrder": 2,
|
|
1055
|
+
"storedValue": "Low+",
|
|
1056
|
+
"cvmlValue": "Low+",
|
|
1057
|
+
"score": "2"
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
"value": "Positive High++",
|
|
1061
|
+
"displayOrder": 3,
|
|
1062
|
+
"storedValue": "High++",
|
|
1063
|
+
"cvmlValue": "High++",
|
|
1064
|
+
"score": "2"
|
|
1065
|
+
}
|
|
1066
|
+
]
|
|
1067
|
+
}
|
|
1068
|
+
]
|
|
1069
|
+
},
|
|
1070
|
+
"resumed": false,
|
|
1071
|
+
"language": "en",
|
|
1072
|
+
"labVendor": "Winx Health",
|
|
1073
|
+
"labTestType": "Home Test (Rapid)",
|
|
1074
|
+
"externalId": "test-user@example.com",
|
|
1075
|
+
"positiveAnalytes": [],
|
|
1076
|
+
"_id": "6a104bba6068dd9a213db810",
|
|
1077
|
+
"modifier": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
|
|
1078
|
+
"created": "2026-05-22T12:27:38.731Z",
|
|
1079
|
+
"modified": "2026-05-22T12:49:26.047Z"
|
|
1080
|
+
},
|
|
1081
|
+
"message": "Success",
|
|
1082
|
+
"code": 0
|
|
1083
|
+
},
|
|
1084
|
+
"ErrorMessage": null,
|
|
1085
|
+
"IsOK": true
|
|
1086
|
+
}
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
---
|
|
1090
|
+
|
|
1091
|
+
### `getTestHistory(...)`
|
|
403
1092
|
|
|
404
|
-
|
|
1093
|
+
Returns test result history for the authenticated patient.
|
|
1094
|
+
|
|
1095
|
+
#### Signature
|
|
405
1096
|
|
|
406
1097
|
```ts
|
|
407
|
-
|
|
1098
|
+
safeCdx.getTestHistory(
|
|
1099
|
+
excludeStatus?: string
|
|
1100
|
+
): Promise<APIResponse<GetTestHistoryData>>
|
|
408
1101
|
```
|
|
409
1102
|
|
|
410
|
-
|
|
1103
|
+
`GetTestHistoryData` is:
|
|
1104
|
+
|
|
1105
|
+
```ts
|
|
1106
|
+
type GetTestHistoryData =
|
|
1107
|
+
SafeAPIResponse<TestResultSummary[]>;
|
|
1108
|
+
```
|
|
411
1109
|
|
|
412
|
-
|
|
1110
|
+
#### Parameters
|
|
413
1111
|
|
|
1112
|
+
| Parameter | Type | Required | Description |
|
|
1113
|
+
|---|---|---:|---|
|
|
1114
|
+
| `excludeStatus` | `string` | No | Status excluded from the returned history. Defaults to `"Invalid"`. |
|
|
414
1115
|
|
|
415
|
-
|
|
1116
|
+
#### Usage
|
|
416
1117
|
|
|
417
1118
|
```ts
|
|
418
|
-
const
|
|
1119
|
+
const history = await safeCdx.getTestHistory();
|
|
419
1120
|
```
|
|
420
1121
|
|
|
421
|
-
|
|
1122
|
+
```ts
|
|
1123
|
+
const history = await safeCdx.getTestHistory("Invalid");
|
|
1124
|
+
```
|
|
422
1125
|
|
|
423
|
-
|
|
1126
|
+
#### API request sent internally
|
|
424
1127
|
|
|
425
|
-
```
|
|
426
|
-
|
|
1128
|
+
```json
|
|
1129
|
+
{
|
|
1130
|
+
"Data": {
|
|
1131
|
+
"ExcludeStatus": "Invalid"
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
427
1134
|
```
|
|
428
1135
|
|
|
429
|
-
|
|
1136
|
+
#### API response example
|
|
430
1137
|
|
|
431
|
-
|
|
1138
|
+
```json
|
|
1139
|
+
{
|
|
1140
|
+
"Data": {
|
|
1141
|
+
"success": true,
|
|
1142
|
+
"data": [
|
|
1143
|
+
{
|
|
1144
|
+
"status": "Started",
|
|
1145
|
+
"cvmlStatus": "Retake",
|
|
1146
|
+
"testInfoHeader": "Winx Health",
|
|
1147
|
+
"testName": "Winx UTI Test + Treat",
|
|
1148
|
+
"recordedDate": "2026-05-22T12:49:26.047Z",
|
|
1149
|
+
"viewed": false,
|
|
1150
|
+
"schemaVersion": 1,
|
|
1151
|
+
"overallResult": "Unknown",
|
|
1152
|
+
"positiveAnalytes": [],
|
|
1153
|
+
"_id": "6a104bba6068dd9a213db810",
|
|
1154
|
+
"created": "2026-05-22T12:27:38.731Z"
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
"status": "Started",
|
|
1158
|
+
"testInfoHeader": "Biolabs International",
|
|
1159
|
+
"testName": "Speedy Swab COVID-19 + Flu Self-Test",
|
|
1160
|
+
"recordedDate": "2026-05-22T10:11:10.181Z",
|
|
1161
|
+
"viewed": false,
|
|
1162
|
+
"schemaVersion": 1,
|
|
1163
|
+
"overallResult": "Unknown",
|
|
1164
|
+
"positiveAnalytes": [],
|
|
1165
|
+
"_id": "6a102bbe6068dd9a213db80f",
|
|
1166
|
+
"created": "2026-05-22T10:11:10.181Z"
|
|
1167
|
+
}
|
|
1168
|
+
],
|
|
1169
|
+
"message": "Success",
|
|
1170
|
+
"code": 0
|
|
1171
|
+
},
|
|
1172
|
+
"ErrorMessage": null,
|
|
1173
|
+
"IsOK": true
|
|
1174
|
+
}
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
---
|
|
1178
|
+
|
|
1179
|
+
### `getResultDetails(...)`
|
|
1180
|
+
|
|
1181
|
+
Returns detailed data for a specific test attempt. This endpoint returns `SafeAPIResponse<T>` directly.
|
|
1182
|
+
|
|
1183
|
+
#### Signature
|
|
432
1184
|
|
|
433
1185
|
```ts
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
1186
|
+
safeCdx.getResultDetails(
|
|
1187
|
+
userTestResultId: string
|
|
1188
|
+
): Promise<GetResultDetailsResponse>
|
|
437
1189
|
```
|
|
438
1190
|
|
|
439
|
-
|
|
1191
|
+
`GetResultDetailsResponse` is:
|
|
440
1192
|
|
|
441
1193
|
```ts
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
gtin,
|
|
445
|
-
"jpg"
|
|
446
|
-
);
|
|
1194
|
+
type GetResultDetailsResponse =
|
|
1195
|
+
SafeAPIResponse<TestResultDetails>;
|
|
447
1196
|
```
|
|
448
1197
|
|
|
449
|
-
|
|
1198
|
+
#### Parameters
|
|
450
1199
|
|
|
451
|
-
|
|
1200
|
+
| Parameter | Type | Required | Description |
|
|
1201
|
+
|---|---|---:|---|
|
|
1202
|
+
| `userTestResultId` | `string` | Yes | Identifier of the test attempt. |
|
|
452
1203
|
|
|
453
|
-
|
|
1204
|
+
#### Usage
|
|
454
1205
|
|
|
455
1206
|
```ts
|
|
456
|
-
await safeCdx.
|
|
1207
|
+
const details = await safeCdx.getResultDetails(userTestResultId);
|
|
457
1208
|
```
|
|
458
1209
|
|
|
459
|
-
|
|
1210
|
+
#### API request sent internally
|
|
460
1211
|
|
|
461
|
-
|
|
1212
|
+
```json
|
|
1213
|
+
{
|
|
1214
|
+
"Data": {
|
|
1215
|
+
"UserTestResultId": "<USER_TEST_RESULT_ID>"
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
```
|
|
462
1219
|
|
|
463
|
-
|
|
1220
|
+
#### API response example
|
|
464
1221
|
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
"
|
|
469
|
-
|
|
1222
|
+
```json
|
|
1223
|
+
{
|
|
1224
|
+
"success": true,
|
|
1225
|
+
"data": {
|
|
1226
|
+
"cvmlUploadId": "6a104bba6068dd9a213db810",
|
|
1227
|
+
"userID": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
|
|
1228
|
+
"tenantID": "healthcheck",
|
|
1229
|
+
"isSelfAssessment": false,
|
|
1230
|
+
"status": "Started",
|
|
1231
|
+
"cvmlStatus": "Retake",
|
|
1232
|
+
"gtin": "860002060439",
|
|
1233
|
+
"testInfoHeader": "Winx Health",
|
|
1234
|
+
"testName": "Winx UTI Test + Treat",
|
|
1235
|
+
"imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
|
|
1236
|
+
"imageOfCaptureUploadAt": "2026-05-22T12:31:18.333Z",
|
|
1237
|
+
"imageOfCaptureResults": [
|
|
1238
|
+
{
|
|
1239
|
+
"imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
|
|
1240
|
+
"uploadAt": "2026-05-22T12:31:18.333Z",
|
|
1241
|
+
"cvmlStatus": "Retake",
|
|
1242
|
+
"parsed": {
|
|
1243
|
+
"outcome": "Retake",
|
|
1244
|
+
"responseCode": "Err4",
|
|
1245
|
+
"responseMessage": "ml_result_code_error_04_msg",
|
|
1246
|
+
"responseTitle": "ml_result_code_error_04_title"
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
],
|
|
1250
|
+
"result": [],
|
|
1251
|
+
"recordedDate": "2026-05-22T12:27:38.731Z",
|
|
1252
|
+
"cvmlTestName": "winx-uti",
|
|
1253
|
+
"isCVMLBackground": false,
|
|
1254
|
+
"isCVMLResult": true,
|
|
1255
|
+
"isSelfReportingPostTest": false,
|
|
1256
|
+
"isSelfReportingPreTest": false,
|
|
1257
|
+
"viewed": false,
|
|
1258
|
+
"finalized": false,
|
|
1259
|
+
"schemaVersion": 1,
|
|
1260
|
+
"capture": {
|
|
1261
|
+
"retryCount": 1,
|
|
1262
|
+
"retryLimit": 2,
|
|
1263
|
+
"failoverEnabled": true,
|
|
1264
|
+
"lastCvmlStatus": "Retake",
|
|
1265
|
+
"failoverTriggered": false
|
|
1266
|
+
},
|
|
1267
|
+
"questionnaireSnapshot": {
|
|
1268
|
+
"isValid": [],
|
|
1269
|
+
"analyte": [
|
|
1270
|
+
{
|
|
1271
|
+
"_id": "1",
|
|
1272
|
+
"displayOrder": 1,
|
|
1273
|
+
"display": "CVML",
|
|
1274
|
+
"name": "Leukocyte",
|
|
1275
|
+
"question": "What is the Leukocyte reading?",
|
|
1276
|
+
"image": "WinxLeuk.png",
|
|
1277
|
+
"cvmlOrder": 1,
|
|
1278
|
+
"responses": [
|
|
1279
|
+
{
|
|
1280
|
+
"value": "Negative -",
|
|
1281
|
+
"displayOrder": 1,
|
|
1282
|
+
"storedValue": "Neg",
|
|
1283
|
+
"cvmlValue": "Neg",
|
|
1284
|
+
"score": "0"
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
"value": "Trace 15+-",
|
|
1288
|
+
"displayOrder": 2,
|
|
1289
|
+
"storedValue": "15+-",
|
|
1290
|
+
"cvmlValue": "15+-",
|
|
1291
|
+
"score": "3"
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
"value": "Positive 70+",
|
|
1295
|
+
"displayOrder": 3,
|
|
1296
|
+
"storedValue": "70+",
|
|
1297
|
+
"cvmlValue": "70+",
|
|
1298
|
+
"score": "4"
|
|
1299
|
+
}
|
|
1300
|
+
]
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
"_id": "2",
|
|
1304
|
+
"displayOrder": 2,
|
|
1305
|
+
"display": "CVML",
|
|
1306
|
+
"name": "Nitrite",
|
|
1307
|
+
"question": "What is the Nitrite reading?",
|
|
1308
|
+
"image": "WinxNitr.png",
|
|
1309
|
+
"cvmlOrder": 2,
|
|
1310
|
+
"responses": [
|
|
1311
|
+
{
|
|
1312
|
+
"value": "Negative",
|
|
1313
|
+
"displayOrder": 1,
|
|
1314
|
+
"storedValue": "Neg",
|
|
1315
|
+
"cvmlValue": "Neg",
|
|
1316
|
+
"score": "0"
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
"value": "Positive Low+",
|
|
1320
|
+
"displayOrder": 2,
|
|
1321
|
+
"storedValue": "Low+",
|
|
1322
|
+
"cvmlValue": "Low+",
|
|
1323
|
+
"score": "2"
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
"value": "Positive High++",
|
|
1327
|
+
"displayOrder": 3,
|
|
1328
|
+
"storedValue": "High++",
|
|
1329
|
+
"cvmlValue": "High++",
|
|
1330
|
+
"score": "2"
|
|
1331
|
+
}
|
|
1332
|
+
]
|
|
1333
|
+
}
|
|
1334
|
+
]
|
|
1335
|
+
},
|
|
1336
|
+
"resumed": false,
|
|
1337
|
+
"language": "en",
|
|
1338
|
+
"labVendor": "Winx Health",
|
|
1339
|
+
"labTestType": "Home Test (Rapid)",
|
|
1340
|
+
"statusResultsText": "",
|
|
1341
|
+
"externalId": "test-user@example.com",
|
|
1342
|
+
"overallResult": "Unknown",
|
|
1343
|
+
"positiveAnalytes": [],
|
|
1344
|
+
"_id": "6a104bba6068dd9a213db810",
|
|
1345
|
+
"modifier": "system",
|
|
1346
|
+
"created": "2026-05-22T12:27:38.731Z",
|
|
1347
|
+
"modified": "2026-05-22T12:33:17.308Z"
|
|
1348
|
+
},
|
|
1349
|
+
"message": "Success",
|
|
1350
|
+
"code": 0
|
|
1351
|
+
}
|
|
470
1352
|
```
|
|
471
1353
|
|
|
472
|
-
|
|
1354
|
+
---
|
|
1355
|
+
|
|
1356
|
+
### `getResultPdf(...)`
|
|
473
1357
|
|
|
474
|
-
|
|
1358
|
+
Requests the PDF result for a specific test attempt.
|
|
475
1359
|
|
|
476
|
-
|
|
1360
|
+
#### Signature
|
|
477
1361
|
|
|
478
1362
|
```ts
|
|
479
|
-
|
|
1363
|
+
safeCdx.getResultPdf(userTestResultId: string): Promise<APIResponse<GetResultPdfData>>
|
|
480
1364
|
```
|
|
481
1365
|
|
|
482
|
-
|
|
1366
|
+
`GetResultPdfData` is:
|
|
483
1367
|
|
|
484
|
-
|
|
1368
|
+
```ts
|
|
1369
|
+
type GetResultPdfData = SafeAPIResponse<string>;
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
#### Parameters
|
|
485
1373
|
|
|
486
|
-
|
|
1374
|
+
| Parameter | Type | Required | Description |
|
|
1375
|
+
|---|---|---:|---|
|
|
1376
|
+
| `userTestResultId` | `string` | Yes | Identifier of the test attempt. |
|
|
1377
|
+
|
|
1378
|
+
#### Usage
|
|
487
1379
|
|
|
488
1380
|
```ts
|
|
489
|
-
const
|
|
1381
|
+
const pdf = await safeCdx.getResultPdf(userTestResultId);
|
|
490
1382
|
```
|
|
491
1383
|
|
|
492
|
-
|
|
1384
|
+
#### API request sent internally
|
|
1385
|
+
|
|
1386
|
+
```json
|
|
1387
|
+
{
|
|
1388
|
+
"Data": {
|
|
1389
|
+
"UserTestResultId": "<USER_TEST_RESULT_ID>"
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
```
|
|
1393
|
+
|
|
1394
|
+
#### API response example
|
|
1395
|
+
|
|
1396
|
+
```json
|
|
1397
|
+
{
|
|
1398
|
+
"Data": {
|
|
1399
|
+
"success": true,
|
|
1400
|
+
"data": "https://safe-manual-test-results-us-west-2-quality-speed.s3.us-west-2.amazonaws.com/archive/2026/05/22/12/48/7ecfd730-c4c3-4cd6-8b12-4210b9a2db28.pdf?X-Amz-Expires=900&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=***&X-Amz-Date=20260522T124843Z&X-Amz-SignedHeaders=host&X-Amz-Signature=***",
|
|
1401
|
+
"message": "Success",
|
|
1402
|
+
"code": 0
|
|
1403
|
+
},
|
|
1404
|
+
"ErrorMessage": null,
|
|
1405
|
+
"IsOK": true
|
|
1406
|
+
}
|
|
1407
|
+
```
|
|
493
1408
|
|
|
494
|
-
|
|
1409
|
+
---
|
|
1410
|
+
|
|
1411
|
+
### `getImageCaptureUrl(...)`
|
|
1412
|
+
|
|
1413
|
+
Requests image capture data for a specific test attempt. This endpoint returns `SafeAPIResponse<T>` directly.
|
|
1414
|
+
|
|
1415
|
+
#### Signature
|
|
495
1416
|
|
|
496
1417
|
```ts
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
1418
|
+
safeCdx.getImageCaptureUrl(
|
|
1419
|
+
userTestResultId: string
|
|
1420
|
+
): Promise<GetImageCaptureUrlResponse>
|
|
500
1421
|
```
|
|
501
1422
|
|
|
502
|
-
|
|
1423
|
+
`GetImageCaptureUrlResponse` is:
|
|
503
1424
|
|
|
504
1425
|
```ts
|
|
505
|
-
|
|
1426
|
+
type GetImageCaptureUrlResponse =
|
|
1427
|
+
SafeAPIResponse<string>;
|
|
506
1428
|
```
|
|
507
1429
|
|
|
508
|
-
|
|
1430
|
+
#### Parameters
|
|
1431
|
+
|
|
1432
|
+
| Parameter | Type | Required | Description |
|
|
1433
|
+
|---|---|---:|---|
|
|
1434
|
+
| `userTestResultId` | `string` | Yes | Identifier of the test attempt. |
|
|
509
1435
|
|
|
510
|
-
|
|
1436
|
+
#### Usage
|
|
511
1437
|
|
|
512
1438
|
```ts
|
|
513
|
-
const
|
|
1439
|
+
const imageUrl = await safeCdx.getImageCaptureUrl(userTestResultId);
|
|
514
1440
|
```
|
|
515
1441
|
|
|
516
|
-
|
|
1442
|
+
#### API request sent internally
|
|
1443
|
+
|
|
1444
|
+
```json
|
|
1445
|
+
{
|
|
1446
|
+
"Data": {
|
|
1447
|
+
"UserTestResultId": "<USER_TEST_RESULT_ID>"
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
```
|
|
517
1451
|
|
|
518
|
-
|
|
1452
|
+
#### API response example
|
|
519
1453
|
|
|
520
|
-
```
|
|
521
|
-
|
|
1454
|
+
```json
|
|
1455
|
+
{
|
|
1456
|
+
"success": true,
|
|
1457
|
+
"data": "https://sf-us-west-2-lower-neural-cdx-img-uploads-quality.s3.us-west-2.amazonaws.com/healthcheck/winx-uti/860002060439_6a104bba6068dd9a213db810-1.jpg?X-Amz-Expires=900&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=***&X-Amz-Date=20260522T125017Z&X-Amz-SignedHeaders=host&X-Amz-Signature=***",
|
|
1458
|
+
"message": "Success",
|
|
1459
|
+
"code": 0
|
|
1460
|
+
}
|
|
522
1461
|
```
|
|
523
1462
|
|
|
524
|
-
|
|
1463
|
+
---
|
|
1464
|
+
|
|
1465
|
+
### `resumeFlow(...)`
|
|
525
1466
|
|
|
526
|
-
|
|
1467
|
+
Marks a test attempt as resumed or not resumed.
|
|
527
1468
|
|
|
528
|
-
|
|
1469
|
+
#### Signature
|
|
529
1470
|
|
|
530
1471
|
```ts
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
1472
|
+
safeCdx.resumeFlow(
|
|
1473
|
+
userTestResultId: string,
|
|
1474
|
+
resumed?: boolean
|
|
1475
|
+
): Promise<APIResponse<ResumeFlowData>>
|
|
534
1476
|
```
|
|
535
1477
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
### getImageCaptureUrl
|
|
1478
|
+
`ResumeFlowData` is:
|
|
539
1479
|
|
|
540
1480
|
```ts
|
|
541
|
-
|
|
1481
|
+
type ResumeFlowData =
|
|
1482
|
+
SafeAPIResponse<ResumeFlowResult>;
|
|
542
1483
|
```
|
|
543
1484
|
|
|
544
|
-
|
|
1485
|
+
#### Parameters
|
|
545
1486
|
|
|
546
|
-
|
|
1487
|
+
| Parameter | Type | Required | Description |
|
|
1488
|
+
|---|---|---:|---|
|
|
1489
|
+
| `userTestResultId` | `string` | Yes | Identifier of the test attempt. |
|
|
1490
|
+
| `resumed` | `boolean` | No | Resume state sent to the API. Defaults to `true`. |
|
|
547
1491
|
|
|
548
|
-
|
|
1492
|
+
#### Usage
|
|
549
1493
|
|
|
550
1494
|
```ts
|
|
551
|
-
const
|
|
1495
|
+
const response = await safeCdx.resumeFlow(userTestResultId);
|
|
552
1496
|
```
|
|
553
1497
|
|
|
554
|
-
|
|
1498
|
+
```ts
|
|
1499
|
+
const response = await safeCdx.resumeFlow(
|
|
1500
|
+
userTestResultId,
|
|
1501
|
+
true
|
|
1502
|
+
);
|
|
1503
|
+
```
|
|
1504
|
+
|
|
1505
|
+
#### API request sent internally
|
|
1506
|
+
|
|
1507
|
+
```json
|
|
1508
|
+
{
|
|
1509
|
+
"Data": {
|
|
1510
|
+
"UserTestResultId": "<USER_TEST_RESULT_ID>",
|
|
1511
|
+
"Resumed": true
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
```
|
|
555
1515
|
|
|
556
|
-
|
|
1516
|
+
#### API response example
|
|
1517
|
+
|
|
1518
|
+
```json
|
|
1519
|
+
{
|
|
1520
|
+
"Data": {
|
|
1521
|
+
"success": true,
|
|
1522
|
+
"data": {
|
|
1523
|
+
"cvmlStatus": "Retake",
|
|
1524
|
+
"reportType": "SELF",
|
|
1525
|
+
"resumed": true,
|
|
1526
|
+
"failoverTriggered": false,
|
|
1527
|
+
"showValidity": false,
|
|
1528
|
+
"_id": "6a02f6da6068dd9a213db7cb"
|
|
1529
|
+
},
|
|
1530
|
+
"message": "Success",
|
|
1531
|
+
"code": 0
|
|
1532
|
+
},
|
|
1533
|
+
"ErrorMessage": null,
|
|
1534
|
+
"IsOK": true
|
|
1535
|
+
}
|
|
1536
|
+
```
|
|
1537
|
+
|
|
1538
|
+
---
|
|
1539
|
+
|
|
1540
|
+
### `submitAnswers(...)`
|
|
1541
|
+
|
|
1542
|
+
Submits analyte answers for a test attempt. This method accepts a structured object because the request contains a collection of answer entries.
|
|
1543
|
+
|
|
1544
|
+
#### Signature
|
|
557
1545
|
|
|
558
1546
|
```ts
|
|
559
|
-
|
|
1547
|
+
safeCdx.submitAnswers(
|
|
1548
|
+
submission: SubmitAnswersRequest
|
|
1549
|
+
): Promise<APIResponse<SubmitAnswersData>>
|
|
560
1550
|
```
|
|
561
1551
|
|
|
562
|
-
|
|
1552
|
+
`SubmitAnswersData` is:
|
|
563
1553
|
|
|
564
1554
|
```ts
|
|
565
|
-
|
|
1555
|
+
type SubmitAnswersData =
|
|
1556
|
+
SafeAPIResponse<EntityReferenceData>;
|
|
566
1557
|
```
|
|
567
1558
|
|
|
568
|
-
|
|
1559
|
+
#### Parameters
|
|
1560
|
+
|
|
1561
|
+
| Parameter | Type | Required | Description |
|
|
1562
|
+
|---|---|---:|---|
|
|
1563
|
+
| `submission.UserTestResultId` | `string` | Yes | Identifier of the test attempt. |
|
|
1564
|
+
| `submission.Result` | `AnswerResult[]` | Yes | Answer entries submitted for the test attempt. |
|
|
569
1565
|
|
|
570
|
-
|
|
1566
|
+
#### Usage
|
|
571
1567
|
|
|
572
1568
|
```ts
|
|
573
|
-
const
|
|
1569
|
+
const response = await safeCdx.submitAnswers({
|
|
1570
|
+
UserTestResultId: userTestResultId,
|
|
1571
|
+
Result: [
|
|
1572
|
+
{
|
|
1573
|
+
Analyte: "Purchase",
|
|
1574
|
+
ReportedValue: "drug store",
|
|
1575
|
+
StoredValue: "drug store",
|
|
1576
|
+
Score: "0"
|
|
1577
|
+
}
|
|
1578
|
+
]
|
|
1579
|
+
});
|
|
574
1580
|
```
|
|
575
1581
|
|
|
576
|
-
|
|
1582
|
+
#### API request sent internally
|
|
577
1583
|
|
|
578
|
-
```
|
|
579
|
-
|
|
1584
|
+
```json
|
|
1585
|
+
{
|
|
1586
|
+
"Data": {
|
|
1587
|
+
"UserTestResultId": "<USER_TEST_RESULT_ID>",
|
|
1588
|
+
"Result": [
|
|
1589
|
+
{
|
|
1590
|
+
"Analyte": "Purchase",
|
|
1591
|
+
"ReportedValue": "drug store",
|
|
1592
|
+
"StoredValue": "drug store",
|
|
1593
|
+
"Score": "0"
|
|
1594
|
+
}
|
|
1595
|
+
]
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
```
|
|
1599
|
+
|
|
1600
|
+
#### API response example
|
|
1601
|
+
|
|
1602
|
+
```json
|
|
1603
|
+
{
|
|
1604
|
+
"Data": {
|
|
1605
|
+
"success": true,
|
|
1606
|
+
"data": {
|
|
1607
|
+
"_id": "6a104bba6068dd9a213db810"
|
|
1608
|
+
},
|
|
1609
|
+
"message": "Success",
|
|
1610
|
+
"code": 0
|
|
1611
|
+
},
|
|
1612
|
+
"ErrorMessage": null,
|
|
1613
|
+
"IsOK": true
|
|
1614
|
+
}
|
|
580
1615
|
```
|
|
581
1616
|
|
|
582
|
-
|
|
1617
|
+
---
|
|
1618
|
+
|
|
1619
|
+
### `finalizeTest(...)`
|
|
583
1620
|
|
|
584
|
-
|
|
1621
|
+
Finalizes the test attempt identified by `userTestResultId`.
|
|
585
1622
|
|
|
586
|
-
|
|
1623
|
+
#### Signature
|
|
587
1624
|
|
|
588
1625
|
```ts
|
|
589
|
-
|
|
1626
|
+
safeCdx.finalizeTest(userTestResultId: string): Promise<APIResponse<FinalizeTestData>>
|
|
590
1627
|
```
|
|
591
1628
|
|
|
592
|
-
|
|
1629
|
+
#### Parameters
|
|
1630
|
+
|
|
1631
|
+
| Parameter | Type | Required | Description |
|
|
1632
|
+
|---|---|---:|---|
|
|
1633
|
+
| `userTestResultId` | `string` | Yes | Identifier of the test attempt to finalize. |
|
|
593
1634
|
|
|
594
|
-
|
|
1635
|
+
#### Usage
|
|
1636
|
+
|
|
1637
|
+
```ts
|
|
1638
|
+
const response = await safeCdx.finalizeTest(userTestResultId);
|
|
1639
|
+
```
|
|
595
1640
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
1641
|
+
#### API request sent internally
|
|
1642
|
+
|
|
1643
|
+
```json
|
|
1644
|
+
{
|
|
1645
|
+
"Data": {
|
|
1646
|
+
"UserTestResultId": "<USER_TEST_RESULT_ID>"
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
```
|
|
600
1650
|
|
|
601
1651
|
|
|
602
1652
|
## Notes
|
|
603
1653
|
|
|
604
|
-
-
|
|
605
|
-
- The connector uses `HCLoginClient` for authenticated
|
|
1654
|
+
- `HCSafeCDXClient` documents the authenticated patient workflow supported by the current client.
|
|
1655
|
+
- The connector does not perform login; it uses `HCLoginClient` for authenticated patient headers.
|
|
1656
|
+
- Safe CDX uses its own service URL derived from `loginClient.getEnvironment()`.
|
|
606
1657
|
- The connector does not use `loginClient.getBaseUrl()` for Safe CDX API routes.
|
|
607
|
-
- Safe CDX routes use the Safe CDX environment host from `SafeCDXConfig`.
|
|
608
|
-
- Standard Safe CDX API methods use the shared `HttpClient`.
|
|
609
1658
|
- POST payloads are wrapped internally as `{ Data: payload }`.
|
|
610
|
-
-
|
|
611
|
-
-
|
|
612
|
-
-
|
|
613
|
-
- Pre-signed URLs are time-bound and should not be logged in production.
|
|
1659
|
+
- Patient tenant and identity context are supplied by the backend from the authenticated request.
|
|
1660
|
+
- API responses are returned in their endpoint-defined shape without being unwrapped or transformed by `HCSafeCDXClient`.
|
|
1661
|
+
- Direct image upload uses the pre-signed URL and sends the raw file body without Health Cloud authorization headers.
|