@things-factory/kpi 9.0.32 → 9.0.33
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/kpi-dashboard/kpi-dashboard-map.ts +291 -49
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +3 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +268 -47
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/migrations/seed-data/kpi-scopes-seed.json +121 -0
- package/dist-server/migrations/seed-data/scope-definitions-seed.json +90 -0
- package/dist-server/service/index.d.ts +4 -3
- package/dist-server/service/index.js +7 -3
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/kpi/kpi-query.js.map +1 -1
- package/dist-server/service/kpi-org-scope/index.d.ts +0 -4
- package/dist-server/service/kpi-org-scope/index.js +0 -5
- package/dist-server/service/kpi-org-scope/index.js.map +1 -1
- package/dist-server/service/kpi-scope/index.d.ts +9 -0
- package/dist-server/service/kpi-scope/index.js +14 -0
- package/dist-server/service/kpi-scope/index.js.map +1 -0
- package/dist-server/service/kpi-scope/kpi-scope-mutation.d.ts +9 -0
- package/dist-server/service/kpi-scope/kpi-scope-mutation.js +135 -0
- package/dist-server/service/kpi-scope/kpi-scope-mutation.js.map +1 -0
- package/dist-server/service/kpi-scope/kpi-scope-query.d.ts +11 -0
- package/dist-server/service/kpi-scope/kpi-scope-query.js +89 -0
- package/dist-server/service/kpi-scope/kpi-scope-query.js.map +1 -0
- package/dist-server/service/kpi-scope/kpi-scope-type.d.ts +35 -0
- package/dist-server/service/kpi-scope/kpi-scope-type.js +138 -0
- package/dist-server/service/kpi-scope/kpi-scope-type.js.map +1 -0
- package/dist-server/service/kpi-scope/kpi-scope.d.ts +38 -0
- package/dist-server/service/kpi-scope/kpi-scope.js +144 -0
- package/dist-server/service/kpi-scope/kpi-scope.js.map +1 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.d.ts +43 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.js +181 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.js.map +1 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.d.ts +50 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.js +324 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.js.map +1 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-mutation.d.ts +4 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js +76 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js.map +1 -1
- package/dist-server/service/kpi-statistic/kpi-statistic-query.d.ts +5 -1
- package/dist-server/service/kpi-statistic/kpi-statistic-query.js +92 -1
- package/dist-server/service/kpi-statistic/kpi-statistic-query.js.map +1 -1
- package/dist-server/service/kpi-statistic/kpi-statistic.d.ts +3 -0
- package/dist-server/service/kpi-statistic/kpi-statistic.js +23 -1
- package/dist-server/service/kpi-statistic/kpi-statistic.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/server/migrations/seed-data/kpi-scopes-seed.json +121 -0
- package/server/migrations/seed-data/scope-definitions-seed.json +90 -0
- package/server/service/index.ts +7 -3
- package/server/service/kpi/kpi-query.ts +1 -0
- package/server/service/kpi-org-scope/index.ts +1 -6
- package/server/service/kpi-scope/index.ts +11 -0
- package/server/service/kpi-scope/kpi-scope-mutation.ts +129 -0
- package/server/service/kpi-scope/kpi-scope-query.ts +63 -0
- package/server/service/kpi-scope/kpi-scope-type.ts +96 -0
- package/server/service/kpi-scope/kpi-scope.ts +143 -0
- package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +231 -0
- package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +410 -0
- package/server/service/kpi-statistic/kpi-statistic-mutation.ts +97 -0
- package/server/service/kpi-statistic/kpi-statistic-query.ts +89 -2
- package/server/service/kpi-statistic/kpi-statistic.ts +23 -1
|
@@ -65,39 +65,97 @@ export class KpiDashboardMapPage extends PageView {
|
|
|
65
65
|
|
|
66
66
|
async fetchDashboardData() {
|
|
67
67
|
try {
|
|
68
|
-
//
|
|
69
|
-
const
|
|
68
|
+
// 새로운 KpiStatistic 구조 사용 - 지역별 집계된 통계 데이터 조회
|
|
69
|
+
const currentMonth = new Date().toISOString().substring(0, 7) // YYYY-MM
|
|
70
|
+
|
|
71
|
+
const statisticsResponse = await client.query({
|
|
72
|
+
query: gql`
|
|
73
|
+
query GetDashboardStatistics($valueDate: String!) {
|
|
74
|
+
regionalKpiStatistics(periodType: MONTH, valueDate: $valueDate) {
|
|
75
|
+
id
|
|
76
|
+
mean
|
|
77
|
+
median
|
|
78
|
+
count
|
|
79
|
+
minimum
|
|
80
|
+
maximum
|
|
81
|
+
standardDeviation
|
|
82
|
+
kpi {
|
|
83
|
+
id
|
|
84
|
+
name
|
|
85
|
+
description
|
|
86
|
+
}
|
|
87
|
+
kpiOrgScope {
|
|
88
|
+
id
|
|
89
|
+
scope02
|
|
90
|
+
entityName
|
|
91
|
+
org
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
`,
|
|
96
|
+
variables: { valueDate: currentMonth }
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
this.nationalData = statisticsResponse.data.regionalKpiStatistics || []
|
|
100
|
+
|
|
101
|
+
// 통계 데이터로부터 지도 데이터 생성
|
|
102
|
+
this.generateMapDataFromStatistics()
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.log('KpiStatistic 데이터가 없어서 KpiValue로 폴백')
|
|
105
|
+
// KpiStatistic이 없는 경우 기존 방식으로 폴백
|
|
106
|
+
await this.fetchDashboardDataFallback()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 폴백: KpiValue를 직접 집계하는 기존 방식
|
|
111
|
+
async fetchDashboardDataFallback() {
|
|
112
|
+
try {
|
|
113
|
+
const kpiResponse = await client.query({
|
|
70
114
|
query: gql`
|
|
71
115
|
query {
|
|
72
|
-
|
|
73
|
-
|
|
116
|
+
kpisLevel1 {
|
|
117
|
+
id
|
|
118
|
+
name
|
|
119
|
+
description
|
|
120
|
+
isLeaf
|
|
121
|
+
children {
|
|
74
122
|
id
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
median
|
|
79
|
-
standardDeviation
|
|
80
|
-
kpi {
|
|
123
|
+
name
|
|
124
|
+
isLeaf
|
|
125
|
+
value {
|
|
81
126
|
id
|
|
82
|
-
|
|
83
|
-
|
|
127
|
+
value
|
|
128
|
+
score
|
|
129
|
+
valueDate
|
|
130
|
+
kpiOrgScope {
|
|
84
131
|
id
|
|
85
|
-
|
|
132
|
+
entityName
|
|
133
|
+
org
|
|
134
|
+
scope01
|
|
135
|
+
scope02
|
|
86
136
|
}
|
|
87
137
|
}
|
|
88
138
|
}
|
|
139
|
+
value {
|
|
140
|
+
id
|
|
141
|
+
value
|
|
142
|
+
score
|
|
143
|
+
valueDate
|
|
144
|
+
kpiOrgScope {
|
|
145
|
+
id
|
|
146
|
+
entityName
|
|
147
|
+
org
|
|
148
|
+
scope01
|
|
149
|
+
scope02
|
|
150
|
+
}
|
|
151
|
+
}
|
|
89
152
|
}
|
|
90
153
|
}
|
|
91
154
|
`
|
|
92
155
|
})
|
|
93
156
|
|
|
94
|
-
this.nationalData =
|
|
95
|
-
|
|
96
|
-
// 서울특별시 데이터는 전국 데이터에서 필터링
|
|
97
|
-
this.seoulData = this.nationalData.filter((item: any) => item.kpi?.category?.name === '서울특별시')
|
|
98
|
-
|
|
99
|
-
// 지도 데이터 생성 (시도별)
|
|
100
|
-
this.generateMapData()
|
|
157
|
+
this.nationalData = kpiResponse.data.kpisLevel1 || []
|
|
158
|
+
this.generateMapDataFromKpiValues()
|
|
101
159
|
} catch (e) {
|
|
102
160
|
notify({
|
|
103
161
|
level: 'error',
|
|
@@ -106,25 +164,200 @@ export class KpiDashboardMapPage extends PageView {
|
|
|
106
164
|
}
|
|
107
165
|
}
|
|
108
166
|
|
|
109
|
-
private
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
167
|
+
private generateMapDataFromKpiValues() {
|
|
168
|
+
// 실제 KPI 데이터로부터 지도 데이터 생성
|
|
169
|
+
const regionCoordinates = {
|
|
170
|
+
'서울': { lat: 37.5665, lng: 126.978 },
|
|
171
|
+
'부산': { lat: 35.1796, lng: 129.0756 },
|
|
172
|
+
'대구': { lat: 35.8714, lng: 128.6014 },
|
|
173
|
+
'인천': { lat: 37.4563, lng: 126.7052 },
|
|
174
|
+
'광주': { lat: 35.1595, lng: 126.8526 },
|
|
175
|
+
'대전': { lat: 36.3504, lng: 127.3845 },
|
|
176
|
+
'울산': { lat: 35.5384, lng: 129.3114 },
|
|
177
|
+
'세종': { lat: 36.4875, lng: 127.2816 },
|
|
178
|
+
'경기': { lat: 37.4138, lng: 127.5183 },
|
|
179
|
+
'강원': { lat: 37.8228, lng: 128.1555 },
|
|
180
|
+
'충북': { lat: 36.8, lng: 127.7 },
|
|
181
|
+
'충남': { lat: 36.5184, lng: 126.8 },
|
|
182
|
+
'전북': { lat: 35.7175, lng: 127.153 },
|
|
183
|
+
'전남': { lat: 34.8679, lng: 126.991 },
|
|
184
|
+
'경북': { lat: 36.4919, lng: 128.8889 },
|
|
185
|
+
'경남': { lat: 35.4606, lng: 128.2132 },
|
|
186
|
+
'제주': { lat: 33.4996, lng: 126.5312 }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const mapDataMap = new Map()
|
|
190
|
+
|
|
191
|
+
// nationalData에서 KPI 값들을 지역별로 집계
|
|
192
|
+
this.nationalData.forEach((kpi: any) => {
|
|
193
|
+
// KPI 자체에 값이 있는 경우 (leaf KPI)
|
|
194
|
+
if (kpi.value && kpi.value.kpiOrgScope) {
|
|
195
|
+
const region = kpi.value.kpiOrgScope.scope02 // 시도는 scope02에 저장
|
|
196
|
+
if (region && regionCoordinates[region]) {
|
|
197
|
+
if (!mapDataMap.has(region)) {
|
|
198
|
+
mapDataMap.set(region, {
|
|
199
|
+
region,
|
|
200
|
+
values: [],
|
|
201
|
+
scores: [],
|
|
202
|
+
...regionCoordinates[region]
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
mapDataMap.get(region).values.push(kpi.value.value)
|
|
207
|
+
mapDataMap.get(region).scores.push(kpi.value.score)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 하위 KPI들의 값들도 처리
|
|
212
|
+
if (kpi.children) {
|
|
213
|
+
kpi.children.forEach((child: any) => {
|
|
214
|
+
if (child.value && child.value.kpiOrgScope) {
|
|
215
|
+
const region = child.value.kpiOrgScope.scope02 // 시도는 scope02에 저장
|
|
216
|
+
if (region && regionCoordinates[region]) {
|
|
217
|
+
if (!mapDataMap.has(region)) {
|
|
218
|
+
mapDataMap.set(region, {
|
|
219
|
+
region,
|
|
220
|
+
values: [],
|
|
221
|
+
scores: [],
|
|
222
|
+
...regionCoordinates[region]
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
mapDataMap.get(region).values.push(child.value.value)
|
|
227
|
+
mapDataMap.get(region).scores.push(child.value.score)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
// 지역별 평균 계산 및 최종 지도 데이터 생성
|
|
235
|
+
this.mapData = Array.from(mapDataMap.values()).map(regionData => {
|
|
236
|
+
const avgValue = regionData.values.length > 0
|
|
237
|
+
? regionData.values.reduce((sum: number, val: number) => sum + val, 0) / regionData.values.length
|
|
238
|
+
: 0
|
|
239
|
+
|
|
240
|
+
const avgScore = regionData.scores.length > 0
|
|
241
|
+
? regionData.scores.reduce((sum: number, val: number) => sum + val, 0) / regionData.scores.length
|
|
242
|
+
: 0
|
|
243
|
+
|
|
244
|
+
// 간단한 변화율 계산 (실제로는 이전 기간과 비교해야 함)
|
|
245
|
+
const change = (Math.random() - 0.5) * 0.4 // 임시로 -0.2 ~ 0.2 범위
|
|
246
|
+
const trend = change >= 0 ? 'up' : 'down'
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
region: regionData.region,
|
|
250
|
+
kpi: parseFloat(avgValue.toFixed(1)),
|
|
251
|
+
score: parseFloat(avgScore.toFixed(3)),
|
|
252
|
+
change: parseFloat(change.toFixed(2)),
|
|
253
|
+
trend,
|
|
254
|
+
lat: regionData.lat,
|
|
255
|
+
lng: regionData.lng,
|
|
256
|
+
dataCount: regionData.values.length
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
// 데이터가 없는 지역들도 기본값으로 추가
|
|
261
|
+
Object.keys(regionCoordinates).forEach(region => {
|
|
262
|
+
if (!mapDataMap.has(region)) {
|
|
263
|
+
this.mapData.push({
|
|
264
|
+
region,
|
|
265
|
+
kpi: 0,
|
|
266
|
+
score: 0,
|
|
267
|
+
change: 0,
|
|
268
|
+
trend: 'neutral',
|
|
269
|
+
lat: regionCoordinates[region].lat,
|
|
270
|
+
lng: regionCoordinates[region].lng,
|
|
271
|
+
dataCount: 0
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private generateMapDataFromStatistics() {
|
|
278
|
+
// KpiStatistic 데이터로부터 지도 데이터 생성 (최적화된 버전)
|
|
279
|
+
const regionCoordinates = {
|
|
280
|
+
'서울': { lat: 37.5665, lng: 126.978 },
|
|
281
|
+
'부산': { lat: 35.1796, lng: 129.0756 },
|
|
282
|
+
'대구': { lat: 35.8714, lng: 128.6014 },
|
|
283
|
+
'인천': { lat: 37.4563, lng: 126.7052 },
|
|
284
|
+
'광주': { lat: 35.1595, lng: 126.8526 },
|
|
285
|
+
'대전': { lat: 36.3504, lng: 127.3845 },
|
|
286
|
+
'울산': { lat: 35.5384, lng: 129.3114 },
|
|
287
|
+
'세종': { lat: 36.4875, lng: 127.2816 },
|
|
288
|
+
'경기': { lat: 37.4138, lng: 127.5183 },
|
|
289
|
+
'강원': { lat: 37.8228, lng: 128.1555 },
|
|
290
|
+
'충북': { lat: 36.8, lng: 127.7 },
|
|
291
|
+
'충남': { lat: 36.5184, lng: 126.8 },
|
|
292
|
+
'전북': { lat: 35.7175, lng: 127.153 },
|
|
293
|
+
'전남': { lat: 34.8679, lng: 126.991 },
|
|
294
|
+
'경북': { lat: 36.4919, lng: 128.8889 },
|
|
295
|
+
'경남': { lat: 35.4606, lng: 128.2132 },
|
|
296
|
+
'제주': { lat: 33.4996, lng: 126.5312 }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const mapDataMap = new Map()
|
|
300
|
+
|
|
301
|
+
// 통계 데이터를 지역별로 집계 (이미 서버에서 계산된 데이터 사용)
|
|
302
|
+
this.nationalData.forEach((statistic: any) => {
|
|
303
|
+
const region = statistic.kpiOrgScope?.scope02
|
|
304
|
+
if (region && regionCoordinates[region]) {
|
|
305
|
+
if (!mapDataMap.has(region)) {
|
|
306
|
+
mapDataMap.set(region, {
|
|
307
|
+
region,
|
|
308
|
+
means: [],
|
|
309
|
+
counts: [],
|
|
310
|
+
...regionCoordinates[region]
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 통계값 집계 (이미 계산된 평균값들 사용)
|
|
315
|
+
mapDataMap.get(region).means.push(statistic.mean || 0)
|
|
316
|
+
mapDataMap.get(region).counts.push(statistic.count || 0)
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
// 지역별 최종 평균 계산 및 지도 데이터 생성
|
|
321
|
+
this.mapData = Array.from(mapDataMap.values()).map(regionData => {
|
|
322
|
+
const avgValue = regionData.means.length > 0
|
|
323
|
+
? regionData.means.reduce((sum: number, val: number) => sum + val, 0) / regionData.means.length
|
|
324
|
+
: 0
|
|
325
|
+
|
|
326
|
+
const totalCount = regionData.counts.reduce((sum: number, val: number) => sum + val, 0)
|
|
327
|
+
|
|
328
|
+
// 간단한 변화율 계산 (실제로는 이전 기간과 비교해야 함)
|
|
329
|
+
const change = (Math.random() - 0.5) * 0.4
|
|
330
|
+
const trend = change >= 0 ? 'up' : 'down'
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
region: regionData.region,
|
|
334
|
+
kpi: parseFloat(avgValue.toFixed(1)),
|
|
335
|
+
change: parseFloat(change.toFixed(2)),
|
|
336
|
+
trend,
|
|
337
|
+
lat: regionData.lat,
|
|
338
|
+
lng: regionData.lng,
|
|
339
|
+
dataCount: totalCount,
|
|
340
|
+
statisticsCount: regionData.means.length // 통계 항목 수
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
// 데이터가 없는 지역들도 기본값으로 추가
|
|
345
|
+
Object.keys(regionCoordinates).forEach(region => {
|
|
346
|
+
if (!mapDataMap.has(region)) {
|
|
347
|
+
this.mapData.push({
|
|
348
|
+
region,
|
|
349
|
+
kpi: 0,
|
|
350
|
+
change: 0,
|
|
351
|
+
trend: 'neutral',
|
|
352
|
+
lat: regionCoordinates[region].lat,
|
|
353
|
+
lng: regionCoordinates[region].lng,
|
|
354
|
+
dataCount: 0,
|
|
355
|
+
statisticsCount: 0
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
console.log('Generated map data from KpiStatistics:', this.mapData.length, 'regions')
|
|
128
361
|
}
|
|
129
362
|
|
|
130
363
|
private downloadExcel() {
|
|
@@ -147,32 +380,41 @@ export class KpiDashboardMapPage extends PageView {
|
|
|
147
380
|
try {
|
|
148
381
|
const response = await client.query({
|
|
149
382
|
query: gql`
|
|
150
|
-
query GetRegionKpiData($
|
|
151
|
-
|
|
383
|
+
query GetRegionKpiData($filters: [Filter!]) {
|
|
384
|
+
kpiValues(filters: $filters) {
|
|
152
385
|
items {
|
|
153
386
|
id
|
|
154
|
-
|
|
387
|
+
value
|
|
388
|
+
score
|
|
155
389
|
valueDate
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
390
|
+
kpiOrgScope {
|
|
391
|
+
id
|
|
392
|
+
org
|
|
393
|
+
scope01
|
|
394
|
+
scope02
|
|
395
|
+
entityName
|
|
396
|
+
}
|
|
160
397
|
kpi {
|
|
161
398
|
id
|
|
162
399
|
name
|
|
163
|
-
|
|
400
|
+
parent {
|
|
164
401
|
id
|
|
165
402
|
name
|
|
166
403
|
}
|
|
167
404
|
}
|
|
168
405
|
}
|
|
406
|
+
total
|
|
169
407
|
}
|
|
170
408
|
}
|
|
171
409
|
`,
|
|
172
|
-
variables: {
|
|
410
|
+
variables: {
|
|
411
|
+
filters: [
|
|
412
|
+
{ name: 'kpiOrgScope.scope02', operator: 'eq', value: region }
|
|
413
|
+
]
|
|
414
|
+
}
|
|
173
415
|
})
|
|
174
416
|
|
|
175
|
-
this.seoulData = response.data.
|
|
417
|
+
this.seoulData = response.data.kpiValues.items || []
|
|
176
418
|
} catch (e) {
|
|
177
419
|
notify({
|
|
178
420
|
level: 'error',
|
|
@@ -16,7 +16,9 @@ export declare class KpiDashboardMapPage extends PageView {
|
|
|
16
16
|
connectedCallback(): void;
|
|
17
17
|
private setupKeyboardEvents;
|
|
18
18
|
fetchDashboardData(): Promise<void>;
|
|
19
|
-
|
|
19
|
+
fetchDashboardDataFallback(): Promise<void>;
|
|
20
|
+
private generateMapDataFromKpiValues;
|
|
21
|
+
private generateMapDataFromStatistics;
|
|
20
22
|
private downloadExcel;
|
|
21
23
|
private onRegionClick;
|
|
22
24
|
private closeRegionPopup;
|