@things-factory/dataset 8.0.0-beta.9 → 8.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. package/client/activities/activity-data-collect-edit.ts +105 -0
  2. package/client/activities/activity-data-collect-view.ts +91 -0
  3. package/client/activities/activity-data-review-edit.ts +278 -0
  4. package/client/activities/activity-data-review-view.ts +226 -0
  5. package/client/activities/activity-ooc-resolve-edit.ts +195 -0
  6. package/client/activities/activity-ooc-resolve-view.ts +143 -0
  7. package/client/activities/activity-ooc-review-edit.ts +173 -0
  8. package/client/activities/activity-ooc-review-view.ts +129 -0
  9. package/client/bootstrap.ts +35 -0
  10. package/client/components/data-entry-form.ts +109 -0
  11. package/client/index.ts +1 -0
  12. package/client/pages/data-archive/data-archive-list-page.ts +277 -0
  13. package/client/pages/data-archive/data-archive-request-popup.ts +177 -0
  14. package/client/pages/data-entry/data-entry-list-page.ts +464 -0
  15. package/client/pages/data-key-set/data-key-item-list.ts +183 -0
  16. package/client/pages/data-key-set/data-key-set-importer.ts +89 -0
  17. package/client/pages/data-key-set/data-key-set-list-page.ts +413 -0
  18. package/client/pages/data-ooc/data-ooc-list-page.ts +549 -0
  19. package/client/pages/data-ooc/data-ooc-page.ts +164 -0
  20. package/client/pages/data-ooc/data-ooc-view.ts +236 -0
  21. package/client/pages/data-ooc/data-oocs-page.ts +200 -0
  22. package/client/pages/data-report/data-report-embed-page.ts +108 -0
  23. package/client/pages/data-report/data-report-list-page.ts +454 -0
  24. package/client/pages/data-report/data-report-samples-page.ts +174 -0
  25. package/client/pages/data-report/jasper-report-oocs-page.ts +110 -0
  26. package/client/pages/data-report/jasper-report-samples-crosstab-page.ts +110 -0
  27. package/client/pages/data-report/jasper-report-samples-page.ts +110 -0
  28. package/client/pages/data-sample/data-sample-list-page.ts +442 -0
  29. package/client/pages/data-sample/data-sample-page.ts +55 -0
  30. package/client/pages/data-sample/data-sample-search-page.ts +424 -0
  31. package/client/pages/data-sample/data-sample-view.ts +292 -0
  32. package/client/pages/data-sample/data-samples-page.ts +249 -0
  33. package/client/pages/data-sensor/data-sensor-list-page.ts +456 -0
  34. package/client/pages/data-set/data-item-list.ts +304 -0
  35. package/client/pages/data-set/data-set-importer.ts +89 -0
  36. package/client/pages/data-set/data-set-list-page.ts +1078 -0
  37. package/client/pages/data-summary/data-summary-list-page.ts +363 -0
  38. package/client/pages/data-summary/data-summary-period-page.ts +439 -0
  39. package/client/pages/data-summary/data-summary-search-page.ts +426 -0
  40. package/client/pages/data-summary/data-summary-view.ts +133 -0
  41. package/client/route.ts +91 -0
  42. package/client/tsconfig.json +13 -0
  43. package/dist-client/activities/activity-data-review-edit.js +19 -10
  44. package/dist-client/activities/activity-data-review-edit.js.map +1 -1
  45. package/dist-client/activities/activity-data-review-view.js +80 -0
  46. package/dist-client/activities/activity-data-review-view.js.map +1 -1
  47. package/dist-client/pages/data-entry/data-entry-list-page.js +2 -2
  48. package/dist-client/pages/data-entry/data-entry-list-page.js.map +1 -1
  49. package/dist-client/tsconfig.tsbuildinfo +1 -1
  50. package/dist-server/controllers/create-data-ooc.js +2 -0
  51. package/dist-server/controllers/create-data-ooc.js.map +1 -1
  52. package/dist-server/service/data-archive/index.d.ts +1 -1
  53. package/dist-server/service/data-ooc/index.d.ts +1 -1
  54. package/dist-server/service/data-sample/data-sample-query.d.ts +1 -1
  55. package/dist-server/service/data-sample/data-sample-query.js +3 -3
  56. package/dist-server/service/data-sample/data-sample-query.js.map +1 -1
  57. package/dist-server/service/data-sample/index.d.ts +1 -1
  58. package/dist-server/service/data-set/index.d.ts +1 -1
  59. package/dist-server/service/index.d.ts +2 -2
  60. package/dist-server/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +26 -26
  62. package/server/activities/activity-data-collect.ts +100 -0
  63. package/server/activities/activity-data-review.ts +109 -0
  64. package/server/activities/activity-ooc-resolve.ts +123 -0
  65. package/server/activities/activity-ooc-review.ts +95 -0
  66. package/server/activities/index.ts +11 -0
  67. package/server/controllers/create-data-ooc.ts +80 -0
  68. package/server/controllers/create-data-sample.ts +323 -0
  69. package/server/controllers/data-use-case.ts +98 -0
  70. package/server/controllers/finalize-data-collection.ts +388 -0
  71. package/server/controllers/index.ts +6 -0
  72. package/server/controllers/issue-data-collection-task.ts +70 -0
  73. package/server/controllers/issue-ooc-resolve.ts +58 -0
  74. package/server/controllers/issue-ooc-review.ts +52 -0
  75. package/server/controllers/jasper-report.ts +186 -0
  76. package/server/controllers/query-data-summary-by-period.ts +178 -0
  77. package/server/controllers/shiny-report.ts +54 -0
  78. package/server/engine/index.ts +1 -0
  79. package/server/engine/task/create-data-sample.ts +100 -0
  80. package/server/engine/task/index.ts +2 -0
  81. package/server/engine/task/issue-collect-data.ts +45 -0
  82. package/server/index.ts +8 -0
  83. package/server/routes.ts +188 -0
  84. package/server/service/data-archive/data-archive-mutation.ts +273 -0
  85. package/server/service/data-archive/data-archive-query.ts +58 -0
  86. package/server/service/data-archive/data-archive-type.ts +48 -0
  87. package/server/service/data-archive/data-archive.ts +69 -0
  88. package/server/service/data-archive/index.ts +6 -0
  89. package/server/service/data-key-set/data-key-item-type.ts +31 -0
  90. package/server/service/data-key-set/data-key-set-mutation.ts +201 -0
  91. package/server/service/data-key-set/data-key-set-query.ts +68 -0
  92. package/server/service/data-key-set/data-key-set-type.ts +70 -0
  93. package/server/service/data-key-set/data-key-set.ts +86 -0
  94. package/server/service/data-key-set/index.ts +6 -0
  95. package/server/service/data-ooc/data-ooc-mutation.ts +154 -0
  96. package/server/service/data-ooc/data-ooc-query.ts +106 -0
  97. package/server/service/data-ooc/data-ooc-subscription.ts +48 -0
  98. package/server/service/data-ooc/data-ooc-type.ts +71 -0
  99. package/server/service/data-ooc/data-ooc.ts +259 -0
  100. package/server/service/data-ooc/index.ts +7 -0
  101. package/server/service/data-sample/data-sample-mutation.ts +18 -0
  102. package/server/service/data-sample/data-sample-query.ts +215 -0
  103. package/server/service/data-sample/data-sample-type.ts +47 -0
  104. package/server/service/data-sample/data-sample.ts +193 -0
  105. package/server/service/data-sample/index.ts +6 -0
  106. package/server/service/data-sensor/data-sensor-mutation.ts +116 -0
  107. package/server/service/data-sensor/data-sensor-query.ts +76 -0
  108. package/server/service/data-sensor/data-sensor-type.ts +104 -0
  109. package/server/service/data-sensor/data-sensor.ts +126 -0
  110. package/server/service/data-sensor/index.ts +6 -0
  111. package/server/service/data-set/data-item-type.ts +155 -0
  112. package/server/service/data-set/data-set-mutation.ts +552 -0
  113. package/server/service/data-set/data-set-query.ts +461 -0
  114. package/server/service/data-set/data-set-type.ts +204 -0
  115. package/server/service/data-set/data-set.ts +326 -0
  116. package/server/service/data-set/index.ts +6 -0
  117. package/server/service/data-set-history/data-set-history-query.ts +126 -0
  118. package/server/service/data-set-history/data-set-history-type.ts +12 -0
  119. package/server/service/data-set-history/data-set-history.ts +217 -0
  120. package/server/service/data-set-history/event-subscriber.ts +17 -0
  121. package/server/service/data-set-history/index.ts +7 -0
  122. package/server/service/data-spec/data-spec-manager.ts +21 -0
  123. package/server/service/data-spec/data-spec-query.ts +21 -0
  124. package/server/service/data-spec/data-spec.ts +45 -0
  125. package/server/service/data-spec/index.ts +5 -0
  126. package/server/service/data-summary/data-summary-mutation.ts +45 -0
  127. package/server/service/data-summary/data-summary-query.ts +179 -0
  128. package/server/service/data-summary/data-summary-type.ts +86 -0
  129. package/server/service/data-summary/data-summary.ts +170 -0
  130. package/server/service/data-summary/index.ts +7 -0
  131. package/server/service/index.ts +57 -0
  132. package/server/tsconfig.json +10 -0
  133. package/server/utils/config-resolver.ts +29 -0
  134. package/server/utils/index.ts +1 -0
@@ -0,0 +1,388 @@
1
+ const statistics = require('simple-statistics')
2
+ const deepClone = require('lodash/cloneDeep')
3
+
4
+ import moment from 'moment-timezone'
5
+ import { In } from 'typeorm'
6
+
7
+ import { Sorting, getQueryBuilderFromListParams } from '@things-factory/shell'
8
+ import { logger } from '@things-factory/env'
9
+ import {
10
+ getDateRangeForWorkDate,
11
+ getDateRangeForWorkShift,
12
+ getLatestWorkDateAndShift,
13
+ getSummaryScheduleForWorkDate,
14
+ getSummaryScheduleForWorkShift
15
+ } from '@things-factory/work-shift'
16
+
17
+ import { DataSample } from '../service/data-sample/data-sample'
18
+ import { DataSet, DataSetSummaryPeriodType } from '../service/data-set/data-set'
19
+
20
+ import { DataSummary } from '../service/data-summary/data-summary'
21
+ import { DataKeyItem } from '../service/data-key-set/data-key-item-type'
22
+
23
+ import { DataItem } from 'service'
24
+
25
+ const STAT_FUNCTION_MAP = {
26
+ sum: 'sum',
27
+ mean: 'mean',
28
+ stddev: 'standardDeviation',
29
+ variance: 'variance',
30
+ min: 'min',
31
+ max: 'max',
32
+ range: 'range',
33
+ median: 'median',
34
+ mode: 'mode'
35
+ }
36
+
37
+ const compareKeys = (dataKeyItems: DataKeyItem[], summary: Partial<DataSummary>, sample: DataSample): boolean => {
38
+ return dataKeyItems.every((item, index) => {
39
+ const prop = `key0${index + 1}`
40
+ return sample[prop] === summary[prop]
41
+ })
42
+ }
43
+
44
+ const buildKeysFromSample = (dataKeyItems: DataKeyItem[], sample: DataSample): Partial<DataSummary> => {
45
+ return dataKeyItems.reduce((sum, item, index) => {
46
+ const prop = `key0${index + 1}`
47
+ sum[prop] = sample[prop]
48
+
49
+ return sum
50
+ }, {} as Partial<DataSummary>)
51
+ }
52
+
53
+ const buildKeySortingList = (dataKeyItems: DataKeyItem[]): Sorting[] => {
54
+ return dataKeyItems.reduce((sum, item, index) => {
55
+ const name = `key0${index + 1}`
56
+ sum.push({ name, desc: true })
57
+ return sum
58
+ }, [])
59
+ }
60
+
61
+ const calculateSummary = (dataItems: DataItem[], base: { [tag: string]: any[] }) => {
62
+ return dataItems.reduce((summary, item) => {
63
+ const tag = item.tag
64
+
65
+ const data = base[tag]
66
+ .flat(Infinity)
67
+ .map(Number)
68
+ .filter(item => !isNaN(item))
69
+
70
+ if (data.length > 0) {
71
+ try {
72
+ switch (item.stat) {
73
+ case 'range':
74
+ summary[tag] = statistics.max(data) - statistics.min(data)
75
+ break
76
+
77
+ default:
78
+ const functionName = STAT_FUNCTION_MAP[item.stat]
79
+ summary[tag] = (functionName && statistics[functionName](data)) || ''
80
+ }
81
+ } catch (err) {
82
+ summary[tag] = null
83
+ console.error(err)
84
+ }
85
+ } else {
86
+ summary[tag] = null
87
+ }
88
+
89
+ return summary
90
+ }, {})
91
+ }
92
+
93
+ const fillSummaryResult = (dataSummary: Partial<DataSummary>, dataItems: DataItem[], base: { [tag: string]: any[] }): void => {
94
+ const summary = calculateSummary(dataItems, base)
95
+
96
+ dataSummary.summary = summary
97
+ dataItems.slice(0, 4).forEach((dataItem, idx) => {
98
+ const value = Number(summary[dataItem.tag])
99
+ dataSummary[`data0${idx + 1}`] = isNaN(value) ? null : value
100
+ })
101
+ }
102
+
103
+ async function getLatestTimesForPeriod(periodType: DataSetSummaryPeriodType, context: ResolverContext): Promise<{ date?: string; period?: string; range: Date[] }> {
104
+ const { domain } = context.state
105
+ const now = moment()
106
+
107
+ if (periodType == DataSetSummaryPeriodType.Hour) {
108
+ const begin = now.clone().subtract(1, 'hour').startOf('hour')
109
+ const end = now.clone().startOf('hour')
110
+ const date = begin.clone().tz(domain.timezone)
111
+
112
+ return {
113
+ date: date.format('YYYY-MM-DD'),
114
+ period: date.format('HH'),
115
+ range: [begin.toDate(), end.toDate()]
116
+ }
117
+ } else if (periodType == DataSetSummaryPeriodType.WorkShift) {
118
+ const { workDate, workShift, shiftRange } = await getLatestWorkDateAndShift(domain, new Date())
119
+
120
+ return { date: workDate, period: workShift, range: shiftRange }
121
+ } else if (periodType == DataSetSummaryPeriodType.WorkDate) {
122
+ const { workDate, dateRange } = await getLatestWorkDateAndShift(domain, new Date())
123
+
124
+ return { date: workDate, range: dateRange }
125
+ } else if (periodType == DataSetSummaryPeriodType.Day) {
126
+ const begin = now.clone().subtract(1, 'day').startOf('day')
127
+ const end = now.clone().startOf('day')
128
+ const date = begin.clone().tz(domain.timezone)
129
+
130
+ return {
131
+ date: date.format('YYYY-MM-DD'),
132
+ range: [begin.toDate(), end.toDate()]
133
+ }
134
+ }
135
+ }
136
+
137
+ async function getTimesForPeriod(
138
+ periodType: DataSetSummaryPeriodType,
139
+ date: string,
140
+ period: string,
141
+ context: ResolverContext
142
+ ): Promise<{ date?: string; period?: string; range: Date[] }> {
143
+ const { domain } = context.state
144
+
145
+ if (periodType == DataSetSummaryPeriodType.Hour) {
146
+ const theDate = moment.tz(`${date} ${period}:00:00`, 'YYYY-MM-DD HH:mm:ss', domain.timezone)
147
+
148
+ const begin = theDate.clone().startOf('hour').toDate()
149
+ const end = theDate.clone().add(+1, 'hour').startOf('hour').toDate()
150
+
151
+ return {
152
+ date,
153
+ period,
154
+ range: [begin, end]
155
+ }
156
+ } else if (periodType == DataSetSummaryPeriodType.WorkShift) {
157
+ const range = await getDateRangeForWorkShift(domain, date, period)
158
+
159
+ return { date, period, range }
160
+ } else if (periodType == DataSetSummaryPeriodType.WorkDate) {
161
+ const range = await getDateRangeForWorkDate(domain, date)
162
+
163
+ return { date, range }
164
+ } else if (periodType == DataSetSummaryPeriodType.Day) {
165
+ const theDate = moment.tz(`${date} 00:00:00`, 'YYYY-MM-DD HH:mm:ss', domain.timezone)
166
+
167
+ const begin = theDate.clone().startOf('day').toDate()
168
+ const end = theDate.clone().add(1, 'day').startOf('day').toDate()
169
+
170
+ return {
171
+ date: moment(begin).tz(domain.timezone).format('YYYY-MM-DD'),
172
+ range: [begin, end]
173
+ }
174
+ }
175
+ }
176
+
177
+ export async function getDataFinalizeCrontabSchedule(dataSet: DataSet, context: ResolverContext): Promise<string> {
178
+ const { domain, user, tx } = context.state
179
+
180
+ try {
181
+ const { summaryPeriod } = dataSet
182
+
183
+ if (summaryPeriod == DataSetSummaryPeriodType.Hour) {
184
+ return '0 5 * * * *'
185
+ } else if (summaryPeriod == DataSetSummaryPeriodType.WorkShift) {
186
+ return await getSummaryScheduleForWorkShift(domain)
187
+ } else if (summaryPeriod == DataSetSummaryPeriodType.WorkDate) {
188
+ return await getSummaryScheduleForWorkDate(domain)
189
+ } else if (summaryPeriod == DataSetSummaryPeriodType.Day) {
190
+ return '0 10 0 * * *'
191
+ }
192
+ } catch (err) {
193
+ console.error(err)
194
+ }
195
+ }
196
+
197
+ export async function finalizeLatestDataCollection(dataSetId: string, context: ResolverContext): Promise<boolean> {
198
+ const { domain, user, tx } = context.state
199
+
200
+ try {
201
+ const dataSet = await tx.getRepository(DataSet).findOne({
202
+ where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id: dataSetId },
203
+ relations: ['dataKeySet']
204
+ })
205
+
206
+ const dataKeyItems = dataSet.dataKeySet?.dataKeyItems || []
207
+ const dataItems = dataSet.dataItems.filter(item => item.stat)
208
+ const initialSummary = dataItems.reduce((sum, item) => {
209
+ sum[item.tag] = []
210
+ return sum
211
+ }, {})
212
+
213
+ const { date, period, range } = await getLatestTimesForPeriod(dataSet.summaryPeriod, context)
214
+ const limit = 100
215
+ var page = 1
216
+
217
+ var summaries: Partial<DataSummary>[] = []
218
+ var summary: Partial<DataSummary>
219
+
220
+ do {
221
+ const samples = await getQueryBuilderFromListParams({
222
+ repository: tx.getRepository(DataSample),
223
+ domain,
224
+ params: {
225
+ filters: [{ name: 'dataSetId', operator: 'eq', value: dataSetId }],
226
+ pagination: { page, limit },
227
+ sortings: [...buildKeySortingList(dataKeyItems), { name: 'collectedAt', desc: true }]
228
+ },
229
+ alias: 'datasample'
230
+ })
231
+ // The 'Between' operator includes the 'to' time in the filtering, making it unsuitable for the desired use case.
232
+ // .andWhere({ collectedAt: Between.apply(null, range) })
233
+ .andWhere('datasample.collectedAt >= :from', { from: range[0] })
234
+ .andWhere('datasample.collectedAt < :to', { to: range[1] })
235
+ .getMany()
236
+
237
+ for (const sample of samples) {
238
+ if (!summary || !compareKeys(dataKeyItems, summary, sample)) {
239
+ if (summary) {
240
+ fillSummaryResult(summary, dataItems, summary.summary)
241
+ summaries.push(summary)
242
+ }
243
+
244
+ summary = {
245
+ domain,
246
+ name: dataSet.name,
247
+ description: dataSet.description,
248
+ date,
249
+ period,
250
+ dataSet,
251
+ ...buildKeysFromSample(dataKeyItems, sample),
252
+ count: 0,
253
+ countOoc: 0,
254
+ countOos: 0,
255
+ summary: deepClone(initialSummary),
256
+ updater: user,
257
+ creator: user
258
+ }
259
+ }
260
+
261
+ summary.count++
262
+ sample.ooc && summary.countOoc++
263
+ sample.oos && summary.countOos++
264
+
265
+ dataItems.forEach(item => {
266
+ summary.summary[item.tag].push(sample.data[item.tag])
267
+ })
268
+ }
269
+
270
+ if (samples.length < limit) {
271
+ if (summary) {
272
+ fillSummaryResult(summary, dataItems, summary.summary)
273
+ summaries.push(summary)
274
+ }
275
+ break
276
+ }
277
+
278
+ page++
279
+ } while (true)
280
+
281
+ tx.getRepository(DataSummary).upsert(summaries, ['domain', 'dataSet', 'key01', 'key02', 'key03', 'key04', 'key05', 'date', 'period'])
282
+
283
+ return true
284
+ } catch (e) {
285
+ logger.error(e)
286
+ }
287
+
288
+ return false
289
+ }
290
+
291
+ export async function finalizeDataCollection(dataSetId: string, date: string, period: string, context: ResolverContext): Promise<boolean> {
292
+ const { domain, user, tx } = context.state
293
+
294
+ try {
295
+ const dataSet =
296
+ dataSetId &&
297
+ (await tx.getRepository(DataSet).findOne({
298
+ where: { domain: In([domain.id, domain.parentId].filter(Boolean)), id: dataSetId },
299
+ relations: ['dataKeySet']
300
+ }))
301
+
302
+ const dataKeyItems = dataSet.dataKeySet?.dataKeyItems || []
303
+ const dataItems = dataSet.dataItems.filter(item => item.stat)
304
+ const initialSummary = dataItems.reduce((sum, item) => {
305
+ sum[item.tag] = []
306
+ return sum
307
+ }, {})
308
+
309
+ const times = await getTimesForPeriod(dataSet.summaryPeriod, date, period, context)
310
+ const range = times.range
311
+ period = times.period
312
+
313
+ const limit = 100
314
+ var page = 1
315
+
316
+ var summaries: Partial<DataSummary>[] = []
317
+ var summary: Partial<DataSummary>
318
+
319
+ do {
320
+ const samples = await getQueryBuilderFromListParams({
321
+ repository: tx.getRepository(DataSample),
322
+ params: {
323
+ filters: [{ name: 'dataSetId', operator: 'eq', value: dataSetId }],
324
+ pagination: { page, limit },
325
+ sortings: [...buildKeySortingList(dataKeyItems), { name: 'collectedAt', desc: true }]
326
+ },
327
+ domain,
328
+ alias: 'datasample'
329
+ })
330
+ // The 'Between' operator includes the 'to' time in the filtering, making it unsuitable for the desired use case.
331
+ // .andWhere({ collectedAt: Between.apply(null, range) })
332
+ .andWhere('datasample.collectedAt >= :from', { from: range[0] })
333
+ .andWhere('datasample.collectedAt < :to', { to: range[1] })
334
+ .getMany()
335
+
336
+ for (const sample of samples) {
337
+ if (!summary || !compareKeys(dataKeyItems, summary, sample)) {
338
+ if (summary) {
339
+ fillSummaryResult(summary, dataItems, summary.summary)
340
+ summaries.push(summary)
341
+ }
342
+
343
+ summary = {
344
+ domain,
345
+ name: dataSet.name,
346
+ description: dataSet.description,
347
+ date,
348
+ period,
349
+ dataSet,
350
+ ...buildKeysFromSample(dataKeyItems, sample),
351
+ count: 0,
352
+ countOoc: 0,
353
+ countOos: 0,
354
+ summary: deepClone(initialSummary),
355
+ updater: user,
356
+ creator: user
357
+ }
358
+ }
359
+
360
+ summary.count++
361
+ sample.ooc && summary.countOoc++
362
+ sample.oos && summary.countOos++
363
+
364
+ dataItems.forEach(item => {
365
+ summary.summary[item.tag].push(sample.data[item.tag])
366
+ })
367
+ }
368
+
369
+ if (samples.length < limit) {
370
+ if (summary) {
371
+ fillSummaryResult(summary, dataItems, summary.summary)
372
+ summaries.push(summary)
373
+ }
374
+ break
375
+ }
376
+
377
+ page++
378
+ } while (true)
379
+
380
+ tx.getRepository(DataSummary).upsert(summaries, ['domain', 'dataSet', 'key01', 'key02', 'key03', 'key04', 'key05', 'date', 'period'])
381
+
382
+ return true
383
+ } catch (e) {
384
+ logger.error(e)
385
+ }
386
+
387
+ return false
388
+ }
@@ -0,0 +1,6 @@
1
+ export * from './create-data-sample'
2
+ export * from './create-data-ooc'
3
+ export * from './issue-ooc-review'
4
+ export * from './issue-ooc-resolve'
5
+ export * from './data-use-case'
6
+ export * from './query-data-summary-by-period'
@@ -0,0 +1,70 @@
1
+ import { In } from 'typeorm'
2
+
3
+ import { Domain, getDataSource } from '@things-factory/shell'
4
+ import { Activity, ActivityInstance, issue } from '@things-factory/worklist'
5
+
6
+ import { DataSet } from '../service/data-set/data-set'
7
+
8
+ export async function issueDataCollectionTask(
9
+ domainId: string,
10
+ dataSetId: string,
11
+ context: ResolverContext
12
+ ): Promise<ActivityInstance | void> {
13
+ await getDataSource().transaction(async tx => {
14
+ const domain = await tx.getRepository(Domain).findOneBy({ id: domainId })
15
+
16
+ if (!domain) {
17
+ throw new Error(`domain(${domainId}) not found`)
18
+ }
19
+
20
+ const dataSet = await tx.getRepository(DataSet).findOne({
21
+ where: {
22
+ domain: {
23
+ id: In([domain.id, domain.parentId].filter(Boolean))
24
+ },
25
+ id: dataSetId
26
+ },
27
+ relations: ['entryRole']
28
+ })
29
+
30
+ const activity = (await tx.getRepository(Activity).findOneBy({
31
+ domain: { id: In([domain.id, domain.parentId].filter(Boolean)) },
32
+ name: 'Collect Data'
33
+ })) as Activity
34
+
35
+ if (activity) {
36
+ const { entryRole, timeLimit } = dataSet
37
+
38
+ /* 해당 dataset에 대한 데이타 수집 태스크를 dataset entryRole에게 할당한다. */
39
+ if (entryRole) {
40
+ const activityInstance = {
41
+ name: `[Data 수집] ${dataSet.name}`,
42
+ description: dataSet.description,
43
+ activityId: activity.id,
44
+ dueAt: new Date(Date.now() + (timeLimit || activity.standardTime || 24 * 60 * 60) * 1000),
45
+ input: {
46
+ dataSetId: dataSet.id,
47
+ dataSetName: dataSet.name
48
+ },
49
+ assigneeRole: entryRole,
50
+ threadsMin: 1,
51
+ threadsMax: 1
52
+ }
53
+
54
+ context.state = {
55
+ ...context.state,
56
+ domain,
57
+ tx
58
+ }
59
+
60
+ return await issue(activityInstance, context)
61
+ } else {
62
+ throw new Error(
63
+ `Data Entry Role not set. So Data Collect Activity for ${dataSet.name}($dataSet.id) could not be issued.`
64
+ )
65
+ }
66
+ } else {
67
+ throw new Error(`Data Collect Activity is not installed.`)
68
+ }
69
+ })
70
+ }
@@ -0,0 +1,58 @@
1
+ import { In } from 'typeorm'
2
+ import { Activity } from '@things-factory/worklist'
3
+ import { issue } from '@things-factory/worklist/dist-server/controllers/activity-instance/issue'
4
+ import { Role } from '@things-factory/auth-base'
5
+
6
+ import { DataOoc } from '../service/data-ooc/data-ooc'
7
+ import { DataSet } from 'service'
8
+
9
+ export async function issueOocResolve(
10
+ dataOoc: DataOoc,
11
+ dataSet: DataSet,
12
+ correctiveInstruction: string,
13
+ context: ResolverContext
14
+ ): Promise<void> {
15
+ const { domain, user, tx } = context.state
16
+ const { resolverRoleId, outlierApprovalLine } = dataSet
17
+
18
+ const activity = (await tx.getRepository(Activity).findOneBy({
19
+ domain: domain.parentId ? In([domain.id, domain.parentId]) : { id: domain.id },
20
+ name: 'OOC Resolve'
21
+ })) as Activity
22
+
23
+ if (activity) {
24
+ const assigneeRole =
25
+ resolverRoleId &&
26
+ (await tx.getRepository(Role).findOneBy({
27
+ domain: { id: In([domain.id, domain.parentId].filter(Boolean)) },
28
+ id: resolverRoleId
29
+ }))
30
+
31
+ /* 해당 dataset의 작업 담당자(resolverRole)에게 OOC 해결을 위한 태스크를 지시한다. */
32
+ if (assigneeRole) {
33
+ const activityInstance = {
34
+ name: `[OOC 조치] ${dataSet.name}`,
35
+ description: dataSet.description,
36
+ activityId: activity.id,
37
+ dueAt: new Date(Date.now() + (activity.standardTime || 24 * 60 * 60) * 1000),
38
+ input: {
39
+ dataOocId: dataOoc.id,
40
+ instruction: correctiveInstruction
41
+ },
42
+ assigneeRole,
43
+ threadsMin: 1,
44
+ threadsMax: 1,
45
+ approvalLine: outlierApprovalLine
46
+ }
47
+
48
+ dataOoc.resolveActivityInstance = await issue(activityInstance, context)
49
+ await tx.getRepository(DataOoc).save(dataOoc)
50
+ } else {
51
+ console.error(
52
+ `Assignees are not set. So, Data OOC Resolve task for ${dataOoc.name}(${dataOoc.id}) could not be issued.`
53
+ )
54
+ }
55
+ } else {
56
+ console.error('OOC Resolve Activity not installed.')
57
+ }
58
+ }
@@ -0,0 +1,52 @@
1
+ import { In } from 'typeorm'
2
+
3
+ import { Role } from '@things-factory/auth-base'
4
+ import { Activity } from '@things-factory/worklist'
5
+ import { issue } from '@things-factory/worklist/dist-server/controllers/activity-instance/issue'
6
+
7
+ import { DataSet } from '../service/data-set/data-set'
8
+ import { DataOoc } from '../service/data-ooc/data-ooc'
9
+
10
+ export async function issueOocReview(dataOoc: DataOoc, dataSet: DataSet, context: ResolverContext): Promise<void> {
11
+ const { domain, tx } = context.state
12
+ const { collectedAt } = dataOoc
13
+
14
+ const activity = (await tx.getRepository(Activity).findOneBy({
15
+ domain: domain.parentId ? { id: In([domain.id, domain.parentId]) } : { id: domain.id },
16
+ name: 'OOC Review'
17
+ })) as Activity
18
+
19
+ if (activity) {
20
+ const assigneeRole =
21
+ dataSet.supervisoryRoleId &&
22
+ (await tx.getRepository(Role).findOneBy({
23
+ domain: { id: In([domain.id, domain.parentId].filter(Boolean)) },
24
+ id: dataSet.supervisoryRoleId
25
+ }))
26
+
27
+ if (assigneeRole) {
28
+ const activityInstance = {
29
+ name: `[OOC 검토] ${dataSet.name}`,
30
+ description: dataSet.description,
31
+ activityId: activity.id,
32
+ dueAt: new Date(collectedAt.getTime() + (activity.standardTime || 24 * 60 * 60) * 1000),
33
+ input: {
34
+ dataOocId: dataOoc.id
35
+ },
36
+ assigneeRole,
37
+ threadsMin: 1,
38
+ threadsMax: 1,
39
+ approvalLine: []
40
+ }
41
+
42
+ dataOoc.reviewActivityInstance = await issue(activityInstance as any, context)
43
+ await tx.getRepository(DataOoc).save(dataOoc)
44
+ } else {
45
+ console.error(
46
+ `Assignees are not set. So Data OOC Review task for ${dataOoc.name}(${dataOoc.id}) could not be issued.`
47
+ )
48
+ }
49
+ } else {
50
+ console.warn('OOC Review Activity not installed.')
51
+ }
52
+ }