@echothink-ui/quality 0.1.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 ADDED
@@ -0,0 +1,3 @@
1
+ # @echothink-ui/quality
2
+
3
+ Quality package for EchoThink voice dataset QC workflows, including reusable metric inspection, profile editing, job monitoring, decision badges, and routing timeline components.
@@ -0,0 +1,6 @@
1
+ export interface QCDecisionBadgeProps {
2
+ decision: "pass" | "fail" | "manual-review-required" | "overridden";
3
+ reason?: string;
4
+ className?: string;
5
+ }
6
+ export declare function QCDecisionBadge({ decision, reason, className }: QCDecisionBadgeProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import type { EthOperationalStatus } from "@echothink-ui/core";
2
+ export interface QCJob extends Record<string, unknown> {
3
+ id: string;
4
+ recordingId: string;
5
+ status: EthOperationalStatus;
6
+ queuedAt: string;
7
+ durationMs?: number;
8
+ profile?: string;
9
+ }
10
+ export interface QCJobMonitorProps {
11
+ jobs: QCJob[];
12
+ className?: string;
13
+ }
14
+ export declare function QCJobMonitor({ jobs, className }: QCJobMonitorProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,21 @@
1
+ export interface QCMetric extends Record<string, unknown> {
2
+ id: string;
3
+ label: string;
4
+ value: number | string;
5
+ unit?: string;
6
+ threshold?: {
7
+ min?: number;
8
+ max?: number;
9
+ };
10
+ status: "pass" | "fail" | "warning" | "manual-review";
11
+ }
12
+ export interface QCDecision {
13
+ status: "pass" | "fail" | "manual-review-required";
14
+ reasons?: string[];
15
+ }
16
+ export interface QCMetricsInspectorProps {
17
+ metrics: QCMetric[];
18
+ decision?: QCDecision;
19
+ className?: string;
20
+ }
21
+ export declare function QCMetricsInspector({ metrics, decision, className }: QCMetricsInspectorProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ export interface QCThreshold {
2
+ id: string;
3
+ label: string;
4
+ operator: string;
5
+ value: number | string;
6
+ }
7
+ export interface QCProfile {
8
+ id: string;
9
+ language: string;
10
+ thresholds: QCThreshold[];
11
+ }
12
+ export interface QCProfileEditorProps {
13
+ profile: QCProfile;
14
+ onChange?: (profile: QCProfile) => void;
15
+ onSubmit?: (profile: QCProfile) => void;
16
+ className?: string;
17
+ }
18
+ export declare function QCProfileEditor({ profile, onChange, onSubmit, className }: QCProfileEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import type { EthSeverity } from "@echothink-ui/core";
2
+ export interface QCRoutingEvent {
3
+ at: string;
4
+ label: string;
5
+ severity?: EthSeverity;
6
+ }
7
+ export interface QCRoutingTimelineProps {
8
+ events: QCRoutingEvent[];
9
+ className?: string;
10
+ }
11
+ export declare function QCRoutingTimeline({ events, className }: QCRoutingTimelineProps): import("react/jsx-runtime").JSX.Element;
package/dist/index.cjs ADDED
@@ -0,0 +1,371 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.tsx
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ QCDecisionBadge: () => QCDecisionBadge,
34
+ QCJobMonitor: () => QCJobMonitor,
35
+ QCMetricsInspector: () => QCMetricsInspector,
36
+ QCProfileEditor: () => QCProfileEditor,
37
+ QCRoutingTimeline: () => QCRoutingTimeline,
38
+ QualityComponentNames: () => QualityComponentNames
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/components/QCMetricsInspector.tsx
43
+ var React = __toESM(require("react"), 1);
44
+ var import_clsx = __toESM(require("clsx"), 1);
45
+ var import_core = require("@echothink-ui/core");
46
+ var import_data = require("@echothink-ui/data");
47
+ var import_jsx_runtime = require("react/jsx-runtime");
48
+ function QCMetricsInspector({ metrics, decision, className }) {
49
+ const columns = React.useMemo(
50
+ () => [
51
+ { key: "label", header: "Metric" },
52
+ {
53
+ key: "value",
54
+ header: "Value",
55
+ render: (metric) => `${metric.value}${metric.unit ? ` ${metric.unit}` : ""}`
56
+ },
57
+ {
58
+ key: "threshold",
59
+ header: "Threshold",
60
+ render: (metric) => thresholdLabel(metric.threshold)
61
+ },
62
+ {
63
+ key: "status",
64
+ header: "Status",
65
+ render: (metric) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.StatusDot, { status: metricStatus(metric.status), label: metricStatusLabel(metric.status) })
66
+ }
67
+ ],
68
+ []
69
+ );
70
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
71
+ import_core.Surface,
72
+ {
73
+ className: (0, import_clsx.default)("eth-quality-qc-metrics-inspector", className),
74
+ title: "QC metrics",
75
+ subtitle: decision ? "Automated decision available" : void 0,
76
+ "data-eth-component": "QCMetricsInspector",
77
+ children: [
78
+ decision ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "eth-quality-qc-metrics-inspector__decision", children: [
79
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.Badge, { severity: decisionSeverity(decision.status), children: decisionLabel(decision.status) }),
80
+ decision.reasons?.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: decision.reasons.map((reason) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: reason }, reason)) }) : null
81
+ ] }) : null,
82
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_data.DataTable, { rows: metrics, columns })
83
+ ]
84
+ }
85
+ );
86
+ }
87
+ function thresholdLabel(threshold) {
88
+ if (!threshold) return "-";
89
+ if (threshold.min !== void 0 && threshold.max !== void 0) {
90
+ return `${threshold.min} - ${threshold.max}`;
91
+ }
92
+ if (threshold.min !== void 0) return `>= ${threshold.min}`;
93
+ if (threshold.max !== void 0) return `<= ${threshold.max}`;
94
+ return "-";
95
+ }
96
+ function metricStatus(status) {
97
+ if (status === "pass") return "succeeded";
98
+ if (status === "fail") return "failed";
99
+ if (status === "warning") return "warning";
100
+ return "approval-required";
101
+ }
102
+ function metricStatusLabel(status) {
103
+ if (status === "manual-review") return "Manual review";
104
+ return status.charAt(0).toUpperCase() + status.slice(1);
105
+ }
106
+ function decisionSeverity(status) {
107
+ if (status === "pass") return "success";
108
+ if (status === "fail") return "danger";
109
+ return "warning";
110
+ }
111
+ function decisionLabel(status) {
112
+ if (status === "manual-review-required") return "Manual review required";
113
+ return status.charAt(0).toUpperCase() + status.slice(1);
114
+ }
115
+
116
+ // src/components/QCProfileEditor.tsx
117
+ var React2 = __toESM(require("react"), 1);
118
+ var import_clsx2 = __toESM(require("clsx"), 1);
119
+ var import_forms = require("@echothink-ui/forms");
120
+ var import_jsx_runtime2 = require("react/jsx-runtime");
121
+ var operatorOptions = [
122
+ { value: "lt", label: "Less than" },
123
+ { value: "lte", label: "Less than or equal" },
124
+ { value: "gt", label: "Greater than" },
125
+ { value: "gte", label: "Greater than or equal" },
126
+ { value: "eq", label: "Equals" },
127
+ { value: "between", label: "Between" }
128
+ ];
129
+ function QCProfileEditor({ profile, onChange, onSubmit, className }) {
130
+ const values = React2.useMemo(() => valuesFromProfile(profile), [profile]);
131
+ const rules = React2.useMemo(() => rulesFromProfile(profile), [profile]);
132
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: (0, import_clsx2.default)("eth-quality-qc-profile-editor", className), "data-eth-component": "QCProfileEditor", children: [
133
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
134
+ import_forms.ConfigForm,
135
+ {
136
+ title: "QC profile",
137
+ description: `Profile ${profile.id}`,
138
+ sections: [
139
+ {
140
+ id: "profile",
141
+ title: "Thresholds",
142
+ description: "Language and threshold values used by the automated QC job.",
143
+ fields: [
144
+ {
145
+ name: "language",
146
+ type: "text",
147
+ label: "Language",
148
+ required: true
149
+ },
150
+ ...profile.thresholds.flatMap((threshold) => [
151
+ {
152
+ name: operatorKey(threshold.id),
153
+ type: "select",
154
+ label: `${threshold.label} operator`,
155
+ options: operatorOptions
156
+ },
157
+ {
158
+ name: valueKey(threshold.id),
159
+ type: typeof threshold.value === "number" ? "number" : "text",
160
+ label: `${threshold.label} value`
161
+ }
162
+ ])
163
+ ]
164
+ }
165
+ ],
166
+ values,
167
+ submitLabel: "Save profile",
168
+ onChange: (nextValues) => onChange?.(profileFromValues(profile, nextValues)),
169
+ onSubmit: (nextValues) => onSubmit?.(profileFromValues(profile, nextValues))
170
+ }
171
+ ),
172
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
173
+ import_forms.RuleBuilder,
174
+ {
175
+ className: "eth-quality-qc-profile-editor__rules",
176
+ rules,
177
+ variables: profile.thresholds.map((threshold) => threshold.id),
178
+ onChange: (nextRules) => onChange?.(profileFromRules(profile, nextRules))
179
+ }
180
+ )
181
+ ] });
182
+ }
183
+ function valuesFromProfile(profile) {
184
+ return profile.thresholds.reduce(
185
+ (values, threshold) => ({
186
+ ...values,
187
+ [operatorKey(threshold.id)]: threshold.operator,
188
+ [valueKey(threshold.id)]: threshold.value
189
+ }),
190
+ { language: profile.language }
191
+ );
192
+ }
193
+ function profileFromValues(profile, values) {
194
+ return {
195
+ ...profile,
196
+ language: String(values.language ?? ""),
197
+ thresholds: profile.thresholds.map((threshold) => ({
198
+ ...threshold,
199
+ operator: String(values[operatorKey(threshold.id)] ?? threshold.operator),
200
+ value: normalizeThresholdValue(threshold.value, values[valueKey(threshold.id)])
201
+ }))
202
+ };
203
+ }
204
+ function rulesFromProfile(profile) {
205
+ return profile.thresholds.map((threshold) => ({
206
+ id: threshold.id,
207
+ when: {
208
+ kind: "leaf",
209
+ field: threshold.id,
210
+ op: threshold.operator,
211
+ value: threshold.value
212
+ },
213
+ then: {
214
+ type: "require-approval",
215
+ target: "manual-review"
216
+ }
217
+ }));
218
+ }
219
+ function profileFromRules(profile, rules) {
220
+ return {
221
+ ...profile,
222
+ thresholds: profile.thresholds.map((threshold) => {
223
+ const rule = rules.find((item) => item.id === threshold.id);
224
+ if (!rule) return threshold;
225
+ return {
226
+ ...threshold,
227
+ operator: rule.when.op ?? threshold.operator,
228
+ value: normalizeThresholdValue(threshold.value, rule.when.value)
229
+ };
230
+ })
231
+ };
232
+ }
233
+ function normalizeThresholdValue(previousValue, nextValue) {
234
+ if (typeof previousValue === "number") {
235
+ const numeric = Number(nextValue);
236
+ return Number.isNaN(numeric) ? previousValue : numeric;
237
+ }
238
+ return String(nextValue ?? "");
239
+ }
240
+ function operatorKey(id) {
241
+ return `${id}.operator`;
242
+ }
243
+ function valueKey(id) {
244
+ return `${id}.value`;
245
+ }
246
+
247
+ // src/components/QCJobMonitor.tsx
248
+ var React3 = __toESM(require("react"), 1);
249
+ var import_clsx3 = __toESM(require("clsx"), 1);
250
+ var import_core2 = require("@echothink-ui/core");
251
+ var import_admin = require("@echothink-ui/admin");
252
+ var import_data2 = require("@echothink-ui/data");
253
+ var import_jsx_runtime3 = require("react/jsx-runtime");
254
+ function QCJobMonitor({ jobs, className }) {
255
+ const columns = React3.useMemo(
256
+ () => [
257
+ { key: "recordingId", header: "Recording" },
258
+ {
259
+ key: "status",
260
+ header: "Status",
261
+ render: (job) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_core2.StatusDot, { status: job.status, label: job.status })
262
+ },
263
+ { key: "queuedAt", header: "Queued" },
264
+ { key: "durationMs", header: "Duration", render: (job) => formatDuration(job.durationMs) },
265
+ { key: "profile", header: "Profile", render: (job) => job.profile ?? "default" }
266
+ ],
267
+ []
268
+ );
269
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
270
+ import_core2.Surface,
271
+ {
272
+ className: (0, import_clsx3.default)("eth-quality-qc-job-monitor", className),
273
+ title: "QC job monitor",
274
+ "data-eth-component": "QCJobMonitor",
275
+ children: [
276
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_admin.JobQueuePanel, { queues: queuesFromJobs(jobs) }),
277
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_data2.DataTable, { rows: jobs, columns })
278
+ ]
279
+ }
280
+ );
281
+ }
282
+ function queuesFromJobs(jobs) {
283
+ const grouped = /* @__PURE__ */ new Map();
284
+ for (const job of jobs) {
285
+ const key = job.profile ?? "default";
286
+ grouped.set(key, [...grouped.get(key) ?? [], job]);
287
+ }
288
+ return Array.from(grouped.entries()).map(([profile, profileJobs]) => ({
289
+ id: profile,
290
+ name: `${profile} QC`,
291
+ depth: profileJobs.filter((job) => job.status === "queued").length,
292
+ processing: profileJobs.filter((job) => job.status === "running" || job.status === "in-progress").length,
293
+ failed: profileJobs.filter((job) => job.status === "failed").length,
294
+ retryRate: retryRate(profileJobs)
295
+ }));
296
+ }
297
+ function retryRate(jobs) {
298
+ if (!jobs.length) return 0;
299
+ return Math.round(jobs.filter((job) => job.status === "failed").length / jobs.length * 100);
300
+ }
301
+ function formatDuration(durationMs) {
302
+ if (durationMs === void 0) return "-";
303
+ if (durationMs < 1e3) return `${durationMs}ms`;
304
+ const seconds = Math.round(durationMs / 1e3);
305
+ return `${seconds}s`;
306
+ }
307
+
308
+ // src/components/QCDecisionBadge.tsx
309
+ var import_clsx4 = __toESM(require("clsx"), 1);
310
+ var import_core3 = require("@echothink-ui/core");
311
+ var import_jsx_runtime4 = require("react/jsx-runtime");
312
+ function QCDecisionBadge({ decision, reason, className }) {
313
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
314
+ "span",
315
+ {
316
+ className: (0, import_clsx4.default)("eth-quality-qc-decision-badge", className),
317
+ title: reason,
318
+ "data-eth-component": "QCDecisionBadge",
319
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_core3.Badge, { severity: decisionSeverity2(decision), children: decisionLabel2(decision) })
320
+ }
321
+ );
322
+ }
323
+ function decisionSeverity2(decision) {
324
+ if (decision === "pass") return "success";
325
+ if (decision === "fail") return "danger";
326
+ if (decision === "overridden") return "info";
327
+ return "warning";
328
+ }
329
+ function decisionLabel2(decision) {
330
+ if (decision === "manual-review-required") return "Manual review required";
331
+ return decision.charAt(0).toUpperCase() + decision.slice(1);
332
+ }
333
+
334
+ // src/components/QCRoutingTimeline.tsx
335
+ var import_clsx5 = __toESM(require("clsx"), 1);
336
+ var import_core4 = require("@echothink-ui/core");
337
+ var import_jsx_runtime5 = require("react/jsx-runtime");
338
+ function QCRoutingTimeline({ events, className }) {
339
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
340
+ import_core4.Surface,
341
+ {
342
+ className: (0, import_clsx5.default)("eth-quality-qc-routing-timeline", className),
343
+ title: "Routing timeline",
344
+ "data-eth-component": "QCRoutingTimeline",
345
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ol", { className: "eth-quality-qc-routing-timeline__events", children: events.map((event) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { children: [
346
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("time", { children: event.at }),
347
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_core4.Badge, { severity: event.severity ?? "info", children: event.severity ?? "info" }),
348
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: event.label })
349
+ ] }, `${event.at}-${event.label}`)) })
350
+ }
351
+ );
352
+ }
353
+
354
+ // src/index.tsx
355
+ var QualityComponentNames = [
356
+ "QCMetricsInspector",
357
+ "QCProfileEditor",
358
+ "QCJobMonitor",
359
+ "QCDecisionBadge",
360
+ "QCRoutingTimeline"
361
+ ];
362
+ // Annotate the CommonJS export names for ESM import in node:
363
+ 0 && (module.exports = {
364
+ QCDecisionBadge,
365
+ QCJobMonitor,
366
+ QCMetricsInspector,
367
+ QCProfileEditor,
368
+ QCRoutingTimeline,
369
+ QualityComponentNames
370
+ });
371
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx","../src/components/QCMetricsInspector.tsx","../src/components/QCProfileEditor.tsx","../src/components/QCJobMonitor.tsx","../src/components/QCDecisionBadge.tsx","../src/components/QCRoutingTimeline.tsx"],"sourcesContent":["export * from \"./components/QCMetricsInspector\";\nexport * from \"./components/QCProfileEditor\";\nexport * from \"./components/QCJobMonitor\";\nexport * from \"./components/QCDecisionBadge\";\nexport * from \"./components/QCRoutingTimeline\";\n\nexport const QualityComponentNames = [\n \"QCMetricsInspector\",\n \"QCProfileEditor\",\n \"QCJobMonitor\",\n \"QCDecisionBadge\",\n \"QCRoutingTimeline\"\n] as const;\nexport type QualityComponentName = (typeof QualityComponentNames)[number];\n","import * as React from \"react\";\nimport clsx from \"clsx\";\nimport { Badge, StatusDot, Surface } from \"@echothink-ui/core\";\nimport type { EthOperationalStatus, EthSeverity } from \"@echothink-ui/core\";\nimport { DataTable } from \"@echothink-ui/data\";\nimport type { DataColumn } from \"@echothink-ui/data\";\n\nexport interface QCMetric extends Record<string, unknown> {\n id: string;\n label: string;\n value: number | string;\n unit?: string;\n threshold?: { min?: number; max?: number };\n status: \"pass\" | \"fail\" | \"warning\" | \"manual-review\";\n}\n\nexport interface QCDecision {\n status: \"pass\" | \"fail\" | \"manual-review-required\";\n reasons?: string[];\n}\n\nexport interface QCMetricsInspectorProps {\n metrics: QCMetric[];\n decision?: QCDecision;\n className?: string;\n}\n\nexport function QCMetricsInspector({ metrics, decision, className }: QCMetricsInspectorProps) {\n const columns = React.useMemo<DataColumn<QCMetric>[]>(\n () => [\n { key: \"label\", header: \"Metric\" },\n {\n key: \"value\",\n header: \"Value\",\n render: (metric) => `${metric.value}${metric.unit ? ` ${metric.unit}` : \"\"}`\n },\n {\n key: \"threshold\",\n header: \"Threshold\",\n render: (metric) => thresholdLabel(metric.threshold)\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (metric) => (\n <StatusDot status={metricStatus(metric.status)} label={metricStatusLabel(metric.status)} />\n )\n }\n ],\n []\n );\n\n return (\n <Surface\n className={clsx(\"eth-quality-qc-metrics-inspector\", className)}\n title=\"QC metrics\"\n subtitle={decision ? \"Automated decision available\" : undefined}\n data-eth-component=\"QCMetricsInspector\"\n >\n {decision ? (\n <div className=\"eth-quality-qc-metrics-inspector__decision\">\n <Badge severity={decisionSeverity(decision.status)}>{decisionLabel(decision.status)}</Badge>\n {decision.reasons?.length ? (\n <ul>\n {decision.reasons.map((reason) => (\n <li key={reason}>{reason}</li>\n ))}\n </ul>\n ) : null}\n </div>\n ) : null}\n <DataTable rows={metrics} columns={columns} />\n </Surface>\n );\n}\n\nfunction thresholdLabel(threshold?: QCMetric[\"threshold\"]) {\n if (!threshold) return \"-\";\n if (threshold.min !== undefined && threshold.max !== undefined) {\n return `${threshold.min} - ${threshold.max}`;\n }\n if (threshold.min !== undefined) return `>= ${threshold.min}`;\n if (threshold.max !== undefined) return `<= ${threshold.max}`;\n return \"-\";\n}\n\nfunction metricStatus(status: QCMetric[\"status\"]): EthOperationalStatus {\n if (status === \"pass\") return \"succeeded\";\n if (status === \"fail\") return \"failed\";\n if (status === \"warning\") return \"warning\";\n return \"approval-required\";\n}\n\nfunction metricStatusLabel(status: QCMetric[\"status\"]) {\n if (status === \"manual-review\") return \"Manual review\";\n return status.charAt(0).toUpperCase() + status.slice(1);\n}\n\nfunction decisionSeverity(status: QCDecision[\"status\"]): EthSeverity {\n if (status === \"pass\") return \"success\";\n if (status === \"fail\") return \"danger\";\n return \"warning\";\n}\n\nfunction decisionLabel(status: QCDecision[\"status\"]) {\n if (status === \"manual-review-required\") return \"Manual review required\";\n return status.charAt(0).toUpperCase() + status.slice(1);\n}\n","import * as React from \"react\";\nimport clsx from \"clsx\";\nimport { ConfigForm, RuleBuilder } from \"@echothink-ui/forms\";\nimport type { FormValues, RuleDefinition } from \"@echothink-ui/forms\";\n\nexport interface QCThreshold {\n id: string;\n label: string;\n operator: string;\n value: number | string;\n}\n\nexport interface QCProfile {\n id: string;\n language: string;\n thresholds: QCThreshold[];\n}\n\nexport interface QCProfileEditorProps {\n profile: QCProfile;\n onChange?: (profile: QCProfile) => void;\n onSubmit?: (profile: QCProfile) => void;\n className?: string;\n}\n\nconst operatorOptions = [\n { value: \"lt\", label: \"Less than\" },\n { value: \"lte\", label: \"Less than or equal\" },\n { value: \"gt\", label: \"Greater than\" },\n { value: \"gte\", label: \"Greater than or equal\" },\n { value: \"eq\", label: \"Equals\" },\n { value: \"between\", label: \"Between\" }\n];\n\nexport function QCProfileEditor({ profile, onChange, onSubmit, className }: QCProfileEditorProps) {\n const values = React.useMemo(() => valuesFromProfile(profile), [profile]);\n const rules = React.useMemo(() => rulesFromProfile(profile), [profile]);\n\n return (\n <div className={clsx(\"eth-quality-qc-profile-editor\", className)} data-eth-component=\"QCProfileEditor\">\n <ConfigForm\n title=\"QC profile\"\n description={`Profile ${profile.id}`}\n sections={[\n {\n id: \"profile\",\n title: \"Thresholds\",\n description: \"Language and threshold values used by the automated QC job.\",\n fields: [\n {\n name: \"language\",\n type: \"text\",\n label: \"Language\",\n required: true\n },\n ...profile.thresholds.flatMap((threshold) => [\n {\n name: operatorKey(threshold.id),\n type: \"select\" as const,\n label: `${threshold.label} operator`,\n options: operatorOptions\n },\n {\n name: valueKey(threshold.id),\n type: typeof threshold.value === \"number\" ? (\"number\" as const) : (\"text\" as const),\n label: `${threshold.label} value`\n }\n ])\n ]\n }\n ]}\n values={values}\n submitLabel=\"Save profile\"\n onChange={(nextValues) => onChange?.(profileFromValues(profile, nextValues))}\n onSubmit={(nextValues) => onSubmit?.(profileFromValues(profile, nextValues))}\n />\n <RuleBuilder\n className=\"eth-quality-qc-profile-editor__rules\"\n rules={rules}\n variables={profile.thresholds.map((threshold) => threshold.id)}\n onChange={(nextRules) => onChange?.(profileFromRules(profile, nextRules))}\n />\n </div>\n );\n}\n\nfunction valuesFromProfile(profile: QCProfile): FormValues {\n return profile.thresholds.reduce<FormValues>(\n (values, threshold) => ({\n ...values,\n [operatorKey(threshold.id)]: threshold.operator,\n [valueKey(threshold.id)]: threshold.value\n }),\n { language: profile.language }\n );\n}\n\nfunction profileFromValues(profile: QCProfile, values: FormValues): QCProfile {\n return {\n ...profile,\n language: String(values.language ?? \"\"),\n thresholds: profile.thresholds.map((threshold) => ({\n ...threshold,\n operator: String(values[operatorKey(threshold.id)] ?? threshold.operator),\n value: normalizeThresholdValue(threshold.value, values[valueKey(threshold.id)])\n }))\n };\n}\n\nfunction rulesFromProfile(profile: QCProfile): RuleDefinition[] {\n return profile.thresholds.map((threshold) => ({\n id: threshold.id,\n when: {\n kind: \"leaf\",\n field: threshold.id,\n op: threshold.operator,\n value: threshold.value\n },\n then: {\n type: \"require-approval\",\n target: \"manual-review\"\n }\n }));\n}\n\nfunction profileFromRules(profile: QCProfile, rules: RuleDefinition[]): QCProfile {\n return {\n ...profile,\n thresholds: profile.thresholds.map((threshold) => {\n const rule = rules.find((item) => item.id === threshold.id);\n if (!rule) return threshold;\n return {\n ...threshold,\n operator: rule.when.op ?? threshold.operator,\n value: normalizeThresholdValue(threshold.value, rule.when.value)\n };\n })\n };\n}\n\nfunction normalizeThresholdValue(previousValue: string | number, nextValue: unknown) {\n if (typeof previousValue === \"number\") {\n const numeric = Number(nextValue);\n return Number.isNaN(numeric) ? previousValue : numeric;\n }\n return String(nextValue ?? \"\");\n}\n\nfunction operatorKey(id: string) {\n return `${id}.operator`;\n}\n\nfunction valueKey(id: string) {\n return `${id}.value`;\n}\n","import * as React from \"react\";\nimport clsx from \"clsx\";\nimport { StatusDot, Surface } from \"@echothink-ui/core\";\nimport type { EthOperationalStatus } from \"@echothink-ui/core\";\nimport { JobQueuePanel } from \"@echothink-ui/admin\";\nimport { DataTable } from \"@echothink-ui/data\";\nimport type { DataColumn } from \"@echothink-ui/data\";\n\nexport interface QCJob extends Record<string, unknown> {\n id: string;\n recordingId: string;\n status: EthOperationalStatus;\n queuedAt: string;\n durationMs?: number;\n profile?: string;\n}\n\nexport interface QCJobMonitorProps {\n jobs: QCJob[];\n className?: string;\n}\n\nexport function QCJobMonitor({ jobs, className }: QCJobMonitorProps) {\n const columns = React.useMemo<DataColumn<QCJob>[]>(\n () => [\n { key: \"recordingId\", header: \"Recording\" },\n {\n key: \"status\",\n header: \"Status\",\n render: (job) => <StatusDot status={job.status} label={job.status} />\n },\n { key: \"queuedAt\", header: \"Queued\" },\n { key: \"durationMs\", header: \"Duration\", render: (job) => formatDuration(job.durationMs) },\n { key: \"profile\", header: \"Profile\", render: (job) => job.profile ?? \"default\" }\n ],\n []\n );\n\n return (\n <Surface\n className={clsx(\"eth-quality-qc-job-monitor\", className)}\n title=\"QC job monitor\"\n data-eth-component=\"QCJobMonitor\"\n >\n <JobQueuePanel queues={queuesFromJobs(jobs)} />\n <DataTable rows={jobs} columns={columns} />\n </Surface>\n );\n}\n\nfunction queuesFromJobs(jobs: QCJob[]) {\n const grouped = new Map<string, QCJob[]>();\n for (const job of jobs) {\n const key = job.profile ?? \"default\";\n grouped.set(key, [...(grouped.get(key) ?? []), job]);\n }\n return Array.from(grouped.entries()).map(([profile, profileJobs]) => ({\n id: profile,\n name: `${profile} QC`,\n depth: profileJobs.filter((job) => job.status === \"queued\").length,\n processing: profileJobs.filter((job) => job.status === \"running\" || job.status === \"in-progress\").length,\n failed: profileJobs.filter((job) => job.status === \"failed\").length,\n retryRate: retryRate(profileJobs)\n }));\n}\n\nfunction retryRate(jobs: QCJob[]) {\n if (!jobs.length) return 0;\n return Math.round((jobs.filter((job) => job.status === \"failed\").length / jobs.length) * 100);\n}\n\nfunction formatDuration(durationMs?: number) {\n if (durationMs === undefined) return \"-\";\n if (durationMs < 1000) return `${durationMs}ms`;\n const seconds = Math.round(durationMs / 1000);\n return `${seconds}s`;\n}\n","import * as React from \"react\";\nimport clsx from \"clsx\";\nimport { Badge } from \"@echothink-ui/core\";\nimport type { EthSeverity } from \"@echothink-ui/core\";\n\nexport interface QCDecisionBadgeProps {\n decision: \"pass\" | \"fail\" | \"manual-review-required\" | \"overridden\";\n reason?: string;\n className?: string;\n}\n\nexport function QCDecisionBadge({ decision, reason, className }: QCDecisionBadgeProps) {\n return (\n <span\n className={clsx(\"eth-quality-qc-decision-badge\", className)}\n title={reason}\n data-eth-component=\"QCDecisionBadge\"\n >\n <Badge severity={decisionSeverity(decision)}>{decisionLabel(decision)}</Badge>\n </span>\n );\n}\n\nfunction decisionSeverity(decision: QCDecisionBadgeProps[\"decision\"]): EthSeverity {\n if (decision === \"pass\") return \"success\";\n if (decision === \"fail\") return \"danger\";\n if (decision === \"overridden\") return \"info\";\n return \"warning\";\n}\n\nfunction decisionLabel(decision: QCDecisionBadgeProps[\"decision\"]) {\n if (decision === \"manual-review-required\") return \"Manual review required\";\n return decision.charAt(0).toUpperCase() + decision.slice(1);\n}\n","import * as React from \"react\";\nimport clsx from \"clsx\";\nimport { Badge, Surface } from \"@echothink-ui/core\";\nimport type { EthSeverity } from \"@echothink-ui/core\";\n\nexport interface QCRoutingEvent {\n at: string;\n label: string;\n severity?: EthSeverity;\n}\n\nexport interface QCRoutingTimelineProps {\n events: QCRoutingEvent[];\n className?: string;\n}\n\nexport function QCRoutingTimeline({ events, className }: QCRoutingTimelineProps) {\n return (\n <Surface\n className={clsx(\"eth-quality-qc-routing-timeline\", className)}\n title=\"Routing timeline\"\n data-eth-component=\"QCRoutingTimeline\"\n >\n <ol className=\"eth-quality-qc-routing-timeline__events\">\n {events.map((event) => (\n <li key={`${event.at}-${event.label}`}>\n <time>{event.at}</time>\n <Badge severity={event.severity ?? \"info\"}>{event.severity ?? \"info\"}</Badge>\n <span>{event.label}</span>\n </li>\n ))}\n </ol>\n </Surface>\n );\n}\n"],"mappings":"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","names":["clsx","React","import_clsx","import_jsx_runtime","clsx","React","import_clsx","import_core","import_data","import_jsx_runtime","clsx","import_clsx","import_core","import_jsx_runtime","clsx","decisionSeverity","decisionLabel","import_clsx","import_core","import_jsx_runtime","clsx"]}
@@ -0,0 +1,7 @@
1
+ export * from "./components/QCMetricsInspector";
2
+ export * from "./components/QCProfileEditor";
3
+ export * from "./components/QCJobMonitor";
4
+ export * from "./components/QCDecisionBadge";
5
+ export * from "./components/QCRoutingTimeline";
6
+ export declare const QualityComponentNames: readonly ["QCMetricsInspector", "QCProfileEditor", "QCJobMonitor", "QCDecisionBadge", "QCRoutingTimeline"];
7
+ export type QualityComponentName = (typeof QualityComponentNames)[number];