@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,362 @@
1
+ import { Field, InputType, ObjectType, Int, Float, registerEnumType } from 'type-graphql'
2
+
3
+ /**
4
+ * Labeling Workflow Scenario Types
5
+ *
6
+ * Defines end-to-end labeling workflows with multiple steps
7
+ */
8
+
9
+ // ============================================================================
10
+ // Enums
11
+ // ============================================================================
12
+
13
+ export enum ScenarioStepType {
14
+ ImportData = 'import_data',
15
+ GeneratePredictions = 'generate_predictions',
16
+ WaitForAnnotations = 'wait_for_annotations',
17
+ SyncAnnotations = 'sync_annotations',
18
+ ValidateQuality = 'validate_quality',
19
+ ExportResults = 'export_results',
20
+ Notification = 'notification'
21
+ }
22
+
23
+ registerEnumType(ScenarioStepType, {
24
+ name: 'ScenarioStepType',
25
+ description: 'Type of workflow step'
26
+ })
27
+
28
+ export enum ScenarioStatus {
29
+ Draft = 'draft',
30
+ Active = 'active',
31
+ Paused = 'paused',
32
+ Completed = 'completed',
33
+ Failed = 'failed'
34
+ }
35
+
36
+ registerEnumType(ScenarioStatus, {
37
+ name: 'ScenarioStatus',
38
+ description: 'Status of workflow scenario'
39
+ })
40
+
41
+ export enum TriggerType {
42
+ Manual = 'manual',
43
+ Schedule = 'schedule',
44
+ DatasetUpdate = 'dataset_update',
45
+ ExternalEvent = 'external_event'
46
+ }
47
+
48
+ registerEnumType(TriggerType, {
49
+ name: 'TriggerType',
50
+ description: 'How the scenario is triggered'
51
+ })
52
+
53
+ // ============================================================================
54
+ // Scenario Step Types
55
+ // ============================================================================
56
+
57
+ @InputType()
58
+ export class ImportDataStepConfig {
59
+ @Field({ description: 'Data source type: dataset, api, file' })
60
+ sourceType: 'dataset' | 'api' | 'file'
61
+
62
+ @Field({ nullable: true, description: 'DataSet ID if sourceType is dataset' })
63
+ dataSetId?: string
64
+
65
+ @Field({ nullable: true, description: 'External source URL if sourceType is api/file' })
66
+ sourceUrl?: string
67
+
68
+ @Field({ nullable: true, description: 'Image field name' })
69
+ imageField?: string
70
+
71
+ @Field({ nullable: true, description: 'Maximum items to import' })
72
+ limit?: number
73
+ }
74
+
75
+ @InputType()
76
+ export class GeneratePredictionsStepConfig {
77
+ @Field({ nullable: true, description: 'AI model ID to use' })
78
+ modelId?: string
79
+
80
+ @Field(type => Float, { nullable: true, description: 'Confidence threshold' })
81
+ confidenceThreshold?: number
82
+
83
+ @Field({ nullable: true, defaultValue: false, description: 'Force regenerate existing predictions' })
84
+ forceRegenerate?: boolean
85
+ }
86
+
87
+ @InputType()
88
+ export class WaitForAnnotationsStepConfig {
89
+ @Field(type => Int, { nullable: true, description: 'Minimum number of annotations required' })
90
+ minAnnotations?: number
91
+
92
+ @Field(type => Float, { nullable: true, description: 'Minimum completion rate (0-1)' })
93
+ minCompletionRate?: number
94
+
95
+ @Field(type => Int, { nullable: true, description: 'Maximum wait time in minutes' })
96
+ maxWaitMinutes?: number
97
+
98
+ @Field({ nullable: true, defaultValue: true, description: 'Auto-proceed when criteria met' })
99
+ autoProceed?: boolean
100
+ }
101
+
102
+ @InputType()
103
+ export class SyncAnnotationsStepConfig {
104
+ @Field({ nullable: true, defaultValue: true, description: 'Only sync completed annotations' })
105
+ completedOnly?: boolean
106
+
107
+ @Field({ nullable: true, description: 'Sync annotations updated after this date' })
108
+ sinceDate?: Date
109
+ }
110
+
111
+ @InputType()
112
+ export class ValidateQualityStepConfig {
113
+ @Field(type => Float, { nullable: true, description: 'Minimum required quality score (0-1)' })
114
+ minQualityScore?: number
115
+
116
+ @Field({ nullable: true, description: 'Validation rules in JSON format' })
117
+ validationRules?: string
118
+ }
119
+
120
+ @InputType()
121
+ export class NotificationStepConfig {
122
+ @Field({ description: 'Notification message template' })
123
+ message: string
124
+
125
+ @Field(type => [String], { nullable: true, description: 'Email recipients' })
126
+ recipients?: string[]
127
+
128
+ @Field({ nullable: true, description: 'Webhook URL to call' })
129
+ webhookUrl?: string
130
+ }
131
+
132
+ // ============================================================================
133
+ // Scenario Step Definition
134
+ // ============================================================================
135
+
136
+ @InputType()
137
+ export class ScenarioStepInput {
138
+ @Field({ description: 'Step name' })
139
+ name: string
140
+
141
+ @Field(type => ScenarioStepType, { description: 'Step type' })
142
+ type: ScenarioStepType
143
+
144
+ @Field({ description: 'Step configuration as JSON string' })
145
+ config: string
146
+
147
+ @Field({ nullable: true, description: 'Condition to execute this step (JSON logic)' })
148
+ condition?: string
149
+
150
+ @Field({ nullable: true, defaultValue: true, description: 'Continue on error' })
151
+ continueOnError?: boolean
152
+
153
+ @Field(type => Int, { nullable: true, description: 'Max retry attempts' })
154
+ maxRetries?: number
155
+ }
156
+
157
+ @ObjectType()
158
+ export class ScenarioStep {
159
+ @Field({ description: 'Step name' })
160
+ name: string
161
+
162
+ @Field(type => ScenarioStepType, { description: 'Step type' })
163
+ type: ScenarioStepType
164
+
165
+ @Field({ description: 'Step configuration' })
166
+ config: string
167
+
168
+ @Field({ nullable: true, description: 'Execution condition' })
169
+ condition?: string
170
+
171
+ @Field({ defaultValue: true, description: 'Continue on error' })
172
+ continueOnError: boolean
173
+
174
+ @Field(type => Int, { nullable: true, description: 'Max retry attempts' })
175
+ maxRetries?: number
176
+
177
+ @Field(type => Int, { description: 'Step order' })
178
+ order: number
179
+ }
180
+
181
+ // ============================================================================
182
+ // Scenario Definition
183
+ // ============================================================================
184
+
185
+ @InputType()
186
+ export class CreateScenarioRequest {
187
+ @Field({ description: 'Scenario name' })
188
+ name: string
189
+
190
+ @Field({ nullable: true, description: 'Scenario description' })
191
+ description?: string
192
+
193
+ @Field(type => Int, { description: 'Label Studio project ID' })
194
+ projectId: number
195
+
196
+ @Field(type => TriggerType, { description: 'How to trigger this scenario' })
197
+ triggerType: TriggerType
198
+
199
+ @Field({ nullable: true, description: 'Trigger configuration (schedule, event, etc.)' })
200
+ triggerConfig?: string
201
+
202
+ @Field(type => [ScenarioStepInput], { description: 'Workflow steps' })
203
+ steps: ScenarioStepInput[]
204
+
205
+ @Field({ nullable: true, defaultValue: true, description: 'Auto-start scenario after creation' })
206
+ autoStart?: boolean
207
+ }
208
+
209
+ @ObjectType()
210
+ export class LabelingScenario {
211
+ @Field({ description: 'Scenario ID' })
212
+ id: string
213
+
214
+ @Field({ description: 'Scenario name' })
215
+ name: string
216
+
217
+ @Field({ nullable: true, description: 'Description' })
218
+ description?: string
219
+
220
+ @Field(type => Int, { description: 'Label Studio project ID' })
221
+ projectId: number
222
+
223
+ @Field(type => TriggerType, { description: 'Trigger type' })
224
+ triggerType: TriggerType
225
+
226
+ @Field({ nullable: true, description: 'Trigger configuration' })
227
+ triggerConfig?: string
228
+
229
+ @Field(type => [ScenarioStep], { description: 'Workflow steps' })
230
+ steps: ScenarioStep[]
231
+
232
+ @Field(type => ScenarioStatus, { description: 'Current status' })
233
+ status: ScenarioStatus
234
+
235
+ @Field({ nullable: true, description: 'Last execution time' })
236
+ lastExecutedAt?: Date
237
+
238
+ @Field({ nullable: true, description: 'Next scheduled execution' })
239
+ nextExecutionAt?: Date
240
+
241
+ @Field({ description: 'Created at' })
242
+ createdAt: Date
243
+
244
+ @Field({ description: 'Updated at' })
245
+ updatedAt: Date
246
+ }
247
+
248
+ // ============================================================================
249
+ // Scenario Execution
250
+ // ============================================================================
251
+
252
+ @InputType()
253
+ export class ExecuteScenarioRequest {
254
+ @Field({ description: 'Scenario ID to execute' })
255
+ scenarioId: string
256
+
257
+ @Field({ nullable: true, description: 'Override parameters as JSON' })
258
+ parameters?: string
259
+ }
260
+
261
+ @ObjectType()
262
+ export class ScenarioExecutionStep {
263
+ @Field({ description: 'Step name' })
264
+ stepName: string
265
+
266
+ @Field(type => ScenarioStepType, { description: 'Step type' })
267
+ stepType: ScenarioStepType
268
+
269
+ @Field({ description: 'Step status: pending, running, completed, failed, skipped' })
270
+ status: string
271
+
272
+ @Field({ nullable: true, description: 'Step output data' })
273
+ output?: string
274
+
275
+ @Field({ nullable: true, description: 'Error message if failed' })
276
+ error?: string
277
+
278
+ @Field({ nullable: true, description: 'Started at' })
279
+ startedAt?: Date
280
+
281
+ @Field({ nullable: true, description: 'Completed at' })
282
+ completedAt?: Date
283
+
284
+ @Field(type => Int, { nullable: true, description: 'Duration in milliseconds' })
285
+ durationMs?: number
286
+ }
287
+
288
+ @ObjectType()
289
+ export class ScenarioExecution {
290
+ @Field({ description: 'Execution ID' })
291
+ id: string
292
+
293
+ @Field({ description: 'Scenario ID' })
294
+ scenarioId: string
295
+
296
+ @Field({ description: 'Scenario name' })
297
+ scenarioName: string
298
+
299
+ @Field({ description: 'Execution status: running, completed, failed' })
300
+ status: string
301
+
302
+ @Field(type => [ScenarioExecutionStep], { description: 'Step executions' })
303
+ steps: ScenarioExecutionStep[]
304
+
305
+ @Field({ nullable: true, description: 'Overall result summary' })
306
+ summary?: string
307
+
308
+ @Field({ description: 'Started at' })
309
+ startedAt: Date
310
+
311
+ @Field({ nullable: true, description: 'Completed at' })
312
+ completedAt?: Date
313
+
314
+ @Field(type => Int, { nullable: true, description: 'Total duration in milliseconds' })
315
+ totalDurationMs?: number
316
+
317
+ @Field({ nullable: true, description: 'Error message if failed' })
318
+ error?: string
319
+ }
320
+
321
+ @ObjectType()
322
+ export class ScenarioExecutionResult {
323
+ @Field({ description: 'Execution ID' })
324
+ executionId: string
325
+
326
+ @Field({ description: 'Execution status' })
327
+ status: string
328
+
329
+ @Field({ nullable: true, description: 'Execution summary' })
330
+ summary?: string
331
+
332
+ @Field({ nullable: true, description: 'Error if failed' })
333
+ error?: string
334
+ }
335
+
336
+ // ============================================================================
337
+ // Scenario List and Status
338
+ // ============================================================================
339
+
340
+ @ObjectType()
341
+ export class LabelingScenarioList {
342
+ @Field(type => [LabelingScenario], { description: 'List of scenarios' })
343
+ items: LabelingScenario[]
344
+
345
+ @Field(type => Int, { description: 'Total count' })
346
+ total: number
347
+ }
348
+
349
+ @ObjectType()
350
+ export class ScenarioExecutionList {
351
+ @Field(type => [ScenarioExecution], { description: 'List of executions' })
352
+ items: ScenarioExecution[]
353
+
354
+ @Field(type => Int, { description: 'Total count' })
355
+ total: number
356
+ }
357
+
358
+ @InputType()
359
+ export class ScenarioStatusRequest {
360
+ @Field({ description: 'Scenario ID' })
361
+ scenarioId: string
362
+ }
@@ -0,0 +1,340 @@
1
+ /**
2
+ * Annotation Exporter
3
+ *
4
+ * Flexible annotation export system with pluggable format converters.
5
+ * Supports custom export formats for different use cases.
6
+ */
7
+
8
+ export interface LabelStudioAnnotation {
9
+ id: number
10
+ task: number
11
+ project: number
12
+ completed_by: {
13
+ id: number
14
+ email: string
15
+ first_name: string
16
+ last_name: string
17
+ }
18
+ result: Array<{
19
+ from_name: string
20
+ to_name: string
21
+ type: string
22
+ value: any
23
+ }>
24
+ created_at: string
25
+ updated_at: string
26
+ lead_time?: number
27
+ }
28
+
29
+ export interface ExportContext {
30
+ projectId: number
31
+ projectTitle?: string
32
+ exportedAt: string
33
+ metadata?: any
34
+ }
35
+
36
+ /**
37
+ * Export format converter function type
38
+ * Converts Label Studio annotations to custom format
39
+ */
40
+ export type FormatConverter<T = any> = (
41
+ annotations: LabelStudioAnnotation[],
42
+ context: ExportContext
43
+ ) => T | Promise<T>
44
+
45
+ /**
46
+ * Registry for custom export formats
47
+ */
48
+ const formatConverters: Map<string, FormatConverter> = new Map()
49
+
50
+ /**
51
+ * Register a custom export format converter
52
+ *
53
+ * @param formatName - Unique format identifier (e.g., "csv", "coco", "yolo")
54
+ * @param converter - Converter function
55
+ *
56
+ * @example
57
+ * registerExportFormat('csv', (annotations, context) => {
58
+ * const rows = annotations.map(a => ({
59
+ * id: a.id,
60
+ * task: a.task,
61
+ * result: JSON.stringify(a.result)
62
+ * }))
63
+ * return Papa.unparse(rows)
64
+ * })
65
+ */
66
+ export function registerExportFormat<T = any>(formatName: string, converter: FormatConverter<T>): void {
67
+ formatConverters.set(formatName.toLowerCase(), converter)
68
+ }
69
+
70
+ /**
71
+ * Unregister an export format
72
+ */
73
+ export function unregisterExportFormat(formatName: string): void {
74
+ formatConverters.delete(formatName.toLowerCase())
75
+ }
76
+
77
+ /**
78
+ * Get all registered format names
79
+ */
80
+ export function getRegisteredFormats(): string[] {
81
+ return Array.from(formatConverters.keys())
82
+ }
83
+
84
+ /**
85
+ * Export annotations to specified format
86
+ *
87
+ * @param annotations - Label Studio annotations
88
+ * @param format - Export format name
89
+ * @param context - Export context
90
+ * @returns Converted data in requested format
91
+ */
92
+ export async function exportAnnotations<T = any>(
93
+ annotations: LabelStudioAnnotation[],
94
+ format: string,
95
+ context: ExportContext
96
+ ): Promise<T> {
97
+ const converter = formatConverters.get(format.toLowerCase())
98
+
99
+ if (!converter) {
100
+ throw new Error(`Unknown export format: ${format}. Available formats: ${getRegisteredFormats().join(', ')}`)
101
+ }
102
+
103
+ return await converter(annotations, context)
104
+ }
105
+
106
+ /**
107
+ * Pre-built export formats
108
+ */
109
+ export class ExportFormats {
110
+ /**
111
+ * Export as JSON (default Label Studio format)
112
+ */
113
+ static json(): FormatConverter<string> {
114
+ return (annotations, context) => {
115
+ return JSON.stringify(
116
+ {
117
+ meta: context,
118
+ annotations
119
+ },
120
+ null,
121
+ 2
122
+ )
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Export as JSON Lines (one annotation per line)
128
+ */
129
+ static jsonl(): FormatConverter<string> {
130
+ return annotations => {
131
+ return annotations.map(a => JSON.stringify(a)).join('\n')
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Export image classification results as CSV
137
+ */
138
+ static imageClassificationCSV(): FormatConverter<string> {
139
+ return annotations => {
140
+ const rows: string[] = ['task_id,annotation_id,label,annotator,lead_time,created_at']
141
+
142
+ for (const annotation of annotations) {
143
+ const label = annotation.result.find(r => r.type === 'choices')
144
+ if (label && label.value.choices) {
145
+ const choice = label.value.choices[0]
146
+ rows.push(
147
+ [
148
+ annotation.task,
149
+ annotation.id,
150
+ choice,
151
+ annotation.completed_by?.email || 'unknown',
152
+ annotation.lead_time || 0,
153
+ annotation.created_at
154
+ ].join(',')
155
+ )
156
+ }
157
+ }
158
+
159
+ return rows.join('\n')
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Export Rank N classification results
165
+ */
166
+ static rankNClassificationCSV(ranks: number = 3): FormatConverter<string> {
167
+ return annotations => {
168
+ const rankHeaders = Array.from({ length: ranks }, (_, i) => `rank${i + 1}`).join(',')
169
+ const rows: string[] = [`task_id,annotation_id,${rankHeaders},annotator,lead_time,created_at`]
170
+
171
+ for (const annotation of annotations) {
172
+ const rankValues: string[] = []
173
+
174
+ for (let i = 1; i <= ranks; i++) {
175
+ const rankResult = annotation.result.find(r => r.from_name === `rank${i}`)
176
+ rankValues.push(rankResult?.value?.choices?.[0] || '')
177
+ }
178
+
179
+ rows.push(
180
+ [
181
+ annotation.task,
182
+ annotation.id,
183
+ ...rankValues,
184
+ annotation.completed_by?.email || 'unknown',
185
+ annotation.lead_time || 0,
186
+ annotation.created_at
187
+ ].join(',')
188
+ )
189
+ }
190
+
191
+ return rows.join('\n')
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Export object detection results as COCO format
197
+ */
198
+ static coco(): FormatConverter<any> {
199
+ return (annotations, context) => {
200
+ const images: any[] = []
201
+ const cocoAnnotations: any[] = []
202
+ const categories: Map<string, number> = new Map()
203
+
204
+ let imageId = 1
205
+ let annotationId = 1
206
+ let categoryId = 1
207
+
208
+ for (const annotation of annotations) {
209
+ // Add image
210
+ images.push({
211
+ id: imageId,
212
+ file_name: `task_${annotation.task}.jpg`,
213
+ width: 0, // TODO: extract from annotation if available
214
+ height: 0
215
+ })
216
+
217
+ // Process bounding boxes
218
+ for (const result of annotation.result) {
219
+ if (result.type === 'rectanglelabels') {
220
+ const label = result.value.rectanglelabels[0]
221
+
222
+ // Register category
223
+ if (!categories.has(label)) {
224
+ categories.set(label, categoryId++)
225
+ }
226
+
227
+ // Add annotation
228
+ cocoAnnotations.push({
229
+ id: annotationId++,
230
+ image_id: imageId,
231
+ category_id: categories.get(label),
232
+ bbox: [result.value.x, result.value.y, result.value.width, result.value.height],
233
+ area: result.value.width * result.value.height,
234
+ iscrowd: 0
235
+ })
236
+ }
237
+ }
238
+
239
+ imageId++
240
+ }
241
+
242
+ return {
243
+ info: {
244
+ description: context.projectTitle || 'Label Studio Export',
245
+ date_created: context.exportedAt
246
+ },
247
+ images,
248
+ annotations: cocoAnnotations,
249
+ categories: Array.from(categories.entries()).map(([name, id]) => ({
250
+ id,
251
+ name,
252
+ supercategory: 'none'
253
+ }))
254
+ }
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Export object detection results as YOLO format
260
+ */
261
+ static yolo(): FormatConverter<Map<number, string>> {
262
+ return annotations => {
263
+ const taskAnnotations: Map<number, string> = new Map()
264
+ const categoryIndex: Map<string, number> = new Map()
265
+ let nextCategoryId = 0
266
+
267
+ for (const annotation of annotations) {
268
+ const lines: string[] = []
269
+
270
+ for (const result of annotation.result) {
271
+ if (result.type === 'rectanglelabels') {
272
+ const label = result.value.rectanglelabels[0]
273
+
274
+ // Register category
275
+ if (!categoryIndex.has(label)) {
276
+ categoryIndex.set(label, nextCategoryId++)
277
+ }
278
+
279
+ const classId = categoryIndex.get(label)!
280
+
281
+ // Convert to YOLO format (normalized center x, center y, width, height)
282
+ const x = result.value.x / 100
283
+ const y = result.value.y / 100
284
+ const w = result.value.width / 100
285
+ const h = result.value.height / 100
286
+
287
+ const centerX = x + w / 2
288
+ const centerY = y + h / 2
289
+
290
+ lines.push(`${classId} ${centerX} ${centerY} ${w} ${h}`)
291
+ }
292
+ }
293
+
294
+ if (lines.length > 0) {
295
+ taskAnnotations.set(annotation.task, lines.join('\n'))
296
+ }
297
+ }
298
+
299
+ return taskAnnotations
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Export NER (Named Entity Recognition) results
305
+ */
306
+ static nerJSON(): FormatConverter<any[]> {
307
+ return annotations => {
308
+ return annotations.map(annotation => {
309
+ const entities: any[] = []
310
+
311
+ for (const result of annotation.result) {
312
+ if (result.type === 'labels') {
313
+ entities.push({
314
+ start: result.value.start,
315
+ end: result.value.end,
316
+ text: result.value.text,
317
+ label: result.value.labels[0]
318
+ })
319
+ }
320
+ }
321
+
322
+ return {
323
+ task_id: annotation.task,
324
+ annotation_id: annotation.id,
325
+ entities,
326
+ annotator: annotation.completed_by?.email
327
+ }
328
+ })
329
+ }
330
+ }
331
+ }
332
+
333
+ // Register default formats
334
+ registerExportFormat('json', ExportFormats.json())
335
+ registerExportFormat('jsonl', ExportFormats.jsonl())
336
+ registerExportFormat('csv', ExportFormats.imageClassificationCSV())
337
+ registerExportFormat('rank-csv', ExportFormats.rankNClassificationCSV())
338
+ registerExportFormat('coco', ExportFormats.coco())
339
+ registerExportFormat('yolo', ExportFormats.yolo())
340
+ registerExportFormat('ner-json', ExportFormats.nerJSON())