@things-factory/kpi 9.0.28 → 9.0.30

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 (131) hide show
  1. package/client/google-map/common-google-map.ts +332 -0
  2. package/client/google-map/google-map-loader.ts +29 -0
  3. package/client/google-map/script-loader.ts +173 -0
  4. package/client/pages/kpi-dashboard/cards/kpi-level1-card.ts +248 -0
  5. package/client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts +369 -0
  6. package/client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts +443 -0
  7. package/client/pages/kpi-dashboard/components/kpi-chart-toggle.ts +73 -0
  8. package/client/pages/kpi-dashboard/components/kpi-map-panel.ts +222 -0
  9. package/client/pages/kpi-dashboard/kpi-dashboard-map.ts +786 -0
  10. package/client/pages/kpi-dashboard/kpi-dashboard.ts +416 -0
  11. package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +761 -0
  12. package/client/pages/kpi-statistic/kpi-statistic-importer.ts +90 -0
  13. package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +505 -0
  14. package/client/route.ts +12 -0
  15. package/dist-client/google-map/common-google-map.d.ts +34 -0
  16. package/dist-client/google-map/common-google-map.js +300 -0
  17. package/dist-client/google-map/common-google-map.js.map +1 -0
  18. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  19. package/dist-client/google-map/google-map-loader.js +22 -0
  20. package/dist-client/google-map/google-map-loader.js.map +1 -0
  21. package/dist-client/google-map/script-loader.d.ts +3 -0
  22. package/dist-client/google-map/script-loader.js +144 -0
  23. package/dist-client/google-map/script-loader.js.map +1 -0
  24. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +17 -0
  25. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +279 -0
  26. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  27. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +19 -0
  28. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +385 -0
  29. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  30. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +23 -0
  31. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +465 -0
  32. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  33. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  34. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +79 -0
  35. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  36. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +23 -0
  37. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +223 -0
  38. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  39. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +38 -0
  40. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +813 -0
  41. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  42. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +21 -0
  43. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +398 -0
  44. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
  45. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.d.ts +53 -0
  46. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +718 -0
  47. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js.map +1 -0
  48. package/dist-client/pages/kpi-statistic/kpi-statistic-importer.d.ts +23 -0
  49. package/dist-client/pages/kpi-statistic/kpi-statistic-importer.js +92 -0
  50. package/dist-client/pages/kpi-statistic/kpi-statistic-importer.js.map +1 -0
  51. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.d.ts +66 -0
  52. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +487 -0
  53. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js.map +1 -0
  54. package/dist-client/route.d.ts +1 -1
  55. package/dist-client/route.js +9 -0
  56. package/dist-client/route.js.map +1 -1
  57. package/dist-client/tsconfig.tsbuildinfo +1 -1
  58. package/dist-server/index.d.ts +1 -0
  59. package/dist-server/index.js +1 -0
  60. package/dist-server/index.js.map +1 -1
  61. package/dist-server/migrations/index.d.ts +1 -0
  62. package/dist-server/migrations/index.js +12 -0
  63. package/dist-server/migrations/index.js.map +1 -0
  64. package/dist-server/service/index.d.ts +3 -2
  65. package/dist-server/service/index.js +4 -0
  66. package/dist-server/service/index.js.map +1 -1
  67. package/dist-server/service/kpi/kpi-mutation.js +9 -0
  68. package/dist-server/service/kpi/kpi-mutation.js.map +1 -1
  69. package/dist-server/service/kpi/kpi-query.js +2 -0
  70. package/dist-server/service/kpi/kpi-query.js.map +1 -1
  71. package/dist-server/service/kpi-alert/kpi-alert-query.js +1 -0
  72. package/dist-server/service/kpi-alert/kpi-alert-query.js.map +1 -1
  73. package/dist-server/service/kpi-category/kpi-category-mutation.js +4 -0
  74. package/dist-server/service/kpi-category/kpi-category-mutation.js.map +1 -1
  75. package/dist-server/service/kpi-category/kpi-category-query.js +2 -0
  76. package/dist-server/service/kpi-category/kpi-category-query.js.map +1 -1
  77. package/dist-server/service/kpi-metric/kpi-metric-mutation.js +6 -0
  78. package/dist-server/service/kpi-metric/kpi-metric-mutation.js.map +1 -1
  79. package/dist-server/service/kpi-metric/kpi-metric-query.js +2 -0
  80. package/dist-server/service/kpi-metric/kpi-metric-query.js.map +1 -1
  81. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +7 -0
  82. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  83. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +2 -0
  84. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -1
  85. package/dist-server/service/kpi-statistic/index.d.ts +6 -0
  86. package/dist-server/service/kpi-statistic/index.js +10 -0
  87. package/dist-server/service/kpi-statistic/index.js.map +1 -0
  88. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.d.ts +10 -0
  89. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js +198 -0
  90. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js.map +1 -0
  91. package/dist-server/service/kpi-statistic/kpi-statistic-query.d.ts +13 -0
  92. package/dist-server/service/kpi-statistic/kpi-statistic-query.js +92 -0
  93. package/dist-server/service/kpi-statistic/kpi-statistic-query.js.map +1 -0
  94. package/dist-server/service/kpi-statistic/kpi-statistic-type.d.ts +59 -0
  95. package/dist-server/service/kpi-statistic/kpi-statistic-type.js +200 -0
  96. package/dist-server/service/kpi-statistic/kpi-statistic-type.js.map +1 -0
  97. package/dist-server/service/kpi-statistic/kpi-statistic.d.ts +38 -0
  98. package/dist-server/service/kpi-statistic/kpi-statistic.js +177 -0
  99. package/dist-server/service/kpi-statistic/kpi-statistic.js.map +1 -0
  100. package/dist-server/service/kpi-value/kpi-value-mutation.js +9 -0
  101. package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
  102. package/dist-server/service/kpi-value/kpi-value-query.js +2 -0
  103. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
  104. package/dist-server/tsconfig.tsbuildinfo +1 -1
  105. package/helps/kpi/kpi-statistic.md +160 -0
  106. package/package.json +2 -2
  107. package/server/index.ts +1 -0
  108. package/server/migrations/index.ts +9 -0
  109. package/server/service/index.ts +8 -0
  110. package/server/service/kpi/kpi-mutation.ts +9 -0
  111. package/server/service/kpi/kpi-query.ts +2 -0
  112. package/server/service/kpi-alert/kpi-alert-query.ts +2 -1
  113. package/server/service/kpi-category/kpi-category-mutation.ts +4 -0
  114. package/server/service/kpi-category/kpi-category-query.ts +2 -0
  115. package/server/service/kpi-metric/kpi-metric-mutation.ts +6 -0
  116. package/server/service/kpi-metric/kpi-metric-query.ts +2 -0
  117. package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +7 -0
  118. package/server/service/kpi-metric-value/kpi-metric-value-query.ts +3 -1
  119. package/server/service/kpi-statistic/index.ts +7 -0
  120. package/server/service/kpi-statistic/kpi-statistic-mutation.ts +194 -0
  121. package/server/service/kpi-statistic/kpi-statistic-query.ts +59 -0
  122. package/server/service/kpi-statistic/kpi-statistic-type.ts +152 -0
  123. package/server/service/kpi-statistic/kpi-statistic.ts +167 -0
  124. package/server/service/kpi-value/kpi-value-mutation.ts +9 -0
  125. package/server/service/kpi-value/kpi-value-query.ts +2 -0
  126. package/things-factory.config.js +4 -1
  127. package/translations/en.json +24 -1
  128. package/translations/ja.json +43 -20
  129. package/translations/ko.json +30 -7
  130. package/translations/ms.json +35 -12
  131. package/translations/zh.json +39 -16
@@ -16,6 +16,11 @@ import './kpi-alert-panel'
16
16
  import '../../charts/kpi-radar-chart'
17
17
  import '../../charts/kpi-boxplot-chart'
18
18
 
19
+ // 3레벨 KPI 플로팅 컴포넌트들
20
+ import './cards/kpi-level1-card'
21
+ import './cards/kpi-level2-comparison'
22
+ import './cards/kpi-level3-comparison'
23
+
19
24
  @customElement('kpi-dashboard')
20
25
  export class KpiDashboardPage extends PageView {
21
26
  static styles = [
@@ -32,6 +37,7 @@ export class KpiDashboardPage extends PageView {
32
37
  }
33
38
  .sample-charts-section {
34
39
  display: flex;
40
+ flex-direction: column;
35
41
  gap: 40px;
36
42
  margin-bottom: 48px;
37
43
  align-items: flex-start;
@@ -109,6 +115,10 @@ export class KpiDashboardPage extends PageView {
109
115
  @state() showHistoryModal: boolean = false
110
116
  @state() modalHistories: any[] = []
111
117
  @state() modalKpiName: string = ''
118
+ @state() kpiStatistics: any[] = [] // 실제 KPI 통계 데이터
119
+ @state() statisticsLoading = true // 통계 데이터 로딩 상태
120
+ @state() selectedPeriodType: string = 'MONTH' // 선택된 기간 타입 (MONTH로 고정)
121
+ @state() selectedValueDate: string = '' // 선택된 값 날짜
112
122
 
113
123
  // 샘플 데이터
114
124
  private get sampleCategories(): string[] {
@@ -231,14 +241,225 @@ export class KpiDashboardPage extends PageView {
231
241
  return 'A'
232
242
  }
233
243
 
244
+ // 현재 월을 YYYY-MM 형식으로 반환
245
+ private get currentMonth(): string {
246
+ const now = new Date()
247
+ const year = now.getFullYear()
248
+ const month = String(now.getMonth() + 1).padStart(2, '0')
249
+ return `${year}-${month}`
250
+ }
251
+
252
+ // 필터링된 KPI 통계 데이터
253
+ private get filteredKpiStatistics(): any[] {
254
+ if (!this.kpiStatistics || this.kpiStatistics.length === 0) return []
255
+
256
+ return this.kpiStatistics.filter(stat => stat.periodType === 'MONTH' && stat.valueDate === this.selectedValueDate)
257
+ }
258
+
259
+ // 사용 가능한 기간 타입들 (MONTH로 고정)
260
+ private get availablePeriodTypes(): string[] {
261
+ return ['MONTH']
262
+ }
263
+
264
+ // MONTH 기간에 사용 가능한 날짜들
265
+ private get availableValueDates(): string[] {
266
+ if (!this.kpiStatistics || this.kpiStatistics.length === 0) return []
267
+
268
+ const valueDates = new Set<string>()
269
+ this.kpiStatistics.forEach(stat => {
270
+ if (stat.periodType === 'MONTH' && stat.valueDate) {
271
+ valueDates.add(stat.valueDate)
272
+ }
273
+ })
274
+
275
+ return Array.from(valueDates).sort().reverse() // 최신 날짜부터 정렬
276
+ }
277
+
278
+ // 통계 요약 정보
279
+ private get statisticsSummary(): any {
280
+ const filteredStats = this.filteredKpiStatistics
281
+ if (filteredStats.length === 0) return null
282
+
283
+ const totalKpis = filteredStats.length
284
+ const categories = new Set(filteredStats.map(s => s.kpi?.category?.name).filter(Boolean))
285
+ const totalCategories = categories.size
286
+
287
+ const means = filteredStats.map(s => s.mean || 0).filter(v => v > 0)
288
+ const medians = filteredStats.map(s => s.median || 0).filter(v => v > 0)
289
+ const stdDevs = filteredStats.map(s => s.standardDeviation || 0).filter(v => v > 0)
290
+
291
+ const avgMean = means.length > 0 ? means.reduce((a, b) => a + b, 0) / means.length : 0
292
+ const avgMedian = medians.length > 0 ? medians.reduce((a, b) => a + b, 0) / medians.length : 0
293
+ const avgStdDev = stdDevs.length > 0 ? stdDevs.reduce((a, b) => a + b, 0) / stdDevs.length : 0
294
+
295
+ return {
296
+ totalKpis,
297
+ totalCategories,
298
+ avgMean: avgMean.toFixed(2),
299
+ avgMedian: avgMedian.toFixed(2),
300
+ avgStdDev: avgStdDev.toFixed(2),
301
+ periodType: this.selectedPeriodType,
302
+ valueDate: this.selectedValueDate
303
+ }
304
+ }
305
+
306
+ // 기간 타입 변경 핸들러 (사용하지 않음 - MONTH로 고정)
307
+ private _onPeriodTypeChange(event: Event) {
308
+ // MONTH로 고정되어 있으므로 변경하지 않음
309
+ }
310
+
311
+ // 날짜 변경 핸들러
312
+ private _onValueDateChange(event: Event) {
313
+ const target = event.target as HTMLSelectElement
314
+ this.selectedValueDate = target.value
315
+ }
316
+
317
+ // 실제 KPI 통계 데이터를 기반으로 한 레이더 차트 데이터
318
+ private get realKpiRadarData(): any[] {
319
+ const filteredStats = this.filteredKpiStatistics
320
+ if (filteredStats.length === 0) return []
321
+
322
+ // 카테고리별로 통계 데이터 그룹화
323
+ const categoryStats = new Map<string, any[]>()
324
+
325
+ filteredStats.forEach(stat => {
326
+ if (stat.kpi?.category?.name) {
327
+ const categoryName = stat.kpi.category.name
328
+ if (!categoryStats.has(categoryName)) {
329
+ categoryStats.set(categoryName, [])
330
+ }
331
+ categoryStats.get(categoryName)!.push(stat)
332
+ }
333
+ })
334
+
335
+ // 각 카테고리별로 평균, 중앙값, 표준편차 계산
336
+ const result: any[] = []
337
+ const categories = Array.from(categoryStats.keys())
338
+
339
+ categories.forEach(category => {
340
+ const stats = categoryStats.get(category)!
341
+ const means = stats.map(s => s.mean || 0).filter(v => v > 0)
342
+ const medians = stats.map(s => s.median || 0).filter(v => v > 0)
343
+ const stdDevs = stats.map(s => s.standardDeviation || 0).filter(v => v > 0)
344
+
345
+ if (means.length > 0) {
346
+ result.push({ group: '평균', category, value: means.reduce((a, b) => a + b, 0) / means.length })
347
+ }
348
+ if (medians.length > 0) {
349
+ result.push({ group: '중앙값', category, value: medians.reduce((a, b) => a + b, 0) / medians.length })
350
+ }
351
+ if (stdDevs.length > 0) {
352
+ result.push({ group: '표준편차', category, value: stdDevs.reduce((a, b) => a + b, 0) / stdDevs.length })
353
+ }
354
+ })
355
+
356
+ return result
357
+ }
358
+
359
+ private get realKpiRadarCategories(): string[] {
360
+ const filteredStats = this.filteredKpiStatistics
361
+ if (filteredStats.length === 0) return []
362
+
363
+ const categories = new Set<string>()
364
+ filteredStats.forEach(stat => {
365
+ if (stat.kpi?.category?.name) {
366
+ categories.add(stat.kpi.category.name)
367
+ }
368
+ })
369
+
370
+ return Array.from(categories)
371
+ }
372
+
373
+ private get realKpiRadarGroups(): string[] {
374
+ return ['평균', '중앙값', '표준편차']
375
+ }
376
+
377
+ // 실제 KPI 통계 데이터를 기반으로 한 박스플롯 데이터
378
+ private get realKpiBoxplotData(): any[] {
379
+ const filteredStats = this.filteredKpiStatistics
380
+ if (filteredStats.length === 0) return []
381
+
382
+ // 카테고리별로 통계 데이터 그룹화
383
+ const categoryStats = new Map<string, any[]>()
384
+
385
+ filteredStats.forEach(stat => {
386
+ if (stat.kpi?.category?.name) {
387
+ const categoryName = stat.kpi.category.name
388
+ if (!categoryStats.has(categoryName)) {
389
+ categoryStats.set(categoryName, [])
390
+ }
391
+ categoryStats.get(categoryName)!.push(stat)
392
+ }
393
+ })
394
+
395
+ const result: any[] = []
396
+ const categories = Array.from(categoryStats.keys())
397
+
398
+ categories.forEach(category => {
399
+ const stats = categoryStats.get(category)!
400
+
401
+ // 각 KPI의 통계값들을 수집
402
+ const allMeans = stats.map(s => s.mean || 0).filter(v => v > 0)
403
+ const allMedians = stats.map(s => s.median || 0).filter(v => v > 0)
404
+ const allMins = stats.map(s => s.minimum || 0).filter(v => v > 0)
405
+ const allMaxs = stats.map(s => s.maximum || 0).filter(v => v > 0)
406
+ const allQ1s = stats.map(s => s.percentile25 || 0).filter(v => v > 0)
407
+ const allQ3s = stats.map(s => s.percentile75 || 0).filter(v => v > 0)
408
+
409
+ if (allMeans.length > 0) {
410
+ const sortedMeans = [...allMeans].sort((a, b) => a - b)
411
+ const min = sortedMeans[0]
412
+ const max = sortedMeans[sortedMeans.length - 1]
413
+ const mean = allMeans.reduce((a, b) => a + b, 0) / allMeans.length
414
+ const median = allMedians.length > 0 ? allMedians.reduce((a, b) => a + b, 0) / allMedians.length : mean
415
+
416
+ const q1 = sortedMeans[Math.floor(sortedMeans.length / 4)]
417
+ const q3 = sortedMeans[Math.floor((sortedMeans.length * 3) / 4)]
418
+
419
+ result.push({
420
+ group: category,
421
+ min,
422
+ q1,
423
+ median,
424
+ q3,
425
+ max,
426
+ mean,
427
+ value: mean // 현재 값으로 평균 사용
428
+ })
429
+ }
430
+ })
431
+
432
+ return result
433
+ }
434
+
435
+ private get realKpiBoxplotGroups(): string[] {
436
+ const filteredStats = this.filteredKpiStatistics
437
+ if (filteredStats.length === 0) return []
438
+
439
+ const categories = new Set<string>()
440
+ filteredStats.forEach(stat => {
441
+ if (stat.kpi?.category?.name) {
442
+ categories.add(stat.kpi.category.name)
443
+ }
444
+ })
445
+
446
+ return Array.from(categories)
447
+ }
448
+
449
+ private get realKpiCurrentGroup() {
450
+ return '평균'
451
+ }
452
+
234
453
  connectedCallback() {
235
454
  super.connectedCallback()
236
455
  this.fetchCategories()
456
+ this.fetchKpiStatistics()
237
457
  }
238
458
 
239
459
  pageUpdated(changes: any, lifecycle: any) {
240
460
  if (this.active) {
241
461
  this.fetchCategories()
462
+ this.fetchKpiStatistics()
242
463
  }
243
464
  }
244
465
 
@@ -296,6 +517,70 @@ export class KpiDashboardPage extends PageView {
296
517
  }
297
518
  }
298
519
 
520
+ async fetchKpiStatistics() {
521
+ this.statisticsLoading = true
522
+ try {
523
+ const response = await client.query({
524
+ query: gql`
525
+ query {
526
+ kpiStatistics {
527
+ items {
528
+ id
529
+ valueDate
530
+ periodType
531
+ count
532
+ sum
533
+ range
534
+ mean
535
+ median
536
+ minimum
537
+ maximum
538
+ standardDeviation
539
+ variance
540
+ percentile25
541
+ percentile75
542
+ iqr
543
+ lowerFence
544
+ upperFence
545
+ additionalStatistics
546
+ metadata
547
+ kpi {
548
+ id
549
+ name
550
+ unit
551
+ category {
552
+ id
553
+ name
554
+ }
555
+ }
556
+ }
557
+ }
558
+ }
559
+ `
560
+ })
561
+ this.kpiStatistics = response.data.kpiStatistics.items || []
562
+
563
+ // 현재 월을 기본값으로 설정
564
+ const currentMonth = this.currentMonth
565
+ const availableDates = this.availableValueDates
566
+
567
+ if (availableDates.includes(currentMonth)) {
568
+ // 현재 월 데이터가 있으면 현재 월로 설정
569
+ this.selectedValueDate = currentMonth
570
+ } else if (availableDates.length > 0) {
571
+ // 현재 월 데이터가 없으면 가장 최근 데이터로 설정
572
+ this.selectedValueDate = availableDates[0]
573
+ } else {
574
+ // 데이터가 없으면 현재 월로 설정
575
+ this.selectedValueDate = currentMonth
576
+ }
577
+ } catch (e) {
578
+ console.error('KPI 통계 데이터를 불러오지 못했습니다:', e)
579
+ } finally {
580
+ this.statisticsLoading = false
581
+ }
582
+ }
583
+
299
584
  async openHistoryModal(kpi) {
300
585
  // 전체 이력 fetch (limit 없이)
301
586
  try {
@@ -501,6 +786,137 @@ export class KpiDashboardPage extends PageView {
501
786
  </div>
502
787
  </div>
503
788
  </div>
789
+
790
+ <!-- 실제 KPI 통계 차트 섹션 -->
791
+ <div class="sample-charts-section">
792
+ <!-- 통계 필터 및 요약 -->
793
+ <div style="margin-bottom: 24px; background: #f8f9fa; padding: 16px; border-radius: 8px;">
794
+ <div style="display: flex; gap: 16px; align-items: center; flex-wrap: wrap;">
795
+ <div style="display: flex; align-items: center; gap: 8px;">
796
+ <label style="font-weight: 500; color: #333;">기간:</label>
797
+ <span style="padding: 6px 12px; background: #e9ecef; border-radius: 4px; color: #495057;">
798
+ MONTH (월별)
799
+ </span>
800
+ </div>
801
+ <div style="display: flex; align-items: center; gap: 8px;">
802
+ <label style="font-weight: 500; color: #333;">날짜:</label>
803
+ <select
804
+ .value=${this.selectedValueDate}
805
+ @change=${this._onValueDateChange}
806
+ style="padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; background: white;"
807
+ >
808
+ ${this.availableValueDates.map(
809
+ date => html`<option value="${date}">${date}${date === this.currentMonth ? ' (현재)' : ''}</option>`
810
+ )}
811
+ </select>
812
+ </div>
813
+ ${this.statisticsSummary
814
+ ? html`
815
+ <div style="display: flex; gap: 16px; margin-left: auto;">
816
+ <span style="color: #666; font-size: 0.9em;">
817
+ <b>KPI:</b> ${this.statisticsSummary.totalKpis}개
818
+ </span>
819
+ <span style="color: #666; font-size: 0.9em;">
820
+ <b>카테고리:</b> ${this.statisticsSummary.totalCategories}개
821
+ </span>
822
+ <span style="color: #666; font-size: 0.9em;">
823
+ <b>평균:</b> ${this.statisticsSummary.avgMean}
824
+ </span>
825
+ <span style="color: #666; font-size: 0.9em;">
826
+ <b>중앙값:</b> ${this.statisticsSummary.avgMedian}
827
+ </span>
828
+ <span style="color: #666; font-size: 0.9em;">
829
+ <b>표준편차:</b> ${this.statisticsSummary.avgStdDev}
830
+ </span>
831
+ </div>
832
+ `
833
+ : nothing}
834
+ </div>
835
+ </div>
836
+
837
+ <div class="sample-chart-card">
838
+ <div class="sample-chart-title">실제 KPI 통계 비교 (Radar)</div>
839
+ <div class="sample-chart-container">
840
+ ${this.statisticsLoading
841
+ ? html`<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
842
+ 통계 데이터 로딩 중...
843
+ </div>`
844
+ : this.realKpiRadarData.length > 0
845
+ ? html`<kpi-radar-chart
846
+ .data=${this.realKpiRadarData}
847
+ .categories=${this.realKpiRadarCategories}
848
+ .currentGroup=${'평균'}
849
+ ></kpi-radar-chart>`
850
+ : html`<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
851
+ 선택된 기간에 통계 데이터가 없습니다.
852
+ </div>`}
853
+ </div>
854
+ <div style="margin-top:12px;color:#666;font-size:0.98em;">
855
+ ※ <b>실제 KPI 통계 Radar 차트</b>는 실제 KPIStatistic 데이터를 기반으로 각 카테고리별 평균, 중앙값,
856
+ 표준편차를 비교합니다.<br />
857
+ <b>평균</b>은 산술평균, <b>중앙값</b>은 50분위수, <b>표준편차</b>는 데이터 분산 정도를 나타냅니다.
858
+ </div>
859
+ </div>
860
+ <div class="sample-chart-card">
861
+ <div class="sample-chart-title">실제 KPI 통계 분포 (Boxplot)</div>
862
+ <div class="sample-chart-container">
863
+ ${this.statisticsLoading
864
+ ? html`<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
865
+ 통계 데이터 로딩 중...
866
+ </div>`
867
+ : this.realKpiBoxplotData.length > 0
868
+ ? html`<kpi-boxplot-chart
869
+ .data=${this.realKpiBoxplotData}
870
+ .groups=${this.realKpiBoxplotGroups}
871
+ .currentGroup=${'평균'}
872
+ ></kpi-boxplot-chart>`
873
+ : html`<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
874
+ 선택된 기간에 통계 데이터가 없습니다.
875
+ </div>`}
876
+ </div>
877
+ <div style="margin-top:12px;color:#666;font-size:0.98em;">
878
+ ※ <b>실제 KPI 통계 Boxplot</b>은 실제 KPIStatistic 데이터를 기반으로 각 카테고리별 통계값의 분포를
879
+ 보여줍니다.<br />
880
+ 각 카테고리의 <b>평균값들</b>을 기준으로 분포를 계산하여, 카테고리 간 통계적 특성을 비교할 수 있습니다.
881
+ </div>
882
+ </div>
883
+ </div>
884
+
885
+ <!-- 3레벨 KPI 플로팅 섹션 -->
886
+ <div class="sample-charts-section">
887
+ <div style="margin-bottom: 24px;">
888
+ <h2 style="font-size: 1.5rem; font-weight: bold; color: #333; text-align: center; margin-bottom: 8px;">
889
+ 3레벨 KPI 분석
890
+ </h2>
891
+ <p style="text-align: center; color: #666; font-size: 0.95rem;">
892
+ 실제 KPIStatistic 데이터를 기반으로 한 다층적 분석
893
+ </p>
894
+ </div>
895
+
896
+ <!-- 1레벨: 그룹 총 스코어 -->
897
+ <div style="margin-bottom: 32px;">
898
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
899
+ 📊 1레벨: 그룹 총 스코어
900
+ </h3>
901
+ <kpi-level1-card></kpi-level1-card>
902
+ </div>
903
+
904
+ <!-- 2레벨: 카테고리 비교 -->
905
+ <div style="margin-bottom: 32px;">
906
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
907
+ 📈 2레벨: 카테고리 비교 분석
908
+ </h3>
909
+ <kpi-level2-comparison></kpi-level2-comparison>
910
+ </div>
911
+
912
+ <!-- 3레벨: 개별 KPI 비교 -->
913
+ <div style="margin-bottom: 32px;">
914
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
915
+ 🔍 3레벨: 개별 KPI 상세 분석
916
+ </h3>
917
+ <kpi-level3-comparison></kpi-level3-comparison>
918
+ </div>
919
+ </div>
504
920
  ${this.showHistoryModal
505
921
  ? html`
506
922
  <div