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

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 (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
+ }