@things-factory/dataset 6.0.77 → 6.0.79
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.
- package/client/pages/data-set/data-set-list-page.ts +93 -91
- package/client/pages/data-summary/data-summary-list-page.ts +7 -7
- package/client/pages/data-summary/data-summary-search-page.ts +18 -34
- package/client/pages/data-summary/data-summary-view.ts +4 -4
- package/dist-client/pages/data-set/data-set-list-page.d.ts +1 -0
- package/dist-client/pages/data-set/data-set-list-page.js +88 -92
- package/dist-client/pages/data-set/data-set-list-page.js.map +1 -1
- package/dist-client/pages/data-summary/data-summary-list-page.js +7 -7
- package/dist-client/pages/data-summary/data-summary-list-page.js.map +1 -1
- package/dist-client/pages/data-summary/data-summary-search-page.js +18 -34
- package/dist-client/pages/data-summary/data-summary-search-page.js.map +1 -1
- package/dist-client/pages/data-summary/data-summary-view.js +2 -2
- package/dist-client/pages/data-summary/data-summary-view.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/generate-data-summary.js +174 -20
- package/dist-server/controllers/generate-data-summary.js.map +1 -1
- package/dist-server/routes.js +20 -0
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/data-set/data-set-type.js +8 -0
- package/dist-server/service/data-set/data-set-type.js.map +1 -1
- package/dist-server/service/data-set/data-set.js +22 -1
- package/dist-server/service/data-set/data-set.js.map +1 -1
- package/dist-server/service/data-summary/data-summary-mutation.js +16 -4
- package/dist-server/service/data-summary/data-summary-mutation.js.map +1 -1
- package/dist-server/service/data-summary/data-summary-type.js.map +1 -1
- package/dist-server/service/data-summary/data-summary.js +10 -23
- package/dist-server/service/data-summary/data-summary.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/helps/dataset/data-set.md +26 -11
- package/helps/dataset/ui/data-item-list.md +16 -9
- package/package.json +11 -11
- package/server/controllers/generate-data-summary.ts +218 -21
- package/server/routes.ts +29 -0
- package/server/service/data-set/data-set-type.ts +7 -1
- package/server/service/data-set/data-set.ts +20 -0
- package/server/service/data-summary/data-summary-mutation.ts +13 -4
- package/server/service/data-summary/data-summary-type.ts +2 -7
- package/server/service/data-summary/data-summary.ts +9 -24
- package/translations/en.json +4 -0
- package/translations/ko.json +5 -1
- package/translations/ms.json +4 -0
- package/translations/zh.json +4 -0
@@ -1,28 +1,43 @@
|
|
1
|
-
# DataSet
|
1
|
+
# 데이터셋 (DataSet)
|
2
|
+
|
2
3
|
데이타 셋을 정의합니다.<br>
|
3
|
-
|
4
|
+
|
5
|
+
- [`데이타 아이템 등록 및 조회`](./ui/data-item-list.md): 데이타 아이템 화면을 팝업으로 띄웁니다.
|
4
6
|
- 데이타 입력: 데이타 입력 화면을 팝업으로 띄웁니다.
|
5
7
|
- 이름: 데이터셋 이름을 설정합니다.<br>
|
6
8
|
- 설명: 데이터셋에 대한 설명을 설정합니다.<br>
|
7
9
|
- 유형: 데이터셋 유형을 설정합니다.<br>
|
8
|
-
|
9
|
-
|
10
|
+
- 수동 수집: 사용자가 입력하는 데이터셋인 경우
|
11
|
+
- 자동 수집: 센서처럼 자동으로 입력되는 데이터셋인 경우
|
10
12
|
- 데이타 키셋: 데이타 키셋 마스터 페이지에서 등록한 항목을 적용합니다.<br>
|
11
13
|
- 파티션 키: (deprecated 예정)<br>
|
12
14
|
- 스케줄: 입력 주기를 설정합니다.<br>
|
13
|
-
-
|
15
|
+
- 입력 주기가 설정되면, 설정된 주기마다 입력담당 역할자에게 데이타 입력 태스크가 할당됩니다.
|
16
|
+
- 타임존: 스케쥴이 반영될 기준 타임존을 설정합니다.<br>
|
14
17
|
- 관리자 역할: 데이터셋 관리자 역할을 설정합니다.<br>
|
15
18
|
- 입력담당 역할: 입력 권한을 부여할 역할을 설정합니다.<br>
|
16
19
|
- 입력용 화면종류: 입력 화면종류를 설정합니다. 주로 Board가 사용됩니다.<br>
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
- Generated: 구현된 화면을 사용합니다.
|
21
|
+
- Board: Board 화면을 사용합니다.
|
22
|
+
- Page: 구현된 페이지로 이동합니다. suburl이 필요합니다.
|
23
|
+
- External URL: 외부 페이지로 이동합니다. 전체 Url이 필요합니다.
|
21
24
|
- 입력용 화면: 화면 종류에 따른 값을 설정합니다. Board 화면 종류는 Board를 선택할 수 있습니다.<br>
|
22
25
|
- 모니터용 화면종류: 모니터용 화면종류를 설정합니다. 선택 목록은 입력용 화면종류와 동일합니다.<br>
|
23
26
|
- 모니터용 화면: 모니터용 화면종류에 해당하는 값을 설정합니다. <br>
|
24
27
|
- 리포트용 화면종류: 리포트용 화면종류를 설정합니다. 선택 목록은 입력용 화면종류에 두 종류가 더 있습니다.
|
25
|
-
|
26
|
-
|
28
|
+
- Jasper: Jasper 서버 설정에 따른 jasper 페이지를 렌더링합니다.
|
29
|
+
- Shiny: shiny 서버 설정에 따른 shiny 페이지를 렌더링합니다.
|
27
30
|
- 리포트용 화면: 리포트용 화면종류에 맞는 값을 설정합니다. Jasper나 Shiny의 경우, 해당 서버에서 유효한 suburl이 필요합니다. <br>
|
28
31
|
- 리포트용 템플릿: 리포트용 화면에 필요한 템플릿 파일을 업로드합니다.
|
32
|
+
- 서머리 주기
|
33
|
+
- 서머리 주기가 설정되면, 자동으로 스케쥴러에 서머리 작업이 등록됩니다.
|
34
|
+
- 현재는 주별, 월별 주기 서머리는 작업의 부하를 고려해서 제공하지 않습니다.
|
35
|
+
- 데이터 키셋으로 그룹핑되어서 서머리 레코드가 생성됩니다.
|
36
|
+
- 주기
|
37
|
+
- hour : 매시간 서머리 작업 진행
|
38
|
+
- shift : 매 작업조별로 진행
|
39
|
+
- workdate : 매 작업일 기준
|
40
|
+
- daily : 매일
|
41
|
+
- 다음 서머리 실행 일시
|
42
|
+
- 설정된 서머리 주기를 기준으로 다음 서머리 실행 일시를 알려줍니다.
|
43
|
+
- 서머리 주기는 설정되었지만, 스케쥴러에 등록되지 않았다면, 등록버튼이 보여지므로 버튼을 눌러서 등록합니다. 등록이 성공되면 다음 스케쥴이 표시됩니다.
|
@@ -1,19 +1,26 @@
|
|
1
|
-
# DataItemList
|
1
|
+
# 데이타 아이템 등록 및 조회 (DataItemList)
|
2
|
+
|
2
3
|
데이타 셋의 아이템 목록을 등록합니다.
|
4
|
+
|
3
5
|
- 이름: 데이타 셋 아이템 이름입니다.
|
4
6
|
- 설명: 데이타 셋 아아템 설명입니다.
|
5
7
|
- 활성화: 사용 여부를 설정합니다.
|
6
8
|
- 숨기기: 레포트 등 2차 가공 데이터로 사용시, 사용자에게 보여줄 지 여부를 설정합니다.
|
7
9
|
- 태그이름: 오브젝트 키를 설정합니다.
|
8
10
|
- 유형: 데이타 셋 아이템 값의 데이타 타입 입니다.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
-
|
11
|
+
- number
|
12
|
+
- text
|
13
|
+
- select
|
14
|
+
- boolean
|
15
|
+
- file
|
16
|
+
- 선택옵션: select 유형인 경우, name과 value 쌍을 추가로 설정합니다.
|
17
|
+
- 통계함수:
|
18
|
+
- sum, mean, stddev, variance, min, max, range, median, mode
|
19
|
+
- 주기적으로 기간별 데이터를 모아서 서머리 레코드를 생성할 때, 해당 아이템에 대해서 적용될 통계함수를 설정한다.
|
20
|
+
- 통계함수가 설정되면, 통계함수의 결과 값이 데이타 아이템 순서대로 서머리 레코드의 data{index} 컬럼에 최대 5개까지 저장된다.
|
15
21
|
- 단위: kg, cm, 등 의 입력 단위를 설정합니다.
|
16
22
|
- 샘플수: 해당 아이템 값을 몇 개 입력받을지 설정합니다.
|
17
23
|
- 명세: 명세를 정의하면 이탈값이 OOC 데이터로 추가됩니다. 또한 해당 데이터샘플에 이탈여부가 기록됩니다.
|
18
|
-
|
19
|
-
|
24
|
+
- CCP: Critical Control Point Data Spec을 정의합니다.
|
25
|
+
- QC: Quality Contol Data Spec을 정의합니다.
|
26
|
+
- SPC: 아직 지원도지 않습니다.
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@things-factory/dataset",
|
3
|
-
"version": "6.0.
|
3
|
+
"version": "6.0.79",
|
4
4
|
"main": "dist-server/index.js",
|
5
5
|
"browser": "dist-client/index.js",
|
6
6
|
"things-factory": true,
|
@@ -36,19 +36,19 @@
|
|
36
36
|
"@operato/shell": "^1.0.1",
|
37
37
|
"@operato/styles": "^1.0.0",
|
38
38
|
"@operato/utils": "^1.0.1",
|
39
|
-
"@things-factory/auth-base": "^6.0.
|
40
|
-
"@things-factory/aws-base": "^6.0.
|
41
|
-
"@things-factory/board-service": "^6.0.
|
42
|
-
"@things-factory/env": "^6.0.
|
43
|
-
"@things-factory/organization": "^6.0.
|
44
|
-
"@things-factory/scheduler-client": "^6.0.
|
45
|
-
"@things-factory/shell": "^6.0.
|
46
|
-
"@things-factory/work-shift": "^6.0.
|
47
|
-
"@things-factory/worklist": "^6.0.
|
39
|
+
"@things-factory/auth-base": "^6.0.78",
|
40
|
+
"@things-factory/aws-base": "^6.0.78",
|
41
|
+
"@things-factory/board-service": "^6.0.78",
|
42
|
+
"@things-factory/env": "^6.0.78",
|
43
|
+
"@things-factory/organization": "^6.0.78",
|
44
|
+
"@things-factory/scheduler-client": "^6.0.78",
|
45
|
+
"@things-factory/shell": "^6.0.78",
|
46
|
+
"@things-factory/work-shift": "^6.0.78",
|
47
|
+
"@things-factory/worklist": "^6.0.78",
|
48
48
|
"cron-parser": "^4.3.0",
|
49
49
|
"moment-timezone": "^0.5.40",
|
50
50
|
"simple-statistics": "^7.8.3",
|
51
51
|
"statistics": "^3.3.0"
|
52
52
|
},
|
53
|
-
"gitHead": "
|
53
|
+
"gitHead": "a7638563e82d7daf5c105caf1ee62fc4ce6f98dc"
|
54
54
|
}
|
@@ -1,11 +1,19 @@
|
|
1
1
|
const statistics = require('simple-statistics')
|
2
2
|
const deepClone = require('lodash/cloneDeep')
|
3
3
|
|
4
|
+
import { Between } from 'typeorm'
|
5
|
+
import moment from 'moment-timezone'
|
6
|
+
|
4
7
|
import { Sorting, getQueryBuilderFromListParams } from '@things-factory/shell'
|
5
8
|
import { logger } from '@things-factory/env'
|
9
|
+
import {
|
10
|
+
getDateRangeForWorkDate,
|
11
|
+
getDateRangeForWorkShift,
|
12
|
+
getLatestWorkDateAndShift
|
13
|
+
} from '@things-factory/work-shift'
|
6
14
|
|
7
15
|
import { DataSample } from '../service/data-sample/data-sample'
|
8
|
-
import { DataSet } from '../service/data-set/data-set'
|
16
|
+
import { DataSet, DataSetSummaryPeriodType } from '../service/data-set/data-set'
|
9
17
|
|
10
18
|
import { DataSummary } from '../service/data-summary/data-summary'
|
11
19
|
import { DataKeyItem } from '../service/data-key-set/data-key-item-type'
|
@@ -57,14 +65,23 @@ const calculateSummary = (dataItems: DataItem[], base: { [tag: string]: any[] })
|
|
57
65
|
.map(Number)
|
58
66
|
.filter(item => !isNaN(item))
|
59
67
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
68
|
+
if (data.length > 0) {
|
69
|
+
try {
|
70
|
+
switch (item.stat) {
|
71
|
+
case 'range':
|
72
|
+
summary[tag] = statistics.max(data) - statistics.min(data)
|
73
|
+
break
|
64
74
|
|
65
|
-
|
66
|
-
|
67
|
-
|
75
|
+
default:
|
76
|
+
const functionName = STAT_FUNCTION_MAP[item.stat]
|
77
|
+
summary[tag] = (functionName && statistics[functionName](data)) || ''
|
78
|
+
}
|
79
|
+
} catch (err) {
|
80
|
+
summary[tag] = null
|
81
|
+
console.error(err)
|
82
|
+
}
|
83
|
+
} else {
|
84
|
+
summary[tag] = null
|
68
85
|
}
|
69
86
|
|
70
87
|
return summary
|
@@ -80,14 +97,190 @@ const fillSummaryResult = (
|
|
80
97
|
|
81
98
|
dataSummary.summary = summary
|
82
99
|
dataItems.slice(0, 4).forEach((dataItem, idx) => {
|
83
|
-
|
100
|
+
const value = Number(summary[dataItem.tag])
|
101
|
+
dataSummary[`data0${idx + 1}`] = isNaN(value) ? null : value
|
84
102
|
})
|
85
103
|
}
|
86
104
|
|
105
|
+
async function getLatestTimesForPeriod(
|
106
|
+
periodType: DataSetSummaryPeriodType,
|
107
|
+
context: ResolverContext
|
108
|
+
): Promise<{ date?: string; period?: string; range: Date[] }> {
|
109
|
+
const { domain } = context.state
|
110
|
+
const now = moment()
|
111
|
+
|
112
|
+
if (periodType == DataSetSummaryPeriodType.Hour) {
|
113
|
+
const begin = now.clone().subtract(1, 'day').startOf('hour').toDate()
|
114
|
+
const end = now.clone().startOf('hour').toDate()
|
115
|
+
|
116
|
+
return {
|
117
|
+
date: moment(begin).tz(domain.timezone).format('YYYY-MM-DD'),
|
118
|
+
period: String(begin.getHours()),
|
119
|
+
range: [begin, end]
|
120
|
+
}
|
121
|
+
} else if (periodType == DataSetSummaryPeriodType.WorkShift) {
|
122
|
+
const { workDate, workShift, shiftRange } = await getLatestWorkDateAndShift(domain, new Date())
|
123
|
+
|
124
|
+
return { date: workDate, period: workShift, range: shiftRange }
|
125
|
+
} else if (periodType == DataSetSummaryPeriodType.WorkDate) {
|
126
|
+
const { workDate, dateRange } = await getLatestWorkDateAndShift(domain, new Date())
|
127
|
+
|
128
|
+
return { date: workDate, range: dateRange }
|
129
|
+
} else if (periodType == DataSetSummaryPeriodType.Day) {
|
130
|
+
const begin = now.clone().subtract(1, 'day').startOf('day').toDate()
|
131
|
+
const end = now.clone().startOf('day').toDate()
|
132
|
+
|
133
|
+
return {
|
134
|
+
date: moment(begin).tz(domain.timezone).format('YYYY-MM-DD'),
|
135
|
+
range: [begin, end]
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
async function getTimesForPeriod(
|
141
|
+
periodType: DataSetSummaryPeriodType,
|
142
|
+
date: string,
|
143
|
+
period: string,
|
144
|
+
context: ResolverContext
|
145
|
+
): Promise<{ date?: string; period?: string; range: Date[] }> {
|
146
|
+
const { domain } = context.state
|
147
|
+
|
148
|
+
if (periodType == DataSetSummaryPeriodType.Hour) {
|
149
|
+
const theDate = moment.tz(`${date} ${period}:00:00`, 'YYYY-MM-DD HH:mm:ss', domain.timezone)
|
150
|
+
|
151
|
+
const begin = theDate.clone().startOf('hour').toDate()
|
152
|
+
const end = theDate.clone().add(+1, 'hour').startOf('hour').toDate()
|
153
|
+
|
154
|
+
return {
|
155
|
+
date,
|
156
|
+
period,
|
157
|
+
range: [begin, end]
|
158
|
+
}
|
159
|
+
} else if (periodType == DataSetSummaryPeriodType.WorkShift) {
|
160
|
+
const range = await getDateRangeForWorkShift(domain, date, period)
|
161
|
+
|
162
|
+
return { date, period, range }
|
163
|
+
} else if (periodType == DataSetSummaryPeriodType.WorkDate) {
|
164
|
+
const range = await getDateRangeForWorkDate(domain, date)
|
165
|
+
|
166
|
+
return { date, range }
|
167
|
+
} else if (periodType == DataSetSummaryPeriodType.Day) {
|
168
|
+
const theDate = moment.tz(`${date} 00:00:00`, 'YYYY-MM-DD HH:mm:ss', domain.timezone)
|
169
|
+
|
170
|
+
const begin = theDate.clone().startOf('day').toDate()
|
171
|
+
const end = theDate.clone().add(1, 'day').startOf('day').toDate()
|
172
|
+
|
173
|
+
return {
|
174
|
+
date: moment(begin).tz(domain.timezone).format('YYYY-MM-DD'),
|
175
|
+
range: [begin, end]
|
176
|
+
}
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
export async function generateLatestDataSummaries(dataSetId: string, context: ResolverContext): Promise<boolean> {
|
181
|
+
const { domain, user, tx } = context.state
|
182
|
+
|
183
|
+
try {
|
184
|
+
const dataSet = await tx.getRepository(DataSet).findOne({
|
185
|
+
where: { domain: { id: domain.id }, id: dataSetId },
|
186
|
+
relations: ['dataKeySet']
|
187
|
+
})
|
188
|
+
|
189
|
+
const dataKeyItems = dataSet.dataKeySet?.dataKeyItems || []
|
190
|
+
const dataItems = dataSet.dataItems.filter(item => item.stat)
|
191
|
+
const initialSummary = dataItems.reduce((sum, item) => {
|
192
|
+
sum[item.tag] = []
|
193
|
+
return sum
|
194
|
+
}, {})
|
195
|
+
|
196
|
+
const { date, period, range } = await getLatestTimesForPeriod(dataSet.summaryPeriod, context)
|
197
|
+
const limit = 100
|
198
|
+
var page = 1
|
199
|
+
|
200
|
+
var summaries: Partial<DataSummary>[] = []
|
201
|
+
var summary: Partial<DataSummary>
|
202
|
+
|
203
|
+
do {
|
204
|
+
const samples = await getQueryBuilderFromListParams({
|
205
|
+
repository: tx.getRepository(DataSample),
|
206
|
+
params: {
|
207
|
+
filters: [{ name: 'dataSetId', operator: 'eq', value: dataSetId }],
|
208
|
+
pagination: { page, limit },
|
209
|
+
sortings: [...buildKeySortingList(dataKeyItems), { name: 'collectedAt', desc: true }]
|
210
|
+
},
|
211
|
+
domain
|
212
|
+
})
|
213
|
+
.andWhere({ collectedAt: Between.call(range) })
|
214
|
+
.getMany()
|
215
|
+
|
216
|
+
for (const sample of samples) {
|
217
|
+
if (!summary || !compareKeys(dataKeyItems, summary, sample)) {
|
218
|
+
if (summary) {
|
219
|
+
fillSummaryResult(summary, dataItems, summary.summary)
|
220
|
+
summaries.push(summary)
|
221
|
+
}
|
222
|
+
|
223
|
+
summary = {
|
224
|
+
domain,
|
225
|
+
name: dataSet.name,
|
226
|
+
description: dataSet.description,
|
227
|
+
date,
|
228
|
+
period,
|
229
|
+
dataSet,
|
230
|
+
...buildKeysFromSample(dataKeyItems, sample),
|
231
|
+
count: 0,
|
232
|
+
countOoc: 0,
|
233
|
+
countOos: 0,
|
234
|
+
summary: deepClone(initialSummary),
|
235
|
+
updater: user,
|
236
|
+
creator: user
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
summary.count++
|
241
|
+
sample.ooc && summary.countOoc++
|
242
|
+
sample.oos && summary.countOos++
|
243
|
+
|
244
|
+
dataItems.forEach(item => {
|
245
|
+
summary.summary[item.tag].push(sample.data[item.tag])
|
246
|
+
})
|
247
|
+
}
|
248
|
+
|
249
|
+
if (samples.length < limit) {
|
250
|
+
if (summary) {
|
251
|
+
fillSummaryResult(summary, dataItems, summary.summary)
|
252
|
+
summaries.push(summary)
|
253
|
+
}
|
254
|
+
break
|
255
|
+
}
|
256
|
+
|
257
|
+
page++
|
258
|
+
} while (true)
|
259
|
+
|
260
|
+
tx.getRepository(DataSummary).upsert(summaries, [
|
261
|
+
'domain',
|
262
|
+
'dataSet',
|
263
|
+
'key01',
|
264
|
+
'key02',
|
265
|
+
'key03',
|
266
|
+
'key04',
|
267
|
+
'key05',
|
268
|
+
'date',
|
269
|
+
'period'
|
270
|
+
])
|
271
|
+
|
272
|
+
return true
|
273
|
+
} catch (e) {
|
274
|
+
logger.error(e)
|
275
|
+
}
|
276
|
+
|
277
|
+
return false
|
278
|
+
}
|
279
|
+
|
87
280
|
export async function generateDataSummary(
|
88
281
|
dataSetId: string,
|
89
|
-
|
90
|
-
|
282
|
+
date: string,
|
283
|
+
period: string,
|
91
284
|
context: ResolverContext
|
92
285
|
): Promise<boolean> {
|
93
286
|
const { domain, user, tx } = context.state
|
@@ -105,6 +298,10 @@ export async function generateDataSummary(
|
|
105
298
|
return sum
|
106
299
|
}, {})
|
107
300
|
|
301
|
+
const times = await getTimesForPeriod(dataSet.summaryPeriod, date, period, context)
|
302
|
+
const range = times.range
|
303
|
+
period = times.period
|
304
|
+
|
108
305
|
const limit = 100
|
109
306
|
var page = 1
|
110
307
|
|
@@ -115,16 +312,16 @@ export async function generateDataSummary(
|
|
115
312
|
const samples = await getQueryBuilderFromListParams({
|
116
313
|
repository: tx.getRepository(DataSample),
|
117
314
|
params: {
|
118
|
-
filters: [
|
119
|
-
{ name: 'dataSetId', operator: 'eq', value: dataSetId },
|
120
|
-
{ name: 'workDate', operator: 'eq', value: workDate },
|
121
|
-
{ name: 'workShift', operator: 'eq', value: workShift }
|
122
|
-
],
|
315
|
+
filters: [{ name: 'dataSetId', operator: 'eq', value: dataSetId }],
|
123
316
|
pagination: { page, limit },
|
124
317
|
sortings: [...buildKeySortingList(dataKeyItems), { name: 'collectedAt', desc: true }]
|
125
318
|
},
|
126
319
|
domain
|
127
|
-
})
|
320
|
+
})
|
321
|
+
.andWhere({
|
322
|
+
collectedAt: Between.apply(null, range)
|
323
|
+
})
|
324
|
+
.getMany()
|
128
325
|
|
129
326
|
for (const sample of samples) {
|
130
327
|
if (!summary || !compareKeys(dataKeyItems, summary, sample)) {
|
@@ -137,8 +334,8 @@ export async function generateDataSummary(
|
|
137
334
|
domain,
|
138
335
|
name: dataSet.name,
|
139
336
|
description: dataSet.description,
|
140
|
-
|
141
|
-
|
337
|
+
date,
|
338
|
+
period,
|
142
339
|
dataSet,
|
143
340
|
...buildKeysFromSample(dataKeyItems, sample),
|
144
341
|
count: 0,
|
@@ -178,8 +375,8 @@ export async function generateDataSummary(
|
|
178
375
|
'key03',
|
179
376
|
'key04',
|
180
377
|
'key05',
|
181
|
-
'
|
182
|
-
'
|
378
|
+
'date',
|
379
|
+
'period'
|
183
380
|
])
|
184
381
|
|
185
382
|
return true
|
package/server/routes.ts
CHANGED
@@ -143,6 +143,35 @@ process.on('bootstrap-module-global-public-route' as any, (app, globalPublicRout
|
|
143
143
|
|
144
144
|
context.status = 200
|
145
145
|
})
|
146
|
+
|
147
|
+
/* When a callback occurs from the scheduler when a scheduled summary is on schedule, data summary procedure for the dataset should be executed. */
|
148
|
+
globalPublicRouter.post('/callback-schedule-for-data-summary', async (context, next) => {
|
149
|
+
const { client } = context.request.body as ScheduleRegisterRequest
|
150
|
+
|
151
|
+
if (!client || typeof client !== 'object') {
|
152
|
+
throw new Error('client property should be a part of callback body.')
|
153
|
+
}
|
154
|
+
|
155
|
+
const { group: domainId, key: dataSetId } = client
|
156
|
+
|
157
|
+
if (!domainId || !dataSetId) {
|
158
|
+
throw new Error(`group(${domainId}) and key(${dataSetId}) properties should not be empty`)
|
159
|
+
}
|
160
|
+
|
161
|
+
await getDataSource().transaction(async tx => {
|
162
|
+
const domain = await tx.getRepository(Domain).findOneBy({ id: domainId })
|
163
|
+
|
164
|
+
if (!domain) {
|
165
|
+
throw new Error(`domain(${domainId}) not found`)
|
166
|
+
}
|
167
|
+
|
168
|
+
const dataSet = await tx.getRepository(DataSet).findOne({ where: { domain: { id: domainId }, id: dataSetId } })
|
169
|
+
|
170
|
+
// do what you gotta do
|
171
|
+
})
|
172
|
+
|
173
|
+
context.status = 200
|
174
|
+
})
|
146
175
|
})
|
147
176
|
|
148
177
|
process.on('bootstrap-module-global-private-route' as any, (app, globalPrivateRouter) => {
|
@@ -7,7 +7,7 @@ import { ApprovalLineItem } from '@things-factory/organization'
|
|
7
7
|
import { AssigneeItem } from '@things-factory/worklist'
|
8
8
|
|
9
9
|
import { DataItemPatch } from './data-item-type'
|
10
|
-
import { DataSet, DataSetEntryType, DataSetMonitorType, DataSetReportType } from './data-set'
|
10
|
+
import { DataSet, DataSetEntryType, DataSetMonitorType, DataSetReportType, DataSetSummaryPeriodType } from './data-set'
|
11
11
|
|
12
12
|
@InputType()
|
13
13
|
export class NewDataSet {
|
@@ -73,6 +73,9 @@ export class NewDataSet {
|
|
73
73
|
|
74
74
|
@Field(type => GraphQLUpload, { nullable: true })
|
75
75
|
reportTemplate?: FileUpload
|
76
|
+
|
77
|
+
@Field({ nullable: true })
|
78
|
+
summaryPeriod?: DataSetSummaryPeriodType
|
76
79
|
}
|
77
80
|
|
78
81
|
@InputType()
|
@@ -143,6 +146,9 @@ export class DataSetPatch {
|
|
143
146
|
@Field(type => GraphQLUpload, { nullable: true })
|
144
147
|
reportTemplate?: FileUpload
|
145
148
|
|
149
|
+
@Field({ nullable: true })
|
150
|
+
summaryPeriod?: DataSetSummaryPeriodType
|
151
|
+
|
146
152
|
@Field()
|
147
153
|
cuFlag: string
|
148
154
|
}
|
@@ -60,6 +60,18 @@ registerEnumType(DataSetReportType, {
|
|
60
60
|
description: 'report type enumeration for data-set'
|
61
61
|
})
|
62
62
|
|
63
|
+
export enum DataSetSummaryPeriodType {
|
64
|
+
Hour = 'hour',
|
65
|
+
WorkShift = 'work-shift',
|
66
|
+
WorkDate = 'work-date',
|
67
|
+
Day = 'day'
|
68
|
+
}
|
69
|
+
|
70
|
+
registerEnumType(DataSetSummaryPeriodType, {
|
71
|
+
name: 'DataSetSummaryPeriodType',
|
72
|
+
description: 'summary period type enumeration for data-set'
|
73
|
+
})
|
74
|
+
|
63
75
|
@ObjectType()
|
64
76
|
export class DataSetState {
|
65
77
|
@Field()
|
@@ -199,6 +211,14 @@ export class DataSet {
|
|
199
211
|
@Field({ nullable: true })
|
200
212
|
scheduleId?: string
|
201
213
|
|
214
|
+
@Column({ nullable: true })
|
215
|
+
@Field({ nullable: true })
|
216
|
+
summaryPeriod?: DataSetSummaryPeriodType
|
217
|
+
|
218
|
+
@Column({ nullable: true })
|
219
|
+
@Field({ nullable: true })
|
220
|
+
summaryScheduleId?: string
|
221
|
+
|
202
222
|
@CreateDateColumn()
|
203
223
|
@Field({ nullable: true })
|
204
224
|
createdAt?: Date
|
@@ -2,18 +2,27 @@ import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'
|
|
2
2
|
|
3
3
|
import { DataSummary } from './data-summary'
|
4
4
|
|
5
|
-
import { generateDataSummary } from '../../controllers/generate-data-summary'
|
5
|
+
import { generateDataSummary, generateLatestDataSummaries } from '../../controllers/generate-data-summary'
|
6
6
|
|
7
7
|
@Resolver(DataSummary)
|
8
8
|
export class DataSummaryMutation {
|
9
|
+
@Directive('@transaction')
|
10
|
+
@Mutation(returns => Boolean, { description: 'To generate new Data Summaries' })
|
11
|
+
async generateLatestDataSummaries(
|
12
|
+
@Arg('dataSetId') dataSetId: string,
|
13
|
+
@Ctx() context: ResolverContext
|
14
|
+
): Promise<boolean> {
|
15
|
+
return await generateLatestDataSummaries(dataSetId, context)
|
16
|
+
}
|
17
|
+
|
9
18
|
@Directive('@transaction')
|
10
19
|
@Mutation(returns => Boolean, { description: 'To generate new Data Summaries' })
|
11
20
|
async generateDataSummaries(
|
12
21
|
@Arg('dataSetId') dataSetId: string,
|
13
|
-
@Arg('
|
14
|
-
@Arg('
|
22
|
+
@Arg('date') date: string,
|
23
|
+
@Arg('period') period: string,
|
15
24
|
@Ctx() context: ResolverContext
|
16
25
|
): Promise<boolean> {
|
17
|
-
return await generateDataSummary(dataSetId,
|
26
|
+
return await generateDataSummary(dataSetId, date, period, context)
|
18
27
|
}
|
19
28
|
}
|
@@ -1,10 +1,5 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
import { ObjectType, Field, InputType, Int, ID, registerEnumType } from 'type-graphql'
|
4
|
-
|
5
|
-
import { ObjectRef, ScalarObject } from '@things-factory/shell'
|
6
|
-
|
7
|
-
import { DataSummary, DataSummaryDurationType } from './data-summary'
|
1
|
+
import { ObjectType, Field, InputType, Int, ID } from 'type-graphql'
|
2
|
+
import { DataSummary } from './data-summary'
|
8
3
|
|
9
4
|
@InputType()
|
10
5
|
export class NewDataSummary {
|
@@ -14,22 +14,7 @@ import { Domain, ScalarObject } from '@things-factory/shell'
|
|
14
14
|
import { User } from '@things-factory/auth-base'
|
15
15
|
|
16
16
|
import { DataItem } from '../data-set/data-item-type'
|
17
|
-
import { DataSet } from '../data-set/data-set'
|
18
|
-
|
19
|
-
export enum DataSummaryDurationType {
|
20
|
-
Shift = 'shift',
|
21
|
-
Daily = 'daily',
|
22
|
-
Weekly = 'weekly',
|
23
|
-
Monthly = 'monthly',
|
24
|
-
Quarterly = 'quarterly',
|
25
|
-
Yearly = 'yearly',
|
26
|
-
Custom = 'custom'
|
27
|
-
}
|
28
|
-
|
29
|
-
registerEnumType(DataSummaryDurationType, {
|
30
|
-
name: 'DataSummaryDurationType',
|
31
|
-
description: 'entry type enumeration for data-summary'
|
32
|
-
})
|
17
|
+
import { DataSet, DataSetSummaryPeriodType } from '../data-set/data-set'
|
33
18
|
|
34
19
|
@Entity()
|
35
20
|
@Index(
|
@@ -42,14 +27,14 @@ registerEnumType(DataSummaryDurationType, {
|
|
42
27
|
dataSummary.key03,
|
43
28
|
dataSummary.key05,
|
44
29
|
dataSummary.key04,
|
45
|
-
dataSummary.
|
46
|
-
dataSummary.
|
30
|
+
dataSummary.date,
|
31
|
+
dataSummary.period
|
47
32
|
],
|
48
33
|
{ unique: true }
|
49
34
|
)
|
50
35
|
@Index(
|
51
36
|
'ix_data_summary_1',
|
52
|
-
(dataSummary: DataSummary) => [dataSummary.domain, dataSummary.dataSet, dataSummary.
|
37
|
+
(dataSummary: DataSummary) => [dataSummary.domain, dataSummary.dataSet, dataSummary.date, dataSummary.period],
|
53
38
|
{ unique: false }
|
54
39
|
)
|
55
40
|
@ObjectType({ description: 'Entity for DataSummary' })
|
@@ -75,15 +60,15 @@ export class DataSummary {
|
|
75
60
|
|
76
61
|
@Column({ nullable: true })
|
77
62
|
@Field({ nullable: true })
|
78
|
-
type?:
|
63
|
+
type?: DataSetSummaryPeriodType
|
79
64
|
|
80
|
-
@Column({ nullable: true })
|
65
|
+
@Column({ nullable: true, default: '' })
|
81
66
|
@Field({ nullable: true })
|
82
|
-
|
67
|
+
date?: string = ''
|
83
68
|
|
84
|
-
@Column({ nullable: true, default: '
|
69
|
+
@Column({ nullable: true, default: '' })
|
85
70
|
@Field({ nullable: true })
|
86
|
-
|
71
|
+
period?: string = ''
|
87
72
|
|
88
73
|
@ManyToOne(type => DataSet)
|
89
74
|
@Field(type => DataSet, { nullable: false })
|
package/translations/en.json
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
"field.data-key-set": "data key set",
|
15
15
|
"field.data-sample": "data sample",
|
16
16
|
"field.data-set": "data set",
|
17
|
+
"field.date": "date",
|
17
18
|
"field.device-id": "device id",
|
18
19
|
"field.download-url": "download url",
|
19
20
|
"field.entry-role": "entry role",
|
@@ -38,7 +39,10 @@
|
|
38
39
|
"field.next-schedule": "next schedule",
|
39
40
|
"field.oos": "out of critical limit",
|
40
41
|
"field.ooc": "out of control limit",
|
42
|
+
"field.time-period": "time period",
|
41
43
|
"field.summary": "summary",
|
44
|
+
"field.summary-period": "summary period",
|
45
|
+
"field.next-summary-schedule": "next summary schedule",
|
42
46
|
"field.stat-function": "stat func.",
|
43
47
|
"field.count": "count",
|
44
48
|
"field.count-oos": "oos count",
|