@healthcloudai/hc-safe-cdx 0.0.1 → 0.1.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 CHANGED
@@ -1,3 +1,561 @@
1
- # Healthcheck Safe CDX
1
+ # Healthcheck Safe CDX Connector
2
2
 
3
- Healthcheck Safe CDX connector placeholder.
3
+ Safe CDX connector provides a thin TypeScript wrapper around Safe CDX API routes.
4
+
5
+ The connector is designed to work with the shared `HttpClient` from `@healthcloudai/hc-http`, the same way as other Health Cloud connector modules. It does not implement its own transport layer for standard API calls.
6
+
7
+ Standard Safe CDX API calls are executed through the injected `HttpClient`.
8
+
9
+ Image upload is handled separately because the upload target is a pre-signed S3 URL, not a Safe CDX API route.
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm install @healthcloudai/hc-safe-cdx @healthcloudai/hc-http
15
+ ```
16
+
17
+ ## Import
18
+
19
+ ```ts
20
+ import { FetchClient } from "@healthcloudai/hc-http";
21
+ import { SafeCDXClient } from "@healthcloudai/hc-safe-cdx";
22
+ ```
23
+
24
+ ## Basic Setup
25
+
26
+ ```ts
27
+ const http = new FetchClient();
28
+
29
+ const safeCdx = new SafeCDXClient(http, {
30
+ environment: "dev",
31
+ defaultLanguage: "en",
32
+ defaultImageType: "jpg",
33
+ });
34
+
35
+ safeCdx.setAccessToken(patientAccessToken);
36
+ ```
37
+
38
+ The connector requires a logged-in patient token.
39
+
40
+ The connector does not perform login. Login should be handled through the patient authentication flow first. After login, the patient token should be passed to Safe CDX by calling `setAccessToken()`.
41
+
42
+ ## How the Client Works
43
+
44
+ The client follows the same structure as the other Health Cloud connector modules:
45
+
46
+ ```ts
47
+ const http = new FetchClient();
48
+ const safeCdx = new SafeCDXClient(http, config);
49
+ ```
50
+
51
+ The shared `HttpClient` is responsible for executing HTTP requests.
52
+
53
+ The Safe CDX client is responsible for:
54
+
55
+ - building Safe CDX URLs
56
+ - resolving the environment host
57
+ - adding the authorization header
58
+ - wrapping POST request payloads into `{ Data: payload }`
59
+ - validating `ApiResponse` results where the backend returns `{ IsOK, Data, ErrorMessage }`
60
+ - exposing one SDK method per API route
61
+
62
+ The Safe CDX client does not create its own internal `fetch` helpers for standard API routes.
63
+
64
+ ## Environment Configuration
65
+
66
+ The connector supports the following environments:
67
+
68
+ ```ts
69
+ const safeCdx = new SafeCDXClient(http, {
70
+ environment: "dev",
71
+ });
72
+ ```
73
+
74
+ Available environments:
75
+
76
+ ```ts
77
+ "dev" | "uat" | "prod"
78
+ ```
79
+
80
+ Environment host mapping is handled internally by the client.
81
+
82
+ ## Authorization
83
+
84
+ Safe CDX API calls require a Bearer token.
85
+
86
+ ```ts
87
+ safeCdx.setAccessToken(patientAccessToken);
88
+ ```
89
+
90
+ The token may be passed with or without the `Bearer` prefix.
91
+
92
+ Both examples are valid:
93
+
94
+ ```ts
95
+ safeCdx.setAccessToken("eyJ...");
96
+ ```
97
+
98
+ ```ts
99
+ safeCdx.setAccessToken("Bearer eyJ...");
100
+ ```
101
+
102
+ The client normalizes the value internally and sends it as:
103
+
104
+ ```ts
105
+ Authorization: Bearer <token>
106
+ ```
107
+
108
+ If no token is set before calling an API method, the client throws a `SafeCDXError`.
109
+
110
+ ## Request Payload Wrapping
111
+
112
+ For POST requests, only the inner payload should be passed to SDK methods.
113
+
114
+ The SDK wraps the payload internally as:
115
+
116
+ ```ts
117
+ {
118
+ "Data": payload
119
+ }
120
+ ```
121
+
122
+ Correct:
123
+
124
+ ```ts
125
+ await safeCdx.scan2Ddm({
126
+ // scan payload
127
+ });
128
+ ```
129
+
130
+ Incorrect:
131
+
132
+ ```ts
133
+ await safeCdx.scan2Ddm({
134
+ Data: {
135
+ // scan payload
136
+ }
137
+ });
138
+ ```
139
+
140
+ The caller should not manually add the `Data` wrapper unless a specific method type explicitly requires it.
141
+
142
+ ## API Response Handling
143
+
144
+ Most Safe CDX endpoints return the standard backend response envelope:
145
+
146
+ ```ts
147
+ {
148
+ Data: T;
149
+ IsOK: boolean;
150
+ ErrorMessage: string | null;
151
+ }
152
+ ```
153
+
154
+ For these endpoints, the client validates `IsOK`.
155
+
156
+ If `IsOK` is `false`, the client throws `SafeCDXError`.
157
+
158
+ Some endpoints return a raw service result instead of the standard `ApiResponse` envelope. Those methods return the raw result directly.
159
+
160
+ Raw result methods include:
161
+
162
+ ```ts
163
+ updateCvmlStatus(...)
164
+ getCvmlResults(...)
165
+ getResultDetails(...)
166
+ getImageCaptureUrl(...)
167
+ ```
168
+
169
+ ## Image Upload Handling
170
+
171
+ Image upload is different from normal Safe CDX API calls.
172
+
173
+ The method `createUploadUrl(...)` returns a pre-signed S3 URL. That URL is then used to upload the image file directly.
174
+
175
+ ```ts
176
+ await safeCdx.uploadImage(preSignedURL, imageBody, "image/jpeg");
177
+ ```
178
+
179
+ This upload does not go through the shared `HttpClient`.
180
+
181
+ The reason is that the shared `FetchClient.put()` is JSON-oriented and sends request bodies with `JSON.stringify(...)`. Image upload needs to send a raw `BodyInit`, such as a `Blob`, `File`, `ArrayBuffer`, or stream-compatible body.
182
+
183
+ Authorization headers should not be sent to the pre-signed URL. The pre-signed URL already contains temporary upload authorization.
184
+
185
+ ## Typical Safe CDX Flow
186
+
187
+ A typical flow looks like this:
188
+
189
+ ```ts
190
+ const http = new FetchClient();
191
+
192
+ const safeCdx = new SafeCDXClient(http, {
193
+ environment: "dev",
194
+ defaultLanguage: "en",
195
+ defaultImageType: "jpg",
196
+ });
197
+
198
+ safeCdx.setAccessToken(patientAccessToken);
199
+ ```
200
+
201
+ ### 1. Get available test profiles
202
+
203
+ ```ts
204
+ const profiles = await safeCdx.getTestProfilesByAccount();
205
+ ```
206
+
207
+ ### 2. Resolve test profile by GTIN
208
+
209
+ ```ts
210
+ const profile = await safeCdx.getTestProfileByGTIN("850024942325");
211
+
212
+ const userTestResultId = profile.Data.ID;
213
+ const gtin = profile.Data.DiagnosticProfile.TestInfo.gtin;
214
+ ```
215
+
216
+ ### 3. Create upload URL
217
+
218
+ ```ts
219
+ const upload = await safeCdx.createUploadUrl(userTestResultId, gtin);
220
+
221
+ const preSignedURL = upload.Data.preSignedURL;
222
+ const imageOfCaptureId = upload.Data.Metadata.UploadId;
223
+ ```
224
+
225
+ ### 4. Upload image
226
+
227
+ ```ts
228
+ await safeCdx.uploadImage(preSignedURL, imageBody, "image/jpeg");
229
+ ```
230
+
231
+ ### 5. Update CVML status
232
+
233
+ ```ts
234
+ await safeCdx.updateCvmlStatus(imageOfCaptureId, "ImageProcessing");
235
+ ```
236
+
237
+ ### 6. Poll CVML results
238
+
239
+ ```ts
240
+ const cvmlResults = await safeCdx.getCvmlResults(imageOfCaptureId);
241
+ ```
242
+
243
+ ### 7. Submit answers
244
+
245
+ ```ts
246
+ await safeCdx.submitAnswers({
247
+ UserTestResultId: userTestResultId,
248
+ Result: [
249
+ {
250
+ Analyte: "Purchase",
251
+ ReportedValue: "drug store",
252
+ StoredValue: "drug store",
253
+ Score: "0",
254
+ },
255
+ ],
256
+ });
257
+ ```
258
+
259
+ ### 8. Finalize test
260
+
261
+ ```ts
262
+ await safeCdx.finalizeTest({
263
+ UserTestResultId: userTestResultId,
264
+ ctaResult: {
265
+ id: "2",
266
+ title: "Connect with a Telehealth Provider for Next Steps",
267
+ instructions: "Share your test results with a telehealth provider for next steps.",
268
+ urlText: "Connect with Telehealth",
269
+ url: "https://example.com/start",
270
+ },
271
+ indicationResult: {
272
+ id: "4",
273
+ title: "Indication",
274
+ text: "Results suggest follow-up may be needed.",
275
+ recommendTitle: "Recommendation",
276
+ recommendText: "Review the result with a healthcare provider.",
277
+ ctaId: "2",
278
+ },
279
+ decisionResult: {
280
+ calculation: "Total: analyte.responses.score",
281
+ results: [
282
+ {
283
+ indicationId: "4",
284
+ value: "4",
285
+ },
286
+ ],
287
+ },
288
+ });
289
+ ```
290
+
291
+ ### 9. Retrieve result data
292
+
293
+ ```ts
294
+ const details = await safeCdx.getResultDetails(userTestResultId);
295
+
296
+ const pdf = await safeCdx.getResultPdf({
297
+ UserTestResultId: userTestResultId,
298
+ });
299
+
300
+ const imageCaptureUrl = await safeCdx.getImageCaptureUrl(userTestResultId);
301
+ ```
302
+
303
+ ## State Management
304
+
305
+ The connector does not manage the full Safe CDX flow state.
306
+
307
+ The application should keep track of values returned between steps, such as:
308
+
309
+ ```ts
310
+ const state = {
311
+ gtin: undefined,
312
+ userTestResultId: undefined,
313
+ preSignedURL: undefined,
314
+ imageOfCaptureId: undefined,
315
+ };
316
+ ```
317
+
318
+ Important values:
319
+
320
+ - `gtin`: product or test kit identifier
321
+ - `userTestResultId`: test result record ID used across the flow
322
+ - `preSignedURL`: temporary URL used for image upload
323
+ - `imageOfCaptureId`: upload identifier returned after creating the upload URL
324
+
325
+ These values should be stored by the consuming app and passed to the next SDK method when needed.
326
+
327
+ ## Public Methods
328
+
329
+ ### getTestProfileByGTIN
330
+
331
+ ```ts
332
+ const profile = await safeCdx.getTestProfileByGTIN(gtin, language);
333
+ ```
334
+
335
+ Resolves a test profile by GTIN barcode.
336
+
337
+ `language` is optional. If not provided, the client uses `defaultLanguage` from config when available.
338
+
339
+ ### scan2Ddm
340
+
341
+ ```ts
342
+ const scan = await safeCdx.scan2Ddm(payload);
343
+ ```
344
+
345
+ Scans a 2D data matrix barcode and resolves the related test data.
346
+
347
+ ### getTestProfile
348
+
349
+ ```ts
350
+ const profile = await safeCdx.getTestProfile(payload);
351
+ ```
352
+
353
+ Returns the full test profile for a given payload.
354
+
355
+ ### getTestProfilesByAccount
356
+
357
+ ```ts
358
+ const profiles = await safeCdx.getTestProfilesByAccount();
359
+ ```
360
+
361
+ Lists test profiles available to the authenticated account.
362
+
363
+ A custom payload may also be provided:
364
+
365
+ ```ts
366
+ const profiles = await safeCdx.getTestProfilesByAccount({
367
+ IncludeRegisterTestDetails: true,
368
+ });
369
+ ```
370
+
371
+ ### createUploadUrl
372
+
373
+ ```ts
374
+ const upload = await safeCdx.createUploadUrl(
375
+ userTestResultId,
376
+ gtin,
377
+ "jpg"
378
+ );
379
+ ```
380
+
381
+ Creates a pre-signed image upload URL.
382
+
383
+ `imageType` is optional. If not provided, the client uses `defaultImageType` from config, or `jpg` by default.
384
+
385
+ ### uploadImage
386
+
387
+ ```ts
388
+ await safeCdx.uploadImage(preSignedURL, imageBody, "image/jpeg");
389
+ ```
390
+
391
+ Uploads the image directly to the pre-signed URL.
392
+
393
+ This method does not call a Safe CDX API route.
394
+
395
+ ### updateCvmlStatus
396
+
397
+ ```ts
398
+ const result = await safeCdx.updateCvmlStatus(
399
+ imageOfCaptureId,
400
+ "ImageProcessing"
401
+ );
402
+ ```
403
+
404
+ Updates the CVML processing status for a captured image.
405
+
406
+ This method returns a raw service result.
407
+
408
+ ### getCvmlResults
409
+
410
+ ```ts
411
+ const result = await safeCdx.getCvmlResults(imageOfCaptureId);
412
+ ```
413
+
414
+ Polls CVML analysis results for a captured image.
415
+
416
+ This method returns a raw service result.
417
+
418
+ ### getPendingResults
419
+
420
+ ```ts
421
+ const pending = await safeCdx.getPendingResults();
422
+ ```
423
+
424
+ Returns pending or incomplete test results.
425
+
426
+ A custom payload may also be provided:
427
+
428
+ ```ts
429
+ const pending = await safeCdx.getPendingResults({
430
+ ExcludeStatus: "Started",
431
+ });
432
+ ```
433
+
434
+ ### getLastResults
435
+
436
+ ```ts
437
+ const last = await safeCdx.getLastResults();
438
+ ```
439
+
440
+ Returns the most recent test result for the authenticated patient.
441
+
442
+ ### getTestHistory
443
+
444
+ ```ts
445
+ const history = await safeCdx.getTestHistory();
446
+ ```
447
+
448
+ Returns the test history for the authenticated patient.
449
+
450
+ ### getResultDetails
451
+
452
+ ```ts
453
+ const details = await safeCdx.getResultDetails(userTestResultId);
454
+ ```
455
+
456
+ Returns detailed result data for a specific test attempt.
457
+
458
+ This method returns a raw service result.
459
+
460
+ ### getResultPdf
461
+
462
+ ```ts
463
+ const pdf = await safeCdx.getResultPdf({
464
+ UserTestResultId: userTestResultId,
465
+ });
466
+ ```
467
+
468
+ Generates or retrieves the PDF report for a specific test result.
469
+
470
+ ### getImageCaptureUrl
471
+
472
+ ```ts
473
+ const imageUrl = await safeCdx.getImageCaptureUrl(userTestResultId);
474
+ ```
475
+
476
+ Returns the image capture URL for a specific test result.
477
+
478
+ This method returns a raw service result.
479
+
480
+ ### getAnalytics
481
+
482
+ ```ts
483
+ const analytics = await safeCdx.getAnalytics();
484
+ ```
485
+
486
+ Returns analytics data for test results.
487
+
488
+ A custom payload may also be provided:
489
+
490
+ ```ts
491
+ const analytics = await safeCdx.getAnalytics(payload);
492
+ ```
493
+
494
+ ### resumeFlow
495
+
496
+ ```ts
497
+ const resumed = await safeCdx.resumeFlow(userTestResultId);
498
+ ```
499
+
500
+ Marks a test attempt as resumed.
501
+
502
+ The second argument can be used to explicitly set the resumed value:
503
+
504
+ ```ts
505
+ const resumed = await safeCdx.resumeFlow(userTestResultId, true);
506
+ ```
507
+
508
+ ### submitAnswers
509
+
510
+ ```ts
511
+ const result = await safeCdx.submitAnswers(payload);
512
+ ```
513
+
514
+ Submits analyte answers for a test attempt.
515
+
516
+ ### finalizeTest
517
+
518
+ ```ts
519
+ const result = await safeCdx.finalizeTest(payload);
520
+ ```
521
+
522
+ Finalizes a test attempt with CTA, indication, and decision results.
523
+
524
+ ## Error Handling
525
+
526
+ The connector throws `SafeCDXError` for connector-level errors.
527
+
528
+ Examples:
529
+
530
+ - invalid environment
531
+ - missing access token
532
+ - failed `ApiResponse` validation
533
+ - failed image upload
534
+
535
+ ```ts
536
+ try {
537
+ const profile = await safeCdx.getTestProfileByGTIN("850024942325");
538
+ } catch (error) {
539
+ if (error instanceof SafeCDXError) {
540
+ console.error(error.message);
541
+ console.error(error.response);
542
+ }
543
+
544
+ throw error;
545
+ }
546
+ ```
547
+
548
+ HTTP-level errors from the shared `HttpClient` may also be thrown depending on the `HttpClient` implementation.
549
+
550
+ For example, `FetchClient` from `@healthcloudai/hc-http` throws an HTTP error when the response status is not successful.
551
+
552
+ ## Notes
553
+
554
+ - The connector does not perform login.
555
+ - The connector expects a valid patient token to be set before API calls.
556
+ - Standard Safe CDX API methods use the shared `HttpClient`.
557
+ - POST payloads are wrapped internally as `{ Data: payload }`.
558
+ - The caller should pass only the inner payload to SDK methods.
559
+ - Image upload uses the pre-signed URL directly and sends the raw file body.
560
+ - Authorization headers should not be sent to the pre-signed upload URL.
561
+ - Pre-signed URLs are time-bound and should not be logged in production.