@healthcloudai/hc-safe-cdx 0.0.1 → 0.1.2

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