@dssp/dkpi 1.0.0-alpha.7 → 1.0.0-alpha.70

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 (232) hide show
  1. package/KPI-STATISTICS-SERVICE.md +233 -0
  2. package/_index.html +0 -5
  3. package/assets/favicon.ico +0 -0
  4. package/assets/images/project-image.png +0 -0
  5. package/assets/manifest/apple-1024.png +0 -0
  6. package/assets/manifest/apple-120.png +0 -0
  7. package/assets/manifest/apple-152.png +0 -0
  8. package/assets/manifest/apple-167.png +0 -0
  9. package/assets/manifest/apple-180.png +0 -0
  10. package/assets/manifest/apple-touch-icon.png +0 -0
  11. package/assets/manifest/badge-128x128.png +0 -0
  12. package/assets/manifest/chrome-splashscreen-icon-384x384.png +0 -0
  13. package/assets/manifest/chrome-touch-icon-192x192.png +0 -0
  14. package/assets/manifest/icon-128x128.png +0 -0
  15. package/assets/manifest/icon-192x192.png +0 -0
  16. package/assets/manifest/icon-512x512.png +0 -0
  17. package/assets/manifest/icon-72x72.png +0 -0
  18. package/assets/manifest/icon-96x96.png +0 -0
  19. package/assets/manifest/image-metaog.png +0 -0
  20. package/assets/manifest/maskable_icon.png +0 -0
  21. package/assets/manifest/ms-icon-144x144.png +0 -0
  22. package/assets/manifest/ms-touch-icon-144x144-precomposed.png +0 -0
  23. package/assets/videos/intro.mp4 +0 -0
  24. package/dist-client/bootstrap.js +64 -4
  25. package/dist-client/bootstrap.js.map +1 -1
  26. package/dist-client/components/kpi-2d-lookup-chart.d.ts +43 -0
  27. package/dist-client/components/kpi-2d-lookup-chart.js +253 -0
  28. package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -0
  29. package/dist-client/components/kpi-boxplot-chart.d.ts +24 -0
  30. package/dist-client/components/kpi-boxplot-chart.js +291 -0
  31. package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
  32. package/dist-client/components/kpi-lookup-chart.d.ts +53 -0
  33. package/dist-client/components/kpi-lookup-chart.js +430 -0
  34. package/dist-client/components/kpi-lookup-chart.js.map +1 -0
  35. package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
  36. package/dist-client/components/kpi-mini-trend-chart.js +148 -0
  37. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
  38. package/dist-client/components/kpi-radar-chart.d.ts +17 -0
  39. package/dist-client/components/kpi-radar-chart.js +259 -0
  40. package/dist-client/components/kpi-radar-chart.js.map +1 -0
  41. package/dist-client/components/kpi-single-boxplot-chart.d.ts +24 -0
  42. package/dist-client/components/kpi-single-boxplot-chart.js +391 -0
  43. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -0
  44. package/dist-client/components/kpi-trend-chart.d.ts +25 -0
  45. package/dist-client/components/kpi-trend-chart.js +220 -0
  46. package/dist-client/components/kpi-trend-chart.js.map +1 -0
  47. package/dist-client/components/sv-pagenation-control.d.ts +18 -0
  48. package/dist-client/components/sv-pagenation-control.js +142 -0
  49. package/dist-client/components/sv-pagenation-control.js.map +1 -0
  50. package/dist-client/google-map/common-google-map.d.ts +35 -0
  51. package/dist-client/google-map/common-google-map.js +345 -0
  52. package/dist-client/google-map/common-google-map.js.map +1 -0
  53. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  54. package/dist-client/google-map/google-map-loader.js +23 -0
  55. package/dist-client/google-map/google-map-loader.js.map +1 -0
  56. package/dist-client/icons/menu-icons.d.ts +6 -0
  57. package/dist-client/icons/menu-icons.js +42 -0
  58. package/dist-client/icons/menu-icons.js.map +1 -1
  59. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.d.ts +22 -0
  60. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js +57 -0
  61. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js.map +1 -0
  62. package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +46 -0
  63. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +378 -0
  64. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
  65. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.d.ts +20 -0
  66. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js +445 -0
  67. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js.map +1 -0
  68. package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +18 -0
  69. package/dist-client/pages/kpi-admin/kpi-system-guide.js +535 -0
  70. package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -0
  71. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +18 -0
  72. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +259 -0
  73. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  74. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +22 -0
  75. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +346 -0
  76. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  77. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +26 -0
  78. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +433 -0
  79. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  80. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  81. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
  82. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  83. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +38 -0
  84. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +851 -0
  85. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  86. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +42 -0
  87. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +316 -0
  88. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  89. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +28 -0
  90. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +497 -0
  91. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  92. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
  93. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
  94. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
  95. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +52 -0
  96. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +798 -0
  97. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  98. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +63 -0
  99. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1089 -0
  100. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
  101. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
  102. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
  103. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
  104. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
  105. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
  106. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
  107. package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
  108. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
  109. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
  110. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
  111. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
  112. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
  113. package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
  114. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
  115. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
  116. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +57 -0
  117. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +719 -0
  118. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -0
  119. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.d.ts +23 -0
  120. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js +76 -0
  121. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js.map +1 -0
  122. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +68 -0
  123. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +380 -0
  124. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -0
  125. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.d.ts +12 -0
  126. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js +174 -0
  127. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -0
  128. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +40 -0
  129. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +190 -0
  130. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -0
  131. package/dist-client/pages/kpi-value/kpi-value-importer.d.ts +23 -0
  132. package/dist-client/pages/kpi-value/kpi-value-importer.js +93 -0
  133. package/dist-client/pages/kpi-value/kpi-value-importer.js.map +1 -0
  134. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +71 -0
  135. package/dist-client/pages/kpi-value/kpi-value-list-page.js +464 -0
  136. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -0
  137. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +14 -0
  138. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +339 -0
  139. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -0
  140. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +14 -0
  141. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +276 -0
  142. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -0
  143. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +18 -0
  144. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +307 -0
  145. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -0
  146. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +18 -0
  147. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +433 -0
  148. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -0
  149. package/dist-client/pages/sv-project-complete.d.ts +21 -0
  150. package/dist-client/pages/sv-project-complete.js +213 -0
  151. package/dist-client/pages/sv-project-complete.js.map +1 -0
  152. package/dist-client/pages/sv-project-completed-list.d.ts +27 -0
  153. package/dist-client/pages/sv-project-completed-list.js +416 -0
  154. package/dist-client/pages/sv-project-completed-list.js.map +1 -0
  155. package/dist-client/pages/sv-project-detail.d.ts +46 -0
  156. package/dist-client/pages/sv-project-detail.js +1236 -0
  157. package/dist-client/pages/sv-project-detail.js.map +1 -0
  158. package/dist-client/pages/sv-project-list.d.ts +165 -0
  159. package/dist-client/pages/sv-project-list.js +488 -0
  160. package/dist-client/pages/sv-project-list.js.map +1 -0
  161. package/dist-client/route.d.ts +1 -1
  162. package/dist-client/route.js +35 -1
  163. package/dist-client/route.js.map +1 -1
  164. package/dist-client/shared/complete-api.d.ts +8 -0
  165. package/dist-client/shared/complete-api.js +177 -0
  166. package/dist-client/shared/complete-api.js.map +1 -0
  167. package/dist-client/shared/func.d.ts +2 -0
  168. package/dist-client/shared/func.js +22 -0
  169. package/dist-client/shared/func.js.map +1 -0
  170. package/dist-client/themes/dark.css +24 -24
  171. package/dist-client/themes/light.css +23 -23
  172. package/dist-client/tsconfig.tsbuildinfo +1 -1
  173. package/dist-client/viewparts/menu-tools.d.ts +40 -5
  174. package/dist-client/viewparts/menu-tools.js +289 -34
  175. package/dist-client/viewparts/menu-tools.js.map +1 -1
  176. package/dist-server/index.d.ts +2 -0
  177. package/dist-server/index.js +5 -0
  178. package/dist-server/index.js.map +1 -1
  179. package/dist-server/migrations/index.d.ts +1 -0
  180. package/dist-server/migrations/index.js +12 -0
  181. package/dist-server/migrations/index.js.map +1 -0
  182. package/dist-server/scripts/calculate-kpi-scores.d.ts +10 -0
  183. package/dist-server/scripts/calculate-kpi-scores.js +333 -0
  184. package/dist-server/scripts/calculate-kpi-scores.js.map +1 -0
  185. package/dist-server/scripts/load-grade-data-migration.d.ts +14 -0
  186. package/dist-server/scripts/load-grade-data-migration.js +279 -0
  187. package/dist-server/scripts/load-grade-data-migration.js.map +1 -0
  188. package/dist-server/scripts/propagate-parent-kpi-values.d.ts +14 -0
  189. package/dist-server/scripts/propagate-parent-kpi-values.js +786 -0
  190. package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -0
  191. package/dist-server/scripts/recalculate-by-project-name.d.ts +2 -0
  192. package/dist-server/scripts/recalculate-by-project-name.js +72 -0
  193. package/dist-server/scripts/recalculate-by-project-name.js.map +1 -0
  194. package/dist-server/service/index.d.ts +4 -0
  195. package/dist-server/service/index.js +20 -0
  196. package/dist-server/service/index.js.map +1 -0
  197. package/dist-server/service/kpi-metric-value/index.d.ts +4 -0
  198. package/dist-server/service/kpi-metric-value/index.js +8 -0
  199. package/dist-server/service/kpi-metric-value/index.js.map +1 -0
  200. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +74 -0
  201. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +687 -0
  202. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -0
  203. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +7 -0
  204. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +52 -0
  205. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -0
  206. package/dist-server/service/kpi-stat/index.d.ts +4 -0
  207. package/dist-server/service/kpi-stat/index.js +8 -0
  208. package/dist-server/service/kpi-stat/index.js.map +1 -0
  209. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +12 -0
  210. package/dist-server/service/kpi-stat/kpi-stat-query.js +662 -0
  211. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
  212. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +32 -0
  213. package/dist-server/service/kpi-stat/kpi-stat-types.js +180 -0
  214. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
  215. package/dist-server/service/kpi-value/index.d.ts +3 -0
  216. package/dist-server/service/kpi-value/index.js +7 -0
  217. package/dist-server/service/kpi-value/index.js.map +1 -0
  218. package/dist-server/service/kpi-value/kpi-value-query.d.ts +8 -0
  219. package/dist-server/service/kpi-value/kpi-value-query.js +69 -0
  220. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -0
  221. package/dist-server/tsconfig.tsbuildinfo +1 -1
  222. package/kpi-module-service-tests.md +1286 -0
  223. package/kpi-module-test-report.md +676 -0
  224. package/kpi-module-unit-test-detailed-report.md +925 -0
  225. package/kpi-module-unit-tests-detailed.md +1452 -0
  226. package/package.json +65 -55
  227. package/recalculate-batch.sh +64 -0
  228. package/recalculate-projects-range.sh +98 -0
  229. package/schema.graphql +2514 -455
  230. package/things-factory.config.js +11 -1
  231. package/views/auth-page.html +0 -1
  232. package/views/public/home.html +0 -1
@@ -0,0 +1,687 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KpiMetricValueMutation = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const typeorm_1 = require("typeorm");
8
+ const kpi_1 = require("@things-factory/kpi");
9
+ const attachment_base_1 = require("@things-factory/attachment-base");
10
+ const project_1 = require("@dssp/project");
11
+ const moment_timezone_1 = tslib_1.__importDefault(require("moment-timezone"));
12
+ const form_data_1 = tslib_1.__importDefault(require("form-data"));
13
+ const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
14
+ const https_1 = tslib_1.__importDefault(require("https"));
15
+ const http_1 = tslib_1.__importDefault(require("http"));
16
+ let KpiMetricValueMutation = class KpiMetricValueMutation {
17
+ /* 프로젝트 수행과정 중에 (시간, 비용, 발생 건수 등) 누적된 값을 반영하여 KPI Metric Value 값을 수정 - 예를 들면, 총 설계 변경 건수, 총 건설 폐기물 발생량, 공사비, 공사기간, 재해 건수 등 */
18
+ async updateKpiMetricValuesCumulative(patches, context) {
19
+ const { domain, user, tx } = context.state;
20
+ const metricValueRepo = (0, shell_1.getRepository)(kpi_1.KpiMetricValue, tx);
21
+ const metricRepo = (0, shell_1.getRepository)(kpi_1.KpiMetric, tx);
22
+ const results = [];
23
+ for (const patch of patches) {
24
+ const metric = await metricRepo.findOne({ where: { id: patch.metricId } });
25
+ if (!metric) {
26
+ throw new Error(`Metric not found: ${patch.metricId}`);
27
+ }
28
+ const result = await metricValueRepo.save(Object.assign(Object.assign({ domain }, patch), { metric, updater: user }));
29
+ results.push(result);
30
+ }
31
+ return results;
32
+ }
33
+ /* 전문 감리사에 의해서 평가된 별점을 반영하여 KPI Metric Value 값을 수정 */
34
+ async updateKpiMetricValuesAssessment(patches, context) {
35
+ const { domain, user, tx } = context.state;
36
+ const metricValueRepo = (0, shell_1.getRepository)(kpi_1.KpiMetricValue, tx);
37
+ const metricRepo = (0, shell_1.getRepository)(kpi_1.KpiMetric, tx);
38
+ const results = [];
39
+ for (const patch of patches) {
40
+ const metric = await metricRepo.findOne({ where: { id: patch.metricId } });
41
+ if (!metric) {
42
+ throw new Error(`Metric not found: ${patch.metricId}`);
43
+ }
44
+ const result = await metricValueRepo.save(Object.assign(Object.assign({ domain }, patch), { metric, updater: user }));
45
+ results.push(result);
46
+ }
47
+ return results;
48
+ }
49
+ /* 전문 감리사에 의 감리 최종 평가서의 긍부정을 텍스트 분석하는 감정 분석 기반 평가값을 수정 */
50
+ async updateKpiMetricValuesSentiment(attachment, context) {
51
+ const { domain, user, tx } = context.state;
52
+ const projectRepo = (0, shell_1.getRepository)(project_1.Project, tx);
53
+ const projectId = attachment.refBy;
54
+ const attachmentRepo = (0, shell_1.getRepository)(attachment_base_1.Attachment, tx);
55
+ const metricValueRepo = (0, shell_1.getRepository)(kpi_1.KpiMetricValue, tx);
56
+ const metricRepo = (0, shell_1.getRepository)(kpi_1.KpiMetric, tx);
57
+ // 1. 기존 파일 삭제
58
+ projectRepo.update(projectId, { completeReport: null });
59
+ await attachmentRepo.delete({ refBy: projectId, refType: attachment.refType });
60
+ // 2. 새 파일 없으면 끝
61
+ if (!attachment.file) {
62
+ return null;
63
+ }
64
+ // 3. 먼저 파일을 버퍼로 읽기 (스트림은 한 번만 읽을 수 있으므로)
65
+ const uploadedFile = await attachment.file;
66
+ const { filename } = uploadedFile;
67
+ const fileBuffer = await this.readFileToBuffer(uploadedFile);
68
+ console.log(`Processing PDF file: ${filename}, size: ${fileBuffer.length} bytes`);
69
+ // 4. 새 파일 저장 (버퍼를 사용하여 attachment 생성)
70
+ const recreatedFile = this.createFileUploadFromBuffer(fileBuffer, filename, 'application/pdf');
71
+ const { id: resultId } = await (0, attachment_base_1.createAttachment)(null, { attachment: Object.assign(Object.assign({}, attachment), { file: recreatedFile }) }, context);
72
+ const result = await attachmentRepo.findOne({ where: { id: resultId } });
73
+ projectRepo.update(projectId, { completeReport: result });
74
+ // 5. pdf 파일을 operator-runner 서비스에 업로드하여 분석
75
+ let safetyScore = 0;
76
+ let qualityScore = 0;
77
+ try {
78
+ const operatorRunnerUrl = process.env.OPERATOR_RUNNER_URL || 'https://hatiolab-korea-uni.ettisoft.com';
79
+ // sl-pa-safety 서비스 호출
80
+ safetyScore = await this.callOperatorRunnerServiceWithBuffer(operatorRunnerUrl, 'sl-pa-safety', fileBuffer, filename);
81
+ // sl-pa-quality 서비스 호출
82
+ qualityScore = await this.callOperatorRunnerServiceWithBuffer(operatorRunnerUrl, 'sl-pa-quality', fileBuffer, filename);
83
+ }
84
+ catch (error) {
85
+ console.error('Failed to analyze PDF with operator-runner:', error);
86
+ }
87
+ // 5. 메트릭 점수 저장
88
+ await this.saveMetricValue(metricValueRepo, metricRepo, projectId, 'SL-PA 안전 평가', safetyScore, domain, user);
89
+ await this.saveMetricValue(metricValueRepo, metricRepo, projectId, 'SL-PA 품질 평가', qualityScore, domain, user);
90
+ return result;
91
+ }
92
+ /**
93
+ * FileUpload 스트림을 버퍼로 읽기
94
+ */
95
+ async readFileToBuffer(fileUpload) {
96
+ const { createReadStream } = fileUpload;
97
+ const chunks = [];
98
+ return new Promise((resolve, reject) => {
99
+ const stream = createReadStream();
100
+ stream.on('data', (chunk) => chunks.push(chunk));
101
+ stream.on('end', () => resolve(Buffer.concat(chunks)));
102
+ stream.on('error', (error) => reject(error));
103
+ });
104
+ }
105
+ /**
106
+ * Buffer로부터 FileUpload 객체 생성
107
+ */
108
+ createFileUploadFromBuffer(buffer, filename, mimetype) {
109
+ const { Readable } = require('stream');
110
+ return {
111
+ filename,
112
+ mimetype,
113
+ encoding: '7bit',
114
+ createReadStream: () => {
115
+ const readable = new Readable();
116
+ readable.push(buffer);
117
+ readable.push(null);
118
+ return readable;
119
+ }
120
+ };
121
+ }
122
+ /**
123
+ * operator-runner 서비스 호출 (Buffer 사용)
124
+ */
125
+ async callOperatorRunnerServiceWithBuffer(baseUrl, serviceName, fileBuffer, filename) {
126
+ var _a;
127
+ console.log(`Calling operator-runner service: ${serviceName}`);
128
+ console.log(`Base URL: ${baseUrl}`);
129
+ console.log(`Filename: ${filename}`);
130
+ console.log(`Buffer size: ${fileBuffer.length} bytes`);
131
+ const formData = new form_data_1.default();
132
+ // Buffer를 스트림으로 변환하여 추가
133
+ const { Readable } = require('stream');
134
+ const bufferStream = Readable.from(fileBuffer);
135
+ formData.append('files', bufferStream, {
136
+ filename,
137
+ contentType: 'application/pdf'
138
+ });
139
+ const url = `${baseUrl}/api/run/${serviceName}`;
140
+ console.log(`Request URL: ${url}`);
141
+ // HTTP/HTTPS agent 설정
142
+ const isHttps = url.startsWith('https');
143
+ const agent = isHttps
144
+ ? new https_1.default.Agent({
145
+ keepAlive: false,
146
+ timeout: 120000,
147
+ rejectUnauthorized: false
148
+ })
149
+ : new http_1.default.Agent({
150
+ keepAlive: false,
151
+ timeout: 120000
152
+ });
153
+ try {
154
+ const response = await (0, node_fetch_1.default)(url, {
155
+ method: 'POST',
156
+ body: formData,
157
+ headers: Object.assign(Object.assign({}, formData.getHeaders()), { Authorization: `Basic ${Buffer.from('admin:admin1234').toString('base64')}` }),
158
+ agent,
159
+ timeout: 120000
160
+ });
161
+ console.log(`Response status: ${response.status}`);
162
+ if (!response.ok) {
163
+ const errorText = await response.text();
164
+ console.error(`Service ${serviceName} error response:`, errorText);
165
+ throw new Error(`Service ${serviceName} failed (${response.status}): ${errorText}`);
166
+ }
167
+ const data = await response.json();
168
+ console.log(`Service ${serviceName} response:`, JSON.stringify(data));
169
+ const score = ((_a = data === null || data === void 0 ? void 0 : data.result) === null || _a === void 0 ? void 0 : _a.score) || (data === null || data === void 0 ? void 0 : data.score) || 0;
170
+ console.log(`Extracted score: ${score}`);
171
+ return score;
172
+ }
173
+ catch (error) {
174
+ console.error(`Error calling ${serviceName}:`, error);
175
+ throw error;
176
+ }
177
+ }
178
+ /**
179
+ * 메트릭 값 저장
180
+ */
181
+ async saveMetricValue(metricValueRepo, metricRepo, projectId, metricName, value, domain, user) {
182
+ const metricValue = await metricValueRepo.findOne({
183
+ where: { org: projectId, metric: { name: metricName } }
184
+ });
185
+ if (metricValue) {
186
+ await metricValueRepo.save(Object.assign(Object.assign({}, metricValue), { value, updater: user }));
187
+ }
188
+ else {
189
+ const originMetric = await metricRepo.findOne({ where: { name: metricName } });
190
+ if (originMetric) {
191
+ await metricValueRepo.save({
192
+ metric: { id: originMetric.id },
193
+ unit: originMetric.unit || '',
194
+ org: projectId,
195
+ periodType: originMetric.periodType,
196
+ valueDate: (0, moment_timezone_1.default)().tz('Asia/Seoul').format('YYYY-MM-DD'),
197
+ value,
198
+ domain,
199
+ updater: user
200
+ });
201
+ }
202
+ }
203
+ }
204
+ async finalizeProjectWithKpiRecalculation(projectId, context) {
205
+ const { domain, user, tx } = context.state;
206
+ const projectRepo = (0, shell_1.getRepository)(project_1.Project, tx);
207
+ const project = await projectRepo.findOne({ where: { id: projectId } });
208
+ console.log('project :', project);
209
+ if (!project) {
210
+ throw new Error(`Project not found: ${projectId}`);
211
+ }
212
+ // 이지점에서 프로젝트 KPI Value 계산을 다시 할 것이다.
213
+ await this.recalculateProjectKpiValues(projectId, context);
214
+ projectRepo.save(Object.assign(Object.assign({}, project), { state: project_1.ProjectState.COMPLETED }));
215
+ return true;
216
+ }
217
+ /**
218
+ * 프로젝트의 모든 KPI Value들을 계층적으로 재계산합니다.
219
+ * 1. 기존 KPI Value 삭제 (중복 방지)
220
+ * 2. Leaf KPIs (자식이 없는 KPI): formula 기반 계산
221
+ * 3. Parent KPIs (자식이 있는 KPI): 자식 KPI scores의 weighted average 계산
222
+ */
223
+ async recalculateProjectKpiValues(projectId, context) {
224
+ const { domain, user, tx } = context.state;
225
+ const kpiValueRepo = (0, shell_1.getRepository)(kpi_1.KpiValue, tx);
226
+ const kpiRepo = (0, shell_1.getRepository)(kpi_1.Kpi, tx);
227
+ try {
228
+ // 1. 기존 KPI Value 삭제 (재계산으로 인한 중복 방지)
229
+ const existingKpiValues = await kpiValueRepo.find({
230
+ where: {
231
+ group: projectId,
232
+ domain: { id: domain.id }
233
+ }
234
+ });
235
+ if (existingKpiValues.length > 0) {
236
+ await kpiValueRepo.remove(existingKpiValues);
237
+ console.log(`Deleted ${existingKpiValues.length} existing KPI values for project ${projectId}`);
238
+ }
239
+ // 2. 모든 KPI들을 조회 (계층 구조 포함)
240
+ const allKpis = await kpiRepo.find({
241
+ where: { domain: { id: domain.id } },
242
+ relations: ['parent', 'children']
243
+ });
244
+ console.log(`Found ${allKpis.length} KPIs total for domain`);
245
+ // 3. Leaf KPIs (자식이 없는 KPI)와 Parent KPIs 구분
246
+ const leafKpis = allKpis.filter(kpi => kpi.isLeaf || !kpi.children || kpi.children.length === 0);
247
+ const parentKpis = allKpis.filter(kpi => !kpi.isLeaf && kpi.children && kpi.children.length > 0);
248
+ console.log(`Processing ${leafKpis.length} leaf KPIs and ${parentKpis.length} parent KPIs`);
249
+ // 4. 먼저 Leaf KPIs를 formula로 계산
250
+ for (const leafKpi of leafKpis) {
251
+ try {
252
+ await this.recalculateLeafKpiValue(leafKpi, projectId, context);
253
+ console.log(`Successfully calculated leaf KPI: ${leafKpi.name}`);
254
+ }
255
+ catch (error) {
256
+ console.error(`Failed to calculate leaf KPI ${leafKpi.name}:`, error.message);
257
+ // 개별 KPI 계산 실패는 전체 프로세스를 중단하지 않음
258
+ }
259
+ }
260
+ // 5. Parent KPIs를 계층 레벨순으로 정렬 (깊은 레벨부터)
261
+ const sortedParentKpis = this.sortKpisByLevel(parentKpis, allKpis);
262
+ // 6. Parent KPIs를 weighted average로 계산
263
+ for (const parentKpi of sortedParentKpis) {
264
+ try {
265
+ await this.recalculateParentKpiValue(parentKpi, projectId, allKpis, context);
266
+ console.log(`Successfully calculated parent KPI: ${parentKpi.name}`);
267
+ }
268
+ catch (error) {
269
+ console.error(`Failed to calculate parent KPI ${parentKpi.name}:`, error.message);
270
+ }
271
+ }
272
+ }
273
+ catch (error) {
274
+ console.error(`Error recalculating KPI values for project ${projectId}:`, error);
275
+ throw error;
276
+ }
277
+ }
278
+ /**
279
+ * 개별 KPI Value를 재계산합니다.
280
+ * 항상 최신 버전의 KPI를 사용하여 계산합니다.
281
+ */
282
+ async recalculateKpiValue(kpiValue, context) {
283
+ var _a;
284
+ const { domain, user, tx } = context.state;
285
+ const kpiValueRepo = (0, shell_1.getRepository)(kpi_1.KpiValue, tx);
286
+ const kpiRepo = (0, shell_1.getRepository)(kpi_1.Kpi, tx);
287
+ const metricRepo = (0, shell_1.getRepository)(kpi_1.KpiMetric, tx);
288
+ const metricValueRepo = (0, shell_1.getRepository)(kpi_1.KpiMetricValue, tx);
289
+ // 항상 최신 버전의 KPI를 조회하여 사용
290
+ // kpiValue.kpi가 이미 로드되어 있으면 사용하고, 없으면 DB에서 최신 버전 조회
291
+ const kpi = kpiValue.kpi ||
292
+ (await kpiRepo.findOne({
293
+ where: { id: kpiValue.kpiId, domain: { id: domain.id } },
294
+ order: { version: 'DESC' }
295
+ }));
296
+ if (!kpi)
297
+ throw new Error('KPI 정보 없음');
298
+ if (!kpi.formula) {
299
+ console.log(`KPI ${kpi.name} has no formula, skipping recalculation`);
300
+ return kpiValue;
301
+ }
302
+ const periodType = kpi.periodType || kpi_1.KpiPeriodType.DAY;
303
+ const valueDate = null; // kpiValue.valueDate
304
+ const org = kpiValue.group || 'unknown'; // project ID를 org로 사용
305
+ // things-factory calculator 기반 formula 계산
306
+ let kpiValueResult;
307
+ try {
308
+ const ast = (0, kpi_1.parseFormula)(kpi.formula);
309
+ const provider = new kpi_1.KpiMetricValueProvider({
310
+ valueDate,
311
+ org,
312
+ domainId: domain.id,
313
+ tx
314
+ });
315
+ const evalContext = { functions: kpi_1.builtinFunctions, provider };
316
+ kpiValueResult = await (0, kpi_1.evaluateFormula)(ast, evalContext);
317
+ if (kpiValueResult == null || isNaN(kpiValueResult)) {
318
+ throw new Error('KPI formula 결과값 없음');
319
+ }
320
+ }
321
+ catch (error) {
322
+ console.error(`Formula evaluation failed for KPI ${kpi.name}: ${kpi.formula}`, error);
323
+ return kpiValue; // 계산 실패시 기존 값 유지
324
+ }
325
+ // KPI Value 업데이트 (최신 버전으로)
326
+ kpiValue.value = kpiValueResult;
327
+ kpiValue.version = kpi.version || 1;
328
+ kpiValue.updater = user;
329
+ // 성과 점수 자동 계산 및 저장
330
+ try {
331
+ // 2D lookup KPI (X13 등)인 경우 project의 totalProgress 조회
332
+ let scoreContext;
333
+ const gradesData = kpi.grades;
334
+ if (gradesData && !Array.isArray(gradesData) && gradesData.type === 'PROGRESS_DEVIATION_LOOKUP') {
335
+ const projectId = kpiValue.group || org;
336
+ const projectRepo = (0, shell_1.getRepository)(project_1.Project, tx);
337
+ const project = await projectRepo.findOne({ where: { id: projectId } });
338
+ scoreContext = { progressRate: (_a = project === null || project === void 0 ? void 0 : project.totalProgress) !== null && _a !== void 0 ? _a : 50 };
339
+ console.log(`2D lookup KPI ${kpi.name}: progressRate=${scoreContext.progressRate}%`);
340
+ }
341
+ await this.calculateAndSaveScore(kpiValue, kpi, scoreContext);
342
+ console.log(`Successfully calculated score for KPI value ${kpiValue.id}`);
343
+ }
344
+ catch (scoreError) {
345
+ console.error(`Failed to calculate score for KPI value ${kpiValue.id}:`, scoreError.message);
346
+ // 성과 점수 계산 실패는 KPI Value 저장을 막지 않음
347
+ }
348
+ return await kpiValueRepo.save(kpiValue);
349
+ }
350
+ /**
351
+ * scoreFormula를 사용한 성과 점수 계산
352
+ */
353
+ async calculateScoreFromFormula(kpi, value) {
354
+ if (!kpi.scoreFormula) {
355
+ return null;
356
+ }
357
+ try {
358
+ const ast = (0, kpi_1.parseFormula)(kpi.scoreFormula);
359
+ const provider = {
360
+ get: async (name) => {
361
+ if (name === 'value')
362
+ return value;
363
+ return null;
364
+ }
365
+ };
366
+ const evalContext = { functions: kpi_1.builtinFunctions, provider };
367
+ const result = await (0, kpi_1.evaluateFormula)(ast, evalContext);
368
+ if (result !== null && result !== undefined && !isNaN(result)) {
369
+ const performanceScore = Number(result);
370
+ return {
371
+ score: performanceScore,
372
+ calculationMethod: 'formula'
373
+ };
374
+ }
375
+ }
376
+ catch (error) {
377
+ console.error(`Score formula evaluation for '${kpi.name}: ${kpi.scoreFormula}' error:`, error);
378
+ }
379
+ return null;
380
+ }
381
+ /**
382
+ * KPI의 grades lookup table을 이용한 성과 점수 변환
383
+ * 1D (기존 배열) 및 2D (X13 공정률×편차) lookup 모두 지원
384
+ */
385
+ calculateScoreFromLookup(kpi, value, context) {
386
+ if (!kpi.grades) {
387
+ return null;
388
+ }
389
+ // 2D lookup table 처리 (X13: 공정률 × 편차 → 점수)
390
+ const grades = kpi.grades;
391
+ if (!Array.isArray(grades) && grades.type === 'PROGRESS_DEVIATION_LOOKUP') {
392
+ return this.calculateScoreFrom2DLookup(value, grades, context === null || context === void 0 ? void 0 : context.progressRate);
393
+ }
394
+ // 1D lookup table 처리 (기존)
395
+ if (!Array.isArray(grades) || grades.length === 0) {
396
+ return null;
397
+ }
398
+ const grade = grades.find((g) => value >= g.minValue && value <= g.maxValue);
399
+ if (!grade) {
400
+ return null;
401
+ }
402
+ return {
403
+ score: grade.score,
404
+ calculationMethod: 'lookup'
405
+ };
406
+ }
407
+ /**
408
+ * 2D lookup table에서 점수 계산 (X13: 공정률 × 편차 → 점수 1~5)
409
+ */
410
+ calculateScoreFrom2DLookup(deviationValue, grades, progressRate) {
411
+ if (!grades.rows || !Array.isArray(grades.rows))
412
+ return null;
413
+ const progress = progressRate !== null && progressRate !== void 0 ? progressRate : 50;
414
+ const progressIndex = Math.min(99, Math.max(0, Math.floor(progress)));
415
+ const row = grades.rows.find((r) => r.progressRate === progressIndex);
416
+ if (!row)
417
+ return null;
418
+ let score;
419
+ if (deviationValue < row.boundary5to4) {
420
+ score = 5;
421
+ }
422
+ else if (deviationValue < row.boundary4to3) {
423
+ score = 4;
424
+ }
425
+ else if (deviationValue < row.boundary3to2) {
426
+ score = 3;
427
+ }
428
+ else if (deviationValue < row.boundary2to1) {
429
+ score = 2;
430
+ }
431
+ else {
432
+ score = 1;
433
+ }
434
+ return { score, calculationMethod: 'PROGRESS_DEVIATION_LOOKUP' };
435
+ }
436
+ /**
437
+ * KpiValue의 성과 점수 계산 및 저장
438
+ * @param context.progressRate X13 등 2D lookup KPI에 필요한 공정률 (0~100)
439
+ */
440
+ async calculateAndSaveScore(kpiValue, kpi, context) {
441
+ // 1. scoreFormula 우선 시도
442
+ let scoreResult = await this.calculateScoreFromFormula(kpi, kpiValue.value);
443
+ // 2. scoreFormula가 없거나 실패하면 grades lookup table 사용
444
+ if (!scoreResult) {
445
+ scoreResult = this.calculateScoreFromLookup(kpi, kpiValue.value, context);
446
+ }
447
+ if (scoreResult) {
448
+ // KpiValue의 score 필드에 성과 점수 저장
449
+ kpiValue.score = scoreResult.score;
450
+ console.log(`Calculated score ${scoreResult.score} for KPI ${kpi.name} with method ${scoreResult.calculationMethod}`);
451
+ }
452
+ else {
453
+ console.log(`No score calculation method available for KPI ${kpi.name}`);
454
+ }
455
+ }
456
+ /**
457
+ * Leaf KPI Value를 formula로 계산합니다.
458
+ * 참고: recalculateProjectKpiValues에서 호출 시 기존 값이 이미 삭제되었으므로 항상 새로 생성됩니다.
459
+ */
460
+ async recalculateLeafKpiValue(kpi, projectId, context) {
461
+ const { domain, user, tx } = context.state;
462
+ const kpiValueRepo = (0, shell_1.getRepository)(kpi_1.KpiValue, tx);
463
+ // 기존 KPI Value 조회 또는 생성 (프로젝트 재계산 시에는 이미 삭제되어 항상 새로 생성됨)
464
+ let kpiValue = await kpiValueRepo.findOne({
465
+ where: {
466
+ kpi: { id: kpi.id },
467
+ group: projectId,
468
+ domain: { id: domain.id }
469
+ }
470
+ });
471
+ if (!kpiValue) {
472
+ // 새 KPI Value 생성
473
+ kpiValue = kpiValueRepo.create({
474
+ kpi,
475
+ version: kpi.version || 1,
476
+ group: projectId,
477
+ domain,
478
+ valueDate: new Date().toISOString().slice(0, 10),
479
+ value: 0,
480
+ score: 0,
481
+ updater: user
482
+ });
483
+ }
484
+ // formula 기반 계산
485
+ await this.recalculateKpiValue(kpiValue, context);
486
+ }
487
+ /**
488
+ * Parent KPI Value를 계산합니다.
489
+ * 1. formula가 있으면 자식 KPI 값들을 변수로 사용하여 formula 계산
490
+ * 2. formula가 없으면 자식 KPI scores의 weighted average로 계산
491
+ * 참고: recalculateProjectKpiValues에서 호출 시 기존 값이 이미 삭제되었으므로 항상 새로 생성됩니다.
492
+ */
493
+ async recalculateParentKpiValue(parentKpi, projectId, allKpis, context) {
494
+ const { domain, user, tx } = context.state;
495
+ const kpiValueRepo = (0, shell_1.getRepository)(kpi_1.KpiValue, tx);
496
+ // 자식 KPIs 조회
497
+ const childKpis = allKpis.filter(kpi => { var _a; return ((_a = kpi.parent) === null || _a === void 0 ? void 0 : _a.id) === parentKpi.id; });
498
+ if (childKpis.length === 0) {
499
+ console.log(`No child KPIs found for parent KPI: ${parentKpi.name}`);
500
+ return;
501
+ }
502
+ // 자식 KPI Values 조회
503
+ const childKpiIds = childKpis.map(kpi => kpi.id);
504
+ const childKpiValues = await kpiValueRepo.find({
505
+ where: {
506
+ kpi: { id: (0, typeorm_1.In)(childKpiIds) },
507
+ group: projectId,
508
+ domain: { id: domain.id }
509
+ },
510
+ relations: ['kpi']
511
+ });
512
+ if (childKpiValues.length === 0) {
513
+ console.log(`No child KPI values found for parent KPI: ${parentKpi.name}`);
514
+ return;
515
+ }
516
+ let calculatedValue;
517
+ let calculatedScore;
518
+ // 1. formula가 있으면 formula로 계산
519
+ if (parentKpi.formula) {
520
+ try {
521
+ const ast = (0, kpi_1.parseFormula)(parentKpi.formula);
522
+ // 자식 KPI 이름 -> score 값 매핑을 위한 provider
523
+ const childKpiScoreMap = {};
524
+ childKpiValues.forEach(kpiValue => {
525
+ var _a;
526
+ const childKpi = childKpis.find(k => k.id === kpiValue.kpi.id);
527
+ if (childKpi) {
528
+ childKpiScoreMap[childKpi.name] = (_a = kpiValue.score) !== null && _a !== void 0 ? _a : 0;
529
+ }
530
+ });
531
+ const provider = {
532
+ get: async (name) => {
533
+ const value = childKpiScoreMap[name];
534
+ if (value === undefined) {
535
+ throw new Error(`Child KPI '${name}' not found in parent KPI formula`);
536
+ }
537
+ return value;
538
+ }
539
+ };
540
+ const evalContext = { functions: kpi_1.builtinFunctions, provider };
541
+ const result = await (0, kpi_1.evaluateFormula)(ast, evalContext);
542
+ calculatedValue = result;
543
+ calculatedScore = result;
544
+ console.log(`Calculated parent KPI ${parentKpi.name} using formula: value=${calculatedValue.toFixed(4)}`);
545
+ }
546
+ catch (error) {
547
+ console.error(`Failed to evaluate formula for parent KPI ${parentKpi.name}:`, error);
548
+ // formula 계산 실패 시 가중치 평균으로 대체
549
+ const { weightedValue, weightedScore } = this.calculateWeightedAverage(childKpiValues, childKpis);
550
+ calculatedValue = weightedValue;
551
+ calculatedScore = weightedScore;
552
+ console.log(`Fallback to weighted average for parent KPI ${parentKpi.name}`);
553
+ }
554
+ }
555
+ else {
556
+ // 2. formula가 없으면 가중치 평균 계산
557
+ const { weightedValue, weightedScore } = this.calculateWeightedAverage(childKpiValues, childKpis);
558
+ calculatedValue = weightedValue;
559
+ calculatedScore = weightedScore;
560
+ console.log(`Calculated parent KPI ${parentKpi.name} using weighted average`);
561
+ }
562
+ // 기존 Parent KPI Value 조회 또는 생성 (프로젝트 재계산 시에는 이미 삭제되어 항상 새로 생성됨)
563
+ let parentKpiValue = await kpiValueRepo.findOne({
564
+ where: {
565
+ kpi: { id: parentKpi.id },
566
+ group: projectId,
567
+ domain: { id: domain.id }
568
+ }
569
+ });
570
+ if (parentKpiValue) {
571
+ // 기존 값 업데이트 (최신 버전으로)
572
+ parentKpiValue.value = calculatedValue;
573
+ parentKpiValue.score = calculatedScore;
574
+ parentKpiValue.version = parentKpi.version || 1;
575
+ parentKpiValue.updater = user;
576
+ }
577
+ else {
578
+ // 새 값 생성
579
+ parentKpiValue = kpiValueRepo.create({
580
+ kpi: parentKpi,
581
+ group: projectId,
582
+ version: parentKpi.version || 1,
583
+ domain,
584
+ valueDate: new Date().toISOString().slice(0, 10),
585
+ value: calculatedValue,
586
+ score: calculatedScore,
587
+ updater: user
588
+ });
589
+ }
590
+ await kpiValueRepo.save(parentKpiValue);
591
+ console.log(`Updated parent KPI ${parentKpi.name}: value=${calculatedValue.toFixed(4)}, score=${calculatedScore.toFixed(4)}`);
592
+ }
593
+ /**
594
+ * KPIs를 계층 레벨순으로 정렬합니다 (깊은 레벨부터)
595
+ */
596
+ sortKpisByLevel(parentKpis, allKpis) {
597
+ const kpiLevels = new Map();
598
+ const calculateLevel = (kpi) => {
599
+ if (kpiLevels.has(kpi.id)) {
600
+ return kpiLevels.get(kpi.id);
601
+ }
602
+ if (!kpi.parent) {
603
+ kpiLevels.set(kpi.id, 0);
604
+ return 0;
605
+ }
606
+ const parentKpi = allKpis.find(k => { var _a; return k.id === ((_a = kpi.parent) === null || _a === void 0 ? void 0 : _a.id); });
607
+ if (!parentKpi) {
608
+ kpiLevels.set(kpi.id, 1);
609
+ return 1;
610
+ }
611
+ const level = calculateLevel(parentKpi) + 1;
612
+ kpiLevels.set(kpi.id, level);
613
+ return level;
614
+ };
615
+ // 각 parent KPI의 레벨 계산
616
+ parentKpis.forEach(kpi => calculateLevel(kpi));
617
+ // 레벨 순으로 정렬 (깊은 레벨부터)
618
+ return parentKpis.sort((a, b) => (kpiLevels.get(b.id) || 0) - (kpiLevels.get(a.id) || 0));
619
+ }
620
+ /**
621
+ * 자식 KPI values의 weighted average를 계산합니다.
622
+ */
623
+ calculateWeightedAverage(childValues, childKpis) {
624
+ let totalWeightedValue = 0;
625
+ let totalWeightedScore = 0;
626
+ let totalWeight = 0;
627
+ for (const value of childValues) {
628
+ const childKpi = childKpis.find(kpi => kpi.id === value.kpi.id);
629
+ const weight = (childKpi === null || childKpi === void 0 ? void 0 : childKpi.weight) || 1;
630
+ totalWeightedValue += value.score * weight; /* value.score 가 value 역할을 value.value 가 아니라 value.score 가 맞다. */
631
+ totalWeightedScore += value.score * weight;
632
+ totalWeight += weight;
633
+ }
634
+ if (totalWeight === 0) {
635
+ return { weightedValue: 0, weightedScore: 0 };
636
+ }
637
+ return {
638
+ weightedValue: totalWeightedValue / totalWeight,
639
+ weightedScore: totalWeightedScore / totalWeight
640
+ };
641
+ }
642
+ };
643
+ exports.KpiMetricValueMutation = KpiMetricValueMutation;
644
+ tslib_1.__decorate([
645
+ (0, type_graphql_1.Directive)('@transaction'),
646
+ (0, type_graphql_1.Directive)('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
647
+ (0, type_graphql_1.Mutation)(returns => [kpi_1.KpiMetricValue], { description: "To modify multiple cumulative KpiMetricValues' value" }),
648
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('patches', type => [kpi_1.KpiMetricValuePatch])),
649
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
650
+ tslib_1.__metadata("design:type", Function),
651
+ tslib_1.__metadata("design:paramtypes", [Array, Object]),
652
+ tslib_1.__metadata("design:returntype", Promise)
653
+ ], KpiMetricValueMutation.prototype, "updateKpiMetricValuesCumulative", null);
654
+ tslib_1.__decorate([
655
+ (0, type_graphql_1.Directive)('@transaction'),
656
+ (0, type_graphql_1.Directive)('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
657
+ (0, type_graphql_1.Mutation)(returns => [kpi_1.KpiMetricValue], { description: "To modify multiple assessment KpiMetricValues' value" }),
658
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('patches', type => [kpi_1.KpiMetricValuePatch])),
659
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
660
+ tslib_1.__metadata("design:type", Function),
661
+ tslib_1.__metadata("design:paramtypes", [Array, Object]),
662
+ tslib_1.__metadata("design:returntype", Promise)
663
+ ], KpiMetricValueMutation.prototype, "updateKpiMetricValuesAssessment", null);
664
+ tslib_1.__decorate([
665
+ (0, type_graphql_1.Directive)('@transaction'),
666
+ (0, type_graphql_1.Directive)('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
667
+ (0, type_graphql_1.Mutation)(returns => attachment_base_1.Attachment, { nullable: true, description: "To modify multiple sentiment KpiMetricValues' value" }),
668
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('attachment', type => attachment_base_1.NewAttachment)),
669
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
670
+ tslib_1.__metadata("design:type", Function),
671
+ tslib_1.__metadata("design:paramtypes", [attachment_base_1.NewAttachment, Object]),
672
+ tslib_1.__metadata("design:returntype", Promise)
673
+ ], KpiMetricValueMutation.prototype, "updateKpiMetricValuesSentiment", null);
674
+ tslib_1.__decorate([
675
+ (0, type_graphql_1.Directive)('@transaction'),
676
+ (0, type_graphql_1.Directive)('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
677
+ (0, type_graphql_1.Mutation)(returns => Boolean, { description: 'To finalize project with KPI recalculation' }),
678
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('projectId')),
679
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
680
+ tslib_1.__metadata("design:type", Function),
681
+ tslib_1.__metadata("design:paramtypes", [String, Object]),
682
+ tslib_1.__metadata("design:returntype", Promise)
683
+ ], KpiMetricValueMutation.prototype, "finalizeProjectWithKpiRecalculation", null);
684
+ exports.KpiMetricValueMutation = KpiMetricValueMutation = tslib_1.__decorate([
685
+ (0, type_graphql_1.Resolver)(kpi_1.KpiMetricValue)
686
+ ], KpiMetricValueMutation);
687
+ //# sourceMappingURL=kpi-metric-value-mutation.js.map