@things-factory/integration-label-studio 9.1.19

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.
Files changed (152) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/EXTERNAL_DATA_SOURCING.md +484 -0
  3. package/IMPLEMENTATION_GUIDE.md +469 -0
  4. package/INTEGRATION.md +279 -0
  5. package/README.md +1014 -0
  6. package/SETUP_GUIDE.md +577 -0
  7. package/TEST_GUIDE.md +387 -0
  8. package/UI_CUSTOMIZATION.md +395 -0
  9. package/USER_SYNC_GUIDE.md +514 -0
  10. package/client/bootstrap.ts +1 -0
  11. package/client/index.ts +1 -0
  12. package/client/label-studio-label-page.ts +52 -0
  13. package/client/label-studio-project-create.ts +216 -0
  14. package/client/label-studio-project-list.ts +214 -0
  15. package/client/label-studio-wrapper.ts +294 -0
  16. package/client/route.ts +15 -0
  17. package/client/tsconfig.json +13 -0
  18. package/config/config.development.js +124 -0
  19. package/config/config.production.js +182 -0
  20. package/dist-client/bootstrap.d.ts +1 -0
  21. package/dist-client/bootstrap.js +2 -0
  22. package/dist-client/bootstrap.js.map +1 -0
  23. package/dist-client/index.d.ts +1 -0
  24. package/dist-client/index.js +2 -0
  25. package/dist-client/index.js.map +1 -0
  26. package/dist-client/label-studio-label-page.d.ts +8 -0
  27. package/dist-client/label-studio-label-page.js +54 -0
  28. package/dist-client/label-studio-label-page.js.map +1 -0
  29. package/dist-client/label-studio-project-create.d.ts +16 -0
  30. package/dist-client/label-studio-project-create.js +235 -0
  31. package/dist-client/label-studio-project-create.js.map +1 -0
  32. package/dist-client/label-studio-project-list.d.ts +16 -0
  33. package/dist-client/label-studio-project-list.js +222 -0
  34. package/dist-client/label-studio-project-list.js.map +1 -0
  35. package/dist-client/label-studio-wrapper.d.ts +57 -0
  36. package/dist-client/label-studio-wrapper.js +304 -0
  37. package/dist-client/label-studio-wrapper.js.map +1 -0
  38. package/dist-client/route.d.ts +1 -0
  39. package/dist-client/route.js +14 -0
  40. package/dist-client/route.js.map +1 -0
  41. package/dist-client/tsconfig.tsbuildinfo +1 -0
  42. package/dist-server/controller/label-studio-role-mapper.d.ts +35 -0
  43. package/dist-server/controller/label-studio-role-mapper.js +65 -0
  44. package/dist-server/controller/label-studio-role-mapper.js.map +1 -0
  45. package/dist-server/controller/user-provisioning-service.d.ts +66 -0
  46. package/dist-server/controller/user-provisioning-service.js +264 -0
  47. package/dist-server/controller/user-provisioning-service.js.map +1 -0
  48. package/dist-server/index.d.ts +7 -0
  49. package/dist-server/index.js +19 -0
  50. package/dist-server/index.js.map +1 -0
  51. package/dist-server/route/label-studio-sso.d.ts +2 -0
  52. package/dist-server/route/label-studio-sso.js +156 -0
  53. package/dist-server/route/label-studio-sso.js.map +1 -0
  54. package/dist-server/route/webhook.d.ts +65 -0
  55. package/dist-server/route/webhook.js +248 -0
  56. package/dist-server/route/webhook.js.map +1 -0
  57. package/dist-server/route.d.ts +1 -0
  58. package/dist-server/route.js +21 -0
  59. package/dist-server/route.js.map +1 -0
  60. package/dist-server/service/ai-prediction-service.d.ts +27 -0
  61. package/dist-server/service/ai-prediction-service.js +222 -0
  62. package/dist-server/service/ai-prediction-service.js.map +1 -0
  63. package/dist-server/service/dataset-labeling-integration.d.ts +44 -0
  64. package/dist-server/service/dataset-labeling-integration.js +512 -0
  65. package/dist-server/service/dataset-labeling-integration.js.map +1 -0
  66. package/dist-server/service/external-data-source-service.d.ts +78 -0
  67. package/dist-server/service/external-data-source-service.js +415 -0
  68. package/dist-server/service/external-data-source-service.js.map +1 -0
  69. package/dist-server/service/index.d.ts +12 -0
  70. package/dist-server/service/index.js +27 -0
  71. package/dist-server/service/index.js.map +1 -0
  72. package/dist-server/service/label-studio-sso-service.d.ts +38 -0
  73. package/dist-server/service/label-studio-sso-service.js +98 -0
  74. package/dist-server/service/label-studio-sso-service.js.map +1 -0
  75. package/dist-server/service/ml/ml-backend-service.d.ts +23 -0
  76. package/dist-server/service/ml/ml-backend-service.js +153 -0
  77. package/dist-server/service/ml/ml-backend-service.js.map +1 -0
  78. package/dist-server/service/prediction/prediction-management.d.ts +32 -0
  79. package/dist-server/service/prediction/prediction-management.js +299 -0
  80. package/dist-server/service/prediction/prediction-management.js.map +1 -0
  81. package/dist-server/service/project/project-management.d.ts +36 -0
  82. package/dist-server/service/project/project-management.js +309 -0
  83. package/dist-server/service/project/project-management.js.map +1 -0
  84. package/dist-server/service/task/task-management.d.ts +42 -0
  85. package/dist-server/service/task/task-management.js +372 -0
  86. package/dist-server/service/task/task-management.js.map +1 -0
  87. package/dist-server/service/user-provisioning/user-sync-mutation.d.ts +28 -0
  88. package/dist-server/service/user-provisioning/user-sync-mutation.js +111 -0
  89. package/dist-server/service/user-provisioning/user-sync-mutation.js.map +1 -0
  90. package/dist-server/service/webhook/webhook-management.d.ts +21 -0
  91. package/dist-server/service/webhook/webhook-management.js +134 -0
  92. package/dist-server/service/webhook/webhook-management.js.map +1 -0
  93. package/dist-server/tsconfig.tsbuildinfo +1 -0
  94. package/dist-server/types/dataset-labeling-types.d.ts +71 -0
  95. package/dist-server/types/dataset-labeling-types.js +259 -0
  96. package/dist-server/types/dataset-labeling-types.js.map +1 -0
  97. package/dist-server/types/label-studio-types.d.ts +128 -0
  98. package/dist-server/types/label-studio-types.js +494 -0
  99. package/dist-server/types/label-studio-types.js.map +1 -0
  100. package/dist-server/types/prediction-types.d.ts +39 -0
  101. package/dist-server/types/prediction-types.js +121 -0
  102. package/dist-server/types/prediction-types.js.map +1 -0
  103. package/dist-server/utils/annotation-exporter.d.ts +104 -0
  104. package/dist-server/utils/annotation-exporter.js +261 -0
  105. package/dist-server/utils/annotation-exporter.js.map +1 -0
  106. package/dist-server/utils/label-config-builder.d.ts +117 -0
  107. package/dist-server/utils/label-config-builder.js +286 -0
  108. package/dist-server/utils/label-config-builder.js.map +1 -0
  109. package/dist-server/utils/label-studio-api-client.d.ts +180 -0
  110. package/dist-server/utils/label-studio-api-client.js +401 -0
  111. package/dist-server/utils/label-studio-api-client.js.map +1 -0
  112. package/dist-server/utils/media-url-extractor.d.ts +45 -0
  113. package/dist-server/utils/media-url-extractor.js +152 -0
  114. package/dist-server/utils/media-url-extractor.js.map +1 -0
  115. package/dist-server/utils/task-transformer.d.ts +108 -0
  116. package/dist-server/utils/task-transformer.js +260 -0
  117. package/dist-server/utils/task-transformer.js.map +1 -0
  118. package/package.json +47 -0
  119. package/server/SERVER_STRUCTURE.md +351 -0
  120. package/server/controller/label-studio-role-mapper.ts +76 -0
  121. package/server/controller/user-provisioning-service.ts +340 -0
  122. package/server/index.ts +19 -0
  123. package/server/route/label-studio-sso.ts +194 -0
  124. package/server/route/webhook.ts +304 -0
  125. package/server/route.ts +35 -0
  126. package/server/service/ai-prediction-service.ts +239 -0
  127. package/server/service/dataset-labeling-integration.ts +590 -0
  128. package/server/service/external-data-source-service.ts +438 -0
  129. package/server/service/index.ts +24 -0
  130. package/server/service/label-studio-sso-service.ts +108 -0
  131. package/server/service/labeling-scenario-service.ts.deprecated +566 -0
  132. package/server/service/ml/ml-backend-service.ts +127 -0
  133. package/server/service/prediction/prediction-management.ts +281 -0
  134. package/server/service/project/project-management.ts +284 -0
  135. package/server/service/task/task-management.ts +363 -0
  136. package/server/service/user-provisioning/user-sync-mutation.ts +80 -0
  137. package/server/service/webhook/webhook-management.ts +109 -0
  138. package/server/tsconfig.json +11 -0
  139. package/server/types/dataset-labeling-types.ts +181 -0
  140. package/server/types/global.d.ts +23 -0
  141. package/server/types/label-studio-types.ts +346 -0
  142. package/server/types/prediction-types.ts +86 -0
  143. package/server/types/scenario-types.ts.deprecated +362 -0
  144. package/server/utils/annotation-exporter.ts +340 -0
  145. package/server/utils/label-config-builder.ts +340 -0
  146. package/server/utils/label-studio-api-client.ts +487 -0
  147. package/server/utils/media-url-extractor.ts +193 -0
  148. package/server/utils/task-transformer.ts +342 -0
  149. package/test-ai-prediction.js +268 -0
  150. package/test-dataset-integration.js +449 -0
  151. package/test-simple.js +89 -0
  152. package/things-factory.config.js +12 -0
@@ -0,0 +1,512 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatasetLabelingIntegration = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const ai_inference_1 = require("@things-factory/ai-inference");
8
+ const dataset_1 = require("@things-factory/dataset");
9
+ const label_studio_api_client_js_1 = require("../utils/label-studio-api-client.js");
10
+ const dataset_labeling_types_js_1 = require("../types/dataset-labeling-types.js");
11
+ const prediction_types_js_1 = require("../types/prediction-types.js");
12
+ const media_url_extractor_js_1 = require("../utils/media-url-extractor.js");
13
+ /**
14
+ * Dataset Labeling Integration Service
15
+ *
16
+ * Integrates Things-Factory Dataset module with Label Studio for AI-assisted labeling
17
+ *
18
+ * Features:
19
+ * - Create Label Studio tasks from DataSamples
20
+ * - Auto-generate AI predictions for labeling tasks
21
+ * - Sync human annotations back to DataSamples
22
+ * - Track labeling progress and status
23
+ */
24
+ let DatasetLabelingIntegration = class DatasetLabelingIntegration {
25
+ /**
26
+ * Create Label Studio labeling tasks from DataSamples in a DataSet
27
+ * Optionally generates AI predictions automatically
28
+ */
29
+ async createLabelingTasksFromDataset(input, context) {
30
+ const { domain } = context.state;
31
+ console.log(`[Dataset Labeling] Creating tasks for DataSet ${input.dataSetId} in LS project ${input.projectId}`);
32
+ try {
33
+ // 1. Get DataSet configuration
34
+ const dataSet = await (0, shell_1.getRepository)(dataset_1.DataSet).findOne({
35
+ where: { domain: { id: domain.id }, id: input.dataSetId }
36
+ });
37
+ if (!dataSet) {
38
+ throw new Error(`DataSet not found: ${input.dataSetId}`);
39
+ }
40
+ // 2. Query DataSamples
41
+ const queryBuilder = (0, shell_1.getRepository)(dataset_1.DataSample)
42
+ .createQueryBuilder('sample')
43
+ .where('sample.domainId = :domainId', { domainId: domain.id })
44
+ .andWhere('sample.dataSetId = :dataSetId', { dataSetId: input.dataSetId });
45
+ if (input.sinceDate) {
46
+ queryBuilder.andWhere('sample.collectedAt >= :sinceDate', { sinceDate: input.sinceDate });
47
+ }
48
+ if (input.limit) {
49
+ queryBuilder.limit(input.limit);
50
+ }
51
+ queryBuilder.orderBy('sample.collectedAt', 'DESC');
52
+ const samples = await queryBuilder.getMany();
53
+ console.log(`[Dataset Labeling] Found ${samples.length} DataSamples to process`);
54
+ // 3. Create tasks for each sample
55
+ const result = {
56
+ totalSamples: samples.length,
57
+ tasksCreated: 0,
58
+ tasksFailed: 0,
59
+ tasksSkipped: 0,
60
+ taskIds: [],
61
+ predictionsCreated: 0
62
+ };
63
+ // Get base URL for converting relative attachment paths to absolute URLs
64
+ const baseUrl = (0, media_url_extractor_js_1.getBaseUrl)(context);
65
+ for (const sample of samples) {
66
+ try {
67
+ // Extract all media URLs from DataSample (image, video, audio)
68
+ const mediaUrls = (0, media_url_extractor_js_1.extractMediaUrls)(sample, baseUrl);
69
+ if (Object.keys(mediaUrls).length === 0) {
70
+ console.log(`[Dataset Labeling] Skipping sample ${sample.id}: no media found`);
71
+ result.tasksSkipped++;
72
+ continue;
73
+ }
74
+ // Create Label Studio task with all media URLs
75
+ const task = await label_studio_api_client_js_1.labelStudioApi.createTask(input.projectId, {
76
+ data: mediaUrls,
77
+ meta: {
78
+ dataSampleId: sample.id,
79
+ dataSampleName: sample.name,
80
+ dataSetId: input.dataSetId,
81
+ dataSetName: dataSet.name,
82
+ collectedAt: sample.collectedAt?.toISOString(),
83
+ source: 'things-factory-dataset'
84
+ }
85
+ });
86
+ result.taskIds.push(task.id);
87
+ result.tasksCreated++;
88
+ console.log(`[Dataset Labeling] Created task ${task.id} for sample ${sample.id}`);
89
+ // 4. Auto-generate AI prediction if requested (for image detection)
90
+ if (input.autoGeneratePredictions && mediaUrls.image) {
91
+ try {
92
+ const modelClient = input.modelId
93
+ ? ai_inference_1.AIModelClientFactory.getClient(input.modelId)
94
+ : ai_inference_1.AIModelClientFactory.getDefaultClient();
95
+ // Use first image URL if multiple images
96
+ const imageUrl = Array.isArray(mediaUrls.image) ? mediaUrls.image[0] : mediaUrls.image;
97
+ const objects = await modelClient.detectObjects(imageUrl, {
98
+ confidenceThreshold: input.confidenceThreshold
99
+ });
100
+ if (objects.length > 0) {
101
+ const labelStudioResult = this.convertToLabelStudioFormat(objects);
102
+ const avgConfidence = objects.reduce((sum, obj) => sum + obj.confidence, 0) / objects.length;
103
+ await label_studio_api_client_js_1.labelStudioApi.createPrediction({
104
+ task: task.id,
105
+ result: labelStudioResult,
106
+ score: avgConfidence,
107
+ model_version: input.modelId || 'default-model-v1.0'
108
+ });
109
+ result.predictionsCreated = (result.predictionsCreated || 0) + 1;
110
+ console.log(`[Dataset Labeling] Created prediction for task ${task.id} (${objects.length} objects)`);
111
+ }
112
+ }
113
+ catch (predError) {
114
+ console.error(`[Dataset Labeling] Failed to create prediction for task ${task.id}:`, predError);
115
+ // Continue even if prediction fails
116
+ }
117
+ }
118
+ }
119
+ catch (taskError) {
120
+ console.error(`[Dataset Labeling] Failed to create task for sample ${sample.id}:`, taskError);
121
+ result.tasksFailed++;
122
+ }
123
+ }
124
+ console.log(`[Dataset Labeling] Completed: ${result.tasksCreated} created, ${result.tasksFailed} failed, ${result.tasksSkipped} skipped`);
125
+ return result;
126
+ }
127
+ catch (error) {
128
+ console.error('[Dataset Labeling] Failed to create tasks:', error);
129
+ throw new Error(`Failed to create labeling tasks: ${error.message}`);
130
+ }
131
+ }
132
+ /**
133
+ * Sync completed annotations from Label Studio back to DataSamples
134
+ */
135
+ async syncAnnotationsToDataset(input, context) {
136
+ const { domain } = context.state;
137
+ console.log(`[Dataset Labeling] Syncing annotations from LS project ${input.projectId} to DataSet ${input.dataSetId}`);
138
+ try {
139
+ // 1. Get all tasks from Label Studio project
140
+ const tasksResponse = await label_studio_api_client_js_1.labelStudioApi.getTasks(input.projectId, {
141
+ page_size: 1000 // Adjust as needed
142
+ });
143
+ const tasks = tasksResponse.tasks || tasksResponse.results || [];
144
+ console.log(`[Dataset Labeling] Found ${tasks.length} tasks in Label Studio`);
145
+ // 2. Filter tasks related to this DataSet
146
+ const dataSetTasks = tasks.filter((task) => task.meta?.dataSetId === input.dataSetId);
147
+ console.log(`[Dataset Labeling] ${dataSetTasks.length} tasks belong to DataSet ${input.dataSetId}`);
148
+ const result = {
149
+ totalAnnotations: 0,
150
+ samplesUpdated: 0,
151
+ updatesFailed: 0,
152
+ skipped: 0
153
+ };
154
+ // 3. Process each task
155
+ for (const task of dataSetTasks) {
156
+ const dataSampleId = task.meta?.dataSampleId;
157
+ if (!dataSampleId) {
158
+ result.skipped++;
159
+ continue;
160
+ }
161
+ // Get annotations for this task
162
+ const annotations = task.annotations || [];
163
+ if (annotations.length === 0) {
164
+ result.skipped++;
165
+ continue;
166
+ }
167
+ // Filter by completion status if requested
168
+ let relevantAnnotations = annotations;
169
+ if (input.completedOnly) {
170
+ relevantAnnotations = annotations.filter((ann) => ann.was_cancelled === false);
171
+ }
172
+ // Filter by date if requested
173
+ if (input.sinceDate) {
174
+ relevantAnnotations = relevantAnnotations.filter((ann) => new Date(ann.updated_at) >= input.sinceDate);
175
+ }
176
+ if (relevantAnnotations.length === 0) {
177
+ result.skipped++;
178
+ continue;
179
+ }
180
+ result.totalAnnotations += relevantAnnotations.length;
181
+ try {
182
+ // 4. Get DataSample
183
+ const sample = await (0, shell_1.getRepository)(dataset_1.DataSample).findOne({
184
+ where: { domain: { id: domain.id }, id: dataSampleId }
185
+ });
186
+ if (!sample) {
187
+ console.log(`[Dataset Labeling] DataSample not found: ${dataSampleId}`);
188
+ result.updatesFailed++;
189
+ continue;
190
+ }
191
+ // 5. Convert annotations to judgment data
192
+ const latestAnnotation = relevantAnnotations[relevantAnnotations.length - 1];
193
+ const judgment = this.convertAnnotationToJudgment(latestAnnotation, task);
194
+ // 6. Update DataSample
195
+ sample.judgment = judgment;
196
+ // Optionally update OOC/OOS flags based on annotation
197
+ // This is application-specific logic
198
+ if (judgment.defectsFound > 0) {
199
+ sample.ooc = true;
200
+ }
201
+ await (0, shell_1.getRepository)(dataset_1.DataSample).save(sample);
202
+ result.samplesUpdated++;
203
+ console.log(`[Dataset Labeling] Updated DataSample ${dataSampleId} with annotation ${latestAnnotation.id}`);
204
+ }
205
+ catch (updateError) {
206
+ console.error(`[Dataset Labeling] Failed to update DataSample ${dataSampleId}:`, updateError);
207
+ result.updatesFailed++;
208
+ }
209
+ }
210
+ console.log(`[Dataset Labeling] Sync completed: ${result.samplesUpdated} updated, ${result.updatesFailed} failed, ${result.skipped} skipped`);
211
+ return result;
212
+ }
213
+ catch (error) {
214
+ console.error('[Dataset Labeling] Failed to sync annotations:', error);
215
+ throw new Error(`Failed to sync annotations: ${error.message}`);
216
+ }
217
+ }
218
+ /**
219
+ * Generate AI predictions for existing DataSet samples
220
+ */
221
+ async generatePredictionsForDataset(input, context) {
222
+ const { domain } = context.state;
223
+ console.log(`[Dataset Labeling] Generating predictions for DataSet ${input.dataSetId} in LS project ${input.projectId}`);
224
+ try {
225
+ // 1. Get all tasks from Label Studio project
226
+ const tasksResponse = await label_studio_api_client_js_1.labelStudioApi.getTasks(input.projectId, {
227
+ page_size: 1000
228
+ });
229
+ const tasks = tasksResponse.tasks || tasksResponse.results || [];
230
+ const dataSetTasks = tasks.filter((task) => task.meta?.dataSetId === input.dataSetId);
231
+ console.log(`[Dataset Labeling] Found ${dataSetTasks.length} tasks for DataSet`);
232
+ const result = {
233
+ total: dataSetTasks.length,
234
+ succeeded: 0,
235
+ failed: 0,
236
+ results: [],
237
+ modelVersion: input.modelId || 'default-model-v1.0'
238
+ };
239
+ // 2. Get AI model client
240
+ const modelClient = input.modelId
241
+ ? ai_inference_1.AIModelClientFactory.getClient(input.modelId)
242
+ : ai_inference_1.AIModelClientFactory.getDefaultClient();
243
+ // 3. Process tasks in batches
244
+ const BATCH_SIZE = 5;
245
+ for (let i = 0; i < dataSetTasks.length; i += BATCH_SIZE) {
246
+ const batch = dataSetTasks.slice(i, i + BATCH_SIZE);
247
+ const batchPromises = batch.map(async (task) => {
248
+ try {
249
+ // Check if prediction already exists
250
+ if (!input.forceRegenerate && task.predictions && task.predictions.length > 0) {
251
+ result.results.push({
252
+ taskId: task.id,
253
+ success: true,
254
+ objectCount: 0,
255
+ error: 'Prediction already exists (skipped)'
256
+ });
257
+ return;
258
+ }
259
+ const imageUrl = task.data?.image;
260
+ if (!imageUrl) {
261
+ result.failed++;
262
+ result.results.push({
263
+ taskId: task.id,
264
+ success: false,
265
+ objectCount: 0,
266
+ error: 'No image URL in task data'
267
+ });
268
+ return;
269
+ }
270
+ // Run AI inference
271
+ const objects = await modelClient.detectObjects(imageUrl, {
272
+ confidenceThreshold: input.confidenceThreshold
273
+ });
274
+ if (objects.length === 0) {
275
+ result.succeeded++;
276
+ result.results.push({
277
+ taskId: task.id,
278
+ success: true,
279
+ objectCount: 0,
280
+ error: 'No objects detected'
281
+ });
282
+ return;
283
+ }
284
+ // Create prediction
285
+ const labelStudioResult = this.convertToLabelStudioFormat(objects);
286
+ const avgConfidence = objects.reduce((sum, obj) => sum + obj.confidence, 0) / objects.length;
287
+ const prediction = await label_studio_api_client_js_1.labelStudioApi.createPrediction({
288
+ task: task.id,
289
+ result: labelStudioResult,
290
+ score: avgConfidence,
291
+ model_version: result.modelVersion
292
+ });
293
+ result.succeeded++;
294
+ result.results.push({
295
+ taskId: task.id,
296
+ predictionId: prediction.id,
297
+ success: true,
298
+ objectCount: objects.length,
299
+ avgConfidence
300
+ });
301
+ console.log(`[Dataset Labeling] Created prediction for task ${task.id} (${objects.length} objects)`);
302
+ }
303
+ catch (error) {
304
+ result.failed++;
305
+ result.results.push({
306
+ taskId: task.id,
307
+ success: false,
308
+ objectCount: 0,
309
+ error: error.message
310
+ });
311
+ }
312
+ });
313
+ await Promise.all(batchPromises);
314
+ }
315
+ console.log(`[Dataset Labeling] Prediction generation completed: ${result.succeeded}/${result.total} succeeded`);
316
+ return result;
317
+ }
318
+ catch (error) {
319
+ console.error('[Dataset Labeling] Failed to generate predictions:', error);
320
+ throw new Error(`Failed to generate predictions: ${error.message}`);
321
+ }
322
+ }
323
+ /**
324
+ * Query labeling status for a DataSet
325
+ */
326
+ async datasetLabelingStatus(input, context) {
327
+ const { domain } = context.state;
328
+ try {
329
+ // 1. Get DataSet
330
+ const dataSet = await (0, shell_1.getRepository)(dataset_1.DataSet).findOne({
331
+ where: { domain: { id: domain.id }, id: input.dataSetId }
332
+ });
333
+ if (!dataSet) {
334
+ throw new Error(`DataSet not found: ${input.dataSetId}`);
335
+ }
336
+ // 2. Count total DataSamples
337
+ const totalSamples = await (0, shell_1.getRepository)(dataset_1.DataSample).count({
338
+ where: { domain: { id: domain.id }, dataSetId: input.dataSetId }
339
+ });
340
+ // 3. Get Label Studio tasks if projectId provided
341
+ let tasksCreated = 0;
342
+ let withPredictions = 0;
343
+ let withAnnotations = 0;
344
+ let annotationsCompleted = 0;
345
+ if (input.projectId) {
346
+ const tasksResponse = await label_studio_api_client_js_1.labelStudioApi.getTasks(input.projectId, {
347
+ page_size: 1000
348
+ });
349
+ const tasks = tasksResponse.tasks || tasksResponse.results || [];
350
+ const dataSetTasks = tasks.filter((task) => task.meta?.dataSetId === input.dataSetId);
351
+ tasksCreated = dataSetTasks.length;
352
+ for (const task of dataSetTasks) {
353
+ if (task.predictions && task.predictions.length > 0) {
354
+ withPredictions++;
355
+ }
356
+ if (task.annotations && task.annotations.length > 0) {
357
+ withAnnotations++;
358
+ const completedAnnotations = task.annotations.filter((ann) => ann.was_cancelled === false);
359
+ if (completedAnnotations.length > 0) {
360
+ annotationsCompleted++;
361
+ }
362
+ }
363
+ }
364
+ }
365
+ const notProcessed = totalSamples - tasksCreated;
366
+ const completionRate = totalSamples > 0 ? annotationsCompleted / totalSamples : 0;
367
+ return {
368
+ dataSetId: input.dataSetId,
369
+ dataSetName: dataSet.name,
370
+ totalSamples,
371
+ tasksCreated,
372
+ withPredictions,
373
+ withAnnotations,
374
+ annotationsCompleted,
375
+ notProcessed,
376
+ completionRate,
377
+ projectId: input.projectId,
378
+ lastSyncedAt: new Date()
379
+ };
380
+ }
381
+ catch (error) {
382
+ console.error('[Dataset Labeling] Failed to get status:', error);
383
+ throw new Error(`Failed to get labeling status: ${error.message}`);
384
+ }
385
+ }
386
+ /**
387
+ * Helper: Extract image URL from DataSample
388
+ */
389
+ extractImageUrl(sample, imageField) {
390
+ // Try data field first
391
+ if (sample.data && typeof sample.data === 'object') {
392
+ if (sample.data[imageField]) {
393
+ return sample.data[imageField];
394
+ }
395
+ }
396
+ // Try rawData field
397
+ if (sample.rawData) {
398
+ try {
399
+ const rawData = typeof sample.rawData === 'string' ? JSON.parse(sample.rawData) : sample.rawData;
400
+ if (rawData[imageField]) {
401
+ return rawData[imageField];
402
+ }
403
+ // If rawData is just a string URL
404
+ if (typeof rawData === 'string' && rawData.startsWith('http')) {
405
+ return rawData;
406
+ }
407
+ }
408
+ catch (e) {
409
+ // If rawData is a plain URL string
410
+ if (typeof sample.rawData === 'string' && sample.rawData.startsWith('http')) {
411
+ return sample.rawData;
412
+ }
413
+ }
414
+ }
415
+ return null;
416
+ }
417
+ /**
418
+ * Helper: Convert AI detection results to Label Studio format
419
+ */
420
+ convertToLabelStudioFormat(objects) {
421
+ return objects.map(obj => ({
422
+ from_name: 'label',
423
+ to_name: 'image',
424
+ type: 'rectanglelabels',
425
+ value: {
426
+ x: obj.bbox.x,
427
+ y: obj.bbox.y,
428
+ width: obj.bbox.width,
429
+ height: obj.bbox.height,
430
+ rectanglelabels: [obj.className]
431
+ }
432
+ }));
433
+ }
434
+ /**
435
+ * Helper: Convert Label Studio annotation to DataSample judgment format
436
+ */
437
+ convertAnnotationToJudgment(annotation, task) {
438
+ const result = annotation.result || [];
439
+ const objects = result
440
+ .filter((item) => item.type === 'rectanglelabels')
441
+ .map((item) => ({
442
+ type: item.value.rectanglelabels?.[0] || 'unknown',
443
+ bbox: {
444
+ x: item.value.x,
445
+ y: item.value.y,
446
+ width: item.value.width,
447
+ height: item.value.height
448
+ },
449
+ source: 'human-annotation'
450
+ }));
451
+ return {
452
+ annotationId: annotation.id,
453
+ taskId: task.id,
454
+ annotatedBy: annotation.completed_by,
455
+ annotatedAt: annotation.updated_at,
456
+ objects,
457
+ objectCount: objects.length,
458
+ defectsFound: objects.length, // Application-specific logic
459
+ verified: true,
460
+ source: 'label-studio'
461
+ };
462
+ }
463
+ };
464
+ exports.DatasetLabelingIntegration = DatasetLabelingIntegration;
465
+ tslib_1.__decorate([
466
+ (0, type_graphql_1.Mutation)(returns => dataset_labeling_types_js_1.TaskCreationResult, {
467
+ description: 'Create Label Studio labeling tasks from DataSamples in a DataSet. Optionally auto-generates AI predictions for each task.'
468
+ }),
469
+ (0, type_graphql_1.Directive)('@privilege(category: "label-studio", privilege: "mutation")'),
470
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
471
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
472
+ tslib_1.__metadata("design:type", Function),
473
+ tslib_1.__metadata("design:paramtypes", [dataset_labeling_types_js_1.CreateLabelingTasksRequest, Object]),
474
+ tslib_1.__metadata("design:returntype", Promise)
475
+ ], DatasetLabelingIntegration.prototype, "createLabelingTasksFromDataset", null);
476
+ tslib_1.__decorate([
477
+ (0, type_graphql_1.Mutation)(returns => dataset_labeling_types_js_1.SyncAnnotationsResult, {
478
+ description: 'Sync Label Studio annotations back to DataSamples, updating judgment fields'
479
+ }),
480
+ (0, type_graphql_1.Directive)('@privilege(category: "label-studio", privilege: "mutation")'),
481
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
482
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
483
+ tslib_1.__metadata("design:type", Function),
484
+ tslib_1.__metadata("design:paramtypes", [dataset_labeling_types_js_1.SyncAnnotationsRequest, Object]),
485
+ tslib_1.__metadata("design:returntype", Promise)
486
+ ], DatasetLabelingIntegration.prototype, "syncAnnotationsToDataset", null);
487
+ tslib_1.__decorate([
488
+ (0, type_graphql_1.Mutation)(returns => prediction_types_js_1.BatchPredictionResult, {
489
+ description: 'Generate AI predictions for DataSet samples that already have Label Studio tasks'
490
+ }),
491
+ (0, type_graphql_1.Directive)('@privilege(category: "label-studio", privilege: "mutation")'),
492
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
493
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
494
+ tslib_1.__metadata("design:type", Function),
495
+ tslib_1.__metadata("design:paramtypes", [dataset_labeling_types_js_1.GeneratePredictionsForDatasetRequest, Object]),
496
+ tslib_1.__metadata("design:returntype", Promise)
497
+ ], DatasetLabelingIntegration.prototype, "generatePredictionsForDataset", null);
498
+ tslib_1.__decorate([
499
+ (0, type_graphql_1.Query)(returns => dataset_labeling_types_js_1.DatasetLabelingStatus, {
500
+ description: 'Get labeling status and progress for a DataSet'
501
+ }),
502
+ (0, type_graphql_1.Directive)('@privilege(category: "label-studio", privilege: "query")'),
503
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
504
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
505
+ tslib_1.__metadata("design:type", Function),
506
+ tslib_1.__metadata("design:paramtypes", [dataset_labeling_types_js_1.DatasetLabelingStatusRequest, Object]),
507
+ tslib_1.__metadata("design:returntype", Promise)
508
+ ], DatasetLabelingIntegration.prototype, "datasetLabelingStatus", null);
509
+ exports.DatasetLabelingIntegration = DatasetLabelingIntegration = tslib_1.__decorate([
510
+ (0, type_graphql_1.Resolver)()
511
+ ], DatasetLabelingIntegration);
512
+ //# sourceMappingURL=dataset-labeling-integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-labeling-integration.js","sourceRoot":"","sources":["../../server/service/dataset-labeling-integration.ts"],"names":[],"mappings":"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","sourcesContent":["import { Resolver, Query, Mutation, Arg, Ctx, Int, Directive } from 'type-graphql'\nimport { getRepository } from '@things-factory/shell'\nimport { AIModelClientFactory } from '@things-factory/ai-inference'\nimport { DataSample, DataSet } from '@things-factory/dataset'\nimport { labelStudioApi } from '../utils/label-studio-api-client.js'\nimport {\n CreateLabelingTasksRequest,\n TaskCreationResult,\n SyncAnnotationsRequest,\n SyncAnnotationsResult,\n DatasetLabelingStatus,\n DatasetLabelingStatusRequest,\n GeneratePredictionsForDatasetRequest\n} from '../types/dataset-labeling-types.js'\nimport { BatchPredictionResult } from '../types/prediction-types.js'\nimport { extractMediaUrls, getBaseUrl } from '../utils/media-url-extractor.js'\n\n/**\n * Dataset Labeling Integration Service\n *\n * Integrates Things-Factory Dataset module with Label Studio for AI-assisted labeling\n *\n * Features:\n * - Create Label Studio tasks from DataSamples\n * - Auto-generate AI predictions for labeling tasks\n * - Sync human annotations back to DataSamples\n * - Track labeling progress and status\n */\n@Resolver()\nexport class DatasetLabelingIntegration {\n /**\n * Create Label Studio labeling tasks from DataSamples in a DataSet\n * Optionally generates AI predictions automatically\n */\n @Mutation(returns => TaskCreationResult, {\n description:\n 'Create Label Studio labeling tasks from DataSamples in a DataSet. Optionally auto-generates AI predictions for each task.'\n })\n @Directive('@privilege(category: \"label-studio\", privilege: \"mutation\")')\n async createLabelingTasksFromDataset(\n @Arg('input') input: CreateLabelingTasksRequest,\n @Ctx() context: ResolverContext\n ): Promise<TaskCreationResult> {\n const { domain } = context.state\n\n console.log(\n `[Dataset Labeling] Creating tasks for DataSet ${input.dataSetId} in LS project ${input.projectId}`\n )\n\n try {\n // 1. Get DataSet configuration\n const dataSet = await getRepository(DataSet).findOne({\n where: { domain: { id: domain.id }, id: input.dataSetId }\n })\n\n if (!dataSet) {\n throw new Error(`DataSet not found: ${input.dataSetId}`)\n }\n\n // 2. Query DataSamples\n const queryBuilder = getRepository(DataSample)\n .createQueryBuilder('sample')\n .where('sample.domainId = :domainId', { domainId: domain.id })\n .andWhere('sample.dataSetId = :dataSetId', { dataSetId: input.dataSetId })\n\n if (input.sinceDate) {\n queryBuilder.andWhere('sample.collectedAt >= :sinceDate', { sinceDate: input.sinceDate })\n }\n\n if (input.limit) {\n queryBuilder.limit(input.limit)\n }\n\n queryBuilder.orderBy('sample.collectedAt', 'DESC')\n\n const samples = await queryBuilder.getMany()\n\n console.log(`[Dataset Labeling] Found ${samples.length} DataSamples to process`)\n\n // 3. Create tasks for each sample\n const result: TaskCreationResult = {\n totalSamples: samples.length,\n tasksCreated: 0,\n tasksFailed: 0,\n tasksSkipped: 0,\n taskIds: [],\n predictionsCreated: 0\n }\n\n // Get base URL for converting relative attachment paths to absolute URLs\n const baseUrl = getBaseUrl(context)\n\n for (const sample of samples) {\n try {\n // Extract all media URLs from DataSample (image, video, audio)\n const mediaUrls = extractMediaUrls(sample, baseUrl)\n\n if (Object.keys(mediaUrls).length === 0) {\n console.log(`[Dataset Labeling] Skipping sample ${sample.id}: no media found`)\n result.tasksSkipped++\n continue\n }\n\n // Create Label Studio task with all media URLs\n const task = await labelStudioApi.createTask(input.projectId, {\n data: mediaUrls,\n meta: {\n dataSampleId: sample.id,\n dataSampleName: sample.name,\n dataSetId: input.dataSetId,\n dataSetName: dataSet.name,\n collectedAt: sample.collectedAt?.toISOString(),\n source: 'things-factory-dataset'\n }\n })\n\n result.taskIds.push(task.id)\n result.tasksCreated++\n\n console.log(`[Dataset Labeling] Created task ${task.id} for sample ${sample.id}`)\n\n // 4. Auto-generate AI prediction if requested (for image detection)\n if (input.autoGeneratePredictions && mediaUrls.image) {\n try {\n const modelClient = input.modelId\n ? AIModelClientFactory.getClient(input.modelId)\n : AIModelClientFactory.getDefaultClient()\n\n // Use first image URL if multiple images\n const imageUrl = Array.isArray(mediaUrls.image) ? mediaUrls.image[0] : mediaUrls.image\n\n const objects = await modelClient.detectObjects(imageUrl, {\n confidenceThreshold: input.confidenceThreshold\n })\n\n if (objects.length > 0) {\n const labelStudioResult = this.convertToLabelStudioFormat(objects)\n const avgConfidence = objects.reduce((sum, obj) => sum + obj.confidence, 0) / objects.length\n\n await labelStudioApi.createPrediction({\n task: task.id,\n result: labelStudioResult,\n score: avgConfidence,\n model_version: input.modelId || 'default-model-v1.0'\n })\n\n result.predictionsCreated = (result.predictionsCreated || 0) + 1\n\n console.log(`[Dataset Labeling] Created prediction for task ${task.id} (${objects.length} objects)`)\n }\n } catch (predError) {\n console.error(`[Dataset Labeling] Failed to create prediction for task ${task.id}:`, predError)\n // Continue even if prediction fails\n }\n }\n } catch (taskError) {\n console.error(`[Dataset Labeling] Failed to create task for sample ${sample.id}:`, taskError)\n result.tasksFailed++\n }\n }\n\n console.log(\n `[Dataset Labeling] Completed: ${result.tasksCreated} created, ${result.tasksFailed} failed, ${result.tasksSkipped} skipped`\n )\n\n return result\n } catch (error) {\n console.error('[Dataset Labeling] Failed to create tasks:', error)\n throw new Error(`Failed to create labeling tasks: ${error.message}`)\n }\n }\n\n /**\n * Sync completed annotations from Label Studio back to DataSamples\n */\n @Mutation(returns => SyncAnnotationsResult, {\n description: 'Sync Label Studio annotations back to DataSamples, updating judgment fields'\n })\n @Directive('@privilege(category: \"label-studio\", privilege: \"mutation\")')\n async syncAnnotationsToDataset(\n @Arg('input') input: SyncAnnotationsRequest,\n @Ctx() context: ResolverContext\n ): Promise<SyncAnnotationsResult> {\n const { domain } = context.state\n\n console.log(`[Dataset Labeling] Syncing annotations from LS project ${input.projectId} to DataSet ${input.dataSetId}`)\n\n try {\n // 1. Get all tasks from Label Studio project\n const tasksResponse = await labelStudioApi.getTasks(input.projectId, {\n page_size: 1000 // Adjust as needed\n })\n\n const tasks = tasksResponse.tasks || tasksResponse.results || []\n\n console.log(`[Dataset Labeling] Found ${tasks.length} tasks in Label Studio`)\n\n // 2. Filter tasks related to this DataSet\n const dataSetTasks = tasks.filter((task: any) => task.meta?.dataSetId === input.dataSetId)\n\n console.log(`[Dataset Labeling] ${dataSetTasks.length} tasks belong to DataSet ${input.dataSetId}`)\n\n const result: SyncAnnotationsResult = {\n totalAnnotations: 0,\n samplesUpdated: 0,\n updatesFailed: 0,\n skipped: 0\n }\n\n // 3. Process each task\n for (const task of dataSetTasks) {\n const dataSampleId = task.meta?.dataSampleId\n\n if (!dataSampleId) {\n result.skipped++\n continue\n }\n\n // Get annotations for this task\n const annotations = task.annotations || []\n\n if (annotations.length === 0) {\n result.skipped++\n continue\n }\n\n // Filter by completion status if requested\n let relevantAnnotations = annotations\n if (input.completedOnly) {\n relevantAnnotations = annotations.filter((ann: any) => ann.was_cancelled === false)\n }\n\n // Filter by date if requested\n if (input.sinceDate) {\n relevantAnnotations = relevantAnnotations.filter(\n (ann: any) => new Date(ann.updated_at) >= input.sinceDate\n )\n }\n\n if (relevantAnnotations.length === 0) {\n result.skipped++\n continue\n }\n\n result.totalAnnotations += relevantAnnotations.length\n\n try {\n // 4. Get DataSample\n const sample = await getRepository(DataSample).findOne({\n where: { domain: { id: domain.id }, id: dataSampleId }\n })\n\n if (!sample) {\n console.log(`[Dataset Labeling] DataSample not found: ${dataSampleId}`)\n result.updatesFailed++\n continue\n }\n\n // 5. Convert annotations to judgment data\n const latestAnnotation = relevantAnnotations[relevantAnnotations.length - 1]\n const judgment = this.convertAnnotationToJudgment(latestAnnotation, task)\n\n // 6. Update DataSample\n sample.judgment = judgment\n\n // Optionally update OOC/OOS flags based on annotation\n // This is application-specific logic\n if (judgment.defectsFound > 0) {\n sample.ooc = true\n }\n\n await getRepository(DataSample).save(sample)\n\n result.samplesUpdated++\n\n console.log(`[Dataset Labeling] Updated DataSample ${dataSampleId} with annotation ${latestAnnotation.id}`)\n } catch (updateError) {\n console.error(`[Dataset Labeling] Failed to update DataSample ${dataSampleId}:`, updateError)\n result.updatesFailed++\n }\n }\n\n console.log(\n `[Dataset Labeling] Sync completed: ${result.samplesUpdated} updated, ${result.updatesFailed} failed, ${result.skipped} skipped`\n )\n\n return result\n } catch (error) {\n console.error('[Dataset Labeling] Failed to sync annotations:', error)\n throw new Error(`Failed to sync annotations: ${error.message}`)\n }\n }\n\n /**\n * Generate AI predictions for existing DataSet samples\n */\n @Mutation(returns => BatchPredictionResult, {\n description: 'Generate AI predictions for DataSet samples that already have Label Studio tasks'\n })\n @Directive('@privilege(category: \"label-studio\", privilege: \"mutation\")')\n async generatePredictionsForDataset(\n @Arg('input') input: GeneratePredictionsForDatasetRequest,\n @Ctx() context: ResolverContext\n ): Promise<BatchPredictionResult> {\n const { domain } = context.state\n\n console.log(\n `[Dataset Labeling] Generating predictions for DataSet ${input.dataSetId} in LS project ${input.projectId}`\n )\n\n try {\n // 1. Get all tasks from Label Studio project\n const tasksResponse = await labelStudioApi.getTasks(input.projectId, {\n page_size: 1000\n })\n\n const tasks = tasksResponse.tasks || tasksResponse.results || []\n const dataSetTasks = tasks.filter((task: any) => task.meta?.dataSetId === input.dataSetId)\n\n console.log(`[Dataset Labeling] Found ${dataSetTasks.length} tasks for DataSet`)\n\n const result: BatchPredictionResult = {\n total: dataSetTasks.length,\n succeeded: 0,\n failed: 0,\n results: [],\n modelVersion: input.modelId || 'default-model-v1.0'\n }\n\n // 2. Get AI model client\n const modelClient = input.modelId\n ? AIModelClientFactory.getClient(input.modelId)\n : AIModelClientFactory.getDefaultClient()\n\n // 3. Process tasks in batches\n const BATCH_SIZE = 5\n for (let i = 0; i < dataSetTasks.length; i += BATCH_SIZE) {\n const batch = dataSetTasks.slice(i, i + BATCH_SIZE)\n\n const batchPromises = batch.map(async (task: any) => {\n try {\n // Check if prediction already exists\n if (!input.forceRegenerate && task.predictions && task.predictions.length > 0) {\n result.results.push({\n taskId: task.id,\n success: true,\n objectCount: 0,\n error: 'Prediction already exists (skipped)'\n })\n return\n }\n\n const imageUrl = task.data?.image\n\n if (!imageUrl) {\n result.failed++\n result.results.push({\n taskId: task.id,\n success: false,\n objectCount: 0,\n error: 'No image URL in task data'\n })\n return\n }\n\n // Run AI inference\n const objects = await modelClient.detectObjects(imageUrl, {\n confidenceThreshold: input.confidenceThreshold\n })\n\n if (objects.length === 0) {\n result.succeeded++\n result.results.push({\n taskId: task.id,\n success: true,\n objectCount: 0,\n error: 'No objects detected'\n })\n return\n }\n\n // Create prediction\n const labelStudioResult = this.convertToLabelStudioFormat(objects)\n const avgConfidence = objects.reduce((sum, obj) => sum + obj.confidence, 0) / objects.length\n\n const prediction = await labelStudioApi.createPrediction({\n task: task.id,\n result: labelStudioResult,\n score: avgConfidence,\n model_version: result.modelVersion\n })\n\n result.succeeded++\n result.results.push({\n taskId: task.id,\n predictionId: prediction.id,\n success: true,\n objectCount: objects.length,\n avgConfidence\n })\n\n console.log(`[Dataset Labeling] Created prediction for task ${task.id} (${objects.length} objects)`)\n } catch (error) {\n result.failed++\n result.results.push({\n taskId: task.id,\n success: false,\n objectCount: 0,\n error: error.message\n })\n }\n })\n\n await Promise.all(batchPromises)\n }\n\n console.log(`[Dataset Labeling] Prediction generation completed: ${result.succeeded}/${result.total} succeeded`)\n\n return result\n } catch (error) {\n console.error('[Dataset Labeling] Failed to generate predictions:', error)\n throw new Error(`Failed to generate predictions: ${error.message}`)\n }\n }\n\n /**\n * Query labeling status for a DataSet\n */\n @Query(returns => DatasetLabelingStatus, {\n description: 'Get labeling status and progress for a DataSet'\n })\n @Directive('@privilege(category: \"label-studio\", privilege: \"query\")')\n async datasetLabelingStatus(\n @Arg('input') input: DatasetLabelingStatusRequest,\n @Ctx() context: ResolverContext\n ): Promise<DatasetLabelingStatus> {\n const { domain } = context.state\n\n try {\n // 1. Get DataSet\n const dataSet = await getRepository(DataSet).findOne({\n where: { domain: { id: domain.id }, id: input.dataSetId }\n })\n\n if (!dataSet) {\n throw new Error(`DataSet not found: ${input.dataSetId}`)\n }\n\n // 2. Count total DataSamples\n const totalSamples = await getRepository(DataSample).count({\n where: { domain: { id: domain.id }, dataSetId: input.dataSetId }\n })\n\n // 3. Get Label Studio tasks if projectId provided\n let tasksCreated = 0\n let withPredictions = 0\n let withAnnotations = 0\n let annotationsCompleted = 0\n\n if (input.projectId) {\n const tasksResponse = await labelStudioApi.getTasks(input.projectId, {\n page_size: 1000\n })\n\n const tasks = tasksResponse.tasks || tasksResponse.results || []\n const dataSetTasks = tasks.filter((task: any) => task.meta?.dataSetId === input.dataSetId)\n\n tasksCreated = dataSetTasks.length\n\n for (const task of dataSetTasks) {\n if (task.predictions && task.predictions.length > 0) {\n withPredictions++\n }\n\n if (task.annotations && task.annotations.length > 0) {\n withAnnotations++\n\n const completedAnnotations = task.annotations.filter((ann: any) => ann.was_cancelled === false)\n if (completedAnnotations.length > 0) {\n annotationsCompleted++\n }\n }\n }\n }\n\n const notProcessed = totalSamples - tasksCreated\n const completionRate = totalSamples > 0 ? annotationsCompleted / totalSamples : 0\n\n return {\n dataSetId: input.dataSetId,\n dataSetName: dataSet.name,\n totalSamples,\n tasksCreated,\n withPredictions,\n withAnnotations,\n annotationsCompleted,\n notProcessed,\n completionRate,\n projectId: input.projectId,\n lastSyncedAt: new Date()\n }\n } catch (error) {\n console.error('[Dataset Labeling] Failed to get status:', error)\n throw new Error(`Failed to get labeling status: ${error.message}`)\n }\n }\n\n /**\n * Helper: Extract image URL from DataSample\n */\n private extractImageUrl(sample: DataSample, imageField: string): string | null {\n // Try data field first\n if (sample.data && typeof sample.data === 'object') {\n if (sample.data[imageField]) {\n return sample.data[imageField]\n }\n }\n\n // Try rawData field\n if (sample.rawData) {\n try {\n const rawData = typeof sample.rawData === 'string' ? JSON.parse(sample.rawData) : sample.rawData\n if (rawData[imageField]) {\n return rawData[imageField]\n }\n // If rawData is just a string URL\n if (typeof rawData === 'string' && rawData.startsWith('http')) {\n return rawData\n }\n } catch (e) {\n // If rawData is a plain URL string\n if (typeof sample.rawData === 'string' && sample.rawData.startsWith('http')) {\n return sample.rawData\n }\n }\n }\n\n return null\n }\n\n /**\n * Helper: Convert AI detection results to Label Studio format\n */\n private convertToLabelStudioFormat(objects: any[]) {\n return objects.map(obj => ({\n from_name: 'label',\n to_name: 'image',\n type: 'rectanglelabels',\n value: {\n x: obj.bbox.x,\n y: obj.bbox.y,\n width: obj.bbox.width,\n height: obj.bbox.height,\n rectanglelabels: [obj.className]\n }\n }))\n }\n\n /**\n * Helper: Convert Label Studio annotation to DataSample judgment format\n */\n private convertAnnotationToJudgment(annotation: any, task: any): any {\n const result = annotation.result || []\n\n const objects = result\n .filter((item: any) => item.type === 'rectanglelabels')\n .map((item: any) => ({\n type: item.value.rectanglelabels?.[0] || 'unknown',\n bbox: {\n x: item.value.x,\n y: item.value.y,\n width: item.value.width,\n height: item.value.height\n },\n source: 'human-annotation'\n }))\n\n return {\n annotationId: annotation.id,\n taskId: task.id,\n annotatedBy: annotation.completed_by,\n annotatedAt: annotation.updated_at,\n objects,\n objectCount: objects.length,\n defectsFound: objects.length, // Application-specific logic\n verified: true,\n source: 'label-studio'\n }\n }\n}\n"]}