@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.
Files changed (61) hide show
  1. package/client/pages/kpi-dashboard/kpi-dashboard-map.ts +291 -49
  2. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +3 -1
  3. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +268 -47
  4. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
  5. package/dist-client/tsconfig.tsbuildinfo +1 -1
  6. package/dist-server/migrations/seed-data/kpi-scopes-seed.json +121 -0
  7. package/dist-server/migrations/seed-data/scope-definitions-seed.json +90 -0
  8. package/dist-server/service/index.d.ts +4 -3
  9. package/dist-server/service/index.js +7 -3
  10. package/dist-server/service/index.js.map +1 -1
  11. package/dist-server/service/kpi/kpi-query.js.map +1 -1
  12. package/dist-server/service/kpi-org-scope/index.d.ts +0 -4
  13. package/dist-server/service/kpi-org-scope/index.js +0 -5
  14. package/dist-server/service/kpi-org-scope/index.js.map +1 -1
  15. package/dist-server/service/kpi-scope/index.d.ts +9 -0
  16. package/dist-server/service/kpi-scope/index.js +14 -0
  17. package/dist-server/service/kpi-scope/index.js.map +1 -0
  18. package/dist-server/service/kpi-scope/kpi-scope-mutation.d.ts +9 -0
  19. package/dist-server/service/kpi-scope/kpi-scope-mutation.js +135 -0
  20. package/dist-server/service/kpi-scope/kpi-scope-mutation.js.map +1 -0
  21. package/dist-server/service/kpi-scope/kpi-scope-query.d.ts +11 -0
  22. package/dist-server/service/kpi-scope/kpi-scope-query.js +89 -0
  23. package/dist-server/service/kpi-scope/kpi-scope-query.js.map +1 -0
  24. package/dist-server/service/kpi-scope/kpi-scope-type.d.ts +35 -0
  25. package/dist-server/service/kpi-scope/kpi-scope-type.js +138 -0
  26. package/dist-server/service/kpi-scope/kpi-scope-type.js.map +1 -0
  27. package/dist-server/service/kpi-scope/kpi-scope.d.ts +38 -0
  28. package/dist-server/service/kpi-scope/kpi-scope.js +144 -0
  29. package/dist-server/service/kpi-scope/kpi-scope.js.map +1 -0
  30. package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.d.ts +43 -0
  31. package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.js +181 -0
  32. package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.js.map +1 -0
  33. package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.d.ts +50 -0
  34. package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.js +324 -0
  35. package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.js.map +1 -0
  36. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.d.ts +4 -0
  37. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js +76 -0
  38. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js.map +1 -1
  39. package/dist-server/service/kpi-statistic/kpi-statistic-query.d.ts +5 -1
  40. package/dist-server/service/kpi-statistic/kpi-statistic-query.js +92 -1
  41. package/dist-server/service/kpi-statistic/kpi-statistic-query.js.map +1 -1
  42. package/dist-server/service/kpi-statistic/kpi-statistic.d.ts +3 -0
  43. package/dist-server/service/kpi-statistic/kpi-statistic.js +23 -1
  44. package/dist-server/service/kpi-statistic/kpi-statistic.js.map +1 -1
  45. package/dist-server/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +3 -3
  47. package/server/migrations/seed-data/kpi-scopes-seed.json +121 -0
  48. package/server/migrations/seed-data/scope-definitions-seed.json +90 -0
  49. package/server/service/index.ts +7 -3
  50. package/server/service/kpi/kpi-query.ts +1 -0
  51. package/server/service/kpi-org-scope/index.ts +1 -6
  52. package/server/service/kpi-scope/index.ts +11 -0
  53. package/server/service/kpi-scope/kpi-scope-mutation.ts +129 -0
  54. package/server/service/kpi-scope/kpi-scope-query.ts +63 -0
  55. package/server/service/kpi-scope/kpi-scope-type.ts +96 -0
  56. package/server/service/kpi-scope/kpi-scope.ts +143 -0
  57. package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +231 -0
  58. package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +410 -0
  59. package/server/service/kpi-statistic/kpi-statistic-mutation.ts +97 -0
  60. package/server/service/kpi-statistic/kpi-statistic-query.ts +89 -2
  61. 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
- // 전국 KPI 데이터 조회
69
- const nationalResponse = await client.query({
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
- kpiStatistics {
73
- items {
116
+ kpisLevel1 {
117
+ id
118
+ name
119
+ description
120
+ isLeaf
121
+ children {
74
122
  id
75
- valueDate
76
- periodType
77
- mean
78
- median
79
- standardDeviation
80
- kpi {
123
+ name
124
+ isLeaf
125
+ value {
81
126
  id
82
- name
83
- category: parent {
127
+ value
128
+ score
129
+ valueDate
130
+ kpiOrgScope {
84
131
  id
85
- name
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 = nationalResponse.data.kpiStatistics.items || []
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 generateMapData() {
110
- // 시도별 샘플 데이터 생성 (실제 좌표 포함)
111
- this.mapData = [
112
- { region: '강원', kpi: 101.5, change: -0.08, trend: 'down', lat: 37.8228, lng: 128.1555 },
113
- { region: '경북', kpi: 94.8, change: -0.1, trend: 'down', lat: 36.4919, lng: 128.8889 },
114
- { region: '대구', kpi: 79.4, change: -0.2, trend: 'down', lat: 35.8714, lng: 128.6014 },
115
- { region: '경남', kpi: 92.1, change: -0.08, trend: 'down', lat: 35.4606, lng: 128.2132 },
116
- { region: '부산', kpi: 85.2, change: -0.18, trend: 'down', lat: 35.1796, lng: 129.0756 },
117
- { region: '울산', kpi: 91.4, change: 0.19, trend: 'up', lat: 35.5384, lng: 129.3114 },
118
- { region: '서울', kpi: 98.1, change: 1.28, trend: 'up', lat: 37.5665, lng: 126.978 },
119
- { region: '인천', kpi: 87.3, change: 0.05, trend: 'up', lat: 37.4563, lng: 126.7052 },
120
- { region: '대전', kpi: 89.7, change: -0.12, trend: 'down', lat: 36.3504, lng: 127.3845 },
121
- { region: '광주', kpi: 91.1, change: -0.16, trend: 'down', lat: 35.1595, lng: 126.8526 },
122
- { region: '전북', kpi: 99.3, change: 0.15, trend: 'up', lat: 35.7175, lng: 127.153 },
123
- { region: '전남', kpi: 91.8, change: -0.12, trend: 'down', lat: 34.8679, lng: 126.991 },
124
- { region: '충북', kpi: 93.2, change: 0.08, trend: 'up', lat: 36.8, lng: 127.7 },
125
- { region: '충남', kpi: 88.9, change: -0.05, trend: 'down', lat: 36.5184, lng: 126.8 },
126
- { region: '제주', kpi: 95.6, change: 0.22, trend: 'up', lat: 33.4996, lng: 126.5312 }
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($region: String) {
151
- kpiStatistics(filters: [{ field: "scope", operator: "eq", value: $region }]) {
383
+ query GetRegionKpiData($filters: [Filter!]) {
384
+ kpiValues(filters: $filters) {
152
385
  items {
153
386
  id
154
- scope
387
+ value
388
+ score
155
389
  valueDate
156
- periodType
157
- mean
158
- median
159
- standardDeviation
390
+ kpiOrgScope {
391
+ id
392
+ org
393
+ scope01
394
+ scope02
395
+ entityName
396
+ }
160
397
  kpi {
161
398
  id
162
399
  name
163
- category: parent {
400
+ parent {
164
401
  id
165
402
  name
166
403
  }
167
404
  }
168
405
  }
406
+ total
169
407
  }
170
408
  }
171
409
  `,
172
- variables: { region }
410
+ variables: {
411
+ filters: [
412
+ { name: 'kpiOrgScope.scope02', operator: 'eq', value: region }
413
+ ]
414
+ }
173
415
  })
174
416
 
175
- this.seoulData = response.data.kpiStatistics.items || []
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
- private generateMapData;
19
+ fetchDashboardDataFallback(): Promise<void>;
20
+ private generateMapDataFromKpiValues;
21
+ private generateMapDataFromStatistics;
20
22
  private downloadExcel;
21
23
  private onRegionClick;
22
24
  private closeRegionPopup;