@things-factory/kpi 1.0.0-alpha.5 → 9.0.9

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 (309) hide show
  1. package/README.md +74 -0
  2. package/client/pages/kpi/kpi-list-page.ts +97 -47
  3. package/client/pages/kpi/kpi-overview-sample.ts +260 -0
  4. package/client/pages/kpi/kpi-overview.ts +271 -0
  5. package/client/pages/kpi-category/kpi-category-list-page.ts +39 -50
  6. package/client/pages/kpi-dashboard/kpi-alert-panel.ts +114 -0
  7. package/client/pages/kpi-dashboard/kpi-dashboard.ts +285 -0
  8. package/client/pages/kpi-dashboard/kpi-grade-visualization.ts +75 -0
  9. package/client/pages/kpi-dashboard/kpi-history-viewer.ts +58 -0
  10. package/client/pages/kpi-dashboard/kpi-list-summary.ts +100 -0
  11. package/client/pages/kpi-dashboard/kpi-performance-summary.ts +124 -0
  12. package/client/pages/kpi-dashboard/kpi-value-entry.ts +78 -0
  13. package/client/pages/kpi-grade/kpi-grade-list-page.ts +58 -68
  14. package/client/pages/kpi-history/kpi-history-list-page.ts +146 -0
  15. package/client/pages/kpi-metric/kpi-metric-list-page.ts +67 -63
  16. package/client/pages/kpi-value/kpi-value-list-page.ts +73 -72
  17. package/client/pages/kpi-value/kpi-value-manual-entry-form.ts +168 -0
  18. package/client/pages/kpi-value/kpi-value-manual-entry-page.ts +173 -0
  19. package/client/route.ts +35 -0
  20. package/dist-client/pages/kpi/kpi-importer.js +16 -17
  21. package/dist-client/pages/kpi/kpi-importer.js.map +1 -1
  22. package/dist-client/pages/kpi/kpi-list-page.js +128 -68
  23. package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
  24. package/dist-client/pages/kpi/kpi-overview-sample.d.ts +36 -0
  25. package/dist-client/pages/kpi/kpi-overview-sample.js +264 -0
  26. package/dist-client/pages/kpi/kpi-overview-sample.js.map +1 -0
  27. package/dist-client/pages/kpi/kpi-overview.d.ts +14 -0
  28. package/dist-client/pages/kpi/kpi-overview.js +290 -0
  29. package/dist-client/pages/kpi/kpi-overview.js.map +1 -0
  30. package/dist-client/pages/kpi-category/kpi-category-importer.js +16 -17
  31. package/dist-client/pages/kpi-category/kpi-category-importer.js.map +1 -1
  32. package/dist-client/pages/kpi-category/kpi-category-list-page.js +70 -71
  33. package/dist-client/pages/kpi-category/kpi-category-list-page.js.map +1 -1
  34. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
  35. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +128 -0
  36. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
  37. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +24 -0
  38. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +305 -0
  39. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
  40. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
  41. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
  42. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
  43. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
  44. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
  45. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
  46. package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
  47. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
  48. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
  49. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
  50. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +139 -0
  51. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
  52. package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
  53. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
  54. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
  55. package/dist-client/pages/kpi-grade/kpi-grade-importer.js +16 -17
  56. package/dist-client/pages/kpi-grade/kpi-grade-importer.js.map +1 -1
  57. package/dist-client/pages/kpi-grade/kpi-grade-list-page.js +89 -89
  58. package/dist-client/pages/kpi-grade/kpi-grade-list-page.js.map +1 -1
  59. package/dist-client/pages/kpi-history/kpi-history-list-page.d.ts +16 -0
  60. package/dist-client/pages/kpi-history/kpi-history-list-page.js +155 -0
  61. package/dist-client/pages/kpi-history/kpi-history-list-page.js.map +1 -0
  62. package/dist-client/pages/kpi-metric/kpi-metric-importer.js +16 -17
  63. package/dist-client/pages/kpi-metric/kpi-metric-importer.js.map +1 -1
  64. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +98 -84
  65. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
  66. package/dist-client/pages/kpi-value/kpi-value-importer.js +16 -17
  67. package/dist-client/pages/kpi-value/kpi-value-importer.js.map +1 -1
  68. package/dist-client/pages/kpi-value/kpi-value-list-page.js +104 -93
  69. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
  70. package/dist-client/pages/kpi-value/kpi-value-manual-entry-form.d.ts +14 -0
  71. package/dist-client/pages/kpi-value/kpi-value-manual-entry-form.js +201 -0
  72. package/dist-client/pages/kpi-value/kpi-value-manual-entry-form.js.map +1 -0
  73. package/dist-client/pages/kpi-value/kpi-value-manual-entry-page.d.ts +41 -0
  74. package/dist-client/pages/kpi-value/kpi-value-manual-entry-page.js +180 -0
  75. package/dist-client/pages/kpi-value/kpi-value-manual-entry-page.js.map +1 -0
  76. package/dist-client/route.d.ts +1 -1
  77. package/dist-client/route.js +27 -0
  78. package/dist-client/route.js.map +1 -1
  79. package/dist-client/tsconfig.tsbuildinfo +1 -1
  80. package/dist-server/index.d.ts +2 -1
  81. package/dist-server/index.js +2 -1
  82. package/dist-server/index.js.map +1 -1
  83. package/dist-server/migrations/1752188906708-SeedKpiCategory.d.ts +5 -0
  84. package/dist-server/migrations/1752188906708-SeedKpiCategory.js +56 -0
  85. package/dist-server/migrations/1752188906708-SeedKpiCategory.js.map +1 -0
  86. package/dist-server/migrations/1752190849681-SeedKpi.d.ts +5 -0
  87. package/dist-server/migrations/1752190849681-SeedKpi.js +107 -0
  88. package/dist-server/migrations/1752190849681-SeedKpi.js.map +1 -0
  89. package/dist-server/migrations/1752191090459-SeedKpiGrade.d.ts +5 -0
  90. package/dist-server/migrations/1752191090459-SeedKpiGrade.js +271 -0
  91. package/dist-server/migrations/1752191090459-SeedKpiGrade.js.map +1 -0
  92. package/dist-server/migrations/index.d.ts +1 -0
  93. package/dist-server/migrations/index.js +12 -0
  94. package/dist-server/migrations/index.js.map +1 -0
  95. package/dist-server/routes.d.ts +1 -0
  96. package/dist-server/routes.js +48 -0
  97. package/dist-server/routes.js.map +1 -1
  98. package/dist-server/service/index.d.ts +3 -4
  99. package/dist-server/service/index.js +4 -6
  100. package/dist-server/service/index.js.map +1 -1
  101. package/dist-server/service/kpi/aggregate-kpi.d.ts +8 -0
  102. package/dist-server/service/kpi/aggregate-kpi.js +88 -0
  103. package/dist-server/service/kpi/aggregate-kpi.js.map +1 -0
  104. package/dist-server/service/kpi/event-subscriber.d.ts +2 -0
  105. package/dist-server/service/kpi/event-subscriber.js +10 -0
  106. package/dist-server/service/kpi/event-subscriber.js.map +1 -1
  107. package/dist-server/service/kpi/index.d.ts +2 -1
  108. package/dist-server/service/kpi/kpi-formula.service.d.ts +24 -0
  109. package/dist-server/service/kpi/kpi-formula.service.js +64 -0
  110. package/dist-server/service/kpi/kpi-formula.service.js.map +1 -0
  111. package/dist-server/service/kpi/kpi-history.d.ts +6 -2
  112. package/dist-server/service/kpi/kpi-history.js +29 -11
  113. package/dist-server/service/kpi/kpi-history.js.map +1 -1
  114. package/dist-server/service/kpi/kpi-mutation.d.ts +2 -0
  115. package/dist-server/service/kpi/kpi-mutation.js +215 -10
  116. package/dist-server/service/kpi/kpi-mutation.js.map +1 -1
  117. package/dist-server/service/kpi/kpi-query.d.ts +10 -0
  118. package/dist-server/service/kpi/kpi-query.js +87 -3
  119. package/dist-server/service/kpi/kpi-query.js.map +1 -1
  120. package/dist-server/service/kpi/kpi-type.d.ts +14 -3
  121. package/dist-server/service/kpi/kpi-type.js +81 -18
  122. package/dist-server/service/kpi/kpi-type.js.map +1 -1
  123. package/dist-server/service/kpi/kpi.d.ts +14 -3
  124. package/dist-server/service/kpi/kpi.js +96 -19
  125. package/dist-server/service/kpi/kpi.js.map +1 -1
  126. package/dist-server/service/kpi-alert/index.d.ts +2 -0
  127. package/dist-server/service/kpi-alert/index.js +6 -0
  128. package/dist-server/service/kpi-alert/index.js.map +1 -0
  129. package/dist-server/service/kpi-alert/kpi-alert-query.d.ts +4 -0
  130. package/dist-server/service/kpi-alert/kpi-alert-query.js +71 -0
  131. package/dist-server/service/kpi-alert/kpi-alert-query.js.map +1 -0
  132. package/dist-server/service/kpi-alert/kpi-alert-type.d.ts +8 -0
  133. package/dist-server/service/kpi-alert/kpi-alert-type.js +33 -0
  134. package/dist-server/service/kpi-alert/kpi-alert-type.js.map +1 -0
  135. package/dist-server/service/kpi-category/kpi-category-mutation.d.ts +1 -1
  136. package/dist-server/service/kpi-category/kpi-category-mutation.js +36 -10
  137. package/dist-server/service/kpi-category/kpi-category-mutation.js.map +1 -1
  138. package/dist-server/service/kpi-category/kpi-category-query.d.ts +2 -0
  139. package/dist-server/service/kpi-category/kpi-category-query.js +18 -2
  140. package/dist-server/service/kpi-category/kpi-category-query.js.map +1 -1
  141. package/dist-server/service/kpi-category/kpi-category-type.d.ts +3 -5
  142. package/dist-server/service/kpi-category/kpi-category-type.js +16 -20
  143. package/dist-server/service/kpi-category/kpi-category-type.js.map +1 -1
  144. package/dist-server/service/kpi-category/kpi-category.d.ts +7 -9
  145. package/dist-server/service/kpi-category/kpi-category.js +40 -38
  146. package/dist-server/service/kpi-category/kpi-category.js.map +1 -1
  147. package/dist-server/service/kpi-grade/index.d.ts +1 -2
  148. package/dist-server/service/kpi-grade/index.js +2 -4
  149. package/dist-server/service/kpi-grade/index.js.map +1 -1
  150. package/dist-server/service/kpi-grade/kpi-grade-mutation.d.ts +1 -1
  151. package/dist-server/service/kpi-grade/kpi-grade-mutation.js +33 -10
  152. package/dist-server/service/kpi-grade/kpi-grade-mutation.js.map +1 -1
  153. package/dist-server/service/kpi-grade/kpi-grade-query.js +2 -2
  154. package/dist-server/service/kpi-grade/kpi-grade-query.js.map +1 -1
  155. package/dist-server/service/kpi-grade/kpi-grade-type.d.ts +13 -4
  156. package/dist-server/service/kpi-grade/kpi-grade-type.js +54 -18
  157. package/dist-server/service/kpi-grade/kpi-grade-type.js.map +1 -1
  158. package/dist-server/service/kpi-grade/kpi-grade.d.ts +8 -8
  159. package/dist-server/service/kpi-grade/kpi-grade.js +48 -40
  160. package/dist-server/service/kpi-grade/kpi-grade.js.map +1 -1
  161. package/dist-server/service/kpi-metric/aggregate-kpi-metric.d.ts +8 -0
  162. package/dist-server/service/kpi-metric/aggregate-kpi-metric.js +134 -0
  163. package/dist-server/service/kpi-metric/aggregate-kpi-metric.js.map +1 -0
  164. package/dist-server/service/kpi-metric/index.d.ts +1 -2
  165. package/dist-server/service/kpi-metric/index.js +2 -4
  166. package/dist-server/service/kpi-metric/index.js.map +1 -1
  167. package/dist-server/service/kpi-metric/kpi-metric-mutation.d.ts +1 -1
  168. package/dist-server/service/kpi-metric/kpi-metric-mutation.js +139 -13
  169. package/dist-server/service/kpi-metric/kpi-metric-mutation.js.map +1 -1
  170. package/dist-server/service/kpi-metric/kpi-metric-query.js +3 -3
  171. package/dist-server/service/kpi-metric/kpi-metric-query.js.map +1 -1
  172. package/dist-server/service/kpi-metric/kpi-metric-type.d.ts +17 -4
  173. package/dist-server/service/kpi-metric/kpi-metric-type.js +69 -11
  174. package/dist-server/service/kpi-metric/kpi-metric-type.js.map +1 -1
  175. package/dist-server/service/kpi-metric/kpi-metric.d.ts +11 -8
  176. package/dist-server/service/kpi-metric/kpi-metric.js +62 -37
  177. package/dist-server/service/kpi-metric/kpi-metric.js.map +1 -1
  178. package/dist-server/service/kpi-value/kpi-value-mutation.js +66 -11
  179. package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
  180. package/dist-server/service/kpi-value/kpi-value-query.d.ts +2 -0
  181. package/dist-server/service/kpi-value/kpi-value-query.js +15 -2
  182. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
  183. package/dist-server/service/kpi-value/kpi-value-type.d.ts +19 -10
  184. package/dist-server/service/kpi-value/kpi-value-type.js +87 -24
  185. package/dist-server/service/kpi-value/kpi-value-type.js.map +1 -1
  186. package/dist-server/service/kpi-value/kpi-value.d.ts +15 -9
  187. package/dist-server/service/kpi-value/kpi-value.js +86 -32
  188. package/dist-server/service/kpi-value/kpi-value.js.map +1 -1
  189. package/dist-server/tsconfig.tsbuildinfo +1 -1
  190. package/implement-plan/01-entity-type-resolver.md +57 -0
  191. package/implement-plan/02-dataset-integration.md +35 -0
  192. package/implement-plan/03-kpivalue-automation.md +34 -0
  193. package/implement-plan/04-version-history.md +33 -0
  194. package/implement-plan/05-grade-visualization.md +33 -0
  195. package/implement-plan/06-graphql-frontend.md +30 -0
  196. package/implement-plan/07-test-docs.md +35 -0
  197. package/implement-plan/TODO.md +41 -0
  198. package/package.json +18 -6
  199. package/server/index.ts +2 -1
  200. package/server/migrations/1752188906708-SeedKpiCategory.ts +61 -0
  201. package/server/migrations/1752190849681-SeedKpi.ts +112 -0
  202. package/server/migrations/1752191090459-SeedKpiGrade.ts +270 -0
  203. package/server/migrations/index.ts +9 -0
  204. package/server/routes.ts +55 -0
  205. package/server/service/index.ts +4 -6
  206. package/server/service/kpi/aggregate-kpi.ts +82 -0
  207. package/server/service/kpi/event-subscriber.ts +12 -0
  208. package/server/service/kpi/kpi-formula.service.ts +63 -0
  209. package/server/service/kpi/kpi-history.ts +29 -21
  210. package/server/service/kpi/kpi-mutation.ts +194 -12
  211. package/server/service/kpi/kpi-query.ts +57 -2
  212. package/server/service/kpi/kpi-type.ts +72 -19
  213. package/server/service/kpi/kpi.ts +96 -20
  214. package/server/service/kpi-alert/index.ts +3 -0
  215. package/server/service/kpi-alert/kpi-alert-query.ts +59 -0
  216. package/server/service/kpi-alert/kpi-alert-type.ts +20 -0
  217. package/server/service/kpi-category/kpi-category-mutation.ts +17 -4
  218. package/server/service/kpi-category/kpi-category-query.ts +20 -3
  219. package/server/service/kpi-category/kpi-category-type.ts +17 -19
  220. package/server/service/kpi-category/kpi-category.ts +39 -37
  221. package/server/service/kpi-grade/index.ts +2 -4
  222. package/server/service/kpi-grade/kpi-grade-mutation.ts +13 -4
  223. package/server/service/kpi-grade/kpi-grade-query.ts +5 -2
  224. package/server/service/kpi-grade/kpi-grade-type.ts +46 -19
  225. package/server/service/kpi-grade/kpi-grade.ts +44 -38
  226. package/server/service/kpi-metric/aggregate-kpi-metric.ts +128 -0
  227. package/server/service/kpi-metric/index.ts +2 -4
  228. package/server/service/kpi-metric/kpi-metric-mutation.ts +123 -7
  229. package/server/service/kpi-metric/kpi-metric-query.ts +9 -3
  230. package/server/service/kpi-metric/kpi-metric-type.ts +56 -13
  231. package/server/service/kpi-metric/kpi-metric.ts +55 -35
  232. package/server/service/kpi-value/kpi-value-mutation.ts +52 -14
  233. package/server/service/kpi-value/kpi-value-query.ts +12 -2
  234. package/server/service/kpi-value/kpi-value-type.ts +80 -25
  235. package/server/service/kpi-value/kpi-value.ts +81 -29
  236. package/things-factory.config.js +12 -4
  237. package/translations/en.json +12 -1
  238. package/translations/ja.json +12 -1
  239. package/translations/ko.json +12 -1
  240. package/translations/ms.json +12 -1
  241. package/translations/zh.json +12 -1
  242. package/client/pages/kpe-metric/kpe-metric-importer.ts +0 -90
  243. package/client/pages/kpe-metric/kpe-metric-list-page.ts +0 -398
  244. package/client/pages/kpi-formula/kpi-formula-importer.ts +0 -90
  245. package/client/pages/kpi-formula/kpi-formula-list-page.ts +0 -398
  246. package/client/pages/metric/metric-importer.ts +0 -90
  247. package/client/pages/metric/metric-list-page.ts +0 -398
  248. package/dist-client/pages/kpe-metric/kpe-metric-importer.d.ts +0 -23
  249. package/dist-client/pages/kpe-metric/kpe-metric-importer.js +0 -93
  250. package/dist-client/pages/kpe-metric/kpe-metric-importer.js.map +0 -1
  251. package/dist-client/pages/kpe-metric/kpe-metric-list-page.d.ts +0 -66
  252. package/dist-client/pages/kpe-metric/kpe-metric-list-page.js +0 -370
  253. package/dist-client/pages/kpe-metric/kpe-metric-list-page.js.map +0 -1
  254. package/dist-client/pages/kpi-formula/kpi-formula-importer.d.ts +0 -23
  255. package/dist-client/pages/kpi-formula/kpi-formula-importer.js +0 -93
  256. package/dist-client/pages/kpi-formula/kpi-formula-importer.js.map +0 -1
  257. package/dist-client/pages/kpi-formula/kpi-formula-list-page.d.ts +0 -66
  258. package/dist-client/pages/kpi-formula/kpi-formula-list-page.js +0 -370
  259. package/dist-client/pages/kpi-formula/kpi-formula-list-page.js.map +0 -1
  260. package/dist-client/pages/metric/metric-importer.d.ts +0 -23
  261. package/dist-client/pages/metric/metric-importer.js +0 -93
  262. package/dist-client/pages/metric/metric-importer.js.map +0 -1
  263. package/dist-client/pages/metric/metric-list-page.d.ts +0 -66
  264. package/dist-client/pages/metric/metric-list-page.js +0 -370
  265. package/dist-client/pages/metric/metric-list-page.js.map +0 -1
  266. package/dist-server/service/kpi-formula/event-subscriber.d.ts +0 -7
  267. package/dist-server/service/kpi-formula/event-subscriber.js +0 -21
  268. package/dist-server/service/kpi-formula/event-subscriber.js.map +0 -1
  269. package/dist-server/service/kpi-formula/index.d.ts +0 -7
  270. package/dist-server/service/kpi-formula/index.js +0 -12
  271. package/dist-server/service/kpi-formula/index.js.map +0 -1
  272. package/dist-server/service/kpi-formula/kpi-formula-history.d.ts +0 -25
  273. package/dist-server/service/kpi-formula/kpi-formula-history.js +0 -128
  274. package/dist-server/service/kpi-formula/kpi-formula-history.js.map +0 -1
  275. package/dist-server/service/kpi-formula/kpi-formula-mutation.d.ts +0 -10
  276. package/dist-server/service/kpi-formula/kpi-formula-mutation.js +0 -128
  277. package/dist-server/service/kpi-formula/kpi-formula-mutation.js.map +0 -1
  278. package/dist-server/service/kpi-formula/kpi-formula-query.d.ts +0 -11
  279. package/dist-server/service/kpi-formula/kpi-formula-query.js +0 -79
  280. package/dist-server/service/kpi-formula/kpi-formula-query.js.map +0 -1
  281. package/dist-server/service/kpi-formula/kpi-formula-type.d.ts +0 -20
  282. package/dist-server/service/kpi-formula/kpi-formula-type.js +0 -77
  283. package/dist-server/service/kpi-formula/kpi-formula-type.js.map +0 -1
  284. package/dist-server/service/kpi-formula/kpi-formula.d.ts +0 -24
  285. package/dist-server/service/kpi-formula/kpi-formula.js +0 -109
  286. package/dist-server/service/kpi-formula/kpi-formula.js.map +0 -1
  287. package/dist-server/service/kpi-grade/event-subscriber.d.ts +0 -7
  288. package/dist-server/service/kpi-grade/event-subscriber.js +0 -21
  289. package/dist-server/service/kpi-grade/event-subscriber.js.map +0 -1
  290. package/dist-server/service/kpi-grade/kpi-grade-history.d.ts +0 -25
  291. package/dist-server/service/kpi-grade/kpi-grade-history.js +0 -128
  292. package/dist-server/service/kpi-grade/kpi-grade-history.js.map +0 -1
  293. package/dist-server/service/kpi-metric/event-subscriber.d.ts +0 -7
  294. package/dist-server/service/kpi-metric/event-subscriber.js +0 -21
  295. package/dist-server/service/kpi-metric/event-subscriber.js.map +0 -1
  296. package/dist-server/service/kpi-metric/kpi-metric-history.d.ts +0 -25
  297. package/dist-server/service/kpi-metric/kpi-metric-history.js +0 -128
  298. package/dist-server/service/kpi-metric/kpi-metric-history.js.map +0 -1
  299. package/server/service/kpi-formula/event-subscriber.ts +0 -17
  300. package/server/service/kpi-formula/index.ts +0 -9
  301. package/server/service/kpi-formula/kpi-formula-history.ts +0 -116
  302. package/server/service/kpi-formula/kpi-formula-mutation.ts +0 -137
  303. package/server/service/kpi-formula/kpi-formula-query.ts +0 -48
  304. package/server/service/kpi-formula/kpi-formula-type.ts +0 -55
  305. package/server/service/kpi-formula/kpi-formula.ts +0 -95
  306. package/server/service/kpi-grade/event-subscriber.ts +0 -17
  307. package/server/service/kpi-grade/kpi-grade-history.ts +0 -116
  308. package/server/service/kpi-metric/event-subscriber.ts +0 -17
  309. package/server/service/kpi-metric/kpi-metric-history.ts +0 -116
@@ -0,0 +1,128 @@
1
+ import { getRepository } from '@things-factory/shell'
2
+ import { DataSummary } from '@things-factory/dataset'
3
+ import { finalizeLatestDataCollection } from '@things-factory/dataset/dist-server/controllers/finalize-data-collection'
4
+ import { KpiMetric } from './kpi-metric'
5
+ import { KpiValue, KpiValueInputType } from '../kpi-value/kpi-value'
6
+ import { Kpi } from '../kpi/kpi'
7
+ import { KpiFormulaService } from '../kpi/kpi-formula.service'
8
+
9
+ /**
10
+ * KPI-Metric 집계/자동화 서비스 함수 (formula 지원, KPI Value 저장)
11
+ * @param metricId KPI-Metric ID
12
+ * @param domainId 도메인 ID
13
+ * @param context ResolverContext (state에 domain, user, tx 등 포함)
14
+ * @returns 저장된 KPI Value 배열
15
+ */
16
+ export async function aggregateKpiMetricValue(metricId: string, domainId: string, context: ResolverContext) {
17
+ const tx = context.state?.tx || getRepository(KpiMetric).manager
18
+
19
+ // 1. metric 정보 조회
20
+ const metric = await getRepository(KpiMetric).findOne({
21
+ where: { id: metricId, domain: { id: domainId } }
22
+ })
23
+ if (!metric) throw new Error('Metric 정보 없음')
24
+ if (!metric.active) throw new Error('비활성화된 Metric')
25
+
26
+ // 2. formula가 있으면 산식 계산, 없으면 dataset 집계
27
+ let values: any[] = []
28
+ if (metric.formula) {
29
+ // formula 파싱 및 metric code별 값 계산
30
+ const formulaService = new KpiFormulaService()
31
+ const metricCodes = formulaService.extractMetricCodes(metric.formula)
32
+ // code별 값 집계 (동일 기간/그룹 기준)
33
+ const codeValueMap: Record<string, any[]> = {}
34
+ for (const code of metricCodes) {
35
+ const subMetric = await getRepository(KpiMetric).findOne({ where: { name: code, domain: { id: domainId } } })
36
+ if (!subMetric) throw new Error(`Formula metric '${code}' not found`)
37
+ codeValueMap[code] = await aggregateKpiMetricValue(subMetric.id, domainId, context)
38
+ }
39
+ // group/date/period별로 값 매핑
40
+ // (여기서는 가장 최근 기간 기준, group key 조합으로 매핑)
41
+ const groupKey = v =>
42
+ [v.date, v.period, v.group?.key01, v.group?.key02, v.group?.key03, v.group?.key04, v.group?.key05].join('|')
43
+ const groupMap: Record<string, any> = {}
44
+ for (const code of metricCodes) {
45
+ for (const v of codeValueMap[code]) {
46
+ const key = groupKey(v)
47
+ groupMap[key] = groupMap[key] || { ...v, _values: {} }
48
+ groupMap[key]._values[code] = v.value
49
+ }
50
+ }
51
+ // formula 계산 (js eval, 보안상 제한적 사용)
52
+ for (const key in groupMap) {
53
+ const ctx = groupMap[key]._values
54
+ let value = null
55
+ try {
56
+ value = Function(...Object.keys(ctx), `return (${metric.formula})`)(...Object.values(ctx))
57
+ } catch (e) {
58
+ value = null
59
+ }
60
+ values.push({ ...groupMap[key], value })
61
+ }
62
+ } else {
63
+ // 3. dataset 집계/마감 실행 (최신 기간 기준)
64
+ if (!metric.dataSetId || !metric.fieldName) throw new Error('Metric 매핑 정보 누락')
65
+ await finalizeLatestDataCollection(metric.dataSetId, context)
66
+ // 4. 집계 결과(summary)에서 metric.fieldName 값 추출
67
+ const summaries = await getRepository(DataSummary).find({
68
+ where: { dataSet: { id: metric.dataSetId }, domain: { id: domainId } },
69
+ order: { date: 'DESC', period: 'DESC' }
70
+ })
71
+ values = summaries
72
+ .map(summary => {
73
+ if (summary.summary && metric.fieldName in summary.summary) {
74
+ return {
75
+ date: summary.date,
76
+ period: summary.period,
77
+ value: summary.summary[metric.fieldName],
78
+ group: {
79
+ key01: summary.key01,
80
+ key02: summary.key02,
81
+ key03: summary.key03,
82
+ key04: summary.key04,
83
+ key05: summary.key05
84
+ }
85
+ }
86
+ }
87
+ return null
88
+ })
89
+ .filter(Boolean)
90
+ }
91
+
92
+ // 5. KPI Value로 저장 (metric 단위, KPI 단위 formula는 후속)
93
+ // metric이 속한 KPI 정보 조회 (여기서는 metric.name == kpi.name인 KPI를 임시로 매핑, 실제 연동 구조에 맞게 보완 필요)
94
+ const kpi = await getRepository(Kpi).findOne({ where: { name: metric.name, domain: { id: domainId } } })
95
+ if (!kpi) throw new Error('KPI 정보 없음 (metric.name과 동일한 KPI name 기준, 실제 연동 구조에 맞게 보완 필요)')
96
+
97
+ // KPI Value version, valueDate, groupId, groupType 등 매핑
98
+ const savedValues = []
99
+ for (const v of values) {
100
+ const valueDate = v.date
101
+ const groupId = v.group?.key01
102
+ const groupType = v.group?.key02
103
+ const version = kpi.version || 1
104
+ const value = v.value
105
+ if (value == null || isNaN(value)) continue
106
+ // upsert(동일 KPI, valueDate, groupId, groupType, version) 기준으로 저장
107
+ const repo = getRepository(KpiValue, context.state?.tx)
108
+ const existing = await repo.findOne({
109
+ where: { kpi: { id: kpi.id }, valueDate, groupId, groupType, version, domain: { id: domainId } }
110
+ })
111
+ let entity = existing || repo.create()
112
+ entity.kpi = kpi
113
+ entity.kpiId = kpi.id
114
+ entity.version = version
115
+ entity.valueDate = valueDate
116
+ entity.value = value
117
+ entity.groupId = groupId
118
+ entity.groupType = groupType
119
+ entity.inputType = KpiValueInputType.AUTO
120
+ entity.source = 'AUTO'
121
+ entity.domain = kpi.domain
122
+ entity.creator = context.state?.user
123
+ entity.updater = context.state?.user
124
+ entity = await repo.save(entity)
125
+ savedValues.push(entity)
126
+ }
127
+ return savedValues
128
+ }
@@ -1,9 +1,7 @@
1
1
  import { KpiMetric } from './kpi-metric'
2
- import { KpiMetricHistory } from './kpi-metric-history'
3
- import { KpiMetricHistoryEntitySubscriber } from './event-subscriber'
4
2
  import { KpiMetricQuery } from './kpi-metric-query'
5
3
  import { KpiMetricMutation } from './kpi-metric-mutation'
6
4
 
7
- export const entities = [KpiMetric, KpiMetricHistory]
5
+ export const entities = [KpiMetric]
8
6
  export const resolvers = [KpiMetricQuery, KpiMetricMutation]
9
- export const subscribers = [KpiMetricHistoryEntitySubscriber]
7
+ export const subscribers = []
@@ -1,31 +1,88 @@
1
1
  import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'
2
2
  import { In } from 'typeorm'
3
3
  import { getRepository } from '@things-factory/shell'
4
+ import { DataSet } from '@things-factory/dataset'
4
5
 
5
6
  import { createAttachment, deleteAttachmentsByRef } from '@things-factory/attachment-base'
6
7
  import { KpiMetric } from './kpi-metric'
7
8
  import { NewKpiMetric, KpiMetricPatch } from './kpi-metric-type'
9
+ import { KpiFormulaService } from '../kpi/kpi-formula.service'
10
+ import { registerSchedule, unregisterSchedule, CallbackBase, Application } from '@things-factory/scheduler-client'
8
11
 
9
12
  @Resolver(KpiMetric)
10
13
  export class KpiMetricMutation {
11
14
  @Directive('@transaction')
12
- @Mutation(returns => KpiMetric, { description: 'To create new KpiMetric' })
13
- async createKpiMetric(@Arg('kpiMetric') kpiMetric: NewKpiMetric, @Ctx() context: ResolverContext): Promise<KpiMetric> {
15
+ @Mutation(returns => KpiMetric, { description: 'Create a new KPI metric with the provided details.' })
16
+ async createKpiMetric(
17
+ @Arg('metric', { description: 'Input object containing details for the new KPI metric.' }) metric: NewKpiMetric,
18
+ @Ctx() context: ResolverContext
19
+ ): Promise<KpiMetric> {
14
20
  const { domain, user, tx } = context.state
15
21
 
22
+ let dataSet = metric.dataSetId
23
+ ? await getRepository(DataSet).findOne({ where: { id: metric.dataSetId } })
24
+ : undefined
25
+
26
+ if (metric.formula) {
27
+ const formulaService = new KpiFormulaService()
28
+ const result = await formulaService.validateFormula(metric.formula)
29
+ if (!result.valid) {
30
+ throw new Error(result.errors.join('\n'))
31
+ }
32
+ }
33
+
16
34
  const result = await getRepository(KpiMetric, tx).save({
17
- ...kpiMetric,
35
+ ...metric,
36
+ dataSet,
18
37
  domain,
19
38
  creator: user,
20
39
  updater: user
21
40
  })
22
41
 
23
- if (kpiMetric.thumbnail) {
42
+ // 스케줄러 등록
43
+ if (metric.schedule) {
44
+ const handle = await registerSchedule({
45
+ name: metric.name,
46
+ client: {
47
+ application: Application,
48
+ group: `${domain.id}`,
49
+ type: 'kpi-metric',
50
+ key: result.id,
51
+ operation: 'aggregate'
52
+ },
53
+ type: 'cron',
54
+ schedule: metric.schedule,
55
+ timezone: metric.timezone,
56
+ task: {
57
+ type: 'rest',
58
+ connection: {
59
+ host: `${CallbackBase}/callback-schedule-for-kpi-metric`,
60
+ headers: {
61
+ 'Content-Type': 'application/json',
62
+ accept: '*/*'
63
+ }
64
+ },
65
+ data: {
66
+ domainId: domain.id,
67
+ metricId: result.id
68
+ },
69
+ history_check: true,
70
+ failed_policy: 'retry_dlq',
71
+ max_retry_count: 3,
72
+ retry_period: 60
73
+ }
74
+ })
75
+ result.scheduleId = handle
76
+ result.timezone = metric.timezone
77
+ await getRepository(KpiMetric, tx).save(result)
78
+ }
79
+
80
+ if (metric.thumbnail) {
24
81
  await createAttachment(
25
82
  null,
26
83
  {
27
84
  attachment: {
28
- file: kpiMetric.thumbnail,
85
+ file: metric.thumbnail,
29
86
  refType: KpiMetric.name,
30
87
  refBy: result.id
31
88
  }
@@ -51,12 +108,67 @@ export class KpiMetricMutation {
51
108
  where: { domain: { id: domain.id }, id }
52
109
  })
53
110
 
111
+ let dataSet = patch.dataSetId
112
+ ? await getRepository(DataSet).findOne({ where: { id: patch.dataSetId } })
113
+ : kpiMetric.dataSet
114
+
115
+ if (patch.formula) {
116
+ const formulaService = new KpiFormulaService()
117
+ const result = await formulaService.validateFormula(patch.formula)
118
+ if (!result.valid) {
119
+ throw new Error(result.errors.join('\n'))
120
+ }
121
+ }
122
+
54
123
  const result = await repository.save({
55
124
  ...kpiMetric,
56
125
  ...patch,
126
+ dataSet,
57
127
  updater: user
58
128
  })
59
129
 
130
+ // 스케줄러 해제/등록 (변경 시)
131
+ if (kpiMetric.scheduleId && (patch.schedule !== kpiMetric.schedule || !patch.schedule)) {
132
+ await unregisterSchedule(kpiMetric.scheduleId)
133
+ result.scheduleId = null
134
+ }
135
+ if (patch.schedule) {
136
+ const handle = await registerSchedule({
137
+ name: patch.name || kpiMetric.name,
138
+ client: {
139
+ application: Application,
140
+ group: `${domain.id}`,
141
+ type: 'kpi-metric',
142
+ key: result.id,
143
+ operation: 'aggregate'
144
+ },
145
+ type: 'cron',
146
+ schedule: patch.schedule,
147
+ timezone: patch.timezone,
148
+ task: {
149
+ type: 'rest',
150
+ connection: {
151
+ host: `${CallbackBase}/callback-schedule-for-kpi-metric`,
152
+ headers: {
153
+ 'Content-Type': 'application/json',
154
+ accept: '*/*'
155
+ }
156
+ },
157
+ data: {
158
+ domainId: domain.id,
159
+ metricId: result.id
160
+ },
161
+ history_check: true,
162
+ failed_policy: 'retry_dlq',
163
+ max_retry_count: 3,
164
+ retry_period: 60
165
+ }
166
+ })
167
+ result.scheduleId = handle
168
+ result.timezone = patch.timezone
169
+ await repository.save(result)
170
+ }
171
+
60
172
  if (patch.thumbnail) {
61
173
  await deleteAttachmentsByRef(null, { refBys: [result.id] }, context)
62
174
  await createAttachment(
@@ -154,8 +266,12 @@ export class KpiMetricMutation {
154
266
  @Mutation(returns => Boolean, { description: 'To delete KpiMetric' })
155
267
  async deleteKpiMetric(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
156
268
  const { domain, tx } = context.state
157
-
158
- await getRepository(KpiMetric, tx).delete({ domain: { id: domain.id }, id })
269
+ const repo = getRepository(KpiMetric, tx)
270
+ const metric = await repo.findOne({ where: { domain: { id: domain.id }, id } })
271
+ if (metric?.scheduleId) {
272
+ await unregisterSchedule(metric.scheduleId)
273
+ }
274
+ await repo.delete({ domain: { id: domain.id }, id })
159
275
  await deleteAttachmentsByRef(null, { refBys: [id] }, context)
160
276
 
161
277
  return true
@@ -7,8 +7,11 @@ import { KpiMetricList } from './kpi-metric-type'
7
7
 
8
8
  @Resolver(KpiMetric)
9
9
  export class KpiMetricQuery {
10
- @Query(returns => KpiMetric!, { nullable: true, description: 'To fetch a KpiMetric' })
11
- async kpiMetric(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<KpiMetric> {
10
+ @Query(returns => KpiMetric!, { nullable: true, description: 'Fetch a single KPI metric by its unique identifier.' })
11
+ async kpiMetric(
12
+ @Arg('id', { description: 'Unique identifier of the KPI metric to fetch.' }) id: string,
13
+ @Ctx() context: ResolverContext
14
+ ): Promise<KpiMetric> {
12
15
  const { domain } = context.state
13
16
 
14
17
  return await getRepository(KpiMetric).findOne({
@@ -17,7 +20,10 @@ export class KpiMetricQuery {
17
20
  }
18
21
 
19
22
  @Query(returns => KpiMetricList, { description: 'To fetch multiple KpiMetrics' })
20
- async kpiMetrics(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<KpiMetricList> {
23
+ async kpiMetrics(
24
+ @Args(type => ListParam) params: ListParam,
25
+ @Ctx() context: ResolverContext
26
+ ): Promise<KpiMetricList> {
21
27
  const { domain } = context.state
22
28
 
23
29
  const queryBuilder = getQueryBuilderFromListParams({
@@ -4,27 +4,47 @@ import { ObjectType, Field, InputType, Int, ID, registerEnumType } from 'type-gr
4
4
 
5
5
  import { ObjectRef, ScalarObject } from '@things-factory/shell'
6
6
 
7
- import { KpiMetric, KpiMetricStatus } from './kpi-metric'
7
+ import { KpiMetric } from './kpi-metric'
8
8
 
9
- @InputType()
9
+ @InputType({ description: 'Input type for creating a new KPI metric. Used in mutations to provide metric details.' })
10
10
  export class NewKpiMetric {
11
- @Field()
11
+ @Field({ description: 'Metric code, unique within the domain, used in KPI formulas.' })
12
12
  name: string
13
13
 
14
- @Field({ nullable: true })
14
+ @Field({ nullable: true, description: 'User-friendly name or description of the metric.' })
15
15
  description?: string
16
16
 
17
- @Field(type => KpiMetricStatus, { nullable: true })
18
- state?: KpiMetricStatus
17
+ @Field({ nullable: true, description: 'Unit of measurement for this metric (e.g., %, count, hours).' })
18
+ unit?: string
19
19
 
20
- @Field({ nullable: true })
20
+ @Field({ nullable: true, description: 'Source of the metric data (e.g., system, method, dataset).' })
21
+ source?: string
22
+
23
+ @Field(type => ID, { nullable: true, description: 'ID of the source dataset for this metric.' })
24
+ dataSetId?: string
25
+
26
+ @Field({ nullable: true, description: 'Name of the field in the dataset this metric maps to.' })
27
+ fieldName?: string
28
+
29
+ @Field({ nullable: true, description: 'Indicates whether this metric is active and usable.' })
21
30
  active?: boolean
22
31
 
23
- @Field({ nullable: true })
24
- params?: string
32
+ @Field({ nullable: true, description: 'Calculation formula for the metric, using other metric codes and operators.' })
33
+ formula?: string
25
34
 
26
- @Field(type => GraphQLUpload, { nullable: true })
35
+ @Field({
36
+ nullable: true,
37
+ description: 'Cron schedule string for periodic KPI value aggregation (e.g., "0 0 * * *" for daily).'
38
+ })
39
+ schedule?: string
40
+
41
+ @Field(type => GraphQLUpload, { nullable: true, description: 'Thumbnail image or file for this metric.' })
27
42
  thumbnail?: FileUpload
43
+
44
+ @Field({ nullable: true, description: 'Timezone for the schedule.' })
45
+ timezone?: string
46
+ @Field({ nullable: true, description: 'Schedule ID for the registered cron job.' })
47
+ scheduleId?: string
28
48
  }
29
49
 
30
50
  @InputType()
@@ -38,17 +58,40 @@ export class KpiMetricPatch {
38
58
  @Field({ nullable: true })
39
59
  description?: string
40
60
 
41
- @Field(type => KpiMetricStatus, { nullable: true })
42
- state?: KpiMetricStatus
61
+ @Field({ nullable: true })
62
+ unit?: string
63
+
64
+ @Field({ nullable: true })
65
+ source?: string
66
+
67
+ @Field(type => ID, { nullable: true })
68
+ dataSetId?: string
69
+
70
+ @Field({ nullable: true })
71
+ fieldName?: string
43
72
 
44
73
  @Field({ nullable: true })
45
74
  active?: boolean
46
75
 
76
+ @Field({ nullable: true, description: 'Calculation formula for the metric, using other metric codes and operators.' })
77
+ formula?: string
78
+
79
+ @Field({
80
+ nullable: true,
81
+ description: 'Cron schedule string for periodic KPI value aggregation (e.g., "0 0 * * *" for daily).'
82
+ })
83
+ schedule?: string
84
+
47
85
  @Field(type => GraphQLUpload, { nullable: true })
48
86
  thumbnail?: FileUpload
49
-
87
+
50
88
  @Field({ nullable: true })
51
89
  cuFlag?: string
90
+
91
+ @Field({ nullable: true, description: 'Timezone for the schedule.' })
92
+ timezone?: string
93
+ @Field({ nullable: true, description: 'Schedule ID for the registered cron job.' })
94
+ scheduleId?: string
52
95
  }
53
96
 
54
97
  @ObjectType()
@@ -7,92 +7,112 @@ import {
7
7
  Column,
8
8
  RelationId,
9
9
  ManyToOne,
10
- VersionColumn,
11
10
  PrimaryGeneratedColumn
12
11
  } from 'typeorm'
13
12
  import { ObjectType, Field, Int, ID, registerEnumType } from 'type-graphql'
14
13
 
15
14
  import { Domain } from '@things-factory/shell'
16
15
  import { User } from '@things-factory/auth-base'
17
-
18
- export enum KpiMetricStatus {
19
- STATUS_A = 'STATUS_A',
20
- STATUS_B = 'STATUS_B'
21
- }
22
-
23
- registerEnumType(KpiMetricStatus, {
24
- name: 'KpiMetricStatus',
25
- description: 'state enumeration of a kpiMetric'
26
- })
16
+ import { DataSet } from '@things-factory/dataset'
27
17
 
28
18
  @Entity()
29
19
  @Index('ix_kpi_metric_0', (kpiMetric: KpiMetric) => [kpiMetric.domain, kpiMetric.name], {
30
20
  where: '"deleted_at" IS NULL',
31
21
  unique: true
32
22
  })
33
- @ObjectType({ description: 'Entity for KpiMetric' })
23
+ @ObjectType({ description: 'KPI metric entity. Represents a source data item used in KPI formulas.' })
34
24
  export class KpiMetric {
35
25
  @PrimaryGeneratedColumn('uuid')
36
- @Field(type => ID)
26
+ @Field(type => ID, { description: 'Unique identifier for this KPI metric.' })
37
27
  readonly id: string
38
-
39
- @VersionColumn()
40
- @Field({ nullable: true })
41
- version?: number = 1
42
28
 
43
29
  @ManyToOne(type => Domain)
44
- @Field({ nullable: true })
30
+ @Field({ nullable: true, description: 'Domain (tenant) to which this KPI metric belongs.' })
45
31
  domain?: Domain
46
32
 
47
- @RelationId((kpiMetric: KpiMetric) => kpiMetric.domain)
33
+ @RelationId((metric: KpiMetric) => metric.domain)
34
+ @Field({ nullable: true, description: 'ID of the domain (tenant) for this KPI metric.' })
48
35
  domainId?: string
49
36
 
50
37
  @Column()
51
- @Field({ nullable: true })
52
- name?: string
38
+ @Field({ description: 'Metric code, unique within the domain, used in KPI formulas.' })
39
+ name: string
53
40
 
54
41
  @Column({ nullable: true })
55
- @Field({ nullable: true })
42
+ @Field({ nullable: true, description: 'User-friendly name or description of the metric.' })
56
43
  description?: string
57
44
 
45
+ @Column({ nullable: true })
46
+ @Field({ nullable: true, description: 'Unit of measurement for this metric (e.g., %, count, hours).' })
47
+ unit?: string
48
+
49
+ @Column({ nullable: true })
50
+ @Field({ nullable: true, description: 'Source of the metric data (e.g., system, method, dataset).' })
51
+ source?: string
52
+
53
+ @ManyToOne(type => DataSet, { nullable: true })
54
+ @Field(type => DataSet, { nullable: true, description: 'Reference to the source dataset for this metric.' })
55
+ dataSet?: DataSet
56
+
57
+ @RelationId((metric: KpiMetric) => metric.dataSet)
58
+ @Field({ nullable: true, description: 'ID of the referenced source dataset.' })
59
+ dataSetId?: string
60
+
61
+ @Column({ nullable: true })
62
+ @Field({ nullable: true, description: 'Name of the field in the dataset this metric maps to.' })
63
+ fieldName?: string
64
+
58
65
  @Column({ nullable: false, default: false })
59
- @Field({ nullable: true })
66
+ @Field({ nullable: true, description: 'Indicates whether this metric is active and usable.' })
60
67
  active?: boolean
61
68
 
62
69
  @Column({ nullable: true })
63
- @Field({ nullable: true })
64
- state?: KpiMetricStatus
70
+ @Field({ nullable: true, description: 'Calculation formula for the metric, using other metric codes and operators.' })
71
+ formula?: string
72
+
73
+ @Column({ nullable: true })
74
+ @Field({
75
+ nullable: true,
76
+ description: 'Cron schedule string for periodic KPI value aggregation (e.g., "0 0 * * *" for daily).'
77
+ })
78
+ schedule?: string
79
+
80
+ @Column({ nullable: true })
81
+ @Field({ nullable: true, description: 'Schedule ID for the registered cron job.' })
82
+ scheduleId?: string
65
83
 
66
84
  @Column({ nullable: true })
67
- @Field({ nullable: true })
68
- params?: string
85
+ @Field({ nullable: true, description: 'Timezone for the schedule.' })
86
+ timezone?: string
69
87
 
70
88
  @CreateDateColumn()
71
- @Field({ nullable: true })
89
+ @Field({ nullable: true, description: 'Timestamp when this KPI metric was created.' })
72
90
  createdAt?: Date
73
91
 
74
92
  @UpdateDateColumn()
75
- @Field({ nullable: true })
93
+ @Field({ nullable: true, description: 'Timestamp when this KPI metric was last updated.' })
76
94
  updatedAt?: Date
77
95
 
78
96
  @DeleteDateColumn()
79
- @Field({ nullable: true })
97
+ @Field({ nullable: true, description: 'Timestamp when this KPI metric was deleted (soft delete).' })
80
98
  deletedAt?: Date
81
99
 
82
100
  @ManyToOne(type => User, { nullable: true })
83
- @Field(type => User, { nullable: true })
101
+ @Field(type => User, { nullable: true, description: 'User who created this KPI metric.' })
84
102
  creator?: User
85
103
 
86
- @RelationId((kpiMetric: KpiMetric) => kpiMetric.creator)
104
+ @RelationId((metric: KpiMetric) => metric.creator)
105
+ @Field({ nullable: true, description: 'ID of the user who created this KPI metric.' })
87
106
  creatorId?: string
88
107
 
89
108
  @ManyToOne(type => User, { nullable: true })
90
- @Field(type => User, { nullable: true })
109
+ @Field(type => User, { nullable: true, description: 'User who last updated this KPI metric.' })
91
110
  updater?: User
92
111
 
93
- @RelationId((kpiMetric: KpiMetric) => kpiMetric.updater)
112
+ @RelationId((metric: KpiMetric) => metric.updater)
113
+ @Field({ nullable: true, description: 'ID of the user who last updated this KPI metric.' })
94
114
  updaterId?: string
95
115
 
96
- @Field(type => String, { nullable: true })
116
+ @Field(type => String, { nullable: true, description: 'Thumbnail image or file path for this KPI metric.' })
97
117
  thumbnail?: string
98
118
  }