@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,786 @@
1
+ #!/usr/bin/env ts-node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.propagateParentKpiValuesBatch = propagateParentKpiValuesBatch;
5
+ exports.propagateParentKpiValues = propagateParentKpiValues;
6
+ exports.listParentKpiHierarchy = listParentKpiHierarchy;
7
+ const typeorm_1 = require("typeorm");
8
+ /**
9
+ * Batch process multiple organizations with single initialization (optimized for performance)
10
+ */
11
+ async function propagateParentKpiValuesBatch(orgScopes, specificKpiName, forceRecalculate = false, targetDate, version) {
12
+ // Set NODE_ENV if not set
13
+ if (!process.env.NODE_ENV) {
14
+ process.env.NODE_ENV = 'development';
15
+ }
16
+ // Initialize Things-Factory environment (ONCE)
17
+ const { config } = require('@things-factory/env');
18
+ const ormconfig = require('@things-factory/shell/dist-server/initializers/ormconfig.js');
19
+ const connectionConfig = config.get('ormconfig');
20
+ let connection;
21
+ console.log('🚀 Starting Batch Parent KPI Value Propagation...');
22
+ console.log(`📦 Processing ${orgScopes.length} organizations`);
23
+ try {
24
+ // Create database connection using Things-Factory pattern (ONCE)
25
+ console.log('🔌 Connecting to database...');
26
+ connection = await (0, typeorm_1.createConnection)(Object.assign(Object.assign(Object.assign({}, ormconfig), connectionConfig), { logging: false }));
27
+ // Register the connection with Things-Factory shell
28
+ const { addDataSource } = require('@things-factory/shell');
29
+ addDataSource('default', connection);
30
+ console.log('✅ Database connected');
31
+ let totalProcessed = 0;
32
+ let totalCreated = 0;
33
+ let totalErrors = 0;
34
+ const startTime = Date.now();
35
+ // Process each organization
36
+ for (let i = 0; i < orgScopes.length; i++) {
37
+ const orgScope = orgScopes[i].trim();
38
+ console.log(`\n[${i + 1}/${orgScopes.length}] 🏢 Processing org: ${orgScope}`);
39
+ try {
40
+ // Call the core processing logic for single org
41
+ const result = await processSingleOrg(connection, orgScope, specificKpiName, forceRecalculate, targetDate, version);
42
+ totalProcessed += result.processed;
43
+ totalCreated += result.created;
44
+ console.log(` ✅ Success: ${result.created} parent KPIs created`);
45
+ }
46
+ catch (error) {
47
+ totalErrors++;
48
+ console.log(` ❌ Failed: ${error.message}`);
49
+ }
50
+ }
51
+ const endTime = Date.now();
52
+ const duration = ((endTime - startTime) / 1000).toFixed(2);
53
+ console.log(`\n🎉 Batch Propagation Complete!`);
54
+ console.log(` 📊 Organizations processed: ${orgScopes.length}`);
55
+ console.log(` ✨ Parent KPIs created: ${totalCreated}`);
56
+ console.log(` ❌ Errors: ${totalErrors}`);
57
+ console.log(` ⏱️ Duration: ${duration}s (${(parseFloat(duration) / orgScopes.length).toFixed(2)}s per org)`);
58
+ }
59
+ catch (error) {
60
+ console.error(`❌ Fatal error during batch propagation:`, error);
61
+ throw error;
62
+ }
63
+ finally {
64
+ if (connection) {
65
+ await connection.close();
66
+ console.log('🔌 Database connection closed');
67
+ }
68
+ }
69
+ }
70
+ /**
71
+ * Process single organization (used by batch processor)
72
+ */
73
+ async function processSingleOrg(connection, orgScope, specificKpiName, forceRecalculate = false, targetDate, version) {
74
+ const { getRepository } = require('@things-factory/shell');
75
+ const { Kpi, KpiValue } = require('@things-factory/kpi');
76
+ const { Domain } = require('@things-factory/shell');
77
+ const kpiRepository = getRepository(Kpi);
78
+ const kpiValueRepository = getRepository(KpiValue);
79
+ const domainRepository = getRepository(Domain);
80
+ const systemDomain = await domainRepository.findOne({ where: { name: 'SYSTEM' } });
81
+ if (!systemDomain) {
82
+ throw new Error('SYSTEM domain not found');
83
+ }
84
+ // Get all KPIs with their hierarchy
85
+ let kpisQuery = kpiRepository
86
+ .createQueryBuilder('kpi')
87
+ .leftJoinAndSelect('kpi.domain', 'domain')
88
+ .leftJoinAndSelect('kpi.parent', 'parent')
89
+ .leftJoinAndSelect('kpi.children', 'children')
90
+ .where('kpi.domain_id = :domainId', { domainId: systemDomain.id });
91
+ if (specificKpiName) {
92
+ kpisQuery = kpisQuery.andWhere('kpi.name = :name', { name: specificKpiName });
93
+ }
94
+ const allKpis = await kpisQuery.getMany();
95
+ const parentKpis = allKpis.filter((kpi) => !kpi.isLeaf);
96
+ const leafKpis = allKpis.filter((kpi) => kpi.isLeaf);
97
+ console.log(` 📊 KPIs: ${allKpis.length} total (${leafKpis.length} leaf, ${parentKpis.length} parent)`);
98
+ // 1. Recalculate leaf KPI scores
99
+ await recalculateLeafKpiScores(kpiRepository, kpiValueRepository, leafKpis, orgScope, targetDate);
100
+ // 2. Delete existing parent KPI values
101
+ if (parentKpis.length > 0) {
102
+ const parentKpiIds = parentKpis.map((kpi) => kpi.id);
103
+ let deleteQuery = kpiValueRepository
104
+ .createQueryBuilder('kpiValue')
105
+ .delete()
106
+ .where('kpi_id IN (:...kpiIds)', { kpiIds: parentKpiIds })
107
+ .andWhere('domain_id = (SELECT id FROM domains WHERE name = :domainName)', { domainName: 'SYSTEM' })
108
+ .andWhere('"group" = :orgScope', { orgScope });
109
+ if (targetDate) {
110
+ deleteQuery = deleteQuery.andWhere('value_date = :targetDate', { targetDate });
111
+ }
112
+ await deleteQuery.execute();
113
+ }
114
+ // 3. Sort parent KPIs and get combinations
115
+ const sortedParentKpis = await sortKpisByLevel(parentKpis, allKpis);
116
+ let groupDateCombinations = [];
117
+ if (targetDate) {
118
+ groupDateCombinations = [{ group: orgScope, valueDate: targetDate }];
119
+ }
120
+ else {
121
+ const dates = await kpiValueRepository.query(`SELECT DISTINCT "value_date" FROM kpi_values WHERE "group" = $1 AND "group" IS NOT NULL ORDER BY "value_date"`, [orgScope]);
122
+ groupDateCombinations = dates.map((row) => ({ group: orgScope, valueDate: row.value_date }));
123
+ }
124
+ console.log(` 📅 Date combinations: ${groupDateCombinations.length}`);
125
+ let totalCreated = 0;
126
+ // 4. Process each parent KPI
127
+ for (const { group: currentGroup, valueDate } of groupDateCombinations) {
128
+ console.log(` 📆 Processing date: ${valueDate}`);
129
+ for (const parentKpi of sortedParentKpis) {
130
+ const childKpis = allKpis.filter((kpi) => { var _a; return ((_a = kpi.parent) === null || _a === void 0 ? void 0 : _a.id) === parentKpi.id; });
131
+ if (childKpis.length === 0)
132
+ continue;
133
+ const childValues = await getChildKpiValues(kpiValueRepository, childKpis, valueDate, version, currentGroup);
134
+ if (childValues.length === 0)
135
+ continue;
136
+ let calculatedValue;
137
+ let calculatedScore;
138
+ if (parentKpi.formula) {
139
+ try {
140
+ const result = await calculateParentKpiWithFormula(parentKpi, childValues, childKpis);
141
+ calculatedValue = result.value;
142
+ calculatedScore = result.score;
143
+ }
144
+ catch (formulaError) {
145
+ const { weightedValue, weightedScore } = calculateWeightedAverage(childValues, childKpis);
146
+ calculatedValue = weightedValue;
147
+ calculatedScore = weightedScore;
148
+ }
149
+ }
150
+ else {
151
+ const { weightedValue, weightedScore } = calculateWeightedAverage(childValues, childKpis);
152
+ calculatedValue = weightedValue;
153
+ calculatedScore = weightedScore;
154
+ }
155
+ const newKpiValue = {
156
+ kpi: parentKpi,
157
+ value: calculatedValue,
158
+ score: calculatedScore,
159
+ valueDate: valueDate,
160
+ version: parentKpi.version || 1,
161
+ group: currentGroup,
162
+ kpiScope: currentGroup,
163
+ domain: systemDomain,
164
+ inputType: 'AUTO',
165
+ createdAt: new Date(),
166
+ updatedAt: new Date()
167
+ };
168
+ await kpiValueRepository.save(newKpiValue);
169
+ totalCreated++;
170
+ }
171
+ }
172
+ return { processed: sortedParentKpis.length, created: totalCreated };
173
+ }
174
+ /**
175
+ * Propagate parent KPI values based on child KPI scores and weights
176
+ * Processes hierarchy from leaf to root, calculating weighted averages
177
+ */
178
+ async function propagateParentKpiValues(specificKpiName, forceRecalculate = false, targetDate, orgScope, version) {
179
+ // Set NODE_ENV if not set
180
+ if (!process.env.NODE_ENV) {
181
+ process.env.NODE_ENV = 'development';
182
+ }
183
+ // Initialize Things-Factory environment
184
+ const { config } = require('@things-factory/env');
185
+ const ormconfig = require('@things-factory/shell/dist-server/initializers/ormconfig.js');
186
+ const connectionConfig = config.get('ormconfig');
187
+ let connection;
188
+ console.log('🚀 Starting Parent KPI Value Propagation...');
189
+ try {
190
+ // Create database connection using Things-Factory pattern
191
+ console.log('🔌 Connecting to database...');
192
+ connection = await (0, typeorm_1.createConnection)(Object.assign(Object.assign(Object.assign({}, ormconfig), connectionConfig), { logging: false }));
193
+ // Register the connection with Things-Factory shell
194
+ const { addDataSource } = require('@things-factory/shell');
195
+ addDataSource('default', connection);
196
+ console.log('✅ Database connected');
197
+ // Now we can use getRepository with the registered connection
198
+ const { getRepository } = require('@things-factory/shell');
199
+ const { Kpi } = require('@things-factory/kpi');
200
+ const kpiRepository = getRepository(Kpi);
201
+ // Get KPI-Value repository
202
+ let kpiValueRepository;
203
+ try {
204
+ const { KpiValue } = require('@things-factory/kpi');
205
+ kpiValueRepository = getRepository(KpiValue);
206
+ }
207
+ catch (error) {
208
+ console.error('❌ KpiValue entity not found. Please ensure it exists in @things-factory/kpi');
209
+ return;
210
+ }
211
+ // Get Domain repository and find SYSTEM domain
212
+ let systemDomain;
213
+ try {
214
+ const { Domain } = require('@things-factory/shell');
215
+ const domainRepository = getRepository(Domain);
216
+ systemDomain = await domainRepository.findOne({ where: { name: 'SYSTEM' } });
217
+ if (!systemDomain) {
218
+ console.error('❌ SYSTEM domain not found');
219
+ return;
220
+ }
221
+ }
222
+ catch (error) {
223
+ console.error('❌ Domain entity not found:', error.message);
224
+ return;
225
+ }
226
+ // Get all KPIs with their hierarchy
227
+ let kpisQuery = kpiRepository
228
+ .createQueryBuilder('kpi')
229
+ .leftJoinAndSelect('kpi.domain', 'domain')
230
+ .leftJoinAndSelect('kpi.parent', 'parent')
231
+ .leftJoinAndSelect('kpi.children', 'children')
232
+ .where('domain.name = :domainName', { domainName: 'SYSTEM' });
233
+ if (specificKpiName) {
234
+ kpisQuery = kpisQuery.andWhere('kpi.name = :name', { name: specificKpiName });
235
+ console.log(`🎯 Processing specific KPI: ${specificKpiName}`);
236
+ }
237
+ else {
238
+ console.log('🎯 Processing all KPIs in hierarchy');
239
+ }
240
+ const allKpis = await kpisQuery.getMany();
241
+ if (allKpis.length === 0) {
242
+ console.log('⚠️ No KPIs found');
243
+ return;
244
+ }
245
+ console.log(`📊 Found ${allKpis.length} KPIs total`);
246
+ // Get parent KPIs (non-leaf KPIs) ordered by level (deepest first)
247
+ const parentKpis = allKpis.filter(kpi => !kpi.isLeaf);
248
+ if (parentKpis.length === 0) {
249
+ console.log('⚠️ No parent KPIs found');
250
+ return;
251
+ }
252
+ // Sort parent KPIs by hierarchy level (deepest first for bottom-up processing)
253
+ const sortedParentKpis = await sortKpisByLevel(parentKpis, allKpis);
254
+ console.log(`🔄 Processing ${sortedParentKpis.length} parent KPIs`);
255
+ // 1. First, recalculate leaf KPI scores using latest KPI versions and grades
256
+ const leafKpis = allKpis.filter(kpi => kpi.isLeaf);
257
+ console.log(`\n📊 Step 1: Recalculating ${leafKpis.length} leaf KPI scores with latest KPI grades...`);
258
+ await recalculateLeafKpiScores(kpiRepository, kpiValueRepository, leafKpis, orgScope, targetDate);
259
+ // 2. Delete existing parent KPI values (not leaf values - they are base data)
260
+ console.log('\n🗑️ Step 2: Deleting existing parent KPI values...');
261
+ const parentKpiIds = parentKpis.map((kpi) => kpi.id);
262
+ let deleteQuery = kpiValueRepository
263
+ .createQueryBuilder('kpiValue')
264
+ .delete()
265
+ .where('kpi_id IN (:...kpiIds)', { kpiIds: parentKpiIds })
266
+ .andWhere('domain_id = (SELECT id FROM domains WHERE name = :domainName)', { domainName: 'SYSTEM' });
267
+ if (orgScope) {
268
+ deleteQuery = deleteQuery.andWhere('"group" = :orgScope', { orgScope });
269
+ }
270
+ if (targetDate) {
271
+ deleteQuery = deleteQuery.andWhere('value_date = :targetDate', { targetDate });
272
+ }
273
+ const deleteResult = await deleteQuery.execute();
274
+ console.log(`✅ Deleted ${deleteResult.affected || 0} existing parent KPI values`);
275
+ let totalProcessed = 0;
276
+ let totalCreated = 0;
277
+ let errorCount = 0;
278
+ // 3. Get all distinct (group, valueDate) combinations from existing KPI values
279
+ console.log('\n📍 Step 3: Creating parent KPI values from recalculated leaf scores...');
280
+ let groupDateCombinations = [];
281
+ if (orgScope && targetDate) {
282
+ groupDateCombinations = [{ group: orgScope, valueDate: targetDate }];
283
+ }
284
+ else if (orgScope) {
285
+ // Get all dates for specific group
286
+ const dates = await kpiValueRepository.query(`
287
+ SELECT DISTINCT "value_date"
288
+ FROM kpi_values
289
+ WHERE "group" = $1 AND "group" IS NOT NULL
290
+ ORDER BY "value_date"
291
+ `, [orgScope]);
292
+ groupDateCombinations = dates.map((row) => ({ group: orgScope, valueDate: row.value_date }));
293
+ }
294
+ else {
295
+ // Get all distinct (group, valueDate) combinations
296
+ const combinations = await kpiValueRepository.query(`
297
+ SELECT DISTINCT "group", "value_date"
298
+ FROM kpi_values
299
+ WHERE "group" IS NOT NULL AND "group" != ''
300
+ ORDER BY "group", "value_date"
301
+ `);
302
+ groupDateCombinations = combinations.map((row) => ({ group: row.group, valueDate: row.value_date }));
303
+ }
304
+ console.log(` Found ${groupDateCombinations.length} distinct (group, date) combinations`);
305
+ // Process each (group, date) combination
306
+ for (const { group: currentGroup, valueDate } of groupDateCombinations) {
307
+ console.log(`\\n🏢 Processing Group: ${currentGroup}, Date: ${valueDate}`);
308
+ // Process each parent KPI from deepest to shallowest for this group and date
309
+ for (const parentKpi of sortedParentKpis) {
310
+ try {
311
+ console.log(`\\n📄 Processing Parent KPI: ${parentKpi.name} (Level ${parentKpi.__level || '?'}) - Group: ${currentGroup}, Date: ${valueDate}`);
312
+ // Get child KPIs
313
+ const childKpis = allKpis.filter(kpi => { var _a; return ((_a = kpi.parent) === null || _a === void 0 ? void 0 : _a.id) === parentKpi.id; });
314
+ if (childKpis.length === 0) {
315
+ console.log(` ⚠️ No child KPIs found, skipping`);
316
+ continue;
317
+ }
318
+ console.log(` 👶 Found ${childKpis.length} child KPIs`);
319
+ // Get child KPI values/scores for the specific group and date
320
+ const childValues = await getChildKpiValues(kpiValueRepository, childKpis, valueDate, version, currentGroup);
321
+ if (childValues.length === 0) {
322
+ console.log(` ⚠️ No child KPI values found for date ${valueDate} and group ${currentGroup}, skipping`);
323
+ continue;
324
+ }
325
+ console.log(` 📊 Found values for ${childValues.length}/${childKpis.length} child KPIs`);
326
+ // Calculate parent KPI value and score
327
+ let calculatedValue;
328
+ let calculatedScore;
329
+ // 1. If parent KPI has formula, use it to calculate
330
+ if (parentKpi.formula) {
331
+ try {
332
+ console.log(` 📐 Using formula: ${parentKpi.formula}`);
333
+ const result = await calculateParentKpiWithFormula(parentKpi, childValues, childKpis);
334
+ calculatedValue = result.value;
335
+ calculatedScore = result.score;
336
+ console.log(` 🧮 Formula result: value=${calculatedValue.toFixed(4)}, score=${calculatedScore.toFixed(4)}`);
337
+ }
338
+ catch (formulaError) {
339
+ console.log(` ⚠️ Formula evaluation failed: ${formulaError.message}, falling back to weighted average`);
340
+ const { weightedValue, weightedScore } = calculateWeightedAverage(childValues, childKpis);
341
+ calculatedValue = weightedValue;
342
+ calculatedScore = weightedScore;
343
+ }
344
+ }
345
+ else {
346
+ // 2. If no formula, use weighted average
347
+ console.log(` ⚖️ Using weighted average`);
348
+ const { weightedValue, weightedScore } = calculateWeightedAverage(childValues, childKpis);
349
+ calculatedValue = weightedValue;
350
+ calculatedScore = weightedScore;
351
+ console.log(` 🧮 Weighted average: value=${calculatedValue.toFixed(4)}, score=${calculatedScore.toFixed(4)}`);
352
+ }
353
+ // Create new parent KPI value (old values already deleted)
354
+ // Use latest KPI version (parentKpi.version) not the old value's version
355
+ const newKpiValue = {
356
+ kpi: parentKpi,
357
+ value: calculatedValue,
358
+ score: calculatedScore,
359
+ valueDate: valueDate,
360
+ version: parentKpi.version || 1, // Use latest KPI version
361
+ group: currentGroup,
362
+ kpiScope: currentGroup, // Set kpiScope same as group
363
+ domain: systemDomain, // Set domain entity
364
+ inputType: 'AUTO', // Set inputType to AUTO
365
+ createdAt: new Date(),
366
+ updatedAt: new Date()
367
+ };
368
+ try {
369
+ const savedValue = await kpiValueRepository.save(newKpiValue);
370
+ totalCreated++;
371
+ console.log(` ✨ Created: value=${calculatedValue.toFixed(4)}, score=${calculatedScore.toFixed(4)}, version=${parentKpi.version || 1}, id=${savedValue.id}`);
372
+ }
373
+ catch (saveError) {
374
+ console.log(` ❌ Save failed: ${saveError.message}`);
375
+ errorCount++;
376
+ }
377
+ totalProcessed++;
378
+ }
379
+ catch (kpiError) {
380
+ console.log(` ❌ Error processing KPI ${parentKpi.name}: ${kpiError.message}`);
381
+ errorCount++;
382
+ }
383
+ }
384
+ }
385
+ console.log(`\\n🎉 Parent KPI Value Propagation Complete!`);
386
+ console.log(` 📊 Parent KPIs processed: ${totalProcessed}`);
387
+ console.log(` ✨ Values created: ${totalCreated}`);
388
+ console.log(` ❌ Errors: ${errorCount}`);
389
+ if (errorCount > 0) {
390
+ console.log(`\\n⚠️ Some parent KPIs had errors. Check the logs above for details.`);
391
+ }
392
+ }
393
+ catch (error) {
394
+ console.error(`❌ Fatal error during parent KPI value propagation:`, error);
395
+ throw error;
396
+ }
397
+ finally {
398
+ if (connection) {
399
+ await connection.close();
400
+ console.log('🔌 Database connection closed');
401
+ }
402
+ }
403
+ }
404
+ /**
405
+ * Sort KPIs by hierarchy level (deepest first for bottom-up processing)
406
+ */
407
+ async function sortKpisByLevel(parentKpis, allKpis) {
408
+ // Calculate hierarchy level for each KPI
409
+ const kpiLevels = new Map();
410
+ function calculateLevel(kpi) {
411
+ if (kpiLevels.has(kpi.id)) {
412
+ return kpiLevels.get(kpi.id);
413
+ }
414
+ if (!kpi.parent) {
415
+ kpiLevels.set(kpi.id, 0);
416
+ return 0;
417
+ }
418
+ const parentKpi = allKpis.find(k => { var _a; return k.id === ((_a = kpi.parent) === null || _a === void 0 ? void 0 : _a.id); });
419
+ if (!parentKpi) {
420
+ kpiLevels.set(kpi.id, 1);
421
+ return 1;
422
+ }
423
+ const level = calculateLevel(parentKpi) + 1;
424
+ kpiLevels.set(kpi.id, level);
425
+ return level;
426
+ }
427
+ // Calculate levels for all parent KPIs
428
+ parentKpis.forEach(kpi => {
429
+ const level = calculateLevel(kpi);
430
+ kpi.__level = level;
431
+ });
432
+ // Sort by level (deepest first)
433
+ return parentKpis.sort((a, b) => (b.__level || 0) - (a.__level || 0));
434
+ }
435
+ /**
436
+ * Get child KPI values for a specific date
437
+ * Note: Version filter is NOT applied - uses latest available child KPI values regardless of version
438
+ * This matches the recalculateProjectKpiValues logic in kpi-metric-value-mutation.ts
439
+ */
440
+ async function getChildKpiValues(kpiValueRepository, childKpis, valueDate, version, orgScope) {
441
+ const childKpiIds = childKpis.map((kpi) => kpi.id);
442
+ let valuesQuery = kpiValueRepository
443
+ .createQueryBuilder('kpiValue')
444
+ .leftJoinAndSelect('kpiValue.kpi', 'kpi')
445
+ .where('kpi.id IN (:...kpiIds)', { kpiIds: childKpiIds })
446
+ .andWhere('kpiValue.valueDate = :valueDate', { valueDate })
447
+ .andWhere('kpiValue.score IS NOT NULL');
448
+ // Version filter is intentionally removed to use latest available child KPI values
449
+ // This ensures we calculate parent KPIs based on the most recent child data
450
+ // Filter by group scope
451
+ if (orgScope) {
452
+ valuesQuery = valuesQuery.andWhere('kpiValue.group = :groupScope', { groupScope: orgScope });
453
+ }
454
+ const values = await valuesQuery.getMany();
455
+ return values;
456
+ }
457
+ /**
458
+ * Recalculate leaf KPI scores using latest KPI grades lookup table
459
+ */
460
+ async function recalculateLeafKpiScores(kpiRepository, kpiValueRepository, leafKpis, orgScope, targetDate) {
461
+ var _a;
462
+ let totalRecalculated = 0;
463
+ let errorCount = 0;
464
+ for (const leafKpi of leafKpis) {
465
+ try {
466
+ // Get latest version of this KPI with grades
467
+ const latestKpi = await kpiRepository.findOne({
468
+ where: { id: leafKpi.id },
469
+ order: { version: 'DESC' }
470
+ });
471
+ if (!latestKpi) {
472
+ console.log(` ⚠️ Latest KPI not found for ${leafKpi.name}`);
473
+ continue;
474
+ }
475
+ // Get all leaf KPI values that need score recalculation
476
+ let valuesQuery = kpiValueRepository
477
+ .createQueryBuilder('kpiValue')
478
+ .where('kpiValue.kpi_id = :kpiId', { kpiId: leafKpi.id })
479
+ .andWhere('kpiValue.value IS NOT NULL');
480
+ if (orgScope) {
481
+ valuesQuery = valuesQuery.andWhere('kpiValue.group = :orgScope', { orgScope });
482
+ }
483
+ if (targetDate) {
484
+ valuesQuery = valuesQuery.andWhere('kpiValue.value_date = :targetDate', { targetDate });
485
+ }
486
+ const kpiValues = await valuesQuery.getMany();
487
+ if (kpiValues.length === 0) {
488
+ continue;
489
+ }
490
+ // 2D lookup 여부 확인
491
+ const is2D = latestKpi.grades && !Array.isArray(latestKpi.grades) && latestKpi.grades.type === 'PROGRESS_DEVIATION_LOOKUP';
492
+ const progressRateCache = new Map();
493
+ let projectRepo = null;
494
+ if (is2D) {
495
+ try {
496
+ const { Project } = require('@dssp/project');
497
+ const { getRepository: getRepo } = require('@things-factory/shell');
498
+ projectRepo = getRepo(Project);
499
+ }
500
+ catch (_b) {
501
+ console.log(` ⚠️ @dssp/project not found — using default progressRate for ${leafKpi.name}`);
502
+ }
503
+ }
504
+ // Recalculate score for each value using latest KPI grades
505
+ for (const kpiValue of kpiValues) {
506
+ const oldScore = kpiValue.score;
507
+ let progressRate;
508
+ if (is2D && kpiValue.group) {
509
+ if (progressRateCache.has(kpiValue.group)) {
510
+ progressRate = progressRateCache.get(kpiValue.group);
511
+ }
512
+ else if (projectRepo) {
513
+ const project = await projectRepo.findOne({ where: { id: kpiValue.group } });
514
+ progressRate = (_a = project === null || project === void 0 ? void 0 : project.totalProgress) !== null && _a !== void 0 ? _a : 50;
515
+ progressRateCache.set(kpiValue.group, progressRate);
516
+ }
517
+ }
518
+ const newScore = calculateScoreFromGrades(latestKpi, kpiValue.value, progressRate);
519
+ if (newScore !== null && newScore !== oldScore) {
520
+ kpiValue.score = newScore;
521
+ kpiValue.version = latestKpi.version || 1;
522
+ await kpiValueRepository.save(kpiValue);
523
+ totalRecalculated++;
524
+ console.log(` ✅ ${leafKpi.name}: score ${(oldScore === null || oldScore === void 0 ? void 0 : oldScore.toFixed(4)) || 'null'} → ${newScore.toFixed(4)} (version ${latestKpi.version || 1})`);
525
+ }
526
+ }
527
+ }
528
+ catch (error) {
529
+ console.log(` ❌ Error recalculating scores for ${leafKpi.name}: ${error.message}`);
530
+ errorCount++;
531
+ }
532
+ }
533
+ console.log(`\n 📊 Leaf scores recalculated: ${totalRecalculated}, Errors: ${errorCount}`);
534
+ }
535
+ /**
536
+ * Calculate score from KPI grades lookup table
537
+ * 1D (기존 배열) 및 2D (X13 공정률×편차) lookup 모두 지원
538
+ */
539
+ function calculateScoreFromGrades(kpi, value, progressRate) {
540
+ if (!kpi.grades) {
541
+ return null;
542
+ }
543
+ // 2D lookup table 처리 (X13: 공정률 × 편차 → 점수)
544
+ if (!Array.isArray(kpi.grades) && kpi.grades.type === 'PROGRESS_DEVIATION_LOOKUP') {
545
+ return calculate2DScore(value, kpi.grades, progressRate !== null && progressRate !== void 0 ? progressRate : 50);
546
+ }
547
+ // 1D lookup (기존)
548
+ if (!Array.isArray(kpi.grades) || kpi.grades.length === 0) {
549
+ return null;
550
+ }
551
+ const grade = kpi.grades.find((g) => value >= g.minValue && value <= g.maxValue);
552
+ if (!grade) {
553
+ return null;
554
+ }
555
+ return grade.score;
556
+ }
557
+ /**
558
+ * 2D lookup table에서 점수 계산 (X13: 공정률 × 편차 → 점수 1~5)
559
+ */
560
+ function calculate2DScore(deviationValue, grades, progressRate) {
561
+ if (!grades.rows || !Array.isArray(grades.rows))
562
+ return null;
563
+ const progressIndex = Math.min(99, Math.max(0, Math.floor(progressRate)));
564
+ const row = grades.rows.find((r) => r.progressRate === progressIndex);
565
+ if (!row)
566
+ return null;
567
+ if (deviationValue < row.boundary5to4)
568
+ return 5;
569
+ if (deviationValue < row.boundary4to3)
570
+ return 4;
571
+ if (deviationValue < row.boundary3to2)
572
+ return 3;
573
+ if (deviationValue < row.boundary2to1)
574
+ return 2;
575
+ return 1;
576
+ }
577
+ /**
578
+ * Calculate parent KPI value using formula
579
+ * This matches the logic in kpi-metric-value-mutation.ts recalculateParentKpiValue
580
+ */
581
+ async function calculateParentKpiWithFormula(parentKpi, childValues, childKpis) {
582
+ // Import formula evaluation functions
583
+ const { parseFormula, evaluateFormula, builtinFunctions } = require('@things-factory/kpi');
584
+ // Create child KPI name -> score mapping
585
+ const childKpiScoreMap = {};
586
+ childValues.forEach(kpiValue => {
587
+ var _a;
588
+ const childKpi = childKpis.find((k) => k.id === kpiValue.kpi.id);
589
+ if (childKpi) {
590
+ childKpiScoreMap[childKpi.name] = (_a = kpiValue.score) !== null && _a !== void 0 ? _a : 0;
591
+ }
592
+ });
593
+ // Create provider for formula evaluation
594
+ const provider = {
595
+ get: async (name) => {
596
+ const value = childKpiScoreMap[name];
597
+ if (value === undefined) {
598
+ throw new Error(`Child KPI '${name}' not found in parent KPI formula`);
599
+ }
600
+ return value;
601
+ }
602
+ };
603
+ // Parse and evaluate formula
604
+ const ast = parseFormula(parentKpi.formula);
605
+ const evalContext = { functions: builtinFunctions, provider };
606
+ const result = await evaluateFormula(ast, evalContext);
607
+ return {
608
+ value: result,
609
+ score: result
610
+ };
611
+ }
612
+ /**
613
+ * Calculate weighted average from child KPI values
614
+ */
615
+ function calculateWeightedAverage(childValues, childKpis) {
616
+ let totalWeightedValue = 0;
617
+ let totalWeightedScore = 0;
618
+ let totalWeight = 0;
619
+ for (const value of childValues) {
620
+ const childKpi = childKpis.find(kpi => kpi.id === value.kpi.id);
621
+ const weight = (childKpi === null || childKpi === void 0 ? void 0 : childKpi.weight) || 1;
622
+ totalWeightedValue += value.score * weight;
623
+ totalWeightedScore += value.score * weight;
624
+ totalWeight += weight;
625
+ }
626
+ if (totalWeight === 0) {
627
+ return { weightedValue: 0, weightedScore: 0 };
628
+ }
629
+ return {
630
+ weightedValue: totalWeightedValue / totalWeight,
631
+ weightedScore: totalWeightedScore / totalWeight
632
+ };
633
+ }
634
+ /**
635
+ * List parent KPIs and their hierarchy
636
+ */
637
+ async function listParentKpiHierarchy() {
638
+ // Set NODE_ENV if not set
639
+ if (!process.env.NODE_ENV) {
640
+ process.env.NODE_ENV = 'development';
641
+ }
642
+ // Initialize Things-Factory environment
643
+ const { config } = require('@things-factory/env');
644
+ const ormconfig = require('@things-factory/shell/dist-server/initializers/ormconfig.js');
645
+ const connectionConfig = config.get('ormconfig');
646
+ let connection;
647
+ try {
648
+ console.log('🔌 Connecting to database...');
649
+ connection = await (0, typeorm_1.createConnection)(Object.assign(Object.assign(Object.assign({}, ormconfig), connectionConfig), { logging: false }));
650
+ const { addDataSource } = require('@things-factory/shell');
651
+ addDataSource('default', connection);
652
+ const { getRepository } = require('@things-factory/shell');
653
+ const { Kpi } = require('@things-factory/kpi');
654
+ const kpiRepository = getRepository(Kpi);
655
+ // Get all KPIs with hierarchy
656
+ const kpis = await kpiRepository
657
+ .createQueryBuilder('kpi')
658
+ .leftJoinAndSelect('kpi.domain', 'domain')
659
+ .leftJoinAndSelect('kpi.parent', 'parent')
660
+ .leftJoinAndSelect('kpi.children', 'children')
661
+ .where('domain.name = :domainName', { domainName: 'SYSTEM' })
662
+ .getMany();
663
+ const parentKpis = kpis.filter(kpi => !kpi.isLeaf);
664
+ const sortedParentKpis = await sortKpisByLevel(parentKpis, kpis);
665
+ console.log(`📁 Parent KPI Hierarchy (${sortedParentKpis.length} parents):\\n`);
666
+ if (sortedParentKpis.length === 0) {
667
+ console.log(' No parent KPIs found.');
668
+ return;
669
+ }
670
+ sortedParentKpis.forEach((kpi, index) => {
671
+ const level = kpi.__level || 0;
672
+ const indent = ' '.repeat(level);
673
+ const childCount = kpis.filter(k => { var _a; return ((_a = k.parent) === null || _a === void 0 ? void 0 : _a.id) === kpi.id; }).length;
674
+ const weight = kpi.weight || 1;
675
+ console.log(` ${(index + 1).toString().padStart(2)}: ${indent}${kpi.name} (L${level}, ${childCount} children, weight: ${weight})`);
676
+ });
677
+ console.log(`\\n📈 Total: ${sortedParentKpis.length} parent KPIs`);
678
+ }
679
+ catch (error) {
680
+ console.error('❌ Error listing parent KPIs:', error);
681
+ }
682
+ finally {
683
+ if (connection) {
684
+ await connection.close();
685
+ }
686
+ }
687
+ }
688
+ /**
689
+ * CLI execution when called directly
690
+ */
691
+ async function main() {
692
+ const args = process.argv.slice(2);
693
+ try {
694
+ if (args.includes('--help') || args.includes('-h')) {
695
+ console.log(`
696
+ 🎯 Parent KPI Value Propagator
697
+
698
+ Usage:
699
+ ts-node server/scripts/propagate-parent-kpi-values.ts [options] [kpiName]
700
+
701
+ Options:
702
+ --list, -l List parent KPIs and their hierarchy
703
+ --force, -f Force recalculate all values (not just missing ones)
704
+ --date YYYY-MM-DD Target date for value calculation (default: today)
705
+ --org orgScope Organization scope filter (default: all orgs)
706
+ --orgs org1,org2 Multiple organization scopes (comma-separated)
707
+ --version N KPI version filter (default: latest version)
708
+ --help, -h Show this help
709
+
710
+ Examples:
711
+ ts-node server/scripts/propagate-parent-kpi-values.ts # Process all parent KPIs for today
712
+ ts-node server/scripts/propagate-parent-kpi-values.ts --force # Recalculate ALL parent values
713
+ ts-node server/scripts/propagate-parent-kpi-values.ts --date 2024-01-15 # Process for specific date
714
+ ts-node server/scripts/propagate-parent-kpi-values.ts --org "PROJECT-123" # Process for specific org
715
+ ts-node server/scripts/propagate-parent-kpi-values.ts --orgs "ID1,ID2,ID3" # Process multiple orgs
716
+ ts-node server/scripts/propagate-parent-kpi-values.ts --version 2 # Process for specific version
717
+ ts-node server/scripts/propagate-parent-kpi-values.ts "상위 KPI명" # Process specific parent KPI
718
+ ts-node server/scripts/propagate-parent-kpi-values.ts --list # List parent KPI hierarchy
719
+
720
+ Note: Processes parent KPIs from deepest level to root, calculating weighted averages.
721
+ Child KPI scores and weights are used to calculate parent values.
722
+ --orgs option processes multiple orgs with single initialization (faster).
723
+ `);
724
+ return;
725
+ }
726
+ if (args.includes('--list') || args.includes('-l')) {
727
+ await listParentKpiHierarchy();
728
+ return;
729
+ }
730
+ const forceRecalculate = args.includes('--force') || args.includes('-f');
731
+ const dateIndex = args.indexOf('--date');
732
+ const targetDate = dateIndex !== -1 && dateIndex + 1 < args.length ? args[dateIndex + 1] : undefined;
733
+ const orgIndex = args.indexOf('--org');
734
+ const orgScope = orgIndex !== -1 && orgIndex + 1 < args.length ? args[orgIndex + 1] : undefined;
735
+ const orgsIndex = args.indexOf('--orgs');
736
+ const orgScopesString = orgsIndex !== -1 && orgsIndex + 1 < args.length ? args[orgsIndex + 1] : undefined;
737
+ const orgScopes = orgScopesString ? orgScopesString.split(',') : undefined;
738
+ const versionIndex = args.indexOf('--version');
739
+ const version = versionIndex !== -1 && versionIndex + 1 < args.length ? parseInt(args[versionIndex + 1]) : undefined;
740
+ const specificKpiName = args.find(arg => !arg.startsWith('--') &&
741
+ arg !== targetDate &&
742
+ arg !== orgScope &&
743
+ arg !== orgScopesString &&
744
+ (version === undefined || arg !== version.toString()));
745
+ // Use batch processing if multiple orgs specified
746
+ if (orgScopes && orgScopes.length > 0) {
747
+ console.log(`🎯 Propagating values for all parent KPIs (Batch mode)`);
748
+ console.log(` Mode: ${forceRecalculate ? 'Force recalculate ALL' : 'Only missing values'}`);
749
+ console.log(` Date: ${targetDate || 'today'}`);
750
+ console.log(` Orgs: ${orgScopes.length} organizations`);
751
+ console.log(` Version: ${version || 'latest'}`);
752
+ await propagateParentKpiValuesBatch(orgScopes, specificKpiName, forceRecalculate, targetDate, version);
753
+ }
754
+ else if (specificKpiName) {
755
+ console.log(`🎯 Propagating values for specific KPI: ${specificKpiName}`);
756
+ console.log(` Mode: ${forceRecalculate ? 'Force recalculate ALL' : 'Only missing values'}`);
757
+ console.log(` Date: ${targetDate || 'today'}`);
758
+ console.log(` Org: ${orgScope || 'all'}`);
759
+ console.log(` Version: ${version || 'latest'}`);
760
+ await propagateParentKpiValues(specificKpiName, forceRecalculate, targetDate, orgScope, version);
761
+ }
762
+ else {
763
+ console.log(`🎯 Propagating values for all parent KPIs`);
764
+ console.log(` Mode: ${forceRecalculate ? 'Force recalculate ALL' : 'Only missing values'}`);
765
+ console.log(` Date: ${targetDate || 'today'}`);
766
+ console.log(` Org: ${orgScope || 'all'}`);
767
+ console.log(` Version: ${version || 'latest'}`);
768
+ await propagateParentKpiValues(undefined, forceRecalculate, targetDate, orgScope, version);
769
+ }
770
+ console.log('\\n✨ Done!');
771
+ }
772
+ catch (error) {
773
+ console.error('\\n❌ Script failed:', error.message);
774
+ process.exit(1);
775
+ }
776
+ }
777
+ // Handle uncaught errors
778
+ process.on('unhandledRejection', (error) => {
779
+ console.error('❌ Unhandled error:', error.message || error);
780
+ process.exit(1);
781
+ });
782
+ // Run if called directly
783
+ if (require.main === module) {
784
+ main();
785
+ }
786
+ //# sourceMappingURL=propagate-parent-kpi-values.js.map