@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,85 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+
7
+ ### Changed
8
+
9
+ - **[2025-10-07] Fixed hardcoded project ID in `label-studio-label-page.ts`**
10
+ - **Impact**: Critical bug fix - Now supports dynamic project routing
11
+ - **Changes**:
12
+ - Replaced hardcoded `/projects/3` with dynamic project ID extraction from URL route
13
+ - Added `projectId` property to LabelStudioLabelPage component
14
+ - Implemented `_extractProjectIdFromRoute()` method to parse project ID from window.location.pathname
15
+ - Added error handling for missing project ID
16
+ - **Files modified**:
17
+ - `client/label-studio-label-page.ts:28,33-60`
18
+ - **Migration**: No breaking changes - component now accepts optional `projectId` property
19
+
20
+ - **[2025-10-07] Enhanced error handling and loading states**
21
+ - **Impact**: Improved user experience with better feedback
22
+ - **Changes**:
23
+ - Added iframe load timeout detection (15 seconds)
24
+ - Added iframe error event handler
25
+ - Added `iframeLoaded` state tracking
26
+ - Improved error messages for iframe loading failures
27
+ - **Files modified**:
28
+ - `client/label-studio-wrapper.ts:124,127-137,168-178,223`
29
+ - **Benefits**: Users now get clear feedback when Label Studio fails to load
30
+
31
+ - **[2025-10-07] Uncommented webhook exports in server/index.ts**
32
+ - **Impact**: Public API now properly exports webhook-related types and functions
33
+ - **Changes**:
34
+ - Uncommented exports for: `WebhookAction`, `WebhookPayload`, `WebhookHandler`, `registerWebhookHandler`, `unregisterWebhookHandler`, `clearWebhookHandlers`
35
+ - These exports were previously commented out but are fully implemented and functional
36
+ - **Files modified**:
37
+ - `server/index.ts:5-12`
38
+ - **Benefits**: Users can now extend webhook functionality for custom Label Studio event handling
39
+
40
+ ### Technical Details
41
+
42
+ #### Dynamic Project ID Implementation
43
+
44
+ ```typescript
45
+ // Before
46
+ const path = '/projects/3' // Hardcoded project ID
47
+
48
+ // After
49
+ const projectId = this.projectId || this._extractProjectIdFromRoute()
50
+ const path = `/projects/${projectId}`
51
+ ```
52
+
53
+ #### Error Handling Enhancement
54
+
55
+ - Added `@error=${this.handleIframeError}` event listener on iframe element
56
+ - Added timeout detection to catch cases where iframe loads but never fires load event
57
+ - Clear error messages guide users to check server connectivity
58
+
59
+ #### Webhook API Exports
60
+
61
+ Users can now register custom webhook handlers:
62
+
63
+ ```typescript
64
+ import { registerWebhookHandler, WebhookAction } from 'integration-label-studio'
65
+
66
+ registerWebhookHandler(WebhookAction.ANNOTATION_CREATED, async (payload, ctx) => {
67
+ // Custom annotation handling logic
68
+ })
69
+ ```
70
+
71
+ ### Summary
72
+
73
+ **Total Files Modified**: 3
74
+
75
+ - `client/label-studio-label-page.ts` - Fixed hardcoded project ID
76
+ - `client/label-studio-wrapper.ts` - Enhanced error handling
77
+ - `server/index.ts` - Enabled webhook API exports
78
+
79
+ **Impact**:
80
+
81
+ - βœ… Fixes critical routing bug
82
+ - βœ… Improves user experience with loading states
83
+ - βœ… Enables webhook extensibility
84
+
85
+ **Production Ready**: Yes - All changes are backward compatible
@@ -0,0 +1,484 @@
1
+ # 🌐 Label Studio μ™ΈλΆ€ 데이터 μ†Œμ‹± κ°€μ΄λ“œ
2
+
3
+ Label Studio ν”„λ‘œμ νŠΈμ— μ™ΈλΆ€ 데이터λ₯Ό importν•˜λŠ” λ‹€μ–‘ν•œ 방법을 μ„€λͺ…ν•©λ‹ˆλ‹€.
4
+
5
+ ## πŸ“‹ λͺ©μ°¨
6
+
7
+ 1. [Dataset λͺ¨λ“ˆ ν™œμš©](#1-dataset-λͺ¨λ“ˆ-ν™œμš©-μΆ”μ²œ)
8
+ 2. [μ™ΈλΆ€ REST API 연동](#2-μ™ΈλΆ€-rest-api-연동)
9
+ 3. [JSON/CSV 파일 Import](#3-jsoncsv-파일-import)
10
+ 4. [ν΄λΌμš°λ“œ μŠ€ν† λ¦¬μ§€ 연동](#4-ν΄λΌμš°λ“œ-μŠ€ν† λ¦¬μ§€-연동)
11
+
12
+ ---
13
+
14
+ ## 1. Dataset λͺ¨λ“ˆ ν™œμš© (μΆ”μ²œ)
15
+
16
+ Things Factory의 Dataset λͺ¨λ“ˆμ—μ„œ DataSample을 Label Studio둜 μžλ™ exportν•©λ‹ˆλ‹€.
17
+
18
+ ### μž₯점
19
+ - βœ… 데이터 ꡬ쑰 μžλ™ λ³€ν™˜
20
+ - βœ… 메타데이터 μžλ™ λ§€ν•‘
21
+ - βœ… AI 예츑 μžλ™ 생성
22
+ - βœ… μ–‘λ°©ν–₯ 동기화 지원
23
+
24
+ ### GraphQL API
25
+
26
+ ```graphql
27
+ mutation {
28
+ createLabelingTasksFromDataset(input: {
29
+ projectId: 2
30
+ dataSetId: "your-dataset-id"
31
+ imageField: "image"
32
+ autoGeneratePredictions: true
33
+ confidenceThreshold: 0.5
34
+ limit: 100
35
+ }) {
36
+ totalSamples
37
+ tasksCreated
38
+ tasksFailed
39
+ predictionsCreated
40
+ taskIds
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### μ‚¬μš© 방법
46
+
47
+ 1. **DataSet 생성**
48
+ ```graphql
49
+ mutation {
50
+ createDataSet(dataSet: {
51
+ name: "Product Images"
52
+ description: "Quality inspection images"
53
+ active: true
54
+ }) {
55
+ id
56
+ name
57
+ }
58
+ }
59
+ ```
60
+
61
+ 2. **DataSample μΆ”κ°€**
62
+ ```graphql
63
+ mutation {
64
+ createDataSample(dataSample: {
65
+ dataSetId: "dataset-id"
66
+ name: "Sample-001"
67
+ data: {
68
+ image: "https://example.com/image1.jpg"
69
+ product_id: "P123"
70
+ batch: "B456"
71
+ }
72
+ collectedAt: "2025-01-13T10:00:00Z"
73
+ }) {
74
+ id
75
+ }
76
+ }
77
+ ```
78
+
79
+ 3. **Label Studio둜 Export**
80
+ ```graphql
81
+ mutation {
82
+ createLabelingTasksFromDataset(input: {
83
+ projectId: 2
84
+ dataSetId: "dataset-id"
85
+ autoGeneratePredictions: true
86
+ }) {
87
+ tasksCreated
88
+ }
89
+ }
90
+ ```
91
+
92
+ ---
93
+
94
+ ## 2. μ™ΈλΆ€ REST API 연동
95
+
96
+ REST API μ—”λ“œν¬μΈνŠΈμ—μ„œ 직접 데이터λ₯Ό 가져와 Label Studio에 importν•©λ‹ˆλ‹€.
97
+
98
+ ### 미리보기 (Preview)
99
+
100
+ λ¨Όμ € 데이터 ꡬ쑰λ₯Ό ν™•μΈν•©λ‹ˆλ‹€:
101
+
102
+ ```graphql
103
+ query {
104
+ previewExternalDataSource(source: {
105
+ sourceType: "api"
106
+ sourceUrl: "https://api.example.com/images"
107
+ authHeader: "Bearer YOUR_TOKEN"
108
+ httpMethod: "GET"
109
+ dataPath: "data.items"
110
+ }) {
111
+ itemCount
112
+ sampleData
113
+ schema
114
+ }
115
+ }
116
+ ```
117
+
118
+ **응닡 μ˜ˆμ‹œ:**
119
+ ```json
120
+ {
121
+ "itemCount": 1,
122
+ "sampleData": "{\n \"id\": \"123\",\n \"imageUrl\": \"https://...\",\n \"category\": \"defect\"\n}",
123
+ "schema": "{\n \"id\": \"string\",\n \"imageUrl\": \"string\",\n \"category\": \"string\"\n}"
124
+ }
125
+ ```
126
+
127
+ ### Import μ‹€ν–‰
128
+
129
+ ```graphql
130
+ mutation {
131
+ importFromExternalSource(input: {
132
+ projectId: 2
133
+ source: {
134
+ sourceType: "api"
135
+ sourceUrl: "https://api.example.com/images"
136
+ authHeader: "Bearer YOUR_TOKEN"
137
+ httpMethod: "GET"
138
+ dataPath: "data.items"
139
+ }
140
+ imageField: "imageUrl"
141
+ fieldMapping: "{\"id\":\"task_id\", \"category\":\"label\"}"
142
+ autoGeneratePredictions: true
143
+ limit: 100
144
+ }) {
145
+ totalFetched
146
+ tasksImported
147
+ tasksFailed
148
+ taskIds
149
+ }
150
+ }
151
+ ```
152
+
153
+ ### API 응닡 ν˜•μ‹
154
+
155
+ μ™ΈλΆ€ APIλŠ” λ‹€μŒ ν˜•μ‹μœΌλ‘œ 응닡해야 ν•©λ‹ˆλ‹€:
156
+
157
+ ```json
158
+ {
159
+ "data": {
160
+ "items": [
161
+ {
162
+ "id": "img-001",
163
+ "imageUrl": "https://storage.example.com/image1.jpg",
164
+ "category": "electronics",
165
+ "metadata": {
166
+ "width": 1920,
167
+ "height": 1080
168
+ }
169
+ }
170
+ ]
171
+ }
172
+ }
173
+ ```
174
+
175
+ λ˜λŠ” λ°°μ—΄ 직접 λ°˜ν™˜:
176
+
177
+ ```json
178
+ [
179
+ {
180
+ "id": "img-001",
181
+ "imageUrl": "https://storage.example.com/image1.jpg"
182
+ }
183
+ ]
184
+ ```
185
+
186
+ ---
187
+
188
+ ## 3. JSON/CSV 파일 Import
189
+
190
+ 파일 URLμ—μ„œ 직접 데이터λ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€.
191
+
192
+ ### JSON 파일
193
+
194
+ ```graphql
195
+ mutation {
196
+ importFromExternalSource(input: {
197
+ projectId: 2
198
+ source: {
199
+ sourceType: "json"
200
+ sourceUrl: "https://example.com/data/images.json"
201
+ dataPath: "images"
202
+ }
203
+ imageField: "url"
204
+ limit: 50
205
+ }) {
206
+ totalFetched
207
+ tasksImported
208
+ }
209
+ }
210
+ ```
211
+
212
+ **images.json μ˜ˆμ‹œ:**
213
+ ```json
214
+ {
215
+ "images": [
216
+ {
217
+ "url": "https://example.com/img1.jpg",
218
+ "label": "cat",
219
+ "confidence": 0.95
220
+ },
221
+ {
222
+ "url": "https://example.com/img2.jpg",
223
+ "label": "dog",
224
+ "confidence": 0.87
225
+ }
226
+ ]
227
+ }
228
+ ```
229
+
230
+ ### CSV 파일
231
+
232
+ ```graphql
233
+ mutation {
234
+ importFromExternalSource(input: {
235
+ projectId: 2
236
+ source: {
237
+ sourceType: "csv"
238
+ sourceUrl: "https://example.com/data/images.csv"
239
+ }
240
+ imageField: "image_url"
241
+ limit: 50
242
+ }) {
243
+ totalFetched
244
+ tasksImported
245
+ }
246
+ }
247
+ ```
248
+
249
+ **images.csv μ˜ˆμ‹œ:**
250
+ ```csv
251
+ id,image_url,category,status
252
+ 1,https://example.com/img1.jpg,electronics,new
253
+ 2,https://example.com/img2.jpg,furniture,new
254
+ 3,https://example.com/img3.jpg,clothing,new
255
+ ```
256
+
257
+ ---
258
+
259
+ ## 4. ν΄λΌμš°λ“œ μŠ€ν† λ¦¬μ§€ 연동
260
+
261
+ ### S3/GCS/Azure Blob (Presigned URLs)
262
+
263
+ ν΄λΌμš°λ“œ μŠ€ν† λ¦¬μ§€μ˜ 경우 presigned URL을 μ‚¬μš©ν•©λ‹ˆλ‹€:
264
+
265
+ ```graphql
266
+ mutation {
267
+ importFromExternalSource(input: {
268
+ projectId: 2
269
+ source: {
270
+ sourceType: "url"
271
+ sourceUrl: "https://my-bucket.s3.amazonaws.com/manifest.json?X-Amz-..."
272
+ }
273
+ imageField: "s3_url"
274
+ }) {
275
+ tasksImported
276
+ }
277
+ }
278
+ ```
279
+
280
+ **manifest.json μ˜ˆμ‹œ:**
281
+ ```json
282
+ [
283
+ {
284
+ "s3_url": "https://my-bucket.s3.amazonaws.com/image1.jpg?X-Amz-...",
285
+ "object_key": "images/2025/01/image1.jpg",
286
+ "size": 245678,
287
+ "uploaded_at": "2025-01-13T10:00:00Z"
288
+ }
289
+ ]
290
+ ```
291
+
292
+ ### Label Studio Cloud Storage 직접 연동
293
+
294
+ Label StudioλŠ” ν΄λΌμš°λ“œ μŠ€ν† λ¦¬μ§€ 직접 연동도 μ§€μ›ν•©λ‹ˆλ‹€:
295
+
296
+ 1. **Label Studio UIμ—μ„œ Storage μ„€μ •**
297
+ - Project Settings β†’ Cloud Storage
298
+ - Add Source Storage (S3/GCS/Azure)
299
+
300
+ 2. **Sync λ²„νŠΌμœΌλ‘œ μžλ™ import**
301
+ - Storageμ—μ„œ μžλ™μœΌλ‘œ task 생성
302
+ - Label Studioκ°€ 직접 μŠ€ν† λ¦¬μ§€ μ ‘κ·Ό
303
+
304
+ ---
305
+
306
+ ## πŸ“Š Field Mapping (ν•„λ“œ λ§€ν•‘)
307
+
308
+ μ†ŒμŠ€ λ°μ΄ν„°μ˜ ν•„λ“œλͺ…κ³Ό Label Studio ν•„λ“œλͺ…이 λ‹€λ₯Ό λ•Œ 맀핑을 μ§€μ •ν•©λ‹ˆλ‹€.
309
+
310
+ ### μ˜ˆμ‹œ
311
+
312
+ **μ†ŒμŠ€ 데이터:**
313
+ ```json
314
+ {
315
+ "product_image": "https://...",
316
+ "sku": "SKU-123",
317
+ "inspection_date": "2025-01-13"
318
+ }
319
+ ```
320
+
321
+ **Field Mapping:**
322
+ ```json
323
+ {
324
+ "product_image": "image",
325
+ "sku": "product_id",
326
+ "inspection_date": "date"
327
+ }
328
+ ```
329
+
330
+ **GraphQL:**
331
+ ```graphql
332
+ mutation {
333
+ importFromExternalSource(input: {
334
+ projectId: 2
335
+ source: { ... }
336
+ imageField: "product_image"
337
+ fieldMapping: "{\"product_image\":\"image\",\"sku\":\"product_id\",\"inspection_date\":\"date\"}"
338
+ }) { ... }
339
+ }
340
+ ```
341
+
342
+ ---
343
+
344
+ ## πŸ”„ 데이터 흐름 λ‹€μ΄μ–΄κ·Έλž¨
345
+
346
+ ```
347
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
348
+ β”‚ External API β”‚
349
+ β”‚ JSON/CSV File β”‚
350
+ β”‚ Cloud Storage β”‚
351
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
352
+ β”‚ HTTP Request
353
+ β–Ό
354
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
355
+ β”‚ ExternalDataSourceServiceβ”‚
356
+ β”‚ - fetchFromSource() β”‚
357
+ β”‚ - transformData() β”‚
358
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
359
+ β”‚ Label Studio API
360
+ β–Ό
361
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
362
+ β”‚ Label Studio β”‚
363
+ β”‚ Project β”‚
364
+ β”‚ (Tasks 생성) β”‚
365
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
366
+ ```
367
+
368
+ ---
369
+
370
+ ## πŸ› οΈ κ³ κΈ‰ μ‚¬μš©λ²•
371
+
372
+ ### 1. 인증 헀더 μ‚¬μš©
373
+
374
+ ```graphql
375
+ source: {
376
+ sourceType: "api"
377
+ sourceUrl: "https://api.example.com/data"
378
+ authHeader: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
379
+ }
380
+ ```
381
+
382
+ ### 2. POST μš”μ²­μœΌλ‘œ 데이터 κ°€μ Έμ˜€κΈ°
383
+
384
+ ```graphql
385
+ source: {
386
+ sourceType: "api"
387
+ sourceUrl: "https://api.example.com/search"
388
+ httpMethod: "POST"
389
+ requestBody: "{\"query\":\"defect images\",\"limit\":100}"
390
+ dataPath: "results"
391
+ }
392
+ ```
393
+
394
+ ### 3. μ€‘μ²©λœ 데이터 μΆ”μΆœ
395
+
396
+ ```graphql
397
+ source: {
398
+ sourceType: "url"
399
+ sourceUrl: "https://example.com/data.json"
400
+ dataPath: "response.data.items" # response.data.items λ°°μ—΄ μΆ”μΆœ
401
+ }
402
+ ```
403
+
404
+ ---
405
+
406
+ ## πŸ“ Best Practices
407
+
408
+ ### 1. 미리보기 λ¨Όμ € μ‹€ν–‰
409
+ 항상 `previewExternalDataSource`둜 데이터 ꡬ쑰λ₯Ό ν™•μΈν•œ ν›„ importλ₯Ό μ‹€ν–‰ν•˜μ„Έμš”.
410
+
411
+ ### 2. 배치 크기 쑰절
412
+ λŒ€λŸ‰ λ°μ΄ν„°λŠ” `limit` νŒŒλΌλ―Έν„°λ‘œ λ‚˜λˆ„μ–΄ importν•©λ‹ˆλ‹€:
413
+ ```graphql
414
+ # 첫 번째 배치
415
+ mutation { importFromExternalSource(input: { limit: 100 }) { ... } }
416
+
417
+ # 두 번째 배치 (APIμ—μ„œ offset/page 지원 ν•„μš”)
418
+ mutation { importFromExternalSource(input: { limit: 100 }) { ... } }
419
+ ```
420
+
421
+ ### 3. 였λ₯˜ 처리
422
+ Import κ²°κ³Όλ₯Ό ν™•μΈν•˜κ³  μ‹€νŒ¨ν•œ ν•­λͺ©μ€ μž¬μ‹œλ„ν•©λ‹ˆλ‹€:
423
+ ```graphql
424
+ mutation {
425
+ importFromExternalSource(input: { ... }) {
426
+ totalFetched
427
+ tasksImported
428
+ tasksFailed # 이 κ°’ 확인
429
+ error
430
+ }
431
+ }
432
+ ```
433
+
434
+ ### 4. 이미지 URL 검증
435
+ Import 전에 이미지 URL이 μœ νš¨ν•œμ§€ ν™•μΈν•˜μ„Έμš”:
436
+ - URL이 곡개 μ ‘κ·Ό κ°€λŠ₯ν•œμ§€
437
+ - CORS 섀정이 μ˜¬λ°”λ₯Έμ§€
438
+ - Presigned URL의 만료 μ‹œκ°„ 확인
439
+
440
+ ---
441
+
442
+ ## 🚨 문제 ν•΄κ²°
443
+
444
+ ### "No image URL found"
445
+ - `imageField` νŒŒλΌλ―Έν„°λ₯Ό μ†ŒμŠ€ λ°μ΄ν„°μ˜ μ‹€μ œ ν•„λ“œλͺ…μœΌλ‘œ μ„€μ •
446
+ - Preview둜 데이터 ꡬ쑰 확인
447
+
448
+ ### "401 Unauthorized"
449
+ - `authHeader`에 μœ νš¨ν•œ 인증 토큰 μ„€μ •
450
+ - API ν‚€λ‚˜ Bearer 토큰 확인
451
+
452
+ ### "Path not found in data"
453
+ - `dataPath`κ°€ μ˜¬λ°”λ₯Έμ§€ 확인
454
+ - Preview둜 μ‹€μ œ 응닡 ꡬ쑰 확인
455
+
456
+ ### ImportλŠ” μ„±κ³΅ν–ˆμ§€λ§Œ 이미지가 μ•ˆ λ³΄μž„
457
+ - Label Studioμ—μ„œ 이미지 URL μ ‘κ·Ό κ°€λŠ₯ν•œμ§€ 확인
458
+ - CORS μ„€μ • 확인
459
+ - Presigned URL 만료 확인
460
+
461
+ ---
462
+
463
+ ## πŸ“š μ°Έκ³  자료
464
+
465
+ - [Label Studio API Documentation](https://labelstud.io/api)
466
+ - [Label Studio Cloud Storage](https://labelstud.io/guide/storage.html)
467
+ - [Things Factory Dataset Module](../dataset/README.md)
468
+
469
+ ---
470
+
471
+ ## 🎯 μš”μ•½
472
+
473
+ | 방법 | μš©λ„ | μž₯점 | 단점 |
474
+ |------|------|------|------|
475
+ | **Dataset λͺ¨λ“ˆ** | Things Factory λ‚΄λΆ€ 데이터 | μžλ™ λ³€ν™˜, μ–‘λ°©ν–₯ 동기화 | Things Factory μ „μš© |
476
+ | **REST API** | μ™ΈλΆ€ μ‹œμŠ€ν…œ 연동 | μ‹€μ‹œκ°„ 데이터, 동적 쑰회 | API 개발 ν•„μš” |
477
+ | **JSON/CSV 파일** | 정적 데이터셋 | 간단함, ν‘œμ€€ ν˜•μ‹ | μˆ˜λ™ μ—…λ°μ΄νŠΈ ν•„μš” |
478
+ | **Cloud Storage** | λŒ€μš©λŸ‰ 이미지 | ν™•μž₯μ„±, λΉ„μš© 효율 | μ„€μ • 볡작 |
479
+
480
+ **μΆ”μ²œ μˆœμ„œ:**
481
+ 1. Things Factory λ‚΄λΆ€ 데이터 β†’ **Dataset λͺ¨λ“ˆ**
482
+ 2. μ™ΈλΆ€ API 있음 β†’ **REST API 연동**
483
+ 3. 파일 ν˜•νƒœ 데이터 β†’ **JSON/CSV Import**
484
+ 4. λŒ€μš©λŸ‰ 이미지 β†’ **Cloud Storage**