@dssp/dkpi 1.0.0-y.0 → 1.0.1
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.
- package/KPI-STATISTICS-SERVICE.md +233 -0
- package/_index.html +4 -9
- package/assets/helps/index.md +1 -1
- package/assets/images/project-image.png +0 -0
- package/assets/manifest.json +2 -2
- package/config/config.development.js +2 -1
- package/config/config.production.js +2 -1
- package/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +15 -0
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/components/kpi-2d-lookup-chart.d.ts +50 -0
- package/dist-client/components/kpi-2d-lookup-chart.js +263 -0
- package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -0
- package/dist-client/components/kpi-boxplot-chart.d.ts +31 -0
- package/dist-client/components/kpi-boxplot-chart.js +306 -0
- package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
- package/dist-client/components/kpi-lookup-chart.d.ts +58 -0
- package/dist-client/components/kpi-lookup-chart.js +464 -0
- package/dist-client/components/kpi-lookup-chart.js.map +1 -0
- package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
- package/dist-client/components/kpi-mini-trend-chart.js +180 -0
- package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
- package/dist-client/components/kpi-radar-chart.d.ts +17 -0
- package/dist-client/components/kpi-radar-chart.js +259 -0
- package/dist-client/components/kpi-radar-chart.js.map +1 -0
- package/dist-client/components/kpi-single-boxplot-chart.d.ts +14 -2
- package/dist-client/components/kpi-single-boxplot-chart.js +237 -137
- package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -1
- package/dist-client/components/kpi-step-lookup-chart.d.ts +33 -0
- package/dist-client/components/kpi-step-lookup-chart.js +181 -0
- package/dist-client/components/kpi-step-lookup-chart.js.map +1 -0
- package/dist-client/components/kpi-trend-chart.d.ts +25 -0
- package/dist-client/components/kpi-trend-chart.js +241 -0
- package/dist-client/components/kpi-trend-chart.js.map +1 -0
- package/dist-client/entries/auth/checkin.d.ts +38 -0
- package/dist-client/entries/auth/checkin.js +551 -0
- package/dist-client/entries/auth/checkin.js.map +1 -0
- package/dist-client/google-map/common-google-map.d.ts +35 -0
- package/dist-client/google-map/common-google-map.js +349 -0
- package/dist-client/google-map/common-google-map.js.map +1 -0
- package/dist-client/google-map/google-map-loader.d.ts +6 -0
- package/dist-client/google-map/google-map-loader.js +23 -0
- package/dist-client/google-map/google-map-loader.js.map +1 -0
- package/dist-client/pages/component/project-update-header.d.ts +1 -0
- package/dist-client/pages/component/project-update-header.js +127 -0
- package/dist-client/pages/component/project-update-header.js.map +1 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-list-page.d.ts +22 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js +57 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js.map +1 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +47 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +393 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
- package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.d.ts +20 -0
- package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js +445 -0
- package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js.map +1 -0
- package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +18 -0
- package/dist-client/pages/kpi-admin/kpi-system-guide.js +696 -0
- package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +18 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +259 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +22 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +346 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +26 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +433 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +46 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +945 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +34 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +238 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +37 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +689 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +57 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +866 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +63 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1097 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
- package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
- package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
- package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
- package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
- package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
- package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
- package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
- package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -2
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +1 -13
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +1 -2
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +20 -11
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +1 -2
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +1 -2
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +1 -2
- package/dist-client/pages/kpi-value/kpi-value-list-page.js +22 -33
- package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +56 -0
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +609 -67
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +15 -3
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +180 -65
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +13 -4
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +213 -91
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +51 -0
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +693 -0
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -0
- package/dist-client/pages/sv-project-complete.d.ts +12 -3
- package/dist-client/pages/sv-project-complete.js +175 -29
- package/dist-client/pages/sv-project-complete.js.map +1 -1
- package/dist-client/pages/sv-project-completed-list.d.ts +3 -0
- package/dist-client/pages/sv-project-completed-list.js +81 -22
- package/dist-client/pages/sv-project-completed-list.js.map +1 -1
- package/dist-client/pages/sv-project-detail.d.ts +60 -0
- package/dist-client/pages/sv-project-detail.js +1092 -85
- package/dist-client/pages/sv-project-detail.js.map +1 -1
- package/dist-client/pages/sv-project-list.d.ts +20 -2
- package/dist-client/pages/sv-project-list.js +180 -30
- package/dist-client/pages/sv-project-list.js.map +1 -1
- package/dist-client/pages/sv-project-update.d.ts +91 -0
- package/dist-client/pages/sv-project-update.js +1150 -0
- package/dist-client/pages/sv-project-update.js.map +1 -0
- package/dist-client/pages/sv-user-management.d.ts +1 -0
- package/dist-client/pages/sv-user-management.js +5 -0
- package/dist-client/pages/sv-user-management.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +29 -2
- package/dist-client/route.js.map +1 -1
- package/dist-client/shared/complete-api.d.ts +45 -5
- package/dist-client/shared/complete-api.js +177 -28
- package/dist-client/shared/complete-api.js.map +1 -1
- package/dist-client/shared/func.d.ts +1 -1
- package/dist-client/shared/func.js.map +1 -1
- package/dist-client/shared/geo-group-mapping.d.ts +25 -0
- package/dist-client/shared/geo-group-mapping.js +189 -0
- package/dist-client/shared/geo-group-mapping.js.map +1 -0
- package/dist-client/shared/integration-fetch.d.ts +35 -0
- package/dist-client/shared/integration-fetch.js +53 -0
- package/dist-client/shared/integration-fetch.js.map +1 -0
- package/dist-client/shared/kpi-project-sync.d.ts +16 -0
- package/dist-client/shared/kpi-project-sync.js +129 -0
- package/dist-client/shared/kpi-project-sync.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/menu-tools.d.ts +11 -3
- package/dist-client/viewparts/menu-tools.js +126 -75
- package/dist-client/viewparts/menu-tools.js.map +1 -1
- package/dist-server/scripts/calculate-kpi-scores.js +65 -3
- package/dist-server/scripts/calculate-kpi-scores.js.map +1 -1
- package/dist-server/scripts/load-grade-data-migration.d.ts +4 -0
- package/dist-server/scripts/load-grade-data-migration.js +95 -10
- package/dist-server/scripts/load-grade-data-migration.js.map +1 -1
- package/dist-server/scripts/propagate-parent-kpi-values.d.ts +4 -0
- package/dist-server/scripts/propagate-parent-kpi-values.js +423 -77
- package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -1
- package/dist-server/scripts/recalculate-by-project-name.d.ts +2 -0
- package/dist-server/scripts/recalculate-by-project-name.js +72 -0
- package/dist-server/scripts/recalculate-by-project-name.js.map +1 -0
- package/dist-server/service/index.d.ts +1 -3
- package/dist-server/service/index.js +3 -4
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +192 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +993 -38
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +9 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +33 -8
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -1
- package/dist-server/service/kpi-stat/index.d.ts +4 -0
- package/dist-server/service/kpi-stat/index.js +8 -0
- package/dist-server/service/kpi-stat/index.js.map +1 -0
- package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +13 -0
- package/dist-server/service/kpi-stat/kpi-stat-query.js +751 -0
- package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
- package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +32 -0
- package/dist-server/service/kpi-stat/kpi-stat-types.js +180 -0
- package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
- package/dist-server/service/kpi-value/kpi-value-query.d.ts +2 -1
- package/dist-server/service/kpi-value/kpi-value-query.js +49 -6
- package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/kpi-module-service-tests.md +1286 -0
- package/kpi-module-test-report.md +676 -0
- package/kpi-module-unit-test-detailed-report.md +925 -0
- package/kpi-module-unit-tests-detailed.md +1452 -0
- package/openapi/unstable.yaml +2 -2
- package/package.json +58 -54
- package/recalculate-batch.sh +64 -0
- package/recalculate-projects-range.sh +98 -0
- package/schema.graphql +1159 -55
- package/things-factory.config.js +6 -1
- package/translations/en.json +5 -1
- package/translations/ko.json +5 -1
- package/views/auth-page.html +4 -5
- package/views/public/home.html +5 -6
|
@@ -1,9 +1,176 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.propagateParentKpiValuesBatch = propagateParentKpiValuesBatch;
|
|
4
5
|
exports.propagateParentKpiValues = propagateParentKpiValues;
|
|
5
6
|
exports.listParentKpiHierarchy = listParentKpiHierarchy;
|
|
6
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
|
+
}
|
|
7
174
|
/**
|
|
8
175
|
* Propagate parent KPI values based on child KPI scores and weights
|
|
9
176
|
* Processes hierarchy from leaf to root, calculating weighted averages
|
|
@@ -85,11 +252,31 @@ async function propagateParentKpiValues(specificKpiName, forceRecalculate = fals
|
|
|
85
252
|
// Sort parent KPIs by hierarchy level (deepest first for bottom-up processing)
|
|
86
253
|
const sortedParentKpis = await sortKpisByLevel(parentKpis, allKpis);
|
|
87
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`);
|
|
88
275
|
let totalProcessed = 0;
|
|
89
276
|
let totalCreated = 0;
|
|
90
|
-
let totalUpdated = 0;
|
|
91
277
|
let errorCount = 0;
|
|
92
|
-
// Get all distinct (group, valueDate) combinations from existing KPI values
|
|
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...');
|
|
93
280
|
let groupDateCombinations = [];
|
|
94
281
|
if (orgScope && targetDate) {
|
|
95
282
|
groupDateCombinations = [{ group: orgScope, valueDate: targetDate }];
|
|
@@ -97,24 +284,24 @@ async function propagateParentKpiValues(specificKpiName, forceRecalculate = fals
|
|
|
97
284
|
else if (orgScope) {
|
|
98
285
|
// Get all dates for specific group
|
|
99
286
|
const dates = await kpiValueRepository.query(`
|
|
100
|
-
SELECT DISTINCT "value_date"
|
|
101
|
-
FROM kpi_values
|
|
287
|
+
SELECT DISTINCT "value_date"
|
|
288
|
+
FROM kpi_values
|
|
102
289
|
WHERE "group" = $1 AND "group" IS NOT NULL
|
|
103
290
|
ORDER BY "value_date"
|
|
104
291
|
`, [orgScope]);
|
|
105
|
-
groupDateCombinations = dates.map(row => ({ group: orgScope, valueDate: row.value_date }));
|
|
292
|
+
groupDateCombinations = dates.map((row) => ({ group: orgScope, valueDate: row.value_date }));
|
|
106
293
|
}
|
|
107
294
|
else {
|
|
108
295
|
// Get all distinct (group, valueDate) combinations
|
|
109
296
|
const combinations = await kpiValueRepository.query(`
|
|
110
|
-
SELECT DISTINCT "group", "value_date"
|
|
111
|
-
FROM kpi_values
|
|
297
|
+
SELECT DISTINCT "group", "value_date"
|
|
298
|
+
FROM kpi_values
|
|
112
299
|
WHERE "group" IS NOT NULL AND "group" != ''
|
|
113
300
|
ORDER BY "group", "value_date"
|
|
114
301
|
`);
|
|
115
|
-
groupDateCombinations = combinations.map(row => ({ group: row.group, valueDate: row.value_date }));
|
|
302
|
+
groupDateCombinations = combinations.map((row) => ({ group: row.group, valueDate: row.value_date }));
|
|
116
303
|
}
|
|
117
|
-
console.log(
|
|
304
|
+
console.log(` Found ${groupDateCombinations.length} distinct (group, date) combinations`);
|
|
118
305
|
// Process each (group, date) combination
|
|
119
306
|
for (const { group: currentGroup, valueDate } of groupDateCombinations) {
|
|
120
307
|
console.log(`\\n🏢 Processing Group: ${currentGroup}, Date: ${valueDate}`);
|
|
@@ -136,71 +323,56 @@ async function propagateParentKpiValues(specificKpiName, forceRecalculate = fals
|
|
|
136
323
|
continue;
|
|
137
324
|
}
|
|
138
325
|
console.log(` 📊 Found values for ${childValues.length}/${childKpis.length} child KPIs`);
|
|
139
|
-
// Calculate
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (version !== undefined) {
|
|
151
|
-
existingValueQuery = existingValueQuery.andWhere('kpiValue.version = :version', { version });
|
|
152
|
-
}
|
|
153
|
-
// Filter by group scope
|
|
154
|
-
existingValueQuery = existingValueQuery.andWhere('kpiValue.group = :groupScope', { groupScope: currentGroup });
|
|
155
|
-
const existingValue = await existingValueQuery.getOne();
|
|
156
|
-
console.log(` 🔍 Checking existing record: ${existingValue ? 'FOUND' : 'NOT FOUND'}`);
|
|
157
|
-
if (existingValue) {
|
|
158
|
-
// Update existing value
|
|
159
|
-
if (forceRecalculate || existingValue.value !== weightedValue || existingValue.score !== weightedScore) {
|
|
160
|
-
const oldValue = existingValue.value;
|
|
161
|
-
const oldScore = existingValue.score;
|
|
162
|
-
existingValue.value = weightedValue;
|
|
163
|
-
existingValue.score = weightedScore;
|
|
164
|
-
existingValue.kpiScope = currentGroup; // Set kpiScope same as group
|
|
165
|
-
existingValue.updatedAt = new Date();
|
|
166
|
-
try {
|
|
167
|
-
await kpiValueRepository.save(existingValue);
|
|
168
|
-
totalUpdated++;
|
|
169
|
-
console.log(` ✅ Updated: ${oldValue.toFixed(4)} → ${weightedValue.toFixed(4)}, score: ${oldScore.toFixed(4)} → ${weightedScore.toFixed(4)}`);
|
|
170
|
-
}
|
|
171
|
-
catch (saveError) {
|
|
172
|
-
console.log(` ❌ Update failed: ${saveError.message}`);
|
|
173
|
-
errorCount++;
|
|
174
|
-
}
|
|
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)}`);
|
|
175
337
|
}
|
|
176
|
-
|
|
177
|
-
console.log(`
|
|
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;
|
|
178
343
|
}
|
|
179
344
|
}
|
|
180
345
|
else {
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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++;
|
|
204
376
|
}
|
|
205
377
|
totalProcessed++;
|
|
206
378
|
}
|
|
@@ -213,7 +385,6 @@ async function propagateParentKpiValues(specificKpiName, forceRecalculate = fals
|
|
|
213
385
|
console.log(`\\n🎉 Parent KPI Value Propagation Complete!`);
|
|
214
386
|
console.log(` 📊 Parent KPIs processed: ${totalProcessed}`);
|
|
215
387
|
console.log(` ✨ Values created: ${totalCreated}`);
|
|
216
|
-
console.log(` ✅ Values updated: ${totalUpdated}`);
|
|
217
388
|
console.log(` ❌ Errors: ${errorCount}`);
|
|
218
389
|
if (errorCount > 0) {
|
|
219
390
|
console.log(`\\n⚠️ Some parent KPIs had errors. Check the logs above for details.`);
|
|
@@ -263,18 +434,19 @@ async function sortKpisByLevel(parentKpis, allKpis) {
|
|
|
263
434
|
}
|
|
264
435
|
/**
|
|
265
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
|
|
266
439
|
*/
|
|
267
440
|
async function getChildKpiValues(kpiValueRepository, childKpis, valueDate, version, orgScope) {
|
|
268
|
-
const childKpiIds = childKpis.map(kpi => kpi.id);
|
|
441
|
+
const childKpiIds = childKpis.map((kpi) => kpi.id);
|
|
269
442
|
let valuesQuery = kpiValueRepository
|
|
270
443
|
.createQueryBuilder('kpiValue')
|
|
271
444
|
.leftJoinAndSelect('kpiValue.kpi', 'kpi')
|
|
272
445
|
.where('kpi.id IN (:...kpiIds)', { kpiIds: childKpiIds })
|
|
273
446
|
.andWhere('kpiValue.valueDate = :valueDate', { valueDate })
|
|
274
447
|
.andWhere('kpiValue.score IS NOT NULL');
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
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
|
|
278
450
|
// Filter by group scope
|
|
279
451
|
if (orgScope) {
|
|
280
452
|
valuesQuery = valuesQuery.andWhere('kpiValue.group = :groupScope', { groupScope: orgScope });
|
|
@@ -282,6 +454,161 @@ async function getChildKpiValues(kpiValueRepository, childKpis, valueDate, versi
|
|
|
282
454
|
const values = await valuesQuery.getMany();
|
|
283
455
|
return values;
|
|
284
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
|
+
}
|
|
285
612
|
/**
|
|
286
613
|
* Calculate weighted average from child KPI values
|
|
287
614
|
*/
|
|
@@ -376,6 +703,7 @@ Options:
|
|
|
376
703
|
--force, -f Force recalculate all values (not just missing ones)
|
|
377
704
|
--date YYYY-MM-DD Target date for value calculation (default: today)
|
|
378
705
|
--org orgScope Organization scope filter (default: all orgs)
|
|
706
|
+
--orgs org1,org2 Multiple organization scopes (comma-separated)
|
|
379
707
|
--version N KPI version filter (default: latest version)
|
|
380
708
|
--help, -h Show this help
|
|
381
709
|
|
|
@@ -384,12 +712,14 @@ Examples:
|
|
|
384
712
|
ts-node server/scripts/propagate-parent-kpi-values.ts --force # Recalculate ALL parent values
|
|
385
713
|
ts-node server/scripts/propagate-parent-kpi-values.ts --date 2024-01-15 # Process for specific date
|
|
386
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
|
|
387
716
|
ts-node server/scripts/propagate-parent-kpi-values.ts --version 2 # Process for specific version
|
|
388
717
|
ts-node server/scripts/propagate-parent-kpi-values.ts "상위 KPI명" # Process specific parent KPI
|
|
389
718
|
ts-node server/scripts/propagate-parent-kpi-values.ts --list # List parent KPI hierarchy
|
|
390
719
|
|
|
391
720
|
Note: Processes parent KPIs from deepest level to root, calculating weighted averages.
|
|
392
721
|
Child KPI scores and weights are used to calculate parent values.
|
|
722
|
+
--orgs option processes multiple orgs with single initialization (faster).
|
|
393
723
|
`);
|
|
394
724
|
return;
|
|
395
725
|
}
|
|
@@ -402,10 +732,26 @@ Note: Processes parent KPIs from deepest level to root, calculating weighted ave
|
|
|
402
732
|
const targetDate = dateIndex !== -1 && dateIndex + 1 < args.length ? args[dateIndex + 1] : undefined;
|
|
403
733
|
const orgIndex = args.indexOf('--org');
|
|
404
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;
|
|
405
738
|
const versionIndex = args.indexOf('--version');
|
|
406
739
|
const version = versionIndex !== -1 && versionIndex + 1 < args.length ? parseInt(args[versionIndex + 1]) : undefined;
|
|
407
|
-
const specificKpiName = args.find(arg => !arg.startsWith('--') &&
|
|
408
|
-
|
|
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) {
|
|
409
755
|
console.log(`🎯 Propagating values for specific KPI: ${specificKpiName}`);
|
|
410
756
|
console.log(` Mode: ${forceRecalculate ? 'Force recalculate ALL' : 'Only missing values'}`);
|
|
411
757
|
console.log(` Date: ${targetDate || 'today'}`);
|