@things-factory/kpi 1.0.0-alpha.5 → 9.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/client/pages/kpi/kpi-list-page.ts +97 -47
- package/client/pages/kpi/kpi-overview-sample.ts +260 -0
- package/client/pages/kpi/kpi-overview.ts +271 -0
- package/client/pages/kpi-category/kpi-category-list-page.ts +39 -50
- package/client/pages/kpi-dashboard/kpi-alert-panel.ts +114 -0
- package/client/pages/kpi-dashboard/kpi-dashboard.ts +285 -0
- package/client/pages/kpi-dashboard/kpi-grade-visualization.ts +75 -0
- package/client/pages/kpi-dashboard/kpi-history-viewer.ts +58 -0
- package/client/pages/kpi-dashboard/kpi-list-summary.ts +100 -0
- package/client/pages/kpi-dashboard/kpi-performance-summary.ts +124 -0
- package/client/pages/kpi-dashboard/kpi-value-entry.ts +78 -0
- package/client/pages/kpi-grade/kpi-grade-list-page.ts +58 -68
- package/client/pages/kpi-history/kpi-history-list-page.ts +146 -0
- package/client/pages/kpi-metric/kpi-metric-list-page.ts +67 -63
- package/client/pages/kpi-value/kpi-value-list-page.ts +73 -72
- package/client/pages/kpi-value/kpi-value-manual-entry-form.ts +168 -0
- package/client/pages/kpi-value/kpi-value-manual-entry-page.ts +173 -0
- package/client/route.ts +35 -0
- package/dist-client/pages/kpi/kpi-importer.js +16 -17
- package/dist-client/pages/kpi/kpi-importer.js.map +1 -1
- package/dist-client/pages/kpi/kpi-list-page.js +128 -68
- package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
- package/dist-client/pages/kpi/kpi-overview-sample.d.ts +36 -0
- package/dist-client/pages/kpi/kpi-overview-sample.js +264 -0
- package/dist-client/pages/kpi/kpi-overview-sample.js.map +1 -0
- package/dist-client/pages/kpi/kpi-overview.d.ts +14 -0
- package/dist-client/pages/kpi/kpi-overview.js +290 -0
- package/dist-client/pages/kpi/kpi-overview.js.map +1 -0
- package/dist-client/pages/kpi-category/kpi-category-importer.js +16 -17
- package/dist-client/pages/kpi-category/kpi-category-importer.js.map +1 -1
- package/dist-client/pages/kpi-category/kpi-category-list-page.js +70 -71
- package/dist-client/pages/kpi-category/kpi-category-list-page.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +128 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +24 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +305 -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 +139 -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-grade/kpi-grade-importer.js +16 -17
- package/dist-client/pages/kpi-grade/kpi-grade-importer.js.map +1 -1
- package/dist-client/pages/kpi-grade/kpi-grade-list-page.js +89 -89
- package/dist-client/pages/kpi-grade/kpi-grade-list-page.js.map +1 -1
- package/dist-client/pages/kpi-history/kpi-history-list-page.d.ts +16 -0
- package/dist-client/pages/kpi-history/kpi-history-list-page.js +155 -0
- package/dist-client/pages/kpi-history/kpi-history-list-page.js.map +1 -0
- package/dist-client/pages/kpi-metric/kpi-metric-importer.js +16 -17
- package/dist-client/pages/kpi-metric/kpi-metric-importer.js.map +1 -1
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +98 -84
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-importer.js +16 -17
- package/dist-client/pages/kpi-value/kpi-value-importer.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-list-page.js +104 -93
- package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-manual-entry-form.d.ts +14 -0
- package/dist-client/pages/kpi-value/kpi-value-manual-entry-form.js +201 -0
- package/dist-client/pages/kpi-value/kpi-value-manual-entry-form.js.map +1 -0
- package/dist-client/pages/kpi-value/kpi-value-manual-entry-page.d.ts +41 -0
- package/dist-client/pages/kpi-value/kpi-value-manual-entry-page.js +180 -0
- package/dist-client/pages/kpi-value/kpi-value-manual-entry-page.js.map +1 -0
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +27 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.d.ts +2 -1
- package/dist-server/index.js +2 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1752188906708-SeedKpiCategory.d.ts +5 -0
- package/dist-server/migrations/1752188906708-SeedKpiCategory.js +56 -0
- package/dist-server/migrations/1752188906708-SeedKpiCategory.js.map +1 -0
- package/dist-server/migrations/1752190849681-SeedKpi.d.ts +5 -0
- package/dist-server/migrations/1752190849681-SeedKpi.js +107 -0
- package/dist-server/migrations/1752190849681-SeedKpi.js.map +1 -0
- package/dist-server/migrations/1752191090459-SeedKpiGrade.d.ts +5 -0
- package/dist-server/migrations/1752191090459-SeedKpiGrade.js +271 -0
- package/dist-server/migrations/1752191090459-SeedKpiGrade.js.map +1 -0
- package/dist-server/migrations/index.d.ts +1 -0
- package/dist-server/migrations/index.js +12 -0
- package/dist-server/migrations/index.js.map +1 -0
- package/dist-server/routes.d.ts +1 -0
- package/dist-server/routes.js +48 -0
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/index.d.ts +3 -4
- package/dist-server/service/index.js +4 -6
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/kpi/aggregate-kpi.d.ts +8 -0
- package/dist-server/service/kpi/aggregate-kpi.js +88 -0
- package/dist-server/service/kpi/aggregate-kpi.js.map +1 -0
- package/dist-server/service/kpi/event-subscriber.d.ts +2 -0
- package/dist-server/service/kpi/event-subscriber.js +10 -0
- package/dist-server/service/kpi/event-subscriber.js.map +1 -1
- package/dist-server/service/kpi/index.d.ts +2 -1
- package/dist-server/service/kpi/kpi-formula.service.d.ts +24 -0
- package/dist-server/service/kpi/kpi-formula.service.js +64 -0
- package/dist-server/service/kpi/kpi-formula.service.js.map +1 -0
- package/dist-server/service/kpi/kpi-history.d.ts +6 -2
- package/dist-server/service/kpi/kpi-history.js +29 -11
- package/dist-server/service/kpi/kpi-history.js.map +1 -1
- package/dist-server/service/kpi/kpi-mutation.d.ts +2 -0
- package/dist-server/service/kpi/kpi-mutation.js +215 -10
- package/dist-server/service/kpi/kpi-mutation.js.map +1 -1
- package/dist-server/service/kpi/kpi-query.d.ts +10 -0
- package/dist-server/service/kpi/kpi-query.js +87 -3
- package/dist-server/service/kpi/kpi-query.js.map +1 -1
- package/dist-server/service/kpi/kpi-type.d.ts +14 -3
- package/dist-server/service/kpi/kpi-type.js +81 -18
- package/dist-server/service/kpi/kpi-type.js.map +1 -1
- package/dist-server/service/kpi/kpi.d.ts +14 -3
- package/dist-server/service/kpi/kpi.js +96 -19
- package/dist-server/service/kpi/kpi.js.map +1 -1
- package/dist-server/service/kpi-alert/index.d.ts +2 -0
- package/dist-server/service/kpi-alert/index.js +6 -0
- package/dist-server/service/kpi-alert/index.js.map +1 -0
- package/dist-server/service/kpi-alert/kpi-alert-query.d.ts +4 -0
- package/dist-server/service/kpi-alert/kpi-alert-query.js +71 -0
- package/dist-server/service/kpi-alert/kpi-alert-query.js.map +1 -0
- package/dist-server/service/kpi-alert/kpi-alert-type.d.ts +8 -0
- package/dist-server/service/kpi-alert/kpi-alert-type.js +33 -0
- package/dist-server/service/kpi-alert/kpi-alert-type.js.map +1 -0
- package/dist-server/service/kpi-category/kpi-category-mutation.d.ts +1 -1
- package/dist-server/service/kpi-category/kpi-category-mutation.js +36 -10
- package/dist-server/service/kpi-category/kpi-category-mutation.js.map +1 -1
- package/dist-server/service/kpi-category/kpi-category-query.d.ts +2 -0
- package/dist-server/service/kpi-category/kpi-category-query.js +18 -2
- package/dist-server/service/kpi-category/kpi-category-query.js.map +1 -1
- package/dist-server/service/kpi-category/kpi-category-type.d.ts +3 -5
- package/dist-server/service/kpi-category/kpi-category-type.js +16 -20
- package/dist-server/service/kpi-category/kpi-category-type.js.map +1 -1
- package/dist-server/service/kpi-category/kpi-category.d.ts +7 -9
- package/dist-server/service/kpi-category/kpi-category.js +40 -38
- package/dist-server/service/kpi-category/kpi-category.js.map +1 -1
- package/dist-server/service/kpi-grade/index.d.ts +1 -2
- package/dist-server/service/kpi-grade/index.js +2 -4
- package/dist-server/service/kpi-grade/index.js.map +1 -1
- package/dist-server/service/kpi-grade/kpi-grade-mutation.d.ts +1 -1
- package/dist-server/service/kpi-grade/kpi-grade-mutation.js +33 -10
- package/dist-server/service/kpi-grade/kpi-grade-mutation.js.map +1 -1
- package/dist-server/service/kpi-grade/kpi-grade-query.js +2 -2
- package/dist-server/service/kpi-grade/kpi-grade-query.js.map +1 -1
- package/dist-server/service/kpi-grade/kpi-grade-type.d.ts +13 -4
- package/dist-server/service/kpi-grade/kpi-grade-type.js +54 -18
- package/dist-server/service/kpi-grade/kpi-grade-type.js.map +1 -1
- package/dist-server/service/kpi-grade/kpi-grade.d.ts +8 -8
- package/dist-server/service/kpi-grade/kpi-grade.js +48 -40
- package/dist-server/service/kpi-grade/kpi-grade.js.map +1 -1
- package/dist-server/service/kpi-metric/aggregate-kpi-metric.d.ts +8 -0
- package/dist-server/service/kpi-metric/aggregate-kpi-metric.js +134 -0
- package/dist-server/service/kpi-metric/aggregate-kpi-metric.js.map +1 -0
- package/dist-server/service/kpi-metric/index.d.ts +1 -2
- package/dist-server/service/kpi-metric/index.js +2 -4
- package/dist-server/service/kpi-metric/index.js.map +1 -1
- package/dist-server/service/kpi-metric/kpi-metric-mutation.d.ts +1 -1
- package/dist-server/service/kpi-metric/kpi-metric-mutation.js +139 -13
- package/dist-server/service/kpi-metric/kpi-metric-mutation.js.map +1 -1
- package/dist-server/service/kpi-metric/kpi-metric-query.js +3 -3
- package/dist-server/service/kpi-metric/kpi-metric-query.js.map +1 -1
- package/dist-server/service/kpi-metric/kpi-metric-type.d.ts +17 -4
- package/dist-server/service/kpi-metric/kpi-metric-type.js +69 -11
- package/dist-server/service/kpi-metric/kpi-metric-type.js.map +1 -1
- package/dist-server/service/kpi-metric/kpi-metric.d.ts +11 -8
- package/dist-server/service/kpi-metric/kpi-metric.js +62 -37
- package/dist-server/service/kpi-metric/kpi-metric.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-mutation.js +66 -11
- package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-query.d.ts +2 -0
- package/dist-server/service/kpi-value/kpi-value-query.js +15 -2
- package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-type.d.ts +19 -10
- package/dist-server/service/kpi-value/kpi-value-type.js +87 -24
- package/dist-server/service/kpi-value/kpi-value-type.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value.d.ts +15 -9
- package/dist-server/service/kpi-value/kpi-value.js +86 -32
- package/dist-server/service/kpi-value/kpi-value.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/implement-plan/01-entity-type-resolver.md +57 -0
- package/implement-plan/02-dataset-integration.md +35 -0
- package/implement-plan/03-kpivalue-automation.md +34 -0
- package/implement-plan/04-version-history.md +33 -0
- package/implement-plan/05-grade-visualization.md +33 -0
- package/implement-plan/06-graphql-frontend.md +30 -0
- package/implement-plan/07-test-docs.md +35 -0
- package/implement-plan/TODO.md +41 -0
- package/package.json +18 -6
- package/server/index.ts +2 -1
- package/server/migrations/1752188906708-SeedKpiCategory.ts +61 -0
- package/server/migrations/1752190849681-SeedKpi.ts +112 -0
- package/server/migrations/1752191090459-SeedKpiGrade.ts +270 -0
- package/server/migrations/index.ts +9 -0
- package/server/routes.ts +55 -0
- package/server/service/index.ts +4 -6
- package/server/service/kpi/aggregate-kpi.ts +82 -0
- package/server/service/kpi/event-subscriber.ts +12 -0
- package/server/service/kpi/kpi-formula.service.ts +63 -0
- package/server/service/kpi/kpi-history.ts +29 -21
- package/server/service/kpi/kpi-mutation.ts +194 -12
- package/server/service/kpi/kpi-query.ts +57 -2
- package/server/service/kpi/kpi-type.ts +72 -19
- package/server/service/kpi/kpi.ts +96 -20
- package/server/service/kpi-alert/index.ts +3 -0
- package/server/service/kpi-alert/kpi-alert-query.ts +59 -0
- package/server/service/kpi-alert/kpi-alert-type.ts +20 -0
- package/server/service/kpi-category/kpi-category-mutation.ts +17 -4
- package/server/service/kpi-category/kpi-category-query.ts +20 -3
- package/server/service/kpi-category/kpi-category-type.ts +17 -19
- package/server/service/kpi-category/kpi-category.ts +39 -37
- package/server/service/kpi-grade/index.ts +2 -4
- package/server/service/kpi-grade/kpi-grade-mutation.ts +13 -4
- package/server/service/kpi-grade/kpi-grade-query.ts +5 -2
- package/server/service/kpi-grade/kpi-grade-type.ts +46 -19
- package/server/service/kpi-grade/kpi-grade.ts +44 -38
- package/server/service/kpi-metric/aggregate-kpi-metric.ts +128 -0
- package/server/service/kpi-metric/index.ts +2 -4
- package/server/service/kpi-metric/kpi-metric-mutation.ts +123 -7
- package/server/service/kpi-metric/kpi-metric-query.ts +9 -3
- package/server/service/kpi-metric/kpi-metric-type.ts +56 -13
- package/server/service/kpi-metric/kpi-metric.ts +55 -35
- package/server/service/kpi-value/kpi-value-mutation.ts +52 -14
- package/server/service/kpi-value/kpi-value-query.ts +12 -2
- package/server/service/kpi-value/kpi-value-type.ts +80 -25
- package/server/service/kpi-value/kpi-value.ts +81 -29
- package/things-factory.config.js +12 -4
- package/translations/en.json +12 -1
- package/translations/ja.json +12 -1
- package/translations/ko.json +12 -1
- package/translations/ms.json +12 -1
- package/translations/zh.json +12 -1
- package/client/pages/kpe-metric/kpe-metric-importer.ts +0 -90
- package/client/pages/kpe-metric/kpe-metric-list-page.ts +0 -398
- package/client/pages/kpi-formula/kpi-formula-importer.ts +0 -90
- package/client/pages/kpi-formula/kpi-formula-list-page.ts +0 -398
- package/client/pages/metric/metric-importer.ts +0 -90
- package/client/pages/metric/metric-list-page.ts +0 -398
- package/dist-client/pages/kpe-metric/kpe-metric-importer.d.ts +0 -23
- package/dist-client/pages/kpe-metric/kpe-metric-importer.js +0 -93
- package/dist-client/pages/kpe-metric/kpe-metric-importer.js.map +0 -1
- package/dist-client/pages/kpe-metric/kpe-metric-list-page.d.ts +0 -66
- package/dist-client/pages/kpe-metric/kpe-metric-list-page.js +0 -370
- package/dist-client/pages/kpe-metric/kpe-metric-list-page.js.map +0 -1
- package/dist-client/pages/kpi-formula/kpi-formula-importer.d.ts +0 -23
- package/dist-client/pages/kpi-formula/kpi-formula-importer.js +0 -93
- package/dist-client/pages/kpi-formula/kpi-formula-importer.js.map +0 -1
- package/dist-client/pages/kpi-formula/kpi-formula-list-page.d.ts +0 -66
- package/dist-client/pages/kpi-formula/kpi-formula-list-page.js +0 -370
- package/dist-client/pages/kpi-formula/kpi-formula-list-page.js.map +0 -1
- package/dist-client/pages/metric/metric-importer.d.ts +0 -23
- package/dist-client/pages/metric/metric-importer.js +0 -93
- package/dist-client/pages/metric/metric-importer.js.map +0 -1
- package/dist-client/pages/metric/metric-list-page.d.ts +0 -66
- package/dist-client/pages/metric/metric-list-page.js +0 -370
- package/dist-client/pages/metric/metric-list-page.js.map +0 -1
- package/dist-server/service/kpi-formula/event-subscriber.d.ts +0 -7
- package/dist-server/service/kpi-formula/event-subscriber.js +0 -21
- package/dist-server/service/kpi-formula/event-subscriber.js.map +0 -1
- package/dist-server/service/kpi-formula/index.d.ts +0 -7
- package/dist-server/service/kpi-formula/index.js +0 -12
- package/dist-server/service/kpi-formula/index.js.map +0 -1
- package/dist-server/service/kpi-formula/kpi-formula-history.d.ts +0 -25
- package/dist-server/service/kpi-formula/kpi-formula-history.js +0 -128
- package/dist-server/service/kpi-formula/kpi-formula-history.js.map +0 -1
- package/dist-server/service/kpi-formula/kpi-formula-mutation.d.ts +0 -10
- package/dist-server/service/kpi-formula/kpi-formula-mutation.js +0 -128
- package/dist-server/service/kpi-formula/kpi-formula-mutation.js.map +0 -1
- package/dist-server/service/kpi-formula/kpi-formula-query.d.ts +0 -11
- package/dist-server/service/kpi-formula/kpi-formula-query.js +0 -79
- package/dist-server/service/kpi-formula/kpi-formula-query.js.map +0 -1
- package/dist-server/service/kpi-formula/kpi-formula-type.d.ts +0 -20
- package/dist-server/service/kpi-formula/kpi-formula-type.js +0 -77
- package/dist-server/service/kpi-formula/kpi-formula-type.js.map +0 -1
- package/dist-server/service/kpi-formula/kpi-formula.d.ts +0 -24
- package/dist-server/service/kpi-formula/kpi-formula.js +0 -109
- package/dist-server/service/kpi-formula/kpi-formula.js.map +0 -1
- package/dist-server/service/kpi-grade/event-subscriber.d.ts +0 -7
- package/dist-server/service/kpi-grade/event-subscriber.js +0 -21
- package/dist-server/service/kpi-grade/event-subscriber.js.map +0 -1
- package/dist-server/service/kpi-grade/kpi-grade-history.d.ts +0 -25
- package/dist-server/service/kpi-grade/kpi-grade-history.js +0 -128
- package/dist-server/service/kpi-grade/kpi-grade-history.js.map +0 -1
- package/dist-server/service/kpi-metric/event-subscriber.d.ts +0 -7
- package/dist-server/service/kpi-metric/event-subscriber.js +0 -21
- package/dist-server/service/kpi-metric/event-subscriber.js.map +0 -1
- package/dist-server/service/kpi-metric/kpi-metric-history.d.ts +0 -25
- package/dist-server/service/kpi-metric/kpi-metric-history.js +0 -128
- package/dist-server/service/kpi-metric/kpi-metric-history.js.map +0 -1
- package/server/service/kpi-formula/event-subscriber.ts +0 -17
- package/server/service/kpi-formula/index.ts +0 -9
- package/server/service/kpi-formula/kpi-formula-history.ts +0 -116
- package/server/service/kpi-formula/kpi-formula-mutation.ts +0 -137
- package/server/service/kpi-formula/kpi-formula-query.ts +0 -48
- package/server/service/kpi-formula/kpi-formula-type.ts +0 -55
- package/server/service/kpi-formula/kpi-formula.ts +0 -95
- package/server/service/kpi-grade/event-subscriber.ts +0 -17
- package/server/service/kpi-grade/kpi-grade-history.ts +0 -116
- package/server/service/kpi-metric/event-subscriber.ts +0 -17
- package/server/service/kpi-metric/kpi-metric-history.ts +0 -116
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from 'typeorm'
|
|
2
|
+
import { Domain, getRepository } from '@things-factory/shell'
|
|
3
|
+
import { KpiGrade } from '../service/kpi-grade/kpi-grade.js'
|
|
4
|
+
import { Kpi } from '../service/kpi/kpi.js'
|
|
5
|
+
|
|
6
|
+
const GRADE_SEED = [
|
|
7
|
+
{
|
|
8
|
+
kpiName: '일정성과 수준 평가',
|
|
9
|
+
version: 1,
|
|
10
|
+
grades: [
|
|
11
|
+
{
|
|
12
|
+
name: '1점',
|
|
13
|
+
minValue: 1,
|
|
14
|
+
maxValue: 1,
|
|
15
|
+
score: 1,
|
|
16
|
+
description: '달성상황(기후, 일반변화 등)과 인근주민 민원 등이 발생하여 공기가 지연됨.'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: '2점',
|
|
20
|
+
minValue: 2,
|
|
21
|
+
maxValue: 2,
|
|
22
|
+
score: 2,
|
|
23
|
+
description: '달성상황(기후, 일반변화 등)과 인근주민 민원 등이 발생하여 공기를 맞춤.'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: '3점',
|
|
27
|
+
minValue: 3,
|
|
28
|
+
maxValue: 3,
|
|
29
|
+
score: 3,
|
|
30
|
+
description: '달성상황(기후, 일반변화 등)과 인근주민 민원 등이 발생하지 않으나 공기를 맞춤.'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: '4점',
|
|
34
|
+
minValue: 4,
|
|
35
|
+
maxValue: 4,
|
|
36
|
+
score: 4,
|
|
37
|
+
description: '달성상황(기후, 일반변화 등)과 인근주민 민원 등이 발생하지 않으며 공기를 단축함.'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: '5점',
|
|
41
|
+
minValue: 5,
|
|
42
|
+
maxValue: 5,
|
|
43
|
+
score: 5,
|
|
44
|
+
description: '달성상황(기후, 일반변화 등)과 인근주민 민원 등이 발생하지 않으며 공기를 단축함.'
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
kpiName: '비용성과 수준 평가',
|
|
50
|
+
version: 1,
|
|
51
|
+
grades: [
|
|
52
|
+
{
|
|
53
|
+
name: '1점',
|
|
54
|
+
minValue: 1,
|
|
55
|
+
maxValue: 1,
|
|
56
|
+
score: 1,
|
|
57
|
+
description: '공사비 변동과 민원 등이 발생하여 공사비 관리가 미흡함.'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: '2점',
|
|
61
|
+
minValue: 2,
|
|
62
|
+
maxValue: 2,
|
|
63
|
+
score: 2,
|
|
64
|
+
description: '공사비 변동과 민원 등이 발생하여 공사비 관리가 미흡함.'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: '3점',
|
|
68
|
+
minValue: 3,
|
|
69
|
+
maxValue: 3,
|
|
70
|
+
score: 3,
|
|
71
|
+
description: '공사비 변동과 민원 등이 발생하였으나 공사비 관리를 적정하게 수행함.'
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: '4점',
|
|
75
|
+
minValue: 4,
|
|
76
|
+
maxValue: 4,
|
|
77
|
+
score: 4,
|
|
78
|
+
description: '공사비 변동과 민원 등이 발생하지 않으며 공사비 관리를 적정하게 수행함.'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: '5점',
|
|
82
|
+
minValue: 5,
|
|
83
|
+
maxValue: 5,
|
|
84
|
+
score: 5,
|
|
85
|
+
description: '공사비 변동과 민원 등이 발생하지 않으며 매우 훌륭하게 수행함.'
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
kpiName: '품질성과 수준 평가',
|
|
91
|
+
version: 1,
|
|
92
|
+
grades: [
|
|
93
|
+
{
|
|
94
|
+
name: '1점',
|
|
95
|
+
minValue: 1,
|
|
96
|
+
maxValue: 1,
|
|
97
|
+
score: 1,
|
|
98
|
+
description: '법적 요구 수준에 못 미치거나 공사 완성도의 개선이 매우 필요한 경우'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: '2점',
|
|
102
|
+
minValue: 2,
|
|
103
|
+
maxValue: 2,
|
|
104
|
+
score: 2,
|
|
105
|
+
description: '법적 요구 수준을 일부 만족하나, 공사 완성도의 개선이 일부 필요한 경우'
|
|
106
|
+
},
|
|
107
|
+
{ name: '3점', minValue: 3, maxValue: 3, score: 3, description: '법적 요구 수준을 충족하는 수준' },
|
|
108
|
+
{
|
|
109
|
+
name: '4점',
|
|
110
|
+
minValue: 4,
|
|
111
|
+
maxValue: 4,
|
|
112
|
+
score: 4,
|
|
113
|
+
description: '법적 요구 수준을 만족하며, 품질관리 활동이 능동적인 경우'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: '5점',
|
|
117
|
+
minValue: 5,
|
|
118
|
+
maxValue: 5,
|
|
119
|
+
score: 5,
|
|
120
|
+
description: '법적 요구 수준을 만족하며, 품질관리 활동이 매우 능동적인 경우'
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
kpiName: '안전성과 수준 평가',
|
|
126
|
+
version: 1,
|
|
127
|
+
grades: [
|
|
128
|
+
{
|
|
129
|
+
name: '1점',
|
|
130
|
+
minValue: 1,
|
|
131
|
+
maxValue: 1,
|
|
132
|
+
score: 1,
|
|
133
|
+
description: '법적 요구 수준을 미달하거나 안전관리 활동이 미흡한 경우'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: '2점',
|
|
137
|
+
minValue: 2,
|
|
138
|
+
maxValue: 2,
|
|
139
|
+
score: 2,
|
|
140
|
+
description: '법적 요구 수준을 일부 만족하나, 안전관리 활동이 다소 필요한 경우'
|
|
141
|
+
},
|
|
142
|
+
{ name: '3점', minValue: 3, maxValue: 3, score: 3, description: '법적 요구 수준을 충족하는 수준' },
|
|
143
|
+
{
|
|
144
|
+
name: '4점',
|
|
145
|
+
minValue: 4,
|
|
146
|
+
maxValue: 4,
|
|
147
|
+
score: 4,
|
|
148
|
+
description: '법적 요구 수준을 만족하며, 안전관리 활동이 능동적인 경우'
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: '5점',
|
|
152
|
+
minValue: 5,
|
|
153
|
+
maxValue: 5,
|
|
154
|
+
score: 5,
|
|
155
|
+
description: '법적 요구 수준을 만족하며, 안전관리 활동이 매우 능동적인 경우'
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
kpiName: '환경성과 수준 평가',
|
|
161
|
+
version: 1,
|
|
162
|
+
grades: [
|
|
163
|
+
{
|
|
164
|
+
name: '1점',
|
|
165
|
+
minValue: 1,
|
|
166
|
+
maxValue: 1,
|
|
167
|
+
score: 1,
|
|
168
|
+
description: '법적 요구 수준에 못 미치거나 환경관리 활동의 개선이 매우 필요한 경우'
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: '2점',
|
|
172
|
+
minValue: 2,
|
|
173
|
+
maxValue: 2,
|
|
174
|
+
score: 2,
|
|
175
|
+
description: '법적 요구 수준을 일부 만족하나, 환경관리 활동이 다소 필요한 경우'
|
|
176
|
+
},
|
|
177
|
+
{ name: '3점', minValue: 3, maxValue: 3, score: 3, description: '법적 요구 수준을 충족하는 수준' },
|
|
178
|
+
{
|
|
179
|
+
name: '4점',
|
|
180
|
+
minValue: 4,
|
|
181
|
+
maxValue: 4,
|
|
182
|
+
score: 4,
|
|
183
|
+
description: '법적 요구 수준을 만족하며, 환경관리 활동의 효율성을 다소 높은 수준'
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: '5점',
|
|
187
|
+
minValue: 5,
|
|
188
|
+
maxValue: 5,
|
|
189
|
+
score: 5,
|
|
190
|
+
description: '법적 요구 수준을 만족하며, 환경관리 활동의 효율성이 확연히 높은 수준'
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
kpiName: '생산성과 수준 평가',
|
|
196
|
+
version: 1,
|
|
197
|
+
grades: [
|
|
198
|
+
{
|
|
199
|
+
name: '1점',
|
|
200
|
+
minValue: 1,
|
|
201
|
+
maxValue: 1,
|
|
202
|
+
score: 1,
|
|
203
|
+
description: '인력 및 장비 투입 규모에 대한 효율성이 매우 낮은 수준'
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: '2점',
|
|
207
|
+
minValue: 2,
|
|
208
|
+
maxValue: 2,
|
|
209
|
+
score: 2,
|
|
210
|
+
description: '인력 및 장비 투입 규모가 법적 요구 수준을 충족하나, 연계에 비해 미흡함.'
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: '3점',
|
|
214
|
+
minValue: 3,
|
|
215
|
+
maxValue: 3,
|
|
216
|
+
score: 3,
|
|
217
|
+
description: '인력 및 장비 투입 규모가 법적 요구 수준을 충족하는 수준'
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: '4점',
|
|
221
|
+
minValue: 4,
|
|
222
|
+
maxValue: 4,
|
|
223
|
+
score: 4,
|
|
224
|
+
description: '투입 규모가 법적 요구 수준을 충족하며, 효율성이 다소 높은 수준'
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: '5점',
|
|
228
|
+
minValue: 5,
|
|
229
|
+
maxValue: 5,
|
|
230
|
+
score: 5,
|
|
231
|
+
description: '투입 규모가 법적 요구 수준을 충족하며, 효율성이 확연히 높은 수준'
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
export class SeedKpiGrade1752191090459 implements MigrationInterface {
|
|
238
|
+
public async up(queryRunner: QueryRunner): Promise<any> {
|
|
239
|
+
const repository = getRepository(KpiGrade)
|
|
240
|
+
const kpiRepository = getRepository(Kpi)
|
|
241
|
+
const domain: Domain = await getRepository(Domain).findOneBy({ name: 'SYSTEM' })
|
|
242
|
+
|
|
243
|
+
for (const seed of GRADE_SEED) {
|
|
244
|
+
const kpi = await kpiRepository.findOneBy({ name: seed.kpiName, domain: { id: domain.id } })
|
|
245
|
+
if (!kpi) continue
|
|
246
|
+
for (const grade of seed.grades) {
|
|
247
|
+
await repository.save({
|
|
248
|
+
...grade,
|
|
249
|
+
version: seed.version,
|
|
250
|
+
kpi,
|
|
251
|
+
domain
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
public async down(queryRunner: QueryRunner): Promise<any> {
|
|
258
|
+
const repository = getRepository(KpiGrade)
|
|
259
|
+
const kpiRepository = getRepository(Kpi)
|
|
260
|
+
const domain: Domain = await getRepository(Domain).findOneBy({ name: 'SYSTEM' })
|
|
261
|
+
|
|
262
|
+
for (const seed of GRADE_SEED) {
|
|
263
|
+
const kpi = await kpiRepository.findOneBy({ name: seed.kpiName, domain: { id: domain.id } })
|
|
264
|
+
if (!kpi) continue
|
|
265
|
+
for (const grade of seed.grades.reverse()) {
|
|
266
|
+
await repository.delete({ name: grade.name, kpi: { id: kpi.id }, domain: { id: domain.id } })
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const glob = require('glob')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
|
|
4
|
+
export var migrations = []
|
|
5
|
+
|
|
6
|
+
glob.sync(path.resolve(__dirname, '.', '**', '*.js')).forEach(function(file) {
|
|
7
|
+
if (file.indexOf('index.js') !== -1) return
|
|
8
|
+
migrations = migrations.concat(Object.values(require(path.resolve(file))) || [])
|
|
9
|
+
})
|
package/server/routes.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { aggregateKpiMetricValue } from './service/kpi-metric/aggregate-kpi-metric'
|
|
2
|
+
import { aggregateKpiValue } from './service/kpi/aggregate-kpi'
|
|
3
|
+
|
|
1
4
|
process.on('bootstrap-module-global-public-route' as any, (app, globalPublicRouter) => {
|
|
2
5
|
/*
|
|
3
6
|
* can add global public routes to application (auth not required, tenancy not required)
|
|
@@ -5,6 +8,58 @@ process.on('bootstrap-module-global-public-route' as any, (app, globalPublicRout
|
|
|
5
8
|
* ex) routes.get('/path', async(context, next) => {})
|
|
6
9
|
* ex) routes.post('/path', async(context, next) => {})
|
|
7
10
|
*/
|
|
11
|
+
|
|
12
|
+
// KPI-Metric 스케줄러 콜백 엔드포인트
|
|
13
|
+
globalPublicRouter.post('/callback-schedule-for-kpi-metric', async (context, next) => {
|
|
14
|
+
const { client } = context.request.body
|
|
15
|
+
|
|
16
|
+
if (!client || typeof client !== 'object') {
|
|
17
|
+
throw new Error('client property should be a part of callback body.')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { group: domainId, key: metricId } = client
|
|
21
|
+
|
|
22
|
+
if (!domainId || !metricId) {
|
|
23
|
+
throw new Error(`group(${domainId}) and key(${metricId}) properties should not be empty`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// KPI-Metric 집계/자동화 함수 호출
|
|
28
|
+
const values = await aggregateKpiMetricValue(metricId, domainId, context)
|
|
29
|
+
console.log(`[KPI-Metric Schedule Callback] domainId=${domainId}, metricId=${metricId}, values=`, values)
|
|
30
|
+
context.status = 200
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error('[KPI-Metric Schedule Callback][ERROR]', err)
|
|
33
|
+
context.status = 500
|
|
34
|
+
context.body = { error: err.message || String(err) }
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// KPI 스케줄러 콜백 엔드포인트
|
|
39
|
+
globalPublicRouter.post('/callback-schedule-for-kpi', async (context, next) => {
|
|
40
|
+
const { client } = context.request.body
|
|
41
|
+
|
|
42
|
+
if (!client || typeof client !== 'object') {
|
|
43
|
+
throw new Error('client property should be a part of callback body.')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { group: domainId, key: kpiId } = client
|
|
47
|
+
|
|
48
|
+
if (!domainId || !kpiId) {
|
|
49
|
+
throw new Error(`group(${domainId}) and key(${kpiId}) properties should not be empty`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
// KPI 집계/자동화 함수 호출
|
|
54
|
+
const values = await aggregateKpiValue(kpiId, domainId, context)
|
|
55
|
+
console.log(`[KPI Schedule Callback] domainId=${domainId}, kpiId=${kpiId}, values=`, values)
|
|
56
|
+
context.status = 200
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error('[KPI Schedule Callback][ERROR]', err)
|
|
59
|
+
context.status = 500
|
|
60
|
+
context.body = { error: err.message || String(err) }
|
|
61
|
+
}
|
|
62
|
+
})
|
|
8
63
|
})
|
|
9
64
|
|
|
10
65
|
process.on('bootstrap-module-global-private-route' as any, (app, globalPrivateRouter) => {
|
package/server/service/index.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/* EXPORT ENTITY TYPES */
|
|
2
2
|
export * from './kpi/kpi'
|
|
3
3
|
export * from './kpi/kpi-type'
|
|
4
|
-
export * from './kpi-formula/kpi-formula'
|
|
5
|
-
export * from './kpi-formula/kpi-formula-type'
|
|
6
4
|
export * from './kpi-category/kpi-category'
|
|
7
5
|
export * from './kpi-category/kpi-category-type'
|
|
8
6
|
export * from './kpi-grade/kpi-grade'
|
|
@@ -11,19 +9,19 @@ export * from './kpi-value/kpi-value'
|
|
|
11
9
|
export * from './kpi-value/kpi-value-type'
|
|
12
10
|
export * from './kpi-metric/kpi-metric'
|
|
13
11
|
export * from './kpi-metric/kpi-metric-type'
|
|
12
|
+
export * from './kpi-alert'
|
|
14
13
|
|
|
15
14
|
/* IMPORT ENTITIES AND RESOLVERS */
|
|
16
15
|
import { entities as KpiEntities, resolvers as KpiResolvers } from './kpi'
|
|
17
|
-
import { entities as KpiFormulaEntities, resolvers as KpiFormulaResolvers } from './kpi-formula'
|
|
18
16
|
import { entities as KpiCategoryEntities, resolvers as KpiCategoryResolvers } from './kpi-category'
|
|
19
17
|
import { entities as KpiGradeEntities, resolvers as KpiGradeResolvers } from './kpi-grade'
|
|
20
18
|
import { entities as KpiValueEntities, resolvers as KpiValueResolvers } from './kpi-value'
|
|
21
19
|
import { entities as KpiMetricEntities, resolvers as KpiMetricResolvers } from './kpi-metric'
|
|
20
|
+
import { resolvers as KpiAlertResolvers } from './kpi-alert'
|
|
22
21
|
|
|
23
22
|
export const entities = [
|
|
24
23
|
/* ENTITIES */
|
|
25
24
|
...KpiEntities,
|
|
26
|
-
...KpiFormulaEntities,
|
|
27
25
|
...KpiCategoryEntities,
|
|
28
26
|
...KpiGradeEntities,
|
|
29
27
|
...KpiValueEntities,
|
|
@@ -34,10 +32,10 @@ export const schema = {
|
|
|
34
32
|
resolverClasses: [
|
|
35
33
|
/* RESOLVER CLASSES */
|
|
36
34
|
...KpiResolvers,
|
|
37
|
-
...KpiFormulaResolvers,
|
|
38
35
|
...KpiCategoryResolvers,
|
|
39
36
|
...KpiGradeResolvers,
|
|
40
37
|
...KpiValueResolvers,
|
|
41
|
-
...KpiMetricResolvers
|
|
38
|
+
...KpiMetricResolvers,
|
|
39
|
+
...KpiAlertResolvers
|
|
42
40
|
]
|
|
43
41
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { getRepository } from '@things-factory/shell'
|
|
2
|
+
import { Kpi } from './kpi'
|
|
3
|
+
import { KpiMetric } from '../kpi-metric/kpi-metric'
|
|
4
|
+
import { KpiValue, KpiValueInputType } from '../kpi-value/kpi-value'
|
|
5
|
+
import { KpiFormulaService } from './kpi-formula.service'
|
|
6
|
+
import { aggregateKpiMetricValue } from '../kpi-metric/aggregate-kpi-metric'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* KPI 단위 집계/산식 자동화 함수
|
|
10
|
+
* @param kpiId KPI ID
|
|
11
|
+
* @param domainId 도메인 ID
|
|
12
|
+
* @param context ResolverContext
|
|
13
|
+
* @returns 저장된 KPI Value 배열
|
|
14
|
+
*/
|
|
15
|
+
export async function aggregateKpiValue(kpiId: string, domainId: string, context: ResolverContext) {
|
|
16
|
+
const tx = context.state?.tx || getRepository(Kpi).manager
|
|
17
|
+
|
|
18
|
+
// 1. KPI 정보 조회
|
|
19
|
+
const kpi = await getRepository(Kpi).findOne({ where: { id: kpiId, domain: { id: domainId } } })
|
|
20
|
+
if (!kpi) throw new Error('KPI 정보 없음')
|
|
21
|
+
if (!kpi.active) throw new Error('비활성화된 KPI')
|
|
22
|
+
if (!kpi.formula) throw new Error('KPI formula 없음')
|
|
23
|
+
|
|
24
|
+
// 2. formula 파싱 및 metric code별 값 집계
|
|
25
|
+
const formulaService = new KpiFormulaService()
|
|
26
|
+
const metricCodes = formulaService.extractMetricCodes(kpi.formula)
|
|
27
|
+
const codeValueMap: Record<string, any[]> = {}
|
|
28
|
+
for (const code of metricCodes) {
|
|
29
|
+
// code로 metric 찾기
|
|
30
|
+
const metric = await getRepository(KpiMetric).findOne({ where: { name: code, domain: { id: domainId } } })
|
|
31
|
+
if (!metric) throw new Error(`KPI formula metric '${code}' not found`)
|
|
32
|
+
codeValueMap[code] = await aggregateKpiMetricValue(metric.id, domainId, context)
|
|
33
|
+
}
|
|
34
|
+
// group/date/period별로 값 매핑(가장 최근 기준, group key 조합)
|
|
35
|
+
const groupKey = v =>
|
|
36
|
+
[v.date, v.period, v.group?.key01, v.group?.key02, v.group?.key03, v.group?.key04, v.group?.key05].join('|')
|
|
37
|
+
const groupMap: Record<string, any> = {}
|
|
38
|
+
for (const code of metricCodes) {
|
|
39
|
+
for (const v of codeValueMap[code]) {
|
|
40
|
+
const key = groupKey(v)
|
|
41
|
+
groupMap[key] = groupMap[key] || { ...v, _values: {} }
|
|
42
|
+
groupMap[key]._values[code] = v.value
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// formula 계산 (js eval)
|
|
46
|
+
const savedValues = []
|
|
47
|
+
for (const key in groupMap) {
|
|
48
|
+
const ctx = groupMap[key]._values
|
|
49
|
+
let value = null
|
|
50
|
+
try {
|
|
51
|
+
value = Function(...Object.keys(ctx), `return (${kpi.formula})`)(...Object.values(ctx))
|
|
52
|
+
} catch (e) {
|
|
53
|
+
value = null
|
|
54
|
+
}
|
|
55
|
+
const valueDate = groupMap[key].date
|
|
56
|
+
const groupId = groupMap[key].group?.key01
|
|
57
|
+
const groupType = groupMap[key].group?.key02
|
|
58
|
+
const version = kpi.version || 1
|
|
59
|
+
if (value == null || isNaN(value)) continue
|
|
60
|
+
// upsert(동일 KPI, valueDate, groupId, groupType, version) 기준으로 저장
|
|
61
|
+
const repo = getRepository(KpiValue, context.state?.tx)
|
|
62
|
+
const existing = await repo.findOne({
|
|
63
|
+
where: { kpi: { id: kpi.id }, valueDate, groupId, groupType, version, domain: { id: domainId } }
|
|
64
|
+
})
|
|
65
|
+
let entity = existing || repo.create()
|
|
66
|
+
entity.kpi = kpi
|
|
67
|
+
entity.kpiId = kpi.id
|
|
68
|
+
entity.version = version
|
|
69
|
+
entity.valueDate = valueDate
|
|
70
|
+
entity.value = value
|
|
71
|
+
entity.groupId = groupId
|
|
72
|
+
entity.groupType = groupType
|
|
73
|
+
entity.inputType = KpiValueInputType.AUTO
|
|
74
|
+
entity.source = 'AUTO'
|
|
75
|
+
entity.domain = kpi.domain
|
|
76
|
+
entity.creator = context.state?.user
|
|
77
|
+
entity.updater = context.state?.user
|
|
78
|
+
entity = await repo.save(entity)
|
|
79
|
+
savedValues.push(entity)
|
|
80
|
+
}
|
|
81
|
+
return savedValues
|
|
82
|
+
}
|
|
@@ -14,4 +14,16 @@ export class KpiHistoryEntitySubscriber extends HistoryEntitySubscriber<Kpi, Kpi
|
|
|
14
14
|
public get historyEntity() {
|
|
15
15
|
return KpiHistory
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
public async afterInsert(event): Promise<void> {
|
|
19
|
+
if (event.entity.state == 'RELEASE') {
|
|
20
|
+
await super.afterInsert(event as any)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async afterUpdate(event): Promise<void> {
|
|
25
|
+
if (event.entity.state == 'RELEASE') {
|
|
26
|
+
await super.afterUpdate(event as any)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
17
29
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getRepository } from '@things-factory/shell'
|
|
2
|
+
import { KpiMetric } from '../kpi-metric/kpi-metric'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* KPI formula 파싱/검증/매핑 유효성 검사 서비스
|
|
6
|
+
* - formula에서 Metric 코드(변수명) 추출
|
|
7
|
+
* - 각 Metric의 존재/매핑 유효성 검사
|
|
8
|
+
* - 구문 오류, 미정의 Metric, 매핑 오류 등 에러 리포팅
|
|
9
|
+
*/
|
|
10
|
+
export class KpiFormulaService {
|
|
11
|
+
/**
|
|
12
|
+
* formula 문자열에서 Metric 코드(변수명) 추출
|
|
13
|
+
* @param formula KPI formula (예: 'defect_count / total_count * 100')
|
|
14
|
+
* @returns string[] 추출된 Metric 코드 리스트
|
|
15
|
+
*/
|
|
16
|
+
extractMetricCodes(formula: string): string[] {
|
|
17
|
+
// 변수명: 알파벳/언더스코어로 시작, 숫자/알파벳/언더스코어 포함
|
|
18
|
+
const regex = /[a-zA-Z_][a-zA-Z0-9_]*/g
|
|
19
|
+
// 숫자/예약어/연산자 제외(후처리 필요)
|
|
20
|
+
const reserved = new Set(['if', 'else', 'return', 'true', 'false'])
|
|
21
|
+
return Array.from(new Set((formula.match(regex) || []).filter(code => isNaN(Number(code)) && !reserved.has(code))))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* formula 내 Metric 코드의 존재/매핑 유효성 검사 + 순환참조 검출
|
|
26
|
+
* @param formula KPI formula
|
|
27
|
+
* @param visited 순환참조 검출용 Set (재귀 호출 시 사용)
|
|
28
|
+
* @returns { valid: boolean, errors: string[] }
|
|
29
|
+
*/
|
|
30
|
+
async validateFormula(
|
|
31
|
+
formula: string,
|
|
32
|
+
visited: Set<string> = new Set()
|
|
33
|
+
): Promise<{ valid: boolean; errors: string[] }> {
|
|
34
|
+
const errors: string[] = []
|
|
35
|
+
const metricCodes = this.extractMetricCodes(formula)
|
|
36
|
+
const metricRepo = getRepository(KpiMetric)
|
|
37
|
+
for (const code of metricCodes) {
|
|
38
|
+
if (visited.has(code)) {
|
|
39
|
+
errors.push(`순환참조 감지: ${Array.from(visited).join(' → ')} → ${code}`)
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
visited.add(code)
|
|
43
|
+
const metric = await metricRepo.findOne({ where: { name: code } })
|
|
44
|
+
if (!metric) {
|
|
45
|
+
errors.push(`Metric '${code}' is not defined.`)
|
|
46
|
+
visited.delete(code)
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
if (!metric.dataSetId || !metric.fieldName) {
|
|
50
|
+
errors.push(`Metric '${code}' is not mapped to a dataset field.`)
|
|
51
|
+
}
|
|
52
|
+
// metric이 formula를 가지고 있다면 재귀적으로 순환참조 검증
|
|
53
|
+
if ((metric as any).formula) {
|
|
54
|
+
const subResult = await this.validateFormula((metric as any).formula, new Set(visited))
|
|
55
|
+
if (!subResult.valid) errors.push(...subResult.errors)
|
|
56
|
+
}
|
|
57
|
+
visited.delete(code)
|
|
58
|
+
}
|
|
59
|
+
// (선택) formula 구문 오류, 괄호 불일치 등 추가 검사 가능
|
|
60
|
+
// ...
|
|
61
|
+
return { valid: errors.length === 0, errors }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Field, ID, ObjectType } from 'type-graphql'
|
|
2
|
-
import { Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId } from 'typeorm'
|
|
2
|
+
import { Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, JoinColumn } from 'typeorm'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
HistoryActionColumn,
|
|
@@ -12,21 +12,16 @@ import { config } from '@things-factory/env'
|
|
|
12
12
|
import { Domain } from '@things-factory/shell'
|
|
13
13
|
|
|
14
14
|
import { Kpi, KpiStatus } from './kpi'
|
|
15
|
+
import { KpiCategory } from '../kpi-category/kpi-category'
|
|
15
16
|
|
|
16
17
|
const ORMCONFIG = config.get('ormconfig', {})
|
|
17
18
|
const DATABASE_TYPE = ORMCONFIG.type
|
|
18
19
|
|
|
19
20
|
@Entity()
|
|
20
|
-
@Index(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
)
|
|
25
|
-
@Index(
|
|
26
|
-
'ix_kpi_history_1',
|
|
27
|
-
(kpiHistory: KpiHistory) => [kpiHistory.domain, kpiHistory.originalId, kpiHistory.version],
|
|
28
|
-
{ unique: true }
|
|
29
|
-
)
|
|
21
|
+
@Index('ix_kpi_history_0', (kpiHistory: KpiHistory) => [kpiHistory.originalId, kpiHistory.version], { unique: true })
|
|
22
|
+
@Index('ix_kpi_history_1', (kpiHistory: KpiHistory) => [kpiHistory.domain, kpiHistory.originalId, kpiHistory.version], {
|
|
23
|
+
unique: true
|
|
24
|
+
})
|
|
30
25
|
@ObjectType({ description: 'History Entity of Kpi' })
|
|
31
26
|
export class KpiHistory implements HistoryEntityInterface<Kpi> {
|
|
32
27
|
@PrimaryGeneratedColumn('uuid')
|
|
@@ -52,17 +47,27 @@ export class KpiHistory implements HistoryEntityInterface<Kpi> {
|
|
|
52
47
|
@Field({ nullable: true })
|
|
53
48
|
description?: string
|
|
54
49
|
|
|
50
|
+
@ManyToOne(() => KpiCategory, { nullable: true })
|
|
51
|
+
@Field(type => KpiCategory, { nullable: true, description: 'Category to which this KPI belongs.' })
|
|
52
|
+
category?: KpiCategory
|
|
53
|
+
|
|
54
|
+
@RelationId((kpi: KpiHistory) => kpi.category)
|
|
55
|
+
categoryId?: string
|
|
56
|
+
|
|
55
57
|
@Column({ nullable: true })
|
|
56
|
-
@Field({ nullable: true })
|
|
58
|
+
@Field({ nullable: true, description: 'Calculation formula for the KPI.' })
|
|
59
|
+
formula?: string
|
|
60
|
+
|
|
61
|
+
@Column({ nullable: false, default: false })
|
|
62
|
+
@Field({ nullable: true, description: 'Whether this KPI is active (usable) or not.' })
|
|
57
63
|
active?: boolean
|
|
58
64
|
|
|
59
65
|
@Column({ nullable: true })
|
|
60
|
-
@Field({ nullable: true })
|
|
66
|
+
@Field({ nullable: true, description: 'Current state of the KPI (DRAFT, RELEASED, ARCHIVED).' })
|
|
61
67
|
state?: KpiStatus
|
|
62
68
|
|
|
63
|
-
@
|
|
64
|
-
|
|
65
|
-
params?: string
|
|
69
|
+
@Field(type => String, { nullable: true, description: 'Thumbnail image or file path for this KPI.' })
|
|
70
|
+
thumbnail?: string
|
|
66
71
|
|
|
67
72
|
@Column({ nullable: true })
|
|
68
73
|
@Field({ nullable: true })
|
|
@@ -90,12 +95,15 @@ export class KpiHistory implements HistoryEntityInterface<Kpi> {
|
|
|
90
95
|
@RelationId((kpi: Kpi) => kpi.updater)
|
|
91
96
|
updaterId?: string
|
|
92
97
|
|
|
93
|
-
@
|
|
94
|
-
thumbnail?: string
|
|
95
|
-
|
|
96
|
-
@HistoryOriginalIdColumn()
|
|
98
|
+
@HistoryOriginalIdColumn({ type: 'uuid' })
|
|
97
99
|
public originalId!: string
|
|
98
100
|
|
|
101
|
+
// Kpi와의 릴레이션 추가 (originalId를 외래키로 사용)
|
|
102
|
+
@ManyToOne(() => Kpi, { nullable: false })
|
|
103
|
+
@JoinColumn({ name: 'originalId', referencedColumnName: 'id' })
|
|
104
|
+
@Field(type => Kpi, { nullable: false, description: '이 이력이 속한 KPI' })
|
|
105
|
+
kpi: Kpi
|
|
106
|
+
|
|
99
107
|
@HistoryActionColumn({
|
|
100
108
|
nullable: false,
|
|
101
109
|
type:
|
|
@@ -110,7 +118,7 @@ export class KpiHistory implements HistoryEntityInterface<Kpi> {
|
|
|
110
118
|
DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
|
111
119
|
? HistoryActionType
|
|
112
120
|
: undefined,
|
|
113
|
-
length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined: 32
|
|
121
|
+
length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32
|
|
114
122
|
})
|
|
115
123
|
public action!: HistoryActionType
|
|
116
124
|
}
|