@nexalink/carecompass-sdk 0.4.0-beta.15 → 0.5.0
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 +282 -3
- package/dist/cli.js +0 -0
- package/dist/client.d.ts +32 -2
- package/dist/client.js +42 -3
- package/dist/compliance.d.ts +69 -0
- package/dist/compliance.js +121 -0
- package/dist/component.d.ts +24 -2
- package/dist/component.js +63 -4
- package/dist/core.d.ts +19 -2
- package/dist/core.js +140 -8
- package/dist/index.d.ts +11 -5
- package/dist/index.js +5 -2
- package/dist/markdown.d.ts +2 -1
- package/dist/mount.d.ts +4 -1
- package/dist/mount.js +2 -2
- package/dist/rcm.d.ts +93 -0
- package/dist/rcm.js +284 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +1 -0
- package/dist/schema.d.ts +49 -0
- package/dist/schema.js +36 -0
- package/dist/telemetry.js +114 -82
- package/dist/ui.d.ts +2 -2
- package/dist/ui.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/dist/web-component.d.ts +13 -0
- package/dist/web-component.js +236 -0
- package/package.json +27 -12
- package/qa-fixtures/DEMO-ENC-1042-rcm-policy.md +27 -0
package/dist/telemetry.js
CHANGED
|
@@ -62,12 +62,60 @@ export const CARECOMPASS_TELEMETRY_ERROR_CODES = {
|
|
|
62
62
|
CLI_COMMAND_UNKNOWN: "CLI_COMMAND_UNKNOWN",
|
|
63
63
|
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
64
64
|
};
|
|
65
|
-
const DEPLOYMENT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{2,127}$/;
|
|
66
65
|
const POSTHOG_PROJECT_TOKEN_RE = /^phc_[A-Za-z0-9_-]{8,}$/;
|
|
67
66
|
const POSTHOG_HOSTNAMES = new Set(["us.i.posthog.com", "eu.i.posthog.com"]);
|
|
68
67
|
const CARECOMPASS_DEFAULT_POSTHOG_API_HOST = "https://us.i.posthog.com";
|
|
69
68
|
const CARECOMPASS_DEFAULT_POSTHOG_PROJECT_TOKEN = "phc_vYl3rpIu6Iz5vCeJUG62BBA3nlPCeF4RSYpCb8FjnOl";
|
|
70
69
|
const TELEMETRY_CAPTURE_TIMEOUT_MS = 1_000;
|
|
70
|
+
const ANONYMOUS_DISTINCT_ID = "carecompass-sdk-anonymous";
|
|
71
|
+
const TELEMETRY_EVENTS = new Set(Object.values(CARECOMPASS_TELEMETRY_EVENTS));
|
|
72
|
+
const TELEMETRY_ERROR_CODES = new Set(Object.values(CARECOMPASS_TELEMETRY_ERROR_CODES));
|
|
73
|
+
const FAILURE_CATEGORIES = new Set([
|
|
74
|
+
"authentication",
|
|
75
|
+
"auth_error",
|
|
76
|
+
"baa_not_signed",
|
|
77
|
+
"configuration",
|
|
78
|
+
"config_error",
|
|
79
|
+
"inference",
|
|
80
|
+
"model_error",
|
|
81
|
+
"network",
|
|
82
|
+
"network_error",
|
|
83
|
+
"permission_error",
|
|
84
|
+
"rate_limit",
|
|
85
|
+
"rate_limit_error",
|
|
86
|
+
"rendering",
|
|
87
|
+
"timeout",
|
|
88
|
+
"unknown",
|
|
89
|
+
]);
|
|
90
|
+
const AUTH_METHODS = new Set(["api_key", "env_var", "config_file"]);
|
|
91
|
+
const BAA_STATUSES = new Set(["signed", "pending", "not_required"]);
|
|
92
|
+
const COMMAND_NAMES = new Set([
|
|
93
|
+
"auth",
|
|
94
|
+
"completion",
|
|
95
|
+
"config",
|
|
96
|
+
"configure",
|
|
97
|
+
"doctor",
|
|
98
|
+
"embed",
|
|
99
|
+
"health",
|
|
100
|
+
"help",
|
|
101
|
+
"init",
|
|
102
|
+
"login",
|
|
103
|
+
"logout",
|
|
104
|
+
"query",
|
|
105
|
+
"setup",
|
|
106
|
+
"snippet",
|
|
107
|
+
"unknown_command",
|
|
108
|
+
"validate",
|
|
109
|
+
"version",
|
|
110
|
+
]);
|
|
111
|
+
const EXIT_REASONS = new Set([
|
|
112
|
+
"user_exit",
|
|
113
|
+
"error",
|
|
114
|
+
"timeout",
|
|
115
|
+
"signal",
|
|
116
|
+
]);
|
|
117
|
+
const AUTH_RESULTS = new Set(["success", "failure"]);
|
|
118
|
+
const KEY_SOURCES = new Set(["env_var", "config_file", "cli_flag"]);
|
|
71
119
|
const disabledTelemetry = {
|
|
72
120
|
enabled: false,
|
|
73
121
|
capture: () => undefined,
|
|
@@ -107,15 +155,19 @@ export const careCompassTelemetryDurationBucket = (durationMs) => {
|
|
|
107
155
|
return "5_to_29_999_ms";
|
|
108
156
|
return "30_s_or_more";
|
|
109
157
|
};
|
|
110
|
-
const integerMs = (value) =>
|
|
111
|
-
|
|
158
|
+
const integerMs = (value) => typeof value === "number" && Number.isFinite(value)
|
|
159
|
+
? Math.max(0, Math.round(value))
|
|
160
|
+
: undefined;
|
|
161
|
+
const integerCount = (value) => typeof value === "number" && Number.isFinite(value)
|
|
162
|
+
? Math.max(0, Math.trunc(value))
|
|
163
|
+
: undefined;
|
|
164
|
+
const allowedValue = (value, values) => typeof value === "string" && values.has(value) ? value : undefined;
|
|
165
|
+
const allowedEnvironment = (value) => value === "development" || value === "staging" || value === "production"
|
|
166
|
+
? value
|
|
167
|
+
: "unknown";
|
|
112
168
|
function createTelemetry(context, options = {}) {
|
|
113
169
|
const projectToken = CARECOMPASS_DEFAULT_POSTHOG_PROJECT_TOKEN;
|
|
114
|
-
|
|
115
|
-
// or sending data under an ambiguous deployment identity.
|
|
116
|
-
if (!DEPLOYMENT_ID_RE.test(context.deploymentId) ||
|
|
117
|
-
!DEPLOYMENT_ID_RE.test(context.distinctId) ||
|
|
118
|
-
!POSTHOG_PROJECT_TOKEN_RE.test(projectToken)) {
|
|
170
|
+
if (!POSTHOG_PROJECT_TOKEN_RE.test(projectToken)) {
|
|
119
171
|
return disabledTelemetry;
|
|
120
172
|
}
|
|
121
173
|
const fetcher = options.fetcher ?? fetch;
|
|
@@ -124,90 +176,77 @@ function createTelemetry(context, options = {}) {
|
|
|
124
176
|
if (!apiHost)
|
|
125
177
|
return disabledTelemetry;
|
|
126
178
|
const captureAsync = async (event, captureOptions = {}) => {
|
|
179
|
+
if (!TELEMETRY_EVENTS.has(event))
|
|
180
|
+
return;
|
|
127
181
|
const eventTimestamp = now().toISOString();
|
|
182
|
+
const durationMs = integerMs(captureOptions.durationMs);
|
|
183
|
+
const failureCategory = captureOptions.failureCategory === null
|
|
184
|
+
? null
|
|
185
|
+
: allowedValue(captureOptions.failureCategory, FAILURE_CATEGORIES);
|
|
186
|
+
const errorCode = allowedValue(captureOptions.errorCode, TELEMETRY_ERROR_CODES);
|
|
187
|
+
const initDurationMs = integerMs(captureOptions.initDurationMs);
|
|
188
|
+
const authMethod = allowedValue(captureOptions.authMethod, AUTH_METHODS);
|
|
189
|
+
const baaStatus = allowedValue(captureOptions.baaStatus, BAA_STATUSES);
|
|
190
|
+
const commandName = allowedValue(captureOptions.commandName, COMMAND_NAMES);
|
|
191
|
+
const commandArgsCount = integerCount(captureOptions.commandArgsCount);
|
|
192
|
+
const commandDurationMs = integerMs(captureOptions.commandDurationMs);
|
|
193
|
+
const tokensUsed = captureOptions.tokensUsed === null
|
|
194
|
+
? null
|
|
195
|
+
: integerCount(captureOptions.tokensUsed);
|
|
196
|
+
const sessionDurationMs = integerMs(captureOptions.sessionDurationMs);
|
|
197
|
+
const commandsAttempted = integerCount(captureOptions.commandsAttempted);
|
|
198
|
+
const commandsSucceeded = integerCount(captureOptions.commandsSucceeded);
|
|
199
|
+
const commandsFailed = integerCount(captureOptions.commandsFailed);
|
|
200
|
+
const exitReason = allowedValue(captureOptions.exitReason, EXIT_REASONS);
|
|
201
|
+
const authResult = allowedValue(captureOptions.authResult, AUTH_RESULTS);
|
|
202
|
+
const keySource = allowedValue(captureOptions.keySource, KEY_SOURCES);
|
|
128
203
|
// Properties are allowlisted here by design. Do not add prompts, responses,
|
|
129
|
-
// user context, credentials,
|
|
204
|
+
// user context, credentials, raw exception messages, or stable identifiers.
|
|
130
205
|
const properties = {
|
|
131
206
|
$process_person_profile: false,
|
|
132
207
|
contract_version: CARECOMPASS_TELEMETRY_CONTRACT_VERSION,
|
|
133
208
|
sdk_version: CARECOMPASS_SDK_VERSION,
|
|
134
209
|
component_version: CARECOMPASS_COMPONENT_VERSION,
|
|
135
210
|
platform: context.platform,
|
|
136
|
-
environment: context.environment,
|
|
137
|
-
payer_id: context.payerId,
|
|
138
|
-
deployment_id: context.deploymentId,
|
|
211
|
+
environment: allowedEnvironment(context.environment),
|
|
139
212
|
event_timestamp: eventTimestamp,
|
|
140
|
-
...(
|
|
141
|
-
...(captureOptions.durationMs === undefined
|
|
213
|
+
...(durationMs === undefined
|
|
142
214
|
? {}
|
|
143
215
|
: {
|
|
144
|
-
duration_bucket: careCompassTelemetryDurationBucket(
|
|
216
|
+
duration_bucket: careCompassTelemetryDurationBucket(durationMs),
|
|
145
217
|
}),
|
|
146
|
-
...(
|
|
218
|
+
...(failureCategory === undefined
|
|
147
219
|
? {}
|
|
148
|
-
: { failure_category:
|
|
149
|
-
...(
|
|
150
|
-
|
|
151
|
-
: {}),
|
|
152
|
-
...(captureOptions.initDurationMs === undefined
|
|
220
|
+
: { failure_category: failureCategory }),
|
|
221
|
+
...(errorCode ? { error_code: errorCode } : {}),
|
|
222
|
+
...(initDurationMs === undefined
|
|
153
223
|
? {}
|
|
154
|
-
: { init_duration_ms:
|
|
155
|
-
...(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
...(
|
|
159
|
-
? { baa_status: captureOptions.baaStatus }
|
|
160
|
-
: {}),
|
|
161
|
-
...(captureOptions.commandName
|
|
162
|
-
? { command_name: captureOptions.commandName }
|
|
163
|
-
: {}),
|
|
164
|
-
...(captureOptions.commandArgsCount === undefined
|
|
224
|
+
: { init_duration_ms: initDurationMs }),
|
|
225
|
+
...(authMethod ? { auth_method: authMethod } : {}),
|
|
226
|
+
...(baaStatus ? { baa_status: baaStatus } : {}),
|
|
227
|
+
...(commandName ? { command_name: commandName } : {}),
|
|
228
|
+
...(commandArgsCount === undefined
|
|
165
229
|
? {}
|
|
166
|
-
: {
|
|
167
|
-
|
|
168
|
-
}),
|
|
169
|
-
...(captureOptions.commandDurationMs === undefined
|
|
170
|
-
? {}
|
|
171
|
-
: {
|
|
172
|
-
command_duration_ms: integerMs(captureOptions.commandDurationMs),
|
|
173
|
-
}),
|
|
174
|
-
...(captureOptions.tokensUsed === undefined
|
|
175
|
-
? {}
|
|
176
|
-
: {
|
|
177
|
-
tokens_used: captureOptions.tokensUsed === null
|
|
178
|
-
? null
|
|
179
|
-
: integerCount(captureOptions.tokensUsed),
|
|
180
|
-
}),
|
|
181
|
-
...(captureOptions.modelId === undefined
|
|
230
|
+
: { command_args_count: commandArgsCount }),
|
|
231
|
+
...(commandDurationMs === undefined
|
|
182
232
|
? {}
|
|
183
|
-
: {
|
|
184
|
-
...(
|
|
233
|
+
: { command_duration_ms: commandDurationMs }),
|
|
234
|
+
...(tokensUsed === undefined ? {} : { tokens_used: tokensUsed }),
|
|
235
|
+
...(sessionDurationMs === undefined
|
|
185
236
|
? {}
|
|
186
|
-
: {
|
|
187
|
-
|
|
188
|
-
}),
|
|
189
|
-
...(captureOptions.commandsAttempted === undefined
|
|
237
|
+
: { session_duration_ms: sessionDurationMs }),
|
|
238
|
+
...(commandsAttempted === undefined
|
|
190
239
|
? {}
|
|
191
|
-
: {
|
|
192
|
-
|
|
193
|
-
}),
|
|
194
|
-
...(captureOptions.commandsSucceeded === undefined
|
|
240
|
+
: { commands_attempted: commandsAttempted }),
|
|
241
|
+
...(commandsSucceeded === undefined
|
|
195
242
|
? {}
|
|
196
|
-
: {
|
|
197
|
-
|
|
198
|
-
}),
|
|
199
|
-
...(captureOptions.commandsFailed === undefined
|
|
243
|
+
: { commands_succeeded: commandsSucceeded }),
|
|
244
|
+
...(commandsFailed === undefined
|
|
200
245
|
? {}
|
|
201
|
-
: { commands_failed:
|
|
202
|
-
...(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
...(captureOptions.authResult
|
|
206
|
-
? { auth_result: captureOptions.authResult }
|
|
207
|
-
: {}),
|
|
208
|
-
...(captureOptions.keySource
|
|
209
|
-
? { key_source: captureOptions.keySource }
|
|
210
|
-
: {}),
|
|
246
|
+
: { commands_failed: commandsFailed }),
|
|
247
|
+
...(exitReason ? { exit_reason: exitReason } : {}),
|
|
248
|
+
...(authResult ? { auth_result: authResult } : {}),
|
|
249
|
+
...(keySource ? { key_source: keySource } : {}),
|
|
211
250
|
};
|
|
212
251
|
const controller = new AbortController();
|
|
213
252
|
const timeout = setTimeout(() => controller.abort(), TELEMETRY_CAPTURE_TIMEOUT_MS);
|
|
@@ -217,7 +256,7 @@ function createTelemetry(context, options = {}) {
|
|
|
217
256
|
headers: { "content-type": "application/json" },
|
|
218
257
|
body: JSON.stringify({
|
|
219
258
|
api_key: projectToken,
|
|
220
|
-
distinct_id:
|
|
259
|
+
distinct_id: ANONYMOUS_DISTINCT_ID,
|
|
221
260
|
event,
|
|
222
261
|
properties,
|
|
223
262
|
timestamp: eventTimestamp,
|
|
@@ -239,24 +278,17 @@ function createTelemetry(context, options = {}) {
|
|
|
239
278
|
};
|
|
240
279
|
}
|
|
241
280
|
export function createCareCompassTelemetry(config, options = {}) {
|
|
242
|
-
const deploymentId = `${config.payerId}-${config.environment}-web`;
|
|
243
281
|
return createTelemetry({
|
|
244
|
-
deploymentId,
|
|
245
|
-
distinctId: deploymentId,
|
|
246
282
|
environment: config.environment,
|
|
247
|
-
payerId: config.payerId,
|
|
248
283
|
platform: "web",
|
|
249
284
|
}, options);
|
|
250
285
|
}
|
|
251
286
|
export function createCareCompassCliTelemetry(sessionId, context = {}, options = {}) {
|
|
287
|
+
// Keep the public signature stable, but session IDs must never reach telemetry.
|
|
288
|
+
void sessionId;
|
|
252
289
|
const environment = context.environment ?? "unknown";
|
|
253
|
-
const payerId = context.payerId ?? "anonymous";
|
|
254
290
|
return createTelemetry({
|
|
255
|
-
cliSessionId: sessionId,
|
|
256
|
-
deploymentId: `${payerId}-${environment}-cli`,
|
|
257
|
-
distinctId: sessionId,
|
|
258
291
|
environment,
|
|
259
|
-
payerId,
|
|
260
292
|
platform: "cli",
|
|
261
293
|
}, options);
|
|
262
294
|
}
|
package/dist/ui.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { CareCompassChat, CareCompassEmbed } from "./component.js";
|
|
2
|
-
export type { CareCompassChatProps } from "./component.js";
|
|
1
|
+
export { CareCompassChat, CareCompassEmbed, useCareCompass, } from "./component.js";
|
|
2
|
+
export type { CareCompassChatProps, CareCompassReactController, UseCareCompassOptions, } from "./component.js";
|
|
3
3
|
export { mountCareCompass } from "./mount.js";
|
package/dist/ui.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { CareCompassChat, CareCompassEmbed } from "./component.js";
|
|
1
|
+
export { CareCompassChat, CareCompassEmbed, useCareCompass, } from "./component.js";
|
|
2
2
|
export { mountCareCompass } from "./mount.js";
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export const CARECOMPASS_SDK_VERSION = "0.
|
|
1
|
+
export const CARECOMPASS_SDK_VERSION = "0.5.0"; // x-release-please-version
|
|
2
2
|
export const CARECOMPASS_COMPONENT_VERSION = "1.0.0";
|
|
3
|
-
export const CARECOMPASS_TELEMETRY_CONTRACT_VERSION = "1.
|
|
3
|
+
export const CARECOMPASS_TELEMETRY_CONTRACT_VERSION = "1.1.0";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CareCompassRuntimeTokenProvider } from "./client.js";
|
|
2
|
+
import { type CareCompassConfig } from "./schema.js";
|
|
3
|
+
export declare const CARECOMPASS_ELEMENT_TAG = "carecompass-chat";
|
|
4
|
+
export type CareCompassWebComponentElement = HTMLElement & {
|
|
5
|
+
config: CareCompassConfig | undefined;
|
|
6
|
+
runtimeTokenProvider: CareCompassRuntimeTokenProvider | undefined;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Register a framework-neutral embed backed directly by the headless core.
|
|
10
|
+
* Deferring the HTMLElement subclass creation keeps this entry point safe to
|
|
11
|
+
* import during server rendering.
|
|
12
|
+
*/
|
|
13
|
+
export declare function defineCareCompassElement(tagName?: string): CustomElementConstructor | undefined;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { createCareCompassCore, } from "./core.js";
|
|
2
|
+
import { validateCareCompassConfig } from "./schema.js";
|
|
3
|
+
export const CARECOMPASS_ELEMENT_TAG = "carecompass-chat";
|
|
4
|
+
/**
|
|
5
|
+
* Register a framework-neutral embed backed directly by the headless core.
|
|
6
|
+
* Deferring the HTMLElement subclass creation keeps this entry point safe to
|
|
7
|
+
* import during server rendering.
|
|
8
|
+
*/
|
|
9
|
+
export function defineCareCompassElement(tagName = CARECOMPASS_ELEMENT_TAG) {
|
|
10
|
+
if (typeof customElements === "undefined" ||
|
|
11
|
+
typeof HTMLElement === "undefined") {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const existing = customElements.get(tagName);
|
|
15
|
+
if (existing)
|
|
16
|
+
return existing;
|
|
17
|
+
class CareCompassElement extends HTMLElement {
|
|
18
|
+
constructor() {
|
|
19
|
+
super(...arguments);
|
|
20
|
+
this.draft = "";
|
|
21
|
+
this.hasPropertyConfig = false;
|
|
22
|
+
}
|
|
23
|
+
static get observedAttributes() {
|
|
24
|
+
return ["config"];
|
|
25
|
+
}
|
|
26
|
+
get config() {
|
|
27
|
+
return this.configuredValue;
|
|
28
|
+
}
|
|
29
|
+
set config(value) {
|
|
30
|
+
this.hasPropertyConfig = true;
|
|
31
|
+
this.configuredValue = value;
|
|
32
|
+
this.remount();
|
|
33
|
+
}
|
|
34
|
+
get runtimeTokenProvider() {
|
|
35
|
+
return this.runtimeTokenProviderValue;
|
|
36
|
+
}
|
|
37
|
+
set runtimeTokenProvider(value) {
|
|
38
|
+
this.runtimeTokenProviderValue = value;
|
|
39
|
+
this.remount();
|
|
40
|
+
}
|
|
41
|
+
connectedCallback() {
|
|
42
|
+
this.remount();
|
|
43
|
+
}
|
|
44
|
+
disconnectedCallback() {
|
|
45
|
+
this.unmount();
|
|
46
|
+
}
|
|
47
|
+
attributeChangedCallback(name, _oldValue, value) {
|
|
48
|
+
if (name !== "config" || this.hasPropertyConfig)
|
|
49
|
+
return;
|
|
50
|
+
if (value === null) {
|
|
51
|
+
this.configuredValue = undefined;
|
|
52
|
+
this.remount();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
this.configuredValue = JSON.parse(value);
|
|
57
|
+
this.remount();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
this.reportConfigurationError();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
unmount() {
|
|
64
|
+
this.unsubscribe?.();
|
|
65
|
+
this.unsubscribe = undefined;
|
|
66
|
+
this.core?.dispose();
|
|
67
|
+
this.core = undefined;
|
|
68
|
+
this.replaceChildren();
|
|
69
|
+
}
|
|
70
|
+
remount() {
|
|
71
|
+
this.unmount();
|
|
72
|
+
if (!this.isConnected || !this.configuredValue)
|
|
73
|
+
return;
|
|
74
|
+
const validation = validateCareCompassConfig(this.configuredValue);
|
|
75
|
+
if (!validation.ok) {
|
|
76
|
+
this.reportConfigurationError();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.core = createCareCompassCore(validation.config, {
|
|
80
|
+
onAuditEvent: (event) => this.dispatchMetadataEvent("carecompass-audit", event),
|
|
81
|
+
onSafetyEvent: (event) => this.dispatchMetadataEvent("carecompass-safety", event),
|
|
82
|
+
runtimeTokenProvider: this.runtimeTokenProviderValue,
|
|
83
|
+
});
|
|
84
|
+
this.unsubscribe = this.core.subscribe((snapshot) => this.renderSnapshot(validation.config, snapshot));
|
|
85
|
+
this.renderSnapshot(validation.config, this.core.getSnapshot());
|
|
86
|
+
}
|
|
87
|
+
dispatchMetadataEvent(name, event) {
|
|
88
|
+
// Core audit and safety records intentionally contain metadata and PHI
|
|
89
|
+
// summaries only; never interpolate message text into DOM events.
|
|
90
|
+
this.dispatchEvent(new CustomEvent(name, {
|
|
91
|
+
bubbles: true,
|
|
92
|
+
detail: event,
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
renderSnapshot(config, snapshot) {
|
|
96
|
+
const section = document.createElement("section");
|
|
97
|
+
section.setAttribute("aria-label", config.labels.title);
|
|
98
|
+
Object.assign(section.style, {
|
|
99
|
+
background: config.theme.backgroundColor,
|
|
100
|
+
border: `1px solid ${config.theme.textColor}22`,
|
|
101
|
+
borderRadius: config.theme.borderRadius,
|
|
102
|
+
color: config.theme.textColor,
|
|
103
|
+
display: "flex",
|
|
104
|
+
flexDirection: "column",
|
|
105
|
+
fontFamily: config.theme.fontFamily,
|
|
106
|
+
maxWidth: config.placement.mode === "sidebar" ? "420px" : "720px",
|
|
107
|
+
minHeight: "420px",
|
|
108
|
+
overflow: "hidden",
|
|
109
|
+
});
|
|
110
|
+
const header = document.createElement("header");
|
|
111
|
+
Object.assign(header.style, {
|
|
112
|
+
borderBottom: `1px solid ${config.theme.textColor}22`,
|
|
113
|
+
padding: "16px",
|
|
114
|
+
});
|
|
115
|
+
const title = document.createElement("strong");
|
|
116
|
+
title.textContent = config.labels.title;
|
|
117
|
+
const brand = document.createElement("div");
|
|
118
|
+
brand.textContent = config.branding.displayName;
|
|
119
|
+
brand.style.fontSize = "12px";
|
|
120
|
+
brand.style.opacity = "0.72";
|
|
121
|
+
header.append(title, brand);
|
|
122
|
+
const messages = document.createElement("div");
|
|
123
|
+
messages.setAttribute("aria-live", "polite");
|
|
124
|
+
Object.assign(messages.style, {
|
|
125
|
+
display: "flex",
|
|
126
|
+
flex: "1",
|
|
127
|
+
flexDirection: "column",
|
|
128
|
+
gap: "12px",
|
|
129
|
+
overflowY: "auto",
|
|
130
|
+
padding: "16px",
|
|
131
|
+
});
|
|
132
|
+
for (const message of snapshot.messages) {
|
|
133
|
+
const bubble = document.createElement("div");
|
|
134
|
+
bubble.textContent = message.text;
|
|
135
|
+
Object.assign(bubble.style, {
|
|
136
|
+
alignSelf: message.role === "user" ? "flex-end" : "flex-start",
|
|
137
|
+
background: message.role === "user"
|
|
138
|
+
? config.theme.primaryColor
|
|
139
|
+
: `${config.theme.textColor}12`,
|
|
140
|
+
borderRadius: "12px",
|
|
141
|
+
color: message.role === "user" ? "#ffffff" : "inherit",
|
|
142
|
+
maxWidth: "82%",
|
|
143
|
+
padding: "10px 12px",
|
|
144
|
+
whiteSpace: "pre-wrap",
|
|
145
|
+
});
|
|
146
|
+
messages.append(bubble);
|
|
147
|
+
}
|
|
148
|
+
if (snapshot.loading) {
|
|
149
|
+
const thinking = document.createElement("div");
|
|
150
|
+
thinking.textContent = "Thinking...";
|
|
151
|
+
thinking.style.fontSize = "13px";
|
|
152
|
+
thinking.style.opacity = "0.72";
|
|
153
|
+
messages.append(thinking);
|
|
154
|
+
}
|
|
155
|
+
const form = document.createElement("form");
|
|
156
|
+
Object.assign(form.style, {
|
|
157
|
+
borderTop: `1px solid ${config.theme.textColor}22`,
|
|
158
|
+
display: "flex",
|
|
159
|
+
flexDirection: "column",
|
|
160
|
+
gap: "8px",
|
|
161
|
+
padding: "12px",
|
|
162
|
+
});
|
|
163
|
+
form.addEventListener("submit", (event) => {
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
void this.sendMessage(snapshot);
|
|
166
|
+
});
|
|
167
|
+
const controls = document.createElement("div");
|
|
168
|
+
controls.style.display = "flex";
|
|
169
|
+
controls.style.gap = "8px";
|
|
170
|
+
const input = document.createElement("input");
|
|
171
|
+
input.setAttribute("aria-label", config.labels.placeholder);
|
|
172
|
+
input.disabled = snapshot.loading;
|
|
173
|
+
input.placeholder = config.labels.placeholder;
|
|
174
|
+
input.value = this.draft;
|
|
175
|
+
input.style.flex = "1";
|
|
176
|
+
input.style.minWidth = "0";
|
|
177
|
+
input.style.padding = "10px 12px";
|
|
178
|
+
input.addEventListener("input", () => {
|
|
179
|
+
this.draft = input.value;
|
|
180
|
+
send.disabled = snapshot.loading || !this.draft.trim();
|
|
181
|
+
});
|
|
182
|
+
const send = document.createElement("button");
|
|
183
|
+
send.disabled = snapshot.loading || !this.draft.trim();
|
|
184
|
+
send.textContent = config.labels.sendButton;
|
|
185
|
+
send.type = "submit";
|
|
186
|
+
Object.assign(send.style, {
|
|
187
|
+
background: config.theme.primaryColor,
|
|
188
|
+
border: "0",
|
|
189
|
+
borderRadius: "8px",
|
|
190
|
+
color: "#ffffff",
|
|
191
|
+
fontWeight: "700",
|
|
192
|
+
padding: "0 14px",
|
|
193
|
+
});
|
|
194
|
+
controls.append(input, send);
|
|
195
|
+
form.append(controls);
|
|
196
|
+
if (snapshot.error) {
|
|
197
|
+
const error = document.createElement("div");
|
|
198
|
+
error.setAttribute("role", "alert");
|
|
199
|
+
error.textContent = snapshot.error.message;
|
|
200
|
+
error.style.color = "#b91c1c";
|
|
201
|
+
error.style.fontSize = "12px";
|
|
202
|
+
form.append(error);
|
|
203
|
+
}
|
|
204
|
+
const disclaimer = document.createElement("div");
|
|
205
|
+
disclaimer.textContent = config.labels.disclaimer;
|
|
206
|
+
disclaimer.style.fontSize = "11px";
|
|
207
|
+
disclaimer.style.opacity = "0.7";
|
|
208
|
+
form.append(disclaimer);
|
|
209
|
+
section.append(header, messages, form);
|
|
210
|
+
this.replaceChildren(section);
|
|
211
|
+
}
|
|
212
|
+
async sendMessage(snapshot) {
|
|
213
|
+
const prompt = this.draft.trim();
|
|
214
|
+
if (!prompt || snapshot.loading || !this.core)
|
|
215
|
+
return;
|
|
216
|
+
this.draft = "";
|
|
217
|
+
try {
|
|
218
|
+
this.core.recordWidgetAction();
|
|
219
|
+
await this.core.sendMessage(prompt);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// The snapshot already carries the safe, user-visible error state.
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
reportConfigurationError() {
|
|
226
|
+
// Do not include raw config or validation detail in DOM events; a config
|
|
227
|
+
// can contain user-controlled branding and labels.
|
|
228
|
+
this.dispatchEvent(new CustomEvent("carecompass-error", {
|
|
229
|
+
bubbles: true,
|
|
230
|
+
detail: { code: "configuration_invalid" },
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
customElements.define(tagName, CareCompassElement);
|
|
235
|
+
return CareCompassElement;
|
|
236
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexalink/carecompass-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Embeddable CareCompass SDK and CLI for healthcare ecosystem integrations with Nexalink onboarding, BAA coverage, and API keys.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "bun@1.3.9",
|
|
@@ -42,11 +42,24 @@
|
|
|
42
42
|
"./ui": {
|
|
43
43
|
"types": "./dist/ui.d.ts",
|
|
44
44
|
"import": "./dist/ui.js"
|
|
45
|
+
},
|
|
46
|
+
"./react": {
|
|
47
|
+
"types": "./dist/react.d.ts",
|
|
48
|
+
"import": "./dist/react.js"
|
|
49
|
+
},
|
|
50
|
+
"./rcm": {
|
|
51
|
+
"types": "./dist/rcm.d.ts",
|
|
52
|
+
"import": "./dist/rcm.js"
|
|
53
|
+
},
|
|
54
|
+
"./web-component": {
|
|
55
|
+
"types": "./dist/web-component.d.ts",
|
|
56
|
+
"import": "./dist/web-component.js"
|
|
45
57
|
}
|
|
46
58
|
},
|
|
47
59
|
"files": [
|
|
48
60
|
"dist/**/*.js",
|
|
49
61
|
"dist/**/*.d.ts",
|
|
62
|
+
"qa-fixtures/DEMO-ENC-1042-rcm-policy.md",
|
|
50
63
|
"README.md",
|
|
51
64
|
"LICENSE"
|
|
52
65
|
],
|
|
@@ -80,26 +93,28 @@
|
|
|
80
93
|
}
|
|
81
94
|
},
|
|
82
95
|
"devDependencies": {
|
|
83
|
-
"@eslint/js": "^9.39.
|
|
84
|
-
"@testing-library/jest-dom": "^6.
|
|
96
|
+
"@eslint/js": "^9.39.5",
|
|
97
|
+
"@testing-library/jest-dom": "^6.10.0",
|
|
85
98
|
"@testing-library/react": "^16.3.2",
|
|
86
99
|
"@types/jest": "^30.0.0",
|
|
87
|
-
"@types/node": "^25.9.
|
|
88
|
-
"@types/react": "^19.2.
|
|
100
|
+
"@types/node": "^25.9.5",
|
|
101
|
+
"@types/react": "^19.2.17",
|
|
89
102
|
"@types/react-dom": "^19.2.3",
|
|
90
|
-
"eslint": "^9.39.
|
|
103
|
+
"eslint": "^9.39.5",
|
|
91
104
|
"globals": "^16.5.0",
|
|
92
105
|
"jest": "^30.4.2",
|
|
93
106
|
"jest-environment-jsdom": "^30.4.1",
|
|
94
|
-
"prettier": "^3.
|
|
95
|
-
"react": "^19.2.
|
|
96
|
-
"react-dom": "^19.2.
|
|
97
|
-
"ts-jest": "^29.4.
|
|
107
|
+
"prettier": "^3.9.6",
|
|
108
|
+
"react": "^19.2.8",
|
|
109
|
+
"react-dom": "^19.2.8",
|
|
110
|
+
"ts-jest": "^29.4.11",
|
|
98
111
|
"typescript": "^5.9.3",
|
|
99
|
-
"typescript-eslint": "^8.
|
|
112
|
+
"typescript-eslint": "^8.65.0"
|
|
100
113
|
},
|
|
101
114
|
"overrides": {
|
|
102
|
-
"js-yaml": "4.
|
|
115
|
+
"js-yaml": "4.3.0",
|
|
116
|
+
"minimatch": "10.2.5",
|
|
117
|
+
"brace-expansion": "5.0.9"
|
|
103
118
|
},
|
|
104
119
|
"publishConfig": {
|
|
105
120
|
"access": "public",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Synthetic RCM coding-review policy
|
|
2
|
+
|
|
3
|
+
**Document ID:** `synthetic-rcm-knee-policy`
|
|
4
|
+
**Version:** `2026.1-demo`
|
|
5
|
+
**Effective date:** `2026-07-01`
|
|
6
|
+
**Policy type:** coding-review
|
|
7
|
+
**Classification:** synthetic, non-PHI demonstration material
|
|
8
|
+
|
|
9
|
+
## Purpose and boundary
|
|
10
|
+
|
|
11
|
+
This document is intentionally synthetic. It is for a CareCompass demonstration
|
|
12
|
+
only and is not a payer policy, coding manual, claim-submission instruction, or
|
|
13
|
+
clinical protocol. It does not authorize a final coding, billing, diagnostic,
|
|
14
|
+
treatment, or claim-submission decision.
|
|
15
|
+
|
|
16
|
+
## Synthetic encounter DEMO-ENC-1042
|
|
17
|
+
|
|
18
|
+
For the synthetic outpatient encounter `DEMO-ENC-1042`, a qualified coder may
|
|
19
|
+
review a right-knee-pain candidate represented in the demo as `M25.561` when
|
|
20
|
+
the note supports persistent knee pain after activity and a focused
|
|
21
|
+
musculoskeletal evaluation. The assistant must present this only as a
|
|
22
|
+
human-review suggestion, cite this document, and state any documentation gaps.
|
|
23
|
+
|
|
24
|
+
If laterality, clinician assessment, medical necessity, procedure detail, or
|
|
25
|
+
other support is unclear, the assistant must abstain or escalate with no coding
|
|
26
|
+
recommendations. A qualified coder remains responsible for verifying all source
|
|
27
|
+
material and making any final decision.
|