@things-factory/kpi 9.0.31 → 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 (365) hide show
  1. package/README.md +1 -2
  2. package/client/charts/kpi-boxplot-chart.ts +182 -42
  3. package/client/charts/kpi-radar-chart.ts +9 -9
  4. package/client/pages/kpi/kpi-list-page.ts +196 -32
  5. package/client/pages/kpi/kpi-overview.ts +9 -11
  6. package/client/pages/kpi/kpi-tree-page.ts +409 -0
  7. package/client/pages/kpi/kpi-view.ts +187 -0
  8. package/client/pages/kpi-dashboard/cards/kpi-level1-card.ts +1 -1
  9. package/client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts +1 -1
  10. package/client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts +1 -1
  11. package/client/pages/kpi-dashboard/components/kpi-left-panel.ts +198 -160
  12. package/client/pages/kpi-dashboard/components/kpi-map-panel.ts +133 -0
  13. package/client/pages/kpi-dashboard/components/kpi-region-popup.ts +3 -2
  14. package/client/pages/kpi-dashboard/kpi-dashboard-map.ts +291 -48
  15. package/client/pages/kpi-dashboard/kpi-dashboard.ts +28 -30
  16. package/client/pages/kpi-history/kpi-history-list-page.ts +11 -11
  17. package/client/pages/kpi-metric/kpi-metric-list-page.ts +10 -2
  18. package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +7 -7
  19. package/client/pages/kpi-metric-value/kpi-metric-value-importer.ts +2 -2
  20. package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +16 -8
  21. package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.ts +5 -5
  22. package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +1 -2
  23. package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +10 -2
  24. package/client/pages/kpi-value/kpi-value-editor-page.ts +11 -7
  25. package/client/pages/kpi-value/kpi-value-list-page.ts +31 -7
  26. package/client/route.ts +2 -9
  27. package/design-entities.md +8 -12
  28. package/dist-client/charts/kpi-boxplot-chart.d.ts +2 -0
  29. package/dist-client/charts/kpi-boxplot-chart.js +168 -42
  30. package/dist-client/charts/kpi-boxplot-chart.js.map +1 -1
  31. package/dist-client/charts/kpi-radar-chart.js +9 -9
  32. package/dist-client/charts/kpi-radar-chart.js.map +1 -1
  33. package/dist-client/pages/kpi/kpi-list-page.d.ts +19 -3
  34. package/dist-client/pages/kpi/kpi-list-page.js +188 -32
  35. package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
  36. package/dist-client/pages/kpi/kpi-overview.js +9 -11
  37. package/dist-client/pages/kpi/kpi-overview.js.map +1 -1
  38. package/dist-client/pages/kpi/kpi-tree-page.d.ts +59 -0
  39. package/dist-client/pages/kpi/kpi-tree-page.js +403 -0
  40. package/dist-client/pages/kpi/kpi-tree-page.js.map +1 -0
  41. package/dist-client/pages/kpi/kpi-view.d.ts +12 -0
  42. package/dist-client/pages/kpi/kpi-view.js +191 -0
  43. package/dist-client/pages/kpi/kpi-view.js.map +1 -0
  44. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +1 -1
  45. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -1
  46. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +1 -1
  47. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -1
  48. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +1 -1
  49. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -1
  50. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +3 -1
  51. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +197 -161
  52. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
  53. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +5 -0
  54. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +146 -0
  55. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -1
  56. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +3 -2
  57. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
  58. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +3 -1
  59. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +268 -46
  60. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
  61. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +28 -30
  62. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
  63. package/dist-client/pages/kpi-history/kpi-history-list-page.d.ts +6 -1
  64. package/dist-client/pages/kpi-history/kpi-history-list-page.js +11 -11
  65. package/dist-client/pages/kpi-history/kpi-history-list-page.js.map +1 -1
  66. package/dist-client/pages/kpi-metric/kpi-metric-list-page.d.ts +5 -0
  67. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +10 -2
  68. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
  69. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -1
  70. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +8 -8
  71. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
  72. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js +2 -2
  73. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js.map +1 -1
  74. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +5 -0
  75. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +16 -8
  76. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
  77. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.d.ts +1 -1
  78. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js +6 -6
  79. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -1
  80. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +1 -2
  81. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js.map +1 -1
  82. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.d.ts +5 -0
  83. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +10 -2
  84. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js.map +1 -1
  85. package/dist-client/pages/kpi-value/kpi-value-editor-page.d.ts +2 -1
  86. package/dist-client/pages/kpi-value/kpi-value-editor-page.js +16 -8
  87. package/dist-client/pages/kpi-value/kpi-value-editor-page.js.map +1 -1
  88. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +5 -0
  89. package/dist-client/pages/kpi-value/kpi-value-list-page.js +31 -7
  90. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
  91. package/dist-client/route.d.ts +1 -1
  92. package/dist-client/route.js +2 -8
  93. package/dist-client/route.js.map +1 -1
  94. package/dist-client/tsconfig.tsbuildinfo +1 -1
  95. package/dist-server/controllers/kpi-metric-value-provider.d.ts +1 -1
  96. package/dist-server/controllers/kpi-metric-value-provider.js +4 -4
  97. package/dist-server/controllers/kpi-metric-value-provider.js.map +1 -1
  98. package/dist-server/controllers/kpi-value-provider.d.ts +1 -1
  99. package/dist-server/controllers/kpi-value-provider.js +3 -3
  100. package/dist-server/controllers/kpi-value-provider.js.map +1 -1
  101. package/dist-server/migrations/1752190849680-seed-kpi-metrics.d.ts +6 -0
  102. package/dist-server/migrations/1752190849680-seed-kpi-metrics.js +101 -0
  103. package/dist-server/migrations/1752190849680-seed-kpi-metrics.js.map +1 -0
  104. package/dist-server/migrations/1752190849681-seed-kpi.d.ts +5 -0
  105. package/dist-server/migrations/1752190849681-seed-kpi.js +315 -0
  106. package/dist-server/migrations/1752190849681-seed-kpi.js.map +1 -0
  107. package/dist-server/migrations/1752192090123-add-grades-to-kpi.d.ts +7 -0
  108. package/dist-server/migrations/1752192090123-add-grades-to-kpi.js +51 -0
  109. package/dist-server/migrations/1752192090123-add-grades-to-kpi.js.map +1 -0
  110. package/dist-server/migrations/1752192090124-add-kpi-statistics.d.ts +5 -0
  111. package/dist-server/migrations/1752192090124-add-kpi-statistics.js +710 -0
  112. package/dist-server/migrations/1752192090124-add-kpi-statistics.js.map +1 -0
  113. package/dist-server/migrations/1752192090128-seed-kpi-org-scope.d.ts +6 -0
  114. package/dist-server/migrations/1752192090128-seed-kpi-org-scope.js +111 -0
  115. package/dist-server/migrations/1752192090128-seed-kpi-org-scope.js.map +1 -0
  116. package/dist-server/migrations/1752192090129-seed-kpi-values.d.ts +6 -0
  117. package/dist-server/migrations/1752192090129-seed-kpi-values.js +187 -0
  118. package/dist-server/migrations/1752192090129-seed-kpi-values.js.map +1 -0
  119. package/dist-server/migrations/grade-data/x11-performance-table.json +962 -0
  120. package/dist-server/migrations/grade-data/x12-performance-table.json +611 -0
  121. package/dist-server/migrations/grade-data/x14-performance-table.json +42 -0
  122. package/dist-server/migrations/grade-data/x21-performance-table.json +889 -0
  123. package/dist-server/migrations/grade-data/x22-performance-table.json +1064 -0
  124. package/dist-server/migrations/grade-data/x23-performance-table.json +42 -0
  125. package/dist-server/migrations/grade-data/x31-performance-table.json +644 -0
  126. package/dist-server/migrations/grade-data/x32-performance-table.json +993 -0
  127. package/dist-server/migrations/grade-data/x33-performance-table.json +195 -0
  128. package/dist-server/migrations/grade-data/x34-performance-table.json +12 -0
  129. package/dist-server/migrations/grade-data/x35-performance-table.json +42 -0
  130. package/dist-server/migrations/grade-data/x41-performance-table.json +825 -0
  131. package/dist-server/migrations/grade-data/x42-performance-table.json +786 -0
  132. package/dist-server/migrations/grade-data/x43-performance-table.json +12 -0
  133. package/dist-server/migrations/grade-data/x44-performance-table.json +42 -0
  134. package/dist-server/migrations/grade-data/x51-performance-table.json +924 -0
  135. package/dist-server/migrations/grade-data/x52-performance-table.json +42 -0
  136. package/dist-server/migrations/grade-data/x61-performance-table.json +261 -0
  137. package/dist-server/migrations/grade-data/x62-performance-table.json +42 -0
  138. package/dist-server/migrations/seed-data/kpi-metrics-seed.json +454 -0
  139. package/dist-server/migrations/seed-data/kpi-org-scope-seed.json +1676 -0
  140. package/dist-server/migrations/seed-data/kpi-scopes-seed.json +121 -0
  141. package/dist-server/migrations/seed-data/kpi-values-seed.json +402 -0
  142. package/dist-server/migrations/seed-data/kpis-seed.json +488 -0
  143. package/dist-server/migrations/seed-data/scope-definitions-seed.json +90 -0
  144. package/dist-server/service/index.d.ts +4 -7
  145. package/dist-server/service/index.js +10 -13
  146. package/dist-server/service/index.js.map +1 -1
  147. package/dist-server/service/kpi/aggregate-kpi.js +30 -13
  148. package/dist-server/service/kpi/aggregate-kpi.js.map +1 -1
  149. package/dist-server/service/kpi/kpi-formula.service.d.ts +15 -0
  150. package/dist-server/service/kpi/kpi-formula.service.js +90 -0
  151. package/dist-server/service/kpi/kpi-formula.service.js.map +1 -1
  152. package/dist-server/service/kpi/kpi-history.d.ts +0 -3
  153. package/dist-server/service/kpi/kpi-history.js +0 -10
  154. package/dist-server/service/kpi/kpi-history.js.map +1 -1
  155. package/dist-server/service/kpi/kpi-mutation.d.ts +1 -1
  156. package/dist-server/service/kpi/kpi-mutation.js +57 -20
  157. package/dist-server/service/kpi/kpi-mutation.js.map +1 -1
  158. package/dist-server/service/kpi/kpi-query.d.ts +7 -3
  159. package/dist-server/service/kpi/kpi-query.js +126 -10
  160. package/dist-server/service/kpi/kpi-query.js.map +1 -1
  161. package/dist-server/service/kpi/kpi-type.d.ts +4 -2
  162. package/dist-server/service/kpi/kpi-type.js +12 -4
  163. package/dist-server/service/kpi/kpi-type.js.map +1 -1
  164. package/dist-server/service/kpi/kpi.d.ts +4 -3
  165. package/dist-server/service/kpi/kpi.js +20 -8
  166. package/dist-server/service/kpi/kpi.js.map +1 -1
  167. package/dist-server/service/kpi-metric/aggregate-kpi-metric.js +46 -11
  168. package/dist-server/service/kpi-metric/aggregate-kpi-metric.js.map +1 -1
  169. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +1 -1
  170. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +6 -6
  171. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  172. package/dist-server/service/kpi-metric-value/kpi-metric-value-type.d.ts +2 -2
  173. package/dist-server/service/kpi-metric-value/kpi-metric-value-type.js +4 -4
  174. package/dist-server/service/kpi-metric-value/kpi-metric-value-type.js.map +1 -1
  175. package/dist-server/service/kpi-metric-value/kpi-metric-value.d.ts +1 -1
  176. package/dist-server/service/kpi-metric-value/kpi-metric-value.js +3 -3
  177. package/dist-server/service/kpi-metric-value/kpi-metric-value.js.map +1 -1
  178. package/dist-server/service/kpi-org-scope/index.d.ts +5 -0
  179. package/dist-server/service/kpi-org-scope/index.js +9 -0
  180. package/dist-server/service/kpi-org-scope/index.js.map +1 -0
  181. package/dist-server/service/kpi-org-scope/kpi-org-scope-mutation.d.ts +8 -0
  182. package/dist-server/service/kpi-org-scope/kpi-org-scope-mutation.js +170 -0
  183. package/dist-server/service/kpi-org-scope/kpi-org-scope-mutation.js.map +1 -0
  184. package/dist-server/service/kpi-org-scope/kpi-org-scope-query.d.ts +14 -0
  185. package/dist-server/service/kpi-org-scope/kpi-org-scope-query.js +152 -0
  186. package/dist-server/service/kpi-org-scope/kpi-org-scope-query.js.map +1 -0
  187. package/dist-server/service/kpi-org-scope/kpi-org-scope-type.d.ts +26 -0
  188. package/dist-server/service/kpi-org-scope/kpi-org-scope-type.js +101 -0
  189. package/dist-server/service/kpi-org-scope/kpi-org-scope-type.js.map +1 -0
  190. package/dist-server/service/kpi-org-scope/kpi-org-scope.d.ts +26 -0
  191. package/dist-server/service/kpi-org-scope/kpi-org-scope.js +135 -0
  192. package/dist-server/service/kpi-org-scope/kpi-org-scope.js.map +1 -0
  193. package/dist-server/service/kpi-scope/index.d.ts +9 -0
  194. package/dist-server/service/kpi-scope/index.js +14 -0
  195. package/dist-server/service/kpi-scope/index.js.map +1 -0
  196. package/dist-server/service/kpi-scope/kpi-scope-mutation.d.ts +9 -0
  197. package/dist-server/service/kpi-scope/kpi-scope-mutation.js +135 -0
  198. package/dist-server/service/kpi-scope/kpi-scope-mutation.js.map +1 -0
  199. package/dist-server/service/kpi-scope/kpi-scope-query.d.ts +11 -0
  200. package/dist-server/service/kpi-scope/kpi-scope-query.js +89 -0
  201. package/dist-server/service/kpi-scope/kpi-scope-query.js.map +1 -0
  202. package/dist-server/service/kpi-scope/kpi-scope-type.d.ts +35 -0
  203. package/dist-server/service/kpi-scope/kpi-scope-type.js +138 -0
  204. package/dist-server/service/kpi-scope/kpi-scope-type.js.map +1 -0
  205. package/dist-server/service/kpi-scope/kpi-scope.d.ts +38 -0
  206. package/dist-server/service/kpi-scope/kpi-scope.js +144 -0
  207. package/dist-server/service/kpi-scope/kpi-scope.js.map +1 -0
  208. package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.d.ts +43 -0
  209. package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.js +181 -0
  210. package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.js.map +1 -0
  211. package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.d.ts +50 -0
  212. package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.js +324 -0
  213. package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.js.map +1 -0
  214. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.d.ts +4 -0
  215. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js +76 -0
  216. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js.map +1 -1
  217. package/dist-server/service/kpi-statistic/kpi-statistic-query.d.ts +5 -1
  218. package/dist-server/service/kpi-statistic/kpi-statistic-query.js +92 -1
  219. package/dist-server/service/kpi-statistic/kpi-statistic-query.js.map +1 -1
  220. package/dist-server/service/kpi-statistic/kpi-statistic.d.ts +4 -0
  221. package/dist-server/service/kpi-statistic/kpi-statistic.js +33 -0
  222. package/dist-server/service/kpi-statistic/kpi-statistic.js.map +1 -1
  223. package/dist-server/service/kpi-value/kpi-value-mutation.js +71 -7
  224. package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
  225. package/dist-server/service/kpi-value/kpi-value-type.d.ts +4 -2
  226. package/dist-server/service/kpi-value/kpi-value-type.js +12 -4
  227. package/dist-server/service/kpi-value/kpi-value-type.js.map +1 -1
  228. package/dist-server/service/kpi-value/kpi-value.d.ts +3 -1
  229. package/dist-server/service/kpi-value/kpi-value.js +11 -5
  230. package/dist-server/service/kpi-value/kpi-value.js.map +1 -1
  231. package/dist-server/service/utils/value-date-util.d.ts +1 -0
  232. package/dist-server/service/utils/value-date-util.js +41 -0
  233. package/dist-server/service/utils/value-date-util.js.map +1 -1
  234. package/dist-server/tsconfig.json +10 -0
  235. package/dist-server/tsconfig.tsbuildinfo +1 -1
  236. package/package.json +7 -6
  237. package/server/@types/index.d.ts +11 -0
  238. package/server/controllers/kpi-metric-value-provider.ts +5 -5
  239. package/server/controllers/kpi-value-provider.ts +4 -4
  240. package/server/migrations/1752190849680-seed-kpi-metrics.ts +124 -0
  241. package/server/migrations/1752190849681-seed-kpi.ts +356 -0
  242. package/server/migrations/1752192090123-add-grades-to-kpi.ts +67 -0
  243. package/server/migrations/1752192090124-add-kpi-statistics.ts +719 -0
  244. package/server/migrations/1752192090128-seed-kpi-org-scope.ts +132 -0
  245. package/server/migrations/1752192090129-seed-kpi-values.ts +207 -0
  246. package/server/migrations/grade-data/x11-performance-table.json +962 -0
  247. package/server/migrations/grade-data/x12-performance-table.json +611 -0
  248. package/server/migrations/grade-data/x14-performance-table.json +42 -0
  249. package/server/migrations/grade-data/x21-performance-table.json +889 -0
  250. package/server/migrations/grade-data/x22-performance-table.json +1064 -0
  251. package/server/migrations/grade-data/x23-performance-table.json +42 -0
  252. package/server/migrations/grade-data/x31-performance-table.json +644 -0
  253. package/server/migrations/grade-data/x32-performance-table.json +993 -0
  254. package/server/migrations/grade-data/x33-performance-table.json +195 -0
  255. package/server/migrations/grade-data/x34-performance-table.json +12 -0
  256. package/server/migrations/grade-data/x35-performance-table.json +42 -0
  257. package/server/migrations/grade-data/x41-performance-table.json +825 -0
  258. package/server/migrations/grade-data/x42-performance-table.json +786 -0
  259. package/server/migrations/grade-data/x43-performance-table.json +12 -0
  260. package/server/migrations/grade-data/x44-performance-table.json +42 -0
  261. package/server/migrations/grade-data/x51-performance-table.json +924 -0
  262. package/server/migrations/grade-data/x52-performance-table.json +42 -0
  263. package/server/migrations/grade-data/x61-performance-table.json +261 -0
  264. package/server/migrations/grade-data/x62-performance-table.json +42 -0
  265. package/server/migrations/seed-data/kpi-metrics-seed.json +454 -0
  266. package/server/migrations/seed-data/kpi-org-scope-seed.json +1676 -0
  267. package/server/migrations/seed-data/kpi-scopes-seed.json +121 -0
  268. package/server/migrations/seed-data/kpi-values-seed.json +402 -0
  269. package/server/migrations/seed-data/kpis-seed.json +488 -0
  270. package/server/migrations/seed-data/scope-definitions-seed.json +90 -0
  271. package/server/service/index.ts +10 -13
  272. package/server/service/kpi/aggregate-kpi.ts +31 -13
  273. package/server/service/kpi/kpi-formula.service.ts +101 -0
  274. package/server/service/kpi/kpi-history.ts +0 -8
  275. package/server/service/kpi/kpi-mutation.ts +59 -19
  276. package/server/service/kpi/kpi-query.ts +119 -8
  277. package/server/service/kpi/kpi-type.ts +10 -4
  278. package/server/service/kpi/kpi.ts +17 -7
  279. package/server/service/kpi-metric/aggregate-kpi-metric.ts +55 -11
  280. package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +6 -6
  281. package/server/service/kpi-metric-value/kpi-metric-value-type.ts +4 -4
  282. package/server/service/kpi-metric-value/kpi-metric-value.ts +3 -3
  283. package/server/service/kpi-org-scope/index.ts +6 -0
  284. package/server/service/kpi-org-scope/kpi-org-scope-mutation.ts +173 -0
  285. package/server/service/kpi-org-scope/kpi-org-scope-query.ts +127 -0
  286. package/server/service/kpi-org-scope/kpi-org-scope-type.ts +68 -0
  287. package/server/service/kpi-org-scope/kpi-org-scope.ts +123 -0
  288. package/server/service/kpi-scope/index.ts +11 -0
  289. package/server/service/kpi-scope/kpi-scope-mutation.ts +129 -0
  290. package/server/service/kpi-scope/kpi-scope-query.ts +63 -0
  291. package/server/service/kpi-scope/kpi-scope-type.ts +96 -0
  292. package/server/service/kpi-scope/kpi-scope.ts +143 -0
  293. package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +231 -0
  294. package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +410 -0
  295. package/server/service/kpi-statistic/kpi-statistic-mutation.ts +97 -0
  296. package/server/service/kpi-statistic/kpi-statistic-query.ts +89 -2
  297. package/server/service/kpi-statistic/kpi-statistic.ts +32 -0
  298. package/server/service/kpi-value/kpi-value-mutation.ts +73 -7
  299. package/server/service/kpi-value/kpi-value-type.ts +10 -4
  300. package/server/service/kpi-value/kpi-value.ts +10 -5
  301. package/server/service/utils/value-date-util.ts +47 -0
  302. package/server/types/global.d.ts +8 -0
  303. package/things-factory.config.js +1 -0
  304. package/translations/en.json +15 -3
  305. package/translations/ja.json +13 -3
  306. package/translations/ko.json +15 -3
  307. package/translations/ms.json +13 -3
  308. package/translations/zh.json +13 -3
  309. package/client/pages/kpi-category/kpi-category-importer.ts +0 -90
  310. package/client/pages/kpi-category/kpi-category-list-page.ts +0 -537
  311. package/client/pages/kpi-category/kpi-category-value-calculator.ts +0 -233
  312. package/client/pages/kpi-category-value/kpi-category-value-list-page.ts +0 -404
  313. package/dist-client/pages/kpi-category/kpi-category-importer.d.ts +0 -23
  314. package/dist-client/pages/kpi-category/kpi-category-importer.js +0 -92
  315. package/dist-client/pages/kpi-category/kpi-category-importer.js.map +0 -1
  316. package/dist-client/pages/kpi-category/kpi-category-list-page.d.ts +0 -74
  317. package/dist-client/pages/kpi-category/kpi-category-list-page.js +0 -517
  318. package/dist-client/pages/kpi-category/kpi-category-list-page.js.map +0 -1
  319. package/dist-client/pages/kpi-category/kpi-category-value-calculator.d.ts +0 -13
  320. package/dist-client/pages/kpi-category/kpi-category-value-calculator.js +0 -256
  321. package/dist-client/pages/kpi-category/kpi-category-value-calculator.js.map +0 -1
  322. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.d.ts +0 -63
  323. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.js +0 -393
  324. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.js.map +0 -1
  325. package/dist-server/service/kpi-category/index.d.ts +0 -6
  326. package/dist-server/service/kpi-category/index.js +0 -10
  327. package/dist-server/service/kpi-category/index.js.map +0 -1
  328. package/dist-server/service/kpi-category/kpi-category-mutation.d.ts +0 -9
  329. package/dist-server/service/kpi-category/kpi-category-mutation.js +0 -221
  330. package/dist-server/service/kpi-category/kpi-category-mutation.js.map +0 -1
  331. package/dist-server/service/kpi-category/kpi-category-query.d.ts +0 -18
  332. package/dist-server/service/kpi-category/kpi-category-query.js +0 -115
  333. package/dist-server/service/kpi-category/kpi-category-query.js.map +0 -1
  334. package/dist-server/service/kpi-category/kpi-category-type.d.ts +0 -24
  335. package/dist-server/service/kpi-category/kpi-category-type.js +0 -100
  336. package/dist-server/service/kpi-category/kpi-category-type.js.map +0 -1
  337. package/dist-server/service/kpi-category/kpi-category.d.ts +0 -22
  338. package/dist-server/service/kpi-category/kpi-category.js +0 -106
  339. package/dist-server/service/kpi-category/kpi-category.js.map +0 -1
  340. package/dist-server/service/kpi-category-value/index.d.ts +0 -6
  341. package/dist-server/service/kpi-category-value/index.js +0 -10
  342. package/dist-server/service/kpi-category-value/index.js.map +0 -1
  343. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.d.ts +0 -8
  344. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.js +0 -102
  345. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.js.map +0 -1
  346. package/dist-server/service/kpi-category-value/kpi-category-value-query.d.ts +0 -13
  347. package/dist-server/service/kpi-category-value/kpi-category-value-query.js +0 -91
  348. package/dist-server/service/kpi-category-value/kpi-category-value-query.js.map +0 -1
  349. package/dist-server/service/kpi-category-value/kpi-category-value-type.d.ts +0 -19
  350. package/dist-server/service/kpi-category-value/kpi-category-value-type.js +0 -73
  351. package/dist-server/service/kpi-category-value/kpi-category-value-type.js.map +0 -1
  352. package/dist-server/service/kpi-category-value/kpi-category-value.d.ts +0 -19
  353. package/dist-server/service/kpi-category-value/kpi-category-value.js +0 -91
  354. package/dist-server/service/kpi-category-value/kpi-category-value.js.map +0 -1
  355. package/helps/kpi/kpi-category.md +0 -160
  356. package/server/service/kpi-category/index.ts +0 -7
  357. package/server/service/kpi-category/kpi-category-mutation.ts +0 -217
  358. package/server/service/kpi-category/kpi-category-query.ts +0 -87
  359. package/server/service/kpi-category/kpi-category-type.ts +0 -73
  360. package/server/service/kpi-category/kpi-category.ts +0 -95
  361. package/server/service/kpi-category-value/index.ts +0 -7
  362. package/server/service/kpi-category-value/kpi-category-value-mutation.ts +0 -88
  363. package/server/service/kpi-category-value/kpi-category-value-query.ts +0 -62
  364. package/server/service/kpi-category-value/kpi-category-value-type.ts +0 -48
  365. package/server/service/kpi-category-value/kpi-category-value.ts +0 -79
@@ -0,0 +1,231 @@
1
+ import { getRepository } from '@things-factory/shell'
2
+ import type ResolverContext from '@things-factory/auth-base'
3
+ import { KpiStatisticCalculationService } from './kpi-statistic-calculation.service'
4
+ import { KpiPeriodType } from '../kpi/kpi'
5
+ import { Domain } from '@things-factory/shell'
6
+
7
+ /**
8
+ * KPI 통계 배치 계산 서비스
9
+ * 정기적으로 실행하여 KpiValue 데이터를 집계해서 KpiStatistic을 생성/업데이트
10
+ */
11
+ export class KpiStatisticBatchService {
12
+
13
+ /**
14
+ * 일일 배치: 어제 날짜의 모든 통계 계산
15
+ */
16
+ static async runDailyBatch(domainId?: string): Promise<void> {
17
+ console.log('[KPI Statistics] Starting daily batch calculation...')
18
+
19
+ const yesterday = new Date()
20
+ yesterday.setDate(yesterday.getDate() - 1)
21
+ const valueDate = yesterday.toISOString().split('T')[0] // YYYY-MM-DD
22
+
23
+ await this.calculateForAllDomains(KpiPeriodType.DAY, valueDate, domainId)
24
+
25
+ console.log(`[KPI Statistics] Daily batch completed for ${valueDate}`)
26
+ }
27
+
28
+ /**
29
+ * 월간 배치: 지난 달의 모든 통계 계산
30
+ */
31
+ static async runMonthlyBatch(domainId?: string): Promise<void> {
32
+ console.log('[KPI Statistics] Starting monthly batch calculation...')
33
+
34
+ const lastMonth = new Date()
35
+ lastMonth.setMonth(lastMonth.getMonth() - 1)
36
+ const valueDate = lastMonth.toISOString().substring(0, 7) // YYYY-MM
37
+
38
+ await this.calculateForAllDomains(KpiPeriodType.MONTH, valueDate, domainId)
39
+
40
+ console.log(`[KPI Statistics] Monthly batch completed for ${valueDate}`)
41
+ }
42
+
43
+ /**
44
+ * 분기 배치: 지난 분기의 모든 통계 계산
45
+ */
46
+ static async runQuarterlyBatch(domainId?: string): Promise<void> {
47
+ console.log('[KPI Statistics] Starting quarterly batch calculation...')
48
+
49
+ const now = new Date()
50
+ const currentQuarter = Math.floor(now.getMonth() / 3) + 1
51
+ const lastQuarter = currentQuarter === 1 ? 4 : currentQuarter - 1
52
+ const year = currentQuarter === 1 ? now.getFullYear() - 1 : now.getFullYear()
53
+ const valueDate = `${year}-Q${lastQuarter}`
54
+
55
+ await this.calculateForAllDomains(KpiPeriodType.QUARTER, valueDate, domainId)
56
+
57
+ console.log(`[KPI Statistics] Quarterly batch completed for ${valueDate}`)
58
+ }
59
+
60
+ /**
61
+ * 연간 배치: 작년의 모든 통계 계산
62
+ */
63
+ static async runYearlyBatch(domainId?: string): Promise<void> {
64
+ console.log('[KPI Statistics] Starting yearly batch calculation...')
65
+
66
+ const lastYear = new Date().getFullYear() - 1
67
+ const valueDate = lastYear.toString()
68
+
69
+ await this.calculateForAllDomains(KpiPeriodType.YEAR, valueDate, domainId)
70
+
71
+ console.log(`[KPI Statistics] Yearly batch completed for ${valueDate}`)
72
+ }
73
+
74
+ /**
75
+ * 대시보드용 지역별 통계 계산
76
+ */
77
+ static async runDashboardBatch(
78
+ periodType: KpiPeriodType = KpiPeriodType.MONTH,
79
+ valueDate?: string,
80
+ domainId?: string
81
+ ): Promise<void> {
82
+ console.log('[KPI Statistics] Starting dashboard batch calculation...')
83
+
84
+ if (!valueDate) {
85
+ const now = new Date()
86
+ if (periodType === KpiPeriodType.MONTH) {
87
+ now.setMonth(now.getMonth() - 1)
88
+ valueDate = now.toISOString().substring(0, 7) // YYYY-MM
89
+ } else {
90
+ now.setDate(now.getDate() - 1)
91
+ valueDate = now.toISOString().split('T')[0] // YYYY-MM-DD
92
+ }
93
+ }
94
+
95
+ await this.calculateForAllDomains(periodType, valueDate, domainId)
96
+
97
+ console.log(`[KPI Statistics] Dashboard batch completed for ${valueDate} (${periodType})`)
98
+ }
99
+
100
+ /**
101
+ * 모든 도메인에 대해 통계 계산
102
+ */
103
+ private static async calculateForAllDomains(
104
+ periodType: KpiPeriodType,
105
+ valueDate: string,
106
+ domainId?: string
107
+ ): Promise<void> {
108
+ try {
109
+ const domains = domainId
110
+ ? await getRepository(Domain).find({ where: { id: domainId } })
111
+ : await getRepository(Domain).find()
112
+
113
+ for (const domain of domains) {
114
+ try {
115
+ await this.calculateForDomain(domain, periodType, valueDate)
116
+ } catch (error) {
117
+ console.error(`[KPI Statistics] Domain ${domain.name} calculation failed, continuing with next domain:`, error)
118
+ // 개별 도메인 실패가 전체 배치를 중단시키지 않도록 계속 진행
119
+ continue
120
+ }
121
+ }
122
+ } catch (error) {
123
+ console.error('[KPI Statistics] Batch calculation failed:', error)
124
+ throw error
125
+ }
126
+ }
127
+
128
+ /**
129
+ * 특정 도메인에 대해 통계 계산
130
+ */
131
+ private static async calculateForDomain(
132
+ domain: Domain,
133
+ periodType: KpiPeriodType,
134
+ valueDate: string
135
+ ): Promise<void> {
136
+ console.log(`[KPI Statistics] Calculating for domain: ${domain.name} (${domain.id})`)
137
+
138
+ // 가짜 ResolverContext 생성 (배치 작업용)
139
+ const context: ResolverContext = {
140
+ state: {
141
+ domain,
142
+ user: { id: 'system', name: 'System Batch' }, // 시스템 사용자
143
+ tx: null // 트랜잭션 없음 (각 계산마다 개별 트랜잭션)
144
+ }
145
+ } as any
146
+
147
+ try {
148
+ const statistics = await KpiStatisticCalculationService.calculateAllStatistics(
149
+ periodType,
150
+ valueDate,
151
+ context
152
+ )
153
+
154
+ console.log(`[KPI Statistics] Generated ${statistics.length} statistics for domain ${domain.name}`)
155
+
156
+ // 성공 로깅
157
+ this.logBatchResult(domain.id, periodType, valueDate, statistics.length, 'SUCCESS')
158
+
159
+ } catch (error) {
160
+ console.error(`[KPI Statistics] Failed to calculate for domain ${domain.name}:`, error)
161
+
162
+ // 실패 로깅
163
+ this.logBatchResult(domain.id, periodType, valueDate, 0, 'FAILED', error.message)
164
+
165
+ // 에러를 던져서 상위에서 처리
166
+ throw error
167
+ }
168
+ }
169
+
170
+ /**
171
+ * 배치 실행 결과 로깅
172
+ */
173
+ private static logBatchResult(
174
+ domainId: string,
175
+ periodType: KpiPeriodType,
176
+ valueDate: string,
177
+ statisticsCount: number,
178
+ status: 'SUCCESS' | 'FAILED',
179
+ errorMessage?: string
180
+ ): void {
181
+ const logData = {
182
+ timestamp: new Date().toISOString(),
183
+ domainId,
184
+ periodType,
185
+ valueDate,
186
+ statisticsCount,
187
+ status,
188
+ errorMessage
189
+ }
190
+
191
+ // 실제 운영에서는 로깅 시스템에 저장
192
+ console.log('[KPI Statistics Batch Log]', JSON.stringify(logData))
193
+ }
194
+
195
+ /**
196
+ * 배치 작업 스케줄링 설정 예시
197
+ */
198
+ static setupBatchSchedule(): void {
199
+ // 실제 구현시에는 cron 등의 스케줄러 사용
200
+ console.log('[KPI Statistics] Batch schedule setup example:')
201
+ console.log('Daily batch: Run at 02:00 AM every day')
202
+ console.log('Monthly batch: Run at 03:00 AM on 1st of every month')
203
+ console.log('Quarterly batch: Run at 04:00 AM on 1st of every quarter')
204
+ console.log('Yearly batch: Run at 05:00 AM on January 1st')
205
+
206
+ /*
207
+ // Node-cron 사용 예시:
208
+ import cron from 'node-cron'
209
+
210
+ // 매일 새벽 2시
211
+ cron.schedule('0 2 * * *', () => {
212
+ this.runDailyBatch()
213
+ })
214
+
215
+ // 매월 1일 새벽 3시
216
+ cron.schedule('0 3 1 * *', () => {
217
+ this.runMonthlyBatch()
218
+ })
219
+
220
+ // 매 분기 첫날 새벽 4시 (1, 4, 7, 10월)
221
+ cron.schedule('0 4 1 1,4,7,10 *', () => {
222
+ this.runQuarterlyBatch()
223
+ })
224
+
225
+ // 매년 1월 1일 새벽 5시
226
+ cron.schedule('0 5 1 1 *', () => {
227
+ this.runYearlyBatch()
228
+ })
229
+ */
230
+ }
231
+ }
@@ -0,0 +1,410 @@
1
+ import { getRepository } from '@things-factory/shell'
2
+ import type ResolverContext from '@things-factory/auth-base'
3
+ import { KpiStatistic } from './kpi-statistic'
4
+ import { KpiValue } from '../kpi-value/kpi-value'
5
+ import { Kpi, KpiPeriodType } from '../kpi/kpi'
6
+ import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
7
+ import { KpiScope } from '../kpi-scope/kpi-scope'
8
+
9
+ /**
10
+ * KPI 통계 계산 서비스
11
+ * KpiValue 데이터로부터 통계값을 계산하여 KpiStatistic에 저장
12
+ */
13
+ export class KpiStatisticCalculationService {
14
+ /**
15
+ * 전체 KPI에 대해 통계를 계산
16
+ */
17
+ static async calculateAllStatistics(
18
+ periodType: KpiPeriodType,
19
+ valueDate: string,
20
+ context: ResolverContext
21
+ ): Promise<KpiStatistic[]> {
22
+ const { domain } = context.state
23
+
24
+ // Leaf KPI들만 대상으로 함 (실제 값을 가진 KPI)
25
+ const leafKpis = await getRepository(Kpi).find({
26
+ where: { domain: { id: domain.id }, isLeaf: true }
27
+ })
28
+
29
+ const results: KpiStatistic[] = []
30
+
31
+ for (const kpi of leafKpis) {
32
+ // 1. 전체 통계 (scope 없음)
33
+ const overallStats = await this.calculateKpiStatistics(
34
+ kpi.id,
35
+ periodType,
36
+ valueDate,
37
+ null, // kpiOrgScope 없음 = 전체 통계
38
+ context
39
+ )
40
+ if (overallStats) results.push(overallStats)
41
+
42
+ // 2. 스코프별 통계
43
+ const scopedStats = await this.calculateScopedStatistics(kpi.id, periodType, valueDate, context)
44
+ results.push(...scopedStats)
45
+ }
46
+
47
+ return results
48
+ }
49
+
50
+ /**
51
+ * 특정 KPI의 통계를 계산
52
+ */
53
+ static async calculateKpiStatistics(
54
+ kpiId: string,
55
+ periodType: KpiPeriodType,
56
+ valueDate: string,
57
+ kpiOrgScope: KpiOrgScope | null,
58
+ context: ResolverContext
59
+ ): Promise<KpiStatistic | null> {
60
+ const { domain, user, tx } = context.state
61
+
62
+ // KPI 값들 조회
63
+ const whereCondition: any = {
64
+ kpi: { id: kpiId },
65
+ domain: { id: domain.id },
66
+ valueDate: this.buildDateFilter(valueDate, periodType)
67
+ }
68
+
69
+ if (kpiOrgScope) {
70
+ whereCondition.kpiOrgScope = { id: kpiOrgScope.id }
71
+ }
72
+
73
+ const kpiValues = await getRepository(KpiValue, tx).find({
74
+ where: whereCondition,
75
+ relations: ['kpi', 'kpiOrgScope']
76
+ })
77
+
78
+ if (kpiValues.length === 0) {
79
+ return null
80
+ }
81
+
82
+ // 통계 계산
83
+ const statistics = this.calculateStatistics(kpiValues.map(v => v.value).filter(v => v !== null))
84
+
85
+ // KpiStatistic 엔티티 생성/업데이트
86
+ const kpiStatisticRepo = getRepository(KpiStatistic, tx)
87
+
88
+ // 기존 통계 조회
89
+ const existingWhere: any = {
90
+ kpi: { id: kpiId },
91
+ domain: { id: domain.id },
92
+ periodType,
93
+ valueDate
94
+ }
95
+
96
+ if (kpiOrgScope) {
97
+ existingWhere.kpiOrgScope = { id: kpiOrgScope.id }
98
+ } else {
99
+ existingWhere.kpiOrgScope = null
100
+ }
101
+
102
+ let kpiStatistic = await kpiStatisticRepo.findOne({ where: existingWhere })
103
+
104
+ if (!kpiStatistic) {
105
+ kpiStatistic = kpiStatisticRepo.create()
106
+ kpiStatistic.kpi = kpiValues[0].kpi
107
+ kpiStatistic.kpiId = kpiId
108
+ kpiStatistic.domain = { id: domain.id } as any
109
+ kpiStatistic.periodType = periodType
110
+ kpiStatistic.valueDate = valueDate
111
+ kpiStatistic.creator = user
112
+ kpiStatistic.kpiOrgScope = kpiOrgScope
113
+ kpiStatistic.kpiOrgScopeId = kpiOrgScope?.id
114
+ }
115
+
116
+ // 통계값 적용
117
+ Object.assign(kpiStatistic, statistics)
118
+ kpiStatistic.updater = user
119
+ kpiStatistic.metadata = {
120
+ ...kpiStatistic.metadata,
121
+ calculationMethod: 'auto-calculated',
122
+ lastCalculated: new Date(),
123
+ dataCount: kpiValues.length,
124
+ calculatedFrom: 'kpi-values'
125
+ }
126
+
127
+ return await kpiStatisticRepo.save(kpiStatistic)
128
+ }
129
+
130
+ /**
131
+ * 스코프별 통계 계산 (KpiScope 기반)
132
+ */
133
+ static async calculateScopedStatistics(
134
+ kpiId: string,
135
+ periodType: KpiPeriodType,
136
+ valueDate: string,
137
+ context: ResolverContext
138
+ ): Promise<KpiStatistic[]> {
139
+ const { domain } = context.state
140
+
141
+ // 통계 계산에 사용할 스코프 정의 조회
142
+ const kpiScopes = await getRepository(KpiScope).find({
143
+ where: {
144
+ domain: { id: domain.id },
145
+ active: true,
146
+ includeInStatistics: true
147
+ },
148
+ order: { displayOrder: 'ASC' }
149
+ })
150
+
151
+ const results: KpiStatistic[] = []
152
+
153
+ for (const kpiScope of kpiScopes) {
154
+ const scopedStats = await this.calculateStatisticsByScope(kpiId, periodType, valueDate, kpiScope, context)
155
+ results.push(...scopedStats)
156
+ }
157
+
158
+ return results
159
+ }
160
+
161
+ /**
162
+ * 특정 스코프 차원별 통계 계산
163
+ */
164
+ static async calculateStatisticsByScope(
165
+ kpiId: string,
166
+ periodType: KpiPeriodType,
167
+ valueDate: string,
168
+ kpiScope: KpiScope,
169
+ context: ResolverContext
170
+ ): Promise<KpiStatistic[]> {
171
+ const { domain } = context.state
172
+ const scopeLevel = kpiScope.level
173
+ const fieldName = `scope${scopeLevel.toString().padStart(2, '0')}`
174
+
175
+ // 해당 스코프 레벨의 유니크한 값들 조회
176
+ const scopeValues = kpiScope.validValues || (await this.getDistinctScopeValues(fieldName, domain.id))
177
+
178
+ const results: KpiStatistic[] = []
179
+
180
+ for (const scopeValue of scopeValues) {
181
+ // 해당 스코프 값을 가진 KpiOrgScope들 조회
182
+ const kpiOrgScopes = await getRepository(KpiOrgScope).find({
183
+ where: {
184
+ domain: { id: domain.id },
185
+ [fieldName]: scopeValue
186
+ }
187
+ })
188
+
189
+ // 각 KpiOrgScope에 대해 통계 계산
190
+ for (const orgScope of kpiOrgScopes) {
191
+ const statistic = await this.calculateKpiStatistics(kpiId, periodType, valueDate, orgScope, context)
192
+ if (statistic) {
193
+ results.push(statistic)
194
+ }
195
+ }
196
+
197
+ // 스코프 값별 집계 통계도 생성 (선택적)
198
+ if (kpiOrgScopes.length > 1) {
199
+ const aggregatedStats = await this.calculateAggregatedScopeStatistics(
200
+ kpiId,
201
+ periodType,
202
+ valueDate,
203
+ kpiScope,
204
+ scopeValue,
205
+ kpiOrgScopes,
206
+ context
207
+ )
208
+ if (aggregatedStats) {
209
+ results.push(aggregatedStats)
210
+ }
211
+ }
212
+ }
213
+
214
+ return results
215
+ }
216
+
217
+ /**
218
+ * 스코프 값별 집계 통계 계산 (예: 서울 전체 평균)
219
+ */
220
+ private static async calculateAggregatedScopeStatistics(
221
+ kpiId: string,
222
+ periodType: KpiPeriodType,
223
+ valueDate: string,
224
+ kpiScope: KpiScope,
225
+ scopeValue: string,
226
+ kpiOrgScopes: KpiOrgScope[],
227
+ context: ResolverContext
228
+ ): Promise<KpiStatistic | null> {
229
+ const { domain, user, tx } = context.state
230
+
231
+ // 모든 KpiOrgScope에서 KpiValue들 수집
232
+ const allValues: number[] = []
233
+ for (const orgScope of kpiOrgScopes) {
234
+ const kpiValues = await getRepository(KpiValue, tx).find({
235
+ where: {
236
+ kpi: { id: kpiId },
237
+ domain: { id: domain.id },
238
+ kpiOrgScope: { id: orgScope.id },
239
+ valueDate: this.buildDateFilter(valueDate, periodType)
240
+ }
241
+ })
242
+ allValues.push(...kpiValues.map(v => v.value).filter(v => v !== null))
243
+ }
244
+
245
+ if (allValues.length === 0) return null
246
+
247
+ // 집계 통계 계산
248
+ const statistics = this.calculateStatistics(allValues)
249
+
250
+ // 가상의 KpiOrgScope 생성 (집계용)
251
+ const aggregatedOrgScope = {
252
+ id: `aggregated-${kpiScope.level}-${scopeValue}`,
253
+ entityType: 'AGGREGATED',
254
+ entityName: `${kpiScope.name}: ${scopeValue}`,
255
+ org: scopeValue,
256
+ [`scope${kpiScope.level.toString().padStart(2, '0')}`]: scopeValue
257
+ }
258
+
259
+ // KpiStatistic 생성
260
+ const kpiStatisticRepo = getRepository(KpiStatistic, tx)
261
+ const kpiStatistic = kpiStatisticRepo.create()
262
+
263
+ const kpi = await getRepository(Kpi).findOneBy({ id: kpiId })
264
+ kpiStatistic.kpi = kpi
265
+ kpiStatistic.kpiId = kpiId
266
+ kpiStatistic.domain = { id: domain.id } as any
267
+ kpiStatistic.periodType = periodType
268
+ kpiStatistic.valueDate = valueDate
269
+ kpiStatistic.creator = user
270
+ kpiStatistic.updater = user
271
+
272
+ // 통계값 적용
273
+ Object.assign(kpiStatistic, statistics)
274
+ kpiStatistic.metadata = {
275
+ ...kpiStatistic.metadata,
276
+ calculationMethod: 'scope-aggregated',
277
+ lastCalculated: new Date(),
278
+ dataCount: allValues.length,
279
+ kpiScope: {
280
+ id: kpiScope.id,
281
+ name: kpiScope.name,
282
+ level: kpiScope.level,
283
+ scopeType: kpiScope.scopeType
284
+ },
285
+ aggregatedScopeValue: scopeValue,
286
+ orgScopeCount: kpiOrgScopes.length
287
+ }
288
+
289
+ return await kpiStatisticRepo.save(kpiStatistic)
290
+ }
291
+
292
+ /**
293
+ * 특정 스코프 필드의 유니크한 값들 조회
294
+ */
295
+ private static async getDistinctScopeValues(fieldName: string, domainId: string): Promise<string[]> {
296
+ const query = `
297
+ SELECT DISTINCT ${fieldName} as value
298
+ FROM kpi_org_scope
299
+ WHERE domainId = ? AND ${fieldName} IS NOT NULL
300
+ ORDER BY ${fieldName}
301
+ `
302
+
303
+ const results = await getRepository(KpiOrgScope).query(query, [domainId])
304
+ return results.map((r: any) => r.value).filter(Boolean)
305
+ }
306
+
307
+ /**
308
+ * 통계값 계산 로직
309
+ */
310
+ private static calculateStatistics(values: number[]) {
311
+ if (values.length === 0) return {}
312
+
313
+ const sortedValues = [...values].sort((a, b) => a - b)
314
+ const count = values.length
315
+ const sum = values.reduce((acc, val) => acc + val, 0)
316
+ const mean = sum / count
317
+
318
+ // 중앙값
319
+ const median =
320
+ count % 2 === 0
321
+ ? (sortedValues[Math.floor(count / 2) - 1] + sortedValues[Math.floor(count / 2)]) / 2
322
+ : sortedValues[Math.floor(count / 2)]
323
+
324
+ // 분산 및 표준편차
325
+ const variance = values.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / count
326
+ const standardDeviation = Math.sqrt(variance)
327
+
328
+ // 최소/최대값 및 범위
329
+ const minimum = Math.min(...values)
330
+ const maximum = Math.max(...values)
331
+ const range = maximum - minimum
332
+
333
+ // 분위수 계산
334
+ const q1Index = Math.floor(count * 0.25)
335
+ const q3Index = Math.floor(count * 0.75)
336
+ const percentile25 = sortedValues[q1Index]
337
+ const percentile75 = sortedValues[q3Index]
338
+ const iqr = percentile75 - percentile25
339
+
340
+ // 이상치 감지를 위한 fence
341
+ const lowerFence = percentile25 - 1.5 * iqr
342
+ const upperFence = percentile75 + 1.5 * iqr
343
+
344
+ return {
345
+ count,
346
+ sum,
347
+ range,
348
+ mean,
349
+ median,
350
+ minimum,
351
+ maximum,
352
+ standardDeviation,
353
+ variance,
354
+ percentile25,
355
+ percentile75,
356
+ iqr,
357
+ lowerFence,
358
+ upperFence,
359
+ additionalStatistics: {
360
+ coefficientOfVariation: standardDeviation / mean,
361
+ skewness: this.calculateSkewness(values, mean, standardDeviation),
362
+ kurtosis: this.calculateKurtosis(values, mean, standardDeviation)
363
+ }
364
+ }
365
+ }
366
+
367
+ /**
368
+ * 왜도 계산
369
+ */
370
+ private static calculateSkewness(values: number[], mean: number, stdDev: number): number {
371
+ if (stdDev === 0) return 0
372
+ const n = values.length
373
+ const sum = values.reduce((acc, val) => acc + Math.pow((val - mean) / stdDev, 3), 0)
374
+ return (n / ((n - 1) * (n - 2))) * sum
375
+ }
376
+
377
+ /**
378
+ * 첨도 계산
379
+ */
380
+ private static calculateKurtosis(values: number[], mean: number, stdDev: number): number {
381
+ if (stdDev === 0) return 0
382
+ const n = values.length
383
+ const sum = values.reduce((acc, val) => acc + Math.pow((val - mean) / stdDev, 4), 0)
384
+ const kurtosis = ((n * (n + 1)) / ((n - 1) * (n - 2) * (n - 3))) * sum
385
+ const adjustment = (3 * Math.pow(n - 1, 2)) / ((n - 2) * (n - 3))
386
+ return kurtosis - adjustment
387
+ }
388
+
389
+ /**
390
+ * 기간 타입에 따른 날짜 필터 생성
391
+ */
392
+ private static buildDateFilter(valueDate: string, periodType: KpiPeriodType): string {
393
+ switch (periodType) {
394
+ case KpiPeriodType.DAY:
395
+ return valueDate // 정확한 날짜 매치
396
+ case KpiPeriodType.WEEK:
397
+ return `${valueDate.substring(0, 8)}%` // YYYY-MM-DD% -> 주 단위
398
+ case KpiPeriodType.MONTH:
399
+ return `${valueDate.substring(0, 7)}%` // YYYY-MM%
400
+ case KpiPeriodType.QUARTER:
401
+ const year = valueDate.substring(0, 4)
402
+ const quarter = valueDate.substring(5, 7) // Q1, Q2, Q3, Q4
403
+ return `${year}-${quarter}%`
404
+ case KpiPeriodType.YEAR:
405
+ return `${valueDate.substring(0, 4)}%` // YYYY%
406
+ default:
407
+ return valueDate
408
+ }
409
+ }
410
+ }