@things-factory/kpi 9.0.21 → 9.0.23

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 (152) hide show
  1. package/client/charts/kpi-boxplot-chart.ts +163 -0
  2. package/client/charts/kpi-radar-chart.ts +128 -0
  3. package/client/pages/kpi/kpi-list-page.ts +180 -22
  4. package/client/pages/kpi-category/kpi-category-list-page.ts +76 -3
  5. package/client/pages/kpi-category/kpi-category-value-calculator.ts +233 -0
  6. package/client/pages/kpi-dashboard/kpi-dashboard.ts +188 -0
  7. package/client/pages/kpi-metric/kpi-metric-list-page.ts +13 -1
  8. package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +43 -1
  9. package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.ts +3 -13
  10. package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.ts +13 -1
  11. package/client/pages/kpi-value/kpi-value-list-page.ts +45 -1
  12. package/dist-client/charts/kpi-boxplot-chart.d.ts +22 -0
  13. package/dist-client/charts/kpi-boxplot-chart.js +198 -0
  14. package/dist-client/charts/kpi-boxplot-chart.js.map +1 -0
  15. package/dist-client/charts/kpi-radar-chart.d.ts +16 -0
  16. package/dist-client/charts/kpi-radar-chart.js +138 -0
  17. package/dist-client/charts/kpi-radar-chart.js.map +1 -0
  18. package/dist-client/pages/kpi/kpi-list-page.d.ts +2 -1
  19. package/dist-client/pages/kpi/kpi-list-page.js +180 -22
  20. package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
  21. package/dist-client/pages/kpi-category/kpi-category-list-page.d.ts +3 -0
  22. package/dist-client/pages/kpi-category/kpi-category-list-page.js +71 -3
  23. package/dist-client/pages/kpi-category/kpi-category-list-page.js.map +1 -1
  24. package/dist-client/pages/kpi-category/kpi-category-value-calculator.d.ts +13 -0
  25. package/dist-client/pages/kpi-category/kpi-category-value-calculator.js +256 -0
  26. package/dist-client/pages/kpi-category/kpi-category-value-calculator.js.map +1 -0
  27. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +11 -0
  28. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +185 -0
  29. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
  30. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +13 -1
  31. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
  32. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +4 -1
  33. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +39 -2
  34. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
  35. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js +3 -13
  36. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -1
  37. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +13 -1
  38. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
  39. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +1 -0
  40. package/dist-client/pages/kpi-value/kpi-value-list-page.js +45 -1
  41. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
  42. package/dist-client/tsconfig.tsbuildinfo +1 -1
  43. package/dist-server/calculator/evaluator.d.ts +8 -0
  44. package/dist-server/calculator/evaluator.js +42 -0
  45. package/dist-server/calculator/evaluator.js.map +1 -0
  46. package/dist-server/calculator/functions.d.ts +3 -0
  47. package/dist-server/calculator/functions.js +62 -0
  48. package/dist-server/calculator/functions.js.map +1 -0
  49. package/dist-server/calculator/index.d.ts +4 -0
  50. package/dist-server/calculator/index.js +8 -0
  51. package/dist-server/calculator/index.js.map +1 -0
  52. package/dist-server/calculator/parser.d.ts +21 -0
  53. package/dist-server/calculator/parser.js +121 -0
  54. package/dist-server/calculator/parser.js.map +1 -0
  55. package/dist-server/calculator/provider.d.ts +8 -0
  56. package/dist-server/calculator/provider.js +13 -0
  57. package/dist-server/calculator/provider.js.map +1 -0
  58. package/dist-server/controllers/kpi-metric-value-provider.d.ts +11 -0
  59. package/dist-server/controllers/kpi-metric-value-provider.js +63 -0
  60. package/dist-server/controllers/kpi-metric-value-provider.js.map +1 -0
  61. package/dist-server/controllers/kpi-value-provider.d.ts +11 -0
  62. package/dist-server/controllers/kpi-value-provider.js +46 -0
  63. package/dist-server/controllers/kpi-value-provider.js.map +1 -0
  64. package/dist-server/service/index.d.ts +2 -2
  65. package/dist-server/service/kpi/aggregate-kpi.js +4 -4
  66. package/dist-server/service/kpi/aggregate-kpi.js.map +1 -1
  67. package/dist-server/service/kpi/kpi-grade.types.d.ts +11 -10
  68. package/dist-server/service/kpi/kpi-grade.types.js.map +1 -1
  69. package/dist-server/service/kpi/kpi-history.d.ts +2 -2
  70. package/dist-server/service/kpi/kpi-history.js.map +1 -1
  71. package/dist-server/service/kpi/kpi-mutation.d.ts +2 -0
  72. package/dist-server/service/kpi/kpi-mutation.js +126 -4
  73. package/dist-server/service/kpi/kpi-mutation.js.map +1 -1
  74. package/dist-server/service/kpi/kpi-type.d.ts +8 -5
  75. package/dist-server/service/kpi/kpi-type.js +22 -8
  76. package/dist-server/service/kpi/kpi-type.js.map +1 -1
  77. package/dist-server/service/kpi/kpi.d.ts +6 -3
  78. package/dist-server/service/kpi/kpi.js +29 -9
  79. package/dist-server/service/kpi/kpi.js.map +1 -1
  80. package/dist-server/service/kpi-category/kpi-category-mutation.d.ts +1 -1
  81. package/dist-server/service/kpi-category/kpi-category-mutation.js +3 -3
  82. package/dist-server/service/kpi-category/kpi-category-mutation.js.map +1 -1
  83. package/dist-server/service/kpi-category/kpi-category-query.d.ts +13 -0
  84. package/dist-server/service/kpi-category/kpi-category-query.js +180 -0
  85. package/dist-server/service/kpi-category/kpi-category-query.js.map +1 -1
  86. package/dist-server/service/kpi-category/kpi-category-type.d.ts +3 -0
  87. package/dist-server/service/kpi-category/kpi-category-type.js +16 -1
  88. package/dist-server/service/kpi-category/kpi-category-type.js.map +1 -1
  89. package/dist-server/service/kpi-category/kpi-category.d.ts +2 -0
  90. package/dist-server/service/kpi-category/kpi-category.js +10 -1
  91. package/dist-server/service/kpi-category/kpi-category.js.map +1 -1
  92. package/dist-server/service/kpi-metric/kpi-metric-type.d.ts +5 -3
  93. package/dist-server/service/kpi-metric/kpi-metric-type.js +5 -3
  94. package/dist-server/service/kpi-metric/kpi-metric-type.js.map +1 -1
  95. package/dist-server/service/kpi-metric/kpi-metric.d.ts +2 -8
  96. package/dist-server/service/kpi-metric/kpi-metric.js +3 -14
  97. package/dist-server/service/kpi-metric/kpi-metric.js.map +1 -1
  98. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +67 -45
  99. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  100. package/dist-server/service/kpi-metric-value/kpi-metric-value.js +3 -2
  101. package/dist-server/service/kpi-metric-value/kpi-metric-value.js.map +1 -1
  102. package/dist-server/service/kpi-value/kpi-value-mutation.d.ts +2 -1
  103. package/dist-server/service/kpi-value/kpi-value-mutation.js +114 -6
  104. package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
  105. package/dist-server/service/kpi-value/kpi-value-query.d.ts +0 -2
  106. package/dist-server/service/kpi-value/kpi-value-query.js +0 -12
  107. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
  108. package/dist-server/service/kpi-value/kpi-value-score.service.d.ts +26 -0
  109. package/dist-server/service/kpi-value/kpi-value-score.service.js +97 -0
  110. package/dist-server/service/kpi-value/kpi-value-score.service.js.map +1 -0
  111. package/dist-server/service/kpi-value/kpi-value-type.d.ts +2 -0
  112. package/dist-server/service/kpi-value/kpi-value-type.js +14 -0
  113. package/dist-server/service/kpi-value/kpi-value-type.js.map +1 -1
  114. package/dist-server/service/kpi-value/kpi-value.d.ts +1 -0
  115. package/dist-server/service/kpi-value/kpi-value.js +9 -1
  116. package/dist-server/service/kpi-value/kpi-value.js.map +1 -1
  117. package/dist-server/service/utils/value-date-util.d.ts +3 -0
  118. package/dist-server/service/utils/value-date-util.js +76 -0
  119. package/dist-server/service/utils/value-date-util.js.map +1 -0
  120. package/dist-server/tsconfig.tsbuildinfo +1 -1
  121. package/package.json +2 -2
  122. package/server/calculator/evaluator.ts +45 -0
  123. package/server/calculator/functions.ts +67 -0
  124. package/server/calculator/index.ts +4 -0
  125. package/server/calculator/parser.ts +128 -0
  126. package/server/calculator/provider.ts +10 -0
  127. package/server/controllers/kpi-metric-value-provider.ts +66 -0
  128. package/server/controllers/kpi-value-provider.ts +51 -0
  129. package/server/service/kpi/aggregate-kpi.ts +4 -4
  130. package/server/service/kpi/kpi-grade.types.ts +11 -10
  131. package/server/service/kpi/kpi-history.ts +2 -2
  132. package/server/service/kpi/kpi-mutation.ts +128 -4
  133. package/server/service/kpi/kpi-type.ts +21 -9
  134. package/server/service/kpi/kpi.ts +32 -10
  135. package/server/service/kpi-category/kpi-category-mutation.ts +3 -3
  136. package/server/service/kpi-category/kpi-category-query.ts +175 -1
  137. package/server/service/kpi-category/kpi-category-type.ts +17 -6
  138. package/server/service/kpi-category/kpi-category.ts +10 -1
  139. package/server/service/kpi-metric/kpi-metric-type.ts +7 -5
  140. package/server/service/kpi-metric/kpi-metric.ts +3 -15
  141. package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +67 -47
  142. package/server/service/kpi-metric-value/kpi-metric-value.ts +4 -2
  143. package/server/service/kpi-value/kpi-value-mutation.ts +110 -6
  144. package/server/service/kpi-value/kpi-value-query.ts +2 -8
  145. package/server/service/kpi-value/kpi-value-score.service.ts +112 -0
  146. package/server/service/kpi-value/kpi-value-type.ts +12 -0
  147. package/server/service/kpi-value/kpi-value.ts +8 -1
  148. package/server/service/utils/value-date-util.ts +72 -0
  149. package/dist-server/service/kpi-value/kpi-value-grade.service.d.ts +0 -34
  150. package/dist-server/service/kpi-value/kpi-value-grade.service.js +0 -117
  151. package/dist-server/service/kpi-value/kpi-value-grade.service.js.map +0 -1
  152. package/server/service/kpi-value/kpi-value-grade.service.ts +0 -127
@@ -0,0 +1,8 @@
1
+ import type { FormulaNode } from './parser';
2
+ import type { FormulaFunctionMap } from './functions';
3
+ import type { ValueProvider } from './provider';
4
+ export interface EvalContext {
5
+ functions: FormulaFunctionMap;
6
+ provider: ValueProvider;
7
+ }
8
+ export declare function evaluateFormula(node: FormulaNode, ctx: EvalContext): Promise<any>;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evaluateFormula = evaluateFormula;
4
+ // 비동기 evaluator만 유지, 함수명은 evaluateFormula로 단순화
5
+ async function evaluateFormula(node, ctx) {
6
+ switch (node.type) {
7
+ case 'number':
8
+ return node.value;
9
+ case 'variable':
10
+ return await ctx.provider.get(node.name);
11
+ case 'function': {
12
+ const fn = ctx.functions[node.name];
13
+ if (!fn)
14
+ throw new Error('Unknown function: ' + node.name);
15
+ const args = await Promise.all(node.args.map(arg => evaluateFormula(arg, ctx)));
16
+ return fn(...args);
17
+ }
18
+ case 'binary': {
19
+ const l = await evaluateFormula(node.left, ctx);
20
+ const r = await evaluateFormula(node.right, ctx);
21
+ switch (node.op) {
22
+ case '+':
23
+ return l + r;
24
+ case '-':
25
+ return l - r;
26
+ case '*':
27
+ return l * r;
28
+ case '/':
29
+ return l / r;
30
+ default:
31
+ throw new Error('Unknown operator: ' + node.op);
32
+ }
33
+ }
34
+ case 'unary':
35
+ if (node.op === '-')
36
+ return -(await evaluateFormula(node.arg, ctx));
37
+ throw new Error('Unknown unary operator: ' + node.op);
38
+ default:
39
+ throw new Error('Unknown node type: ' + node.type);
40
+ }
41
+ }
42
+ //# sourceMappingURL=evaluator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluator.js","sourceRoot":"","sources":["../../server/calculator/evaluator.ts"],"names":[],"mappings":";;AAUA,0CAkCC;AAnCD,+CAA+C;AACxC,KAAK,UAAU,eAAe,CAAC,IAAiB,EAAE,GAAgB;IACvE,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,QAAQ;YACX,OAAO,IAAI,CAAC,KAAK,CAAA;QACnB,KAAK,UAAU;YACb,OAAO,MAAM,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC1C,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,EAAE,GAAG,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACnC,IAAI,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,CAAA;YAC1D,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;YAC/E,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;QACpB,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,CAAC,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;YAC/C,MAAM,CAAC,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;YAChD,QAAQ,IAAI,CAAC,EAAE,EAAE,CAAC;gBAChB,KAAK,GAAG;oBACN,OAAO,CAAC,GAAG,CAAC,CAAA;gBACd,KAAK,GAAG;oBACN,OAAO,CAAC,GAAG,CAAC,CAAA;gBACd,KAAK,GAAG;oBACN,OAAO,CAAC,GAAG,CAAC,CAAA;gBACd,KAAK,GAAG;oBACN,OAAO,CAAC,GAAG,CAAC,CAAA;gBACd;oBACE,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,EAAE,CAAC,CAAA;YACnD,CAAC;QACH,CAAC;QACD,KAAK,OAAO;YACV,IAAI,IAAI,CAAC,EAAE,KAAK,GAAG;gBAAE,OAAO,CAAC,CAAC,MAAM,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;YACnE,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,IAAI,CAAC,EAAE,CAAC,CAAA;QACvD;YACE,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAI,IAAY,CAAC,IAAI,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC","sourcesContent":["import type { FormulaNode } from './parser'\nimport type { FormulaFunctionMap } from './functions'\nimport type { ValueProvider } from './provider'\n\nexport interface EvalContext {\n functions: FormulaFunctionMap\n provider: ValueProvider\n}\n\n// 비동기 evaluator만 유지, 함수명은 evaluateFormula로 단순화\nexport async function evaluateFormula(node: FormulaNode, ctx: EvalContext): Promise<any> {\n switch (node.type) {\n case 'number':\n return node.value\n case 'variable':\n return await ctx.provider.get(node.name)\n case 'function': {\n const fn = ctx.functions[node.name]\n if (!fn) throw new Error('Unknown function: ' + node.name)\n const args = await Promise.all(node.args.map(arg => evaluateFormula(arg, ctx)))\n return fn(...args)\n }\n case 'binary': {\n const l = await evaluateFormula(node.left, ctx)\n const r = await evaluateFormula(node.right, ctx)\n switch (node.op) {\n case '+':\n return l + r\n case '-':\n return l - r\n case '*':\n return l * r\n case '/':\n return l / r\n default:\n throw new Error('Unknown operator: ' + node.op)\n }\n }\n case 'unary':\n if (node.op === '-') return -(await evaluateFormula(node.arg, ctx))\n throw new Error('Unknown unary operator: ' + node.op)\n default:\n throw new Error('Unknown node type: ' + (node as any).type)\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export type FormulaFunction = (...args: any[]) => any;
2
+ export type FormulaFunctionMap = Record<string, FormulaFunction>;
3
+ export declare const builtinFunctions: FormulaFunctionMap;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.builtinFunctions = void 0;
4
+ exports.builtinFunctions = {
5
+ sum: (...args) => args.reduce((a, b) => a + b, 0),
6
+ avg: (...args) => (args.length ? args.reduce((a, b) => a + b, 0) / args.length : 0),
7
+ min: (...args) => Math.min(...args),
8
+ max: (...args) => Math.max(...args),
9
+ round: (v, d = 0) => Number(v.toFixed(d)),
10
+ if: (cond, t, f) => (cond ? t : f),
11
+ // 성과 지수 계산을 위한 수치 적분 함수들
12
+ integrate: (func, a, b, n = 1000) => {
13
+ // 사다리꼴 적분법 (Trapezoidal Rule)
14
+ const h = (b - a) / n;
15
+ let sum = (func(a) + func(b)) / 2;
16
+ for (let i = 1; i < n; i++) {
17
+ sum += func(a + i * h);
18
+ }
19
+ return sum * h;
20
+ },
21
+ beta_integrand: (t, alpha, beta) => {
22
+ // 베타 분포 피적분함수: t^(α-1) × (1-t)^(β-1)
23
+ return Math.pow(t, alpha - 1) * Math.pow(1 - t, beta - 1);
24
+ },
25
+ incomplete_beta: (x, alpha, beta) => {
26
+ // 불완전 베타 함수 계산
27
+ const func = (t) => Math.pow(t, alpha - 1) * Math.pow(1 - t, beta - 1);
28
+ const h = x / 1000;
29
+ let sum = (func(0) + func(x)) / 2;
30
+ for (let i = 1; i < 1000; i++) {
31
+ sum += func(i * h);
32
+ }
33
+ return sum * h;
34
+ },
35
+ complete_beta: (alpha, beta) => {
36
+ // 완전 베타 함수 계산
37
+ const func = (t) => Math.pow(t, alpha - 1) * Math.pow(1 - t, beta - 1);
38
+ const h = 1 / 1000;
39
+ let sum = (func(0) + func(1)) / 2;
40
+ for (let i = 1; i < 1000; i++) {
41
+ sum += func(i * h);
42
+ }
43
+ return sum * h;
44
+ },
45
+ performance_index: (x, alpha1, beta1, alpha2, beta2) => {
46
+ // 성과 지수 계산: 1 - (불완전 베타 / 완전 베타)
47
+ const numerator = exports.builtinFunctions.incomplete_beta(x, alpha1, beta1);
48
+ const denominator = exports.builtinFunctions.complete_beta(alpha2, beta2);
49
+ return 1 - numerator / denominator;
50
+ },
51
+ // 지수 함수들
52
+ exp: (x) => Math.exp(x),
53
+ log: (x) => Math.log(x),
54
+ pow: (x, y) => Math.pow(x, y),
55
+ // 지수 감쇠 성과 지수
56
+ exponential_decay: (value, scale, power) => {
57
+ // exp(-(value / scale)^power)
58
+ return Math.exp(-Math.pow(value / scale, power));
59
+ }
60
+ // 필요시 확장
61
+ };
62
+ //# sourceMappingURL=functions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"functions.js","sourceRoot":"","sources":["../../server/calculator/functions.ts"],"names":[],"mappings":"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","sourcesContent":["export type FormulaFunction = (...args: any[]) => any\nexport type FormulaFunctionMap = Record<string, FormulaFunction>\n\nexport const builtinFunctions: FormulaFunctionMap = {\n sum: (...args: any[]) => args.reduce((a, b) => a + b, 0),\n avg: (...args: any[]) => (args.length ? args.reduce((a, b) => a + b, 0) / args.length : 0),\n min: (...args: any[]) => Math.min(...args),\n max: (...args: any[]) => Math.max(...args),\n round: (v: number, d: number = 0) => Number(v.toFixed(d)),\n if: (cond: any, t: any, f: any) => (cond ? t : f),\n // 성과 지수 계산을 위한 수치 적분 함수들\n integrate: (func: Function, a: number, b: number, n: number = 1000) => {\n // 사다리꼴 적분법 (Trapezoidal Rule)\n const h = (b - a) / n\n let sum = (func(a) + func(b)) / 2\n for (let i = 1; i < n; i++) {\n sum += func(a + i * h)\n }\n return sum * h\n },\n\n beta_integrand: (t: number, alpha: number, beta: number) => {\n // 베타 분포 피적분함수: t^(α-1) × (1-t)^(β-1)\n return Math.pow(t, alpha - 1) * Math.pow(1 - t, beta - 1)\n },\n\n incomplete_beta: (x: number, alpha: number, beta: number) => {\n // 불완전 베타 함수 계산\n const func = (t: number) => Math.pow(t, alpha - 1) * Math.pow(1 - t, beta - 1)\n const h = x / 1000\n let sum = (func(0) + func(x)) / 2\n for (let i = 1; i < 1000; i++) {\n sum += func(i * h)\n }\n return sum * h\n },\n\n complete_beta: (alpha: number, beta: number) => {\n // 완전 베타 함수 계산\n const func = (t: number) => Math.pow(t, alpha - 1) * Math.pow(1 - t, beta - 1)\n const h = 1 / 1000\n let sum = (func(0) + func(1)) / 2\n for (let i = 1; i < 1000; i++) {\n sum += func(i * h)\n }\n return sum * h\n },\n\n performance_index: (x: number, alpha1: number, beta1: number, alpha2: number, beta2: number) => {\n // 성과 지수 계산: 1 - (불완전 베타 / 완전 베타)\n const numerator = builtinFunctions.incomplete_beta(x, alpha1, beta1)\n const denominator = builtinFunctions.complete_beta(alpha2, beta2)\n return 1 - numerator / denominator\n },\n\n // 지수 함수들\n exp: (x: number) => Math.exp(x),\n log: (x: number) => Math.log(x),\n pow: (x: number, y: number) => Math.pow(x, y),\n\n // 지수 감쇠 성과 지수\n exponential_decay: (value: number, scale: number, power: number) => {\n // exp(-(value / scale)^power)\n return Math.exp(-Math.pow(value / scale, power))\n }\n // 필요시 확장\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export * from './parser';
2
+ export * from './evaluator';
3
+ export * from './functions';
4
+ export * from './provider';
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./parser"), exports);
5
+ tslib_1.__exportStar(require("./evaluator"), exports);
6
+ tslib_1.__exportStar(require("./functions"), exports);
7
+ tslib_1.__exportStar(require("./provider"), exports);
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/calculator/index.ts"],"names":[],"mappings":";;;AAAA,mDAAwB;AACxB,sDAA2B;AAC3B,sDAA2B;AAC3B,qDAA0B","sourcesContent":["export * from './parser'\nexport * from './evaluator'\nexport * from './functions'\nexport * from './provider'\n"]}
@@ -0,0 +1,21 @@
1
+ export type FormulaNode = {
2
+ type: 'number';
3
+ value: number;
4
+ } | {
5
+ type: 'variable';
6
+ name: string;
7
+ } | {
8
+ type: 'function';
9
+ name: string;
10
+ args: FormulaNode[];
11
+ } | {
12
+ type: 'binary';
13
+ op: string;
14
+ left: FormulaNode;
15
+ right: FormulaNode;
16
+ } | {
17
+ type: 'unary';
18
+ op: string;
19
+ arg: FormulaNode;
20
+ };
21
+ export declare function parseFormula(formula: string): FormulaNode;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ // KPI formula 파서: 수식 문자열을 AST로 변환
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.parseFormula = parseFormula;
5
+ // 토큰 타입 정의
6
+ const TOKEN_REGEX = /\s*(\d+\.\d+|\d+|\[.*?\]|[A-Za-z_][A-Za-z0-9_]*|[+\-*/(),])/y;
7
+ function tokenize(formula) {
8
+ const tokens = [];
9
+ let m;
10
+ let lastIndex = 0;
11
+ while ((m = TOKEN_REGEX.exec(formula))) {
12
+ lastIndex = TOKEN_REGEX.lastIndex;
13
+ const [raw] = m;
14
+ if (/^\d/.test(raw)) {
15
+ tokens.push({ type: 'number', value: raw });
16
+ }
17
+ else if (/^\[.*\]$/.test(raw)) {
18
+ tokens.push({ type: 'variable', value: raw.slice(1, -1) });
19
+ }
20
+ else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(raw)) {
21
+ tokens.push({ type: 'function', value: raw });
22
+ }
23
+ else if (/^[+\-*/]$/.test(raw)) {
24
+ tokens.push({ type: 'operator', value: raw });
25
+ }
26
+ else if (/^[(),]$/.test(raw)) {
27
+ if (raw === ',')
28
+ tokens.push({ type: 'comma', value: raw });
29
+ else
30
+ tokens.push({ type: 'paren', value: raw });
31
+ }
32
+ }
33
+ if (lastIndex < formula.length) {
34
+ throw new Error('Unexpected token at: ' + formula.slice(lastIndex));
35
+ }
36
+ return tokens;
37
+ }
38
+ // 파서 구현 (재귀 하향, 연산자 우선순위, 함수/변수/숫자/괄호 지원)
39
+ function parseFormula(formula) {
40
+ const tokens = tokenize(formula);
41
+ let pos = 0;
42
+ function peek() {
43
+ return tokens[pos];
44
+ }
45
+ function next() {
46
+ return tokens[pos++];
47
+ }
48
+ function expect(type, value) {
49
+ const t = next();
50
+ if (!t || t.type !== type || (value && t.value !== value))
51
+ throw new Error('Expected ' + type + (value ? ':' + value : ''));
52
+ return t;
53
+ }
54
+ function parsePrimary() {
55
+ const t = peek();
56
+ if (!t)
57
+ throw new Error('Unexpected end');
58
+ if (t.type === 'number') {
59
+ next();
60
+ return { type: 'number', value: parseFloat(t.value) };
61
+ }
62
+ if (t.type === 'variable') {
63
+ next();
64
+ return { type: 'variable', name: t.value };
65
+ }
66
+ if (t.type === 'function') {
67
+ // 함수 호출: func(expr, ...)
68
+ const name = t.value;
69
+ next();
70
+ expect('paren', '(');
71
+ const args = [];
72
+ if ((peek() && peek().type !== 'paren') || (peek() && peek().value !== ')')) {
73
+ while (true) {
74
+ args.push(parseExpr());
75
+ if (peek() && peek().type === 'comma') {
76
+ next();
77
+ }
78
+ else
79
+ break;
80
+ }
81
+ }
82
+ expect('paren', ')');
83
+ return { type: 'function', name, args };
84
+ }
85
+ if (t.type === 'operator' && t.value === '-') {
86
+ next();
87
+ return { type: 'unary', op: '-', arg: parsePrimary() };
88
+ }
89
+ if (t.type === 'paren' && t.value === '(') {
90
+ next();
91
+ const node = parseExpr();
92
+ expect('paren', ')');
93
+ return node;
94
+ }
95
+ throw new Error('Unexpected token: ' + t.type + ':' + t.value);
96
+ }
97
+ function parseMulDiv() {
98
+ let node = parsePrimary();
99
+ while (peek() && peek().type === 'operator' && (peek().value === '*' || peek().value === '/')) {
100
+ const op = next().value;
101
+ node = { type: 'binary', op, left: node, right: parsePrimary() };
102
+ }
103
+ return node;
104
+ }
105
+ function parseAddSub() {
106
+ let node = parseMulDiv();
107
+ while (peek() && peek().type === 'operator' && (peek().value === '+' || peek().value === '-')) {
108
+ const op = next().value;
109
+ node = { type: 'binary', op, left: node, right: parseMulDiv() };
110
+ }
111
+ return node;
112
+ }
113
+ function parseExpr() {
114
+ return parseAddSub();
115
+ }
116
+ const ast = parseExpr();
117
+ if (pos < tokens.length)
118
+ throw new Error('Unexpected token at end: ' + JSON.stringify(tokens[pos]));
119
+ return ast;
120
+ }
121
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../server/calculator/parser.ts"],"names":[],"mappings":"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","sourcesContent":["// KPI formula 파서: 수식 문자열을 AST로 변환\n\nexport type FormulaNode =\n | { type: 'number'; value: number }\n | { type: 'variable'; name: string }\n | { type: 'function'; name: string; args: FormulaNode[] }\n | { type: 'binary'; op: string; left: FormulaNode; right: FormulaNode }\n | { type: 'unary'; op: string; arg: FormulaNode }\n\n// 토큰 타입 정의\nconst TOKEN_REGEX = /\\s*(\\d+\\.\\d+|\\d+|\\[.*?\\]|[A-Za-z_][A-Za-z0-9_]*|[+\\-*/(),])/y\n\ninterface Token {\n type: 'number' | 'variable' | 'function' | 'operator' | 'paren' | 'comma'\n value: string\n}\n\nfunction tokenize(formula: string): Token[] {\n const tokens: Token[] = []\n let m: RegExpExecArray | null\n let lastIndex = 0\n while ((m = TOKEN_REGEX.exec(formula))) {\n lastIndex = TOKEN_REGEX.lastIndex\n const [raw] = m\n if (/^\\d/.test(raw)) {\n tokens.push({ type: 'number', value: raw })\n } else if (/^\\[.*\\]$/.test(raw)) {\n tokens.push({ type: 'variable', value: raw.slice(1, -1) })\n } else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(raw)) {\n tokens.push({ type: 'function', value: raw })\n } else if (/^[+\\-*/]$/.test(raw)) {\n tokens.push({ type: 'operator', value: raw })\n } else if (/^[(),]$/.test(raw)) {\n if (raw === ',') tokens.push({ type: 'comma', value: raw })\n else tokens.push({ type: 'paren', value: raw })\n }\n }\n if (lastIndex < formula.length) {\n throw new Error('Unexpected token at: ' + formula.slice(lastIndex))\n }\n return tokens\n}\n\n// 파서 구현 (재귀 하향, 연산자 우선순위, 함수/변수/숫자/괄호 지원)\nexport function parseFormula(formula: string): FormulaNode {\n const tokens = tokenize(formula)\n let pos = 0\n\n function peek() {\n return tokens[pos]\n }\n function next() {\n return tokens[pos++]\n }\n function expect(type: string, value?: string) {\n const t = next()\n if (!t || t.type !== type || (value && t.value !== value))\n throw new Error('Expected ' + type + (value ? ':' + value : ''))\n return t\n }\n\n function parsePrimary(): FormulaNode {\n const t = peek()\n if (!t) throw new Error('Unexpected end')\n if (t.type === 'number') {\n next()\n return { type: 'number', value: parseFloat(t.value) }\n }\n if (t.type === 'variable') {\n next()\n return { type: 'variable', name: t.value }\n }\n if (t.type === 'function') {\n // 함수 호출: func(expr, ...)\n const name = t.value\n next()\n expect('paren', '(')\n const args: FormulaNode[] = []\n if ((peek() && peek().type !== 'paren') || (peek() && peek().value !== ')')) {\n while (true) {\n args.push(parseExpr())\n if (peek() && peek().type === 'comma') {\n next()\n } else break\n }\n }\n expect('paren', ')')\n return { type: 'function', name, args }\n }\n if (t.type === 'operator' && t.value === '-') {\n next()\n return { type: 'unary', op: '-', arg: parsePrimary() }\n }\n if (t.type === 'paren' && t.value === '(') {\n next()\n const node = parseExpr()\n expect('paren', ')')\n return node\n }\n throw new Error('Unexpected token: ' + t.type + ':' + t.value)\n }\n\n function parseMulDiv(): FormulaNode {\n let node = parsePrimary()\n while (peek() && peek().type === 'operator' && (peek().value === '*' || peek().value === '/')) {\n const op = next().value\n node = { type: 'binary', op, left: node, right: parsePrimary() }\n }\n return node\n }\n\n function parseAddSub(): FormulaNode {\n let node = parseMulDiv()\n while (peek() && peek().type === 'operator' && (peek().value === '+' || peek().value === '-')) {\n const op = next().value\n node = { type: 'binary', op, left: node, right: parseMulDiv() }\n }\n return node\n }\n\n function parseExpr(): FormulaNode {\n return parseAddSub()\n }\n\n const ast = parseExpr()\n if (pos < tokens.length) throw new Error('Unexpected token at end: ' + JSON.stringify(tokens[pos]))\n return ast\n}\n"]}
@@ -0,0 +1,8 @@
1
+ export interface ValueProvider {
2
+ get(name: string): any | Promise<any>;
3
+ }
4
+ export declare class ObjectValueProvider implements ValueProvider {
5
+ private values;
6
+ constructor(values: Record<string, any>);
7
+ get(name: string): any;
8
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ObjectValueProvider = void 0;
4
+ class ObjectValueProvider {
5
+ constructor(values) {
6
+ this.values = values;
7
+ }
8
+ get(name) {
9
+ return this.values[name];
10
+ }
11
+ }
12
+ exports.ObjectValueProvider = ObjectValueProvider;
13
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../server/calculator/provider.ts"],"names":[],"mappings":";;;AAIA,MAAa,mBAAmB;IAC9B,YAAoB,MAA2B;QAA3B,WAAM,GAAN,MAAM,CAAqB;IAAG,CAAC;IACnD,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;CACF;AALD,kDAKC","sourcesContent":["export interface ValueProvider {\n get(name: string): any | Promise<any>\n}\n\nexport class ObjectValueProvider implements ValueProvider {\n constructor(private values: Record<string, any>) {}\n get(name: string) {\n return this.values[name]\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import { ValueProvider } from '../calculator/provider';
2
+ export declare class KpiMetricValueProvider implements ValueProvider {
3
+ private options;
4
+ constructor(options: {
5
+ valueDate: string;
6
+ group?: string;
7
+ domainId: string;
8
+ tx?: any;
9
+ });
10
+ get(name: string): Promise<number>;
11
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KpiMetricValueProvider = void 0;
4
+ const shell_1 = require("@things-factory/shell");
5
+ const kpi_metric_1 = require("../service/kpi-metric/kpi-metric");
6
+ const kpi_metric_value_1 = require("../service/kpi-metric-value/kpi-metric-value");
7
+ const value_date_util_1 = require("../service/utils/value-date-util");
8
+ class KpiMetricValueProvider {
9
+ constructor(options) {
10
+ this.options = options;
11
+ }
12
+ async get(name) {
13
+ const metricRepo = (0, shell_1.getRepository)(kpi_metric_1.KpiMetric, this.options.tx);
14
+ const metric = await metricRepo.findOne({ where: { name, domain: { id: this.options.domainId } } });
15
+ if (!metric)
16
+ throw new Error(`Metric not found: ${name}`);
17
+ // ALLTIME 타입인 경우 valueDate 무시하고 최신 값 조회
18
+ if (metric.periodType === 'ALLTIME') {
19
+ const valueRepo = (0, shell_1.getRepository)(kpi_metric_value_1.KpiMetricValue, this.options.tx);
20
+ const value = await valueRepo.findOne({
21
+ where: {
22
+ metric: { id: metric.id },
23
+ group: this.options.group ?? '',
24
+ domain: { id: this.options.domainId }
25
+ },
26
+ order: { createdAt: 'DESC' }
27
+ });
28
+ if (!value) {
29
+ throw new Error(`Metric value not found: metric='${name}', group='${this.options.group ?? ''}' (ALLTIME type)`);
30
+ }
31
+ return value.value;
32
+ }
33
+ // metric의 periodType에 맞게 valueDate 보정
34
+ let valueDate = this.options.valueDate;
35
+ if (!valueDate) {
36
+ valueDate = (0, value_date_util_1.getDefaultValueDate)(metric.periodType, 'current');
37
+ }
38
+ else {
39
+ // valueDate가 metric.periodType에 맞는 포맷인지 검사, 아니면 보정
40
+ // (간단히: 길이로 구분, 실제는 정규식 등으로 더 정교하게 가능)
41
+ if (metric.periodType === 'MONTH' && valueDate.length > 7)
42
+ valueDate = valueDate.slice(0, 7);
43
+ if (metric.periodType === 'DAY' && valueDate.length > 10)
44
+ valueDate = valueDate.slice(0, 10);
45
+ // 기타 periodType별 보정 추가 가능
46
+ }
47
+ const valueRepo = (0, shell_1.getRepository)(kpi_metric_value_1.KpiMetricValue, this.options.tx);
48
+ const value = await valueRepo.findOne({
49
+ where: {
50
+ metric: { id: metric.id },
51
+ valueDate,
52
+ group: this.options.group ?? '',
53
+ domain: { id: this.options.domainId }
54
+ }
55
+ });
56
+ if (!value) {
57
+ throw new Error(`Metric value not found: metric='${name}', group='${this.options.group ?? ''}', valueDate='${valueDate}', periodType='${metric.periodType}'`);
58
+ }
59
+ return value.value;
60
+ }
61
+ }
62
+ exports.KpiMetricValueProvider = KpiMetricValueProvider;
63
+ //# sourceMappingURL=kpi-metric-value-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kpi-metric-value-provider.js","sourceRoot":"","sources":["../../server/controllers/kpi-metric-value-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository } from '@things-factory/shell'\nimport { KpiMetric } from '../service/kpi-metric/kpi-metric'\nimport { KpiMetricValue } from '../service/kpi-metric-value/kpi-metric-value'\nimport { ValueProvider } from '../calculator/provider'\nimport { getDefaultValueDate } from '../service/utils/value-date-util'\n\nexport class KpiMetricValueProvider implements ValueProvider {\n constructor(\n private options: {\n valueDate: string\n group?: string\n domainId: string\n tx?: any\n }\n ) {}\n\n async get(name: string) {\n const metricRepo = getRepository(KpiMetric, this.options.tx)\n const metric = await metricRepo.findOne({ where: { name, domain: { id: this.options.domainId } } })\n if (!metric) throw new Error(`Metric not found: ${name}`)\n\n // ALLTIME 타입인 경우 valueDate 무시하고 최신 값 조회\n if (metric.periodType === 'ALLTIME') {\n const valueRepo = getRepository(KpiMetricValue, this.options.tx)\n const value = await valueRepo.findOne({\n where: {\n metric: { id: metric.id },\n group: this.options.group ?? '',\n domain: { id: this.options.domainId }\n },\n order: { createdAt: 'DESC' }\n })\n if (!value) {\n throw new Error(`Metric value not found: metric='${name}', group='${this.options.group ?? ''}' (ALLTIME type)`)\n }\n return value.value\n }\n\n // metric의 periodType에 맞게 valueDate 보정\n let valueDate = this.options.valueDate\n if (!valueDate) {\n valueDate = getDefaultValueDate(metric.periodType, 'current')\n } else {\n // valueDate가 metric.periodType에 맞는 포맷인지 검사, 아니면 보정\n // (간단히: 길이로 구분, 실제는 정규식 등으로 더 정교하게 가능)\n if (metric.periodType === 'MONTH' && valueDate.length > 7) valueDate = valueDate.slice(0, 7)\n if (metric.periodType === 'DAY' && valueDate.length > 10) valueDate = valueDate.slice(0, 10)\n // 기타 periodType별 보정 추가 가능\n }\n const valueRepo = getRepository(KpiMetricValue, this.options.tx)\n const value = await valueRepo.findOne({\n where: {\n metric: { id: metric.id },\n valueDate,\n group: this.options.group ?? '',\n domain: { id: this.options.domainId }\n }\n })\n if (!value) {\n throw new Error(\n `Metric value not found: metric='${name}', group='${this.options.group ?? ''}', valueDate='${valueDate}', periodType='${metric.periodType}'`\n )\n }\n return value.value\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import { ValueProvider } from '../calculator/provider';
2
+ export declare class KpiValueProvider implements ValueProvider {
3
+ private options;
4
+ constructor(options: {
5
+ valueDate: string;
6
+ group?: string;
7
+ domainId: string;
8
+ tx?: any;
9
+ });
10
+ get(name: string): Promise<number>;
11
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KpiValueProvider = void 0;
4
+ const shell_1 = require("@things-factory/shell");
5
+ const kpi_1 = require("../service/kpi/kpi");
6
+ const kpi_value_1 = require("../service/kpi-value/kpi-value");
7
+ class KpiValueProvider {
8
+ constructor(options) {
9
+ this.options = options;
10
+ }
11
+ async get(name) {
12
+ const kpiRepo = (0, shell_1.getRepository)(kpi_1.Kpi, this.options.tx);
13
+ const kpi = await kpiRepo.findOne({ where: { name, domain: { id: this.options.domainId } } });
14
+ if (!kpi)
15
+ throw new Error(`KPI not found: ${name}`);
16
+ const valueRepo = (0, shell_1.getRepository)(kpi_value_1.KpiValue, this.options.tx);
17
+ // SINGLE 타입인 경우 valueDate 무시하고 최신 값 조회
18
+ const whereCondition = {
19
+ kpi: { id: kpi.id },
20
+ group: this.options.group ?? '',
21
+ domain: { id: this.options.domainId }
22
+ };
23
+ if (kpi.periodType === 'ALLTIME') {
24
+ // ALLTIME 타입은 valueDate 무시하고 최신 값 사용
25
+ const value = await valueRepo.findOne({
26
+ where: whereCondition,
27
+ order: { createdAt: 'DESC' }
28
+ });
29
+ if (!value) {
30
+ throw new Error(`KPI value not found: kpi='${name}', group='${this.options.group ?? ''}' (ALLTIME type)`);
31
+ }
32
+ return value.value;
33
+ }
34
+ else {
35
+ // 기존 로직: 특정 날짜의 값 조회
36
+ whereCondition.valueDate = this.options.valueDate;
37
+ const value = await valueRepo.findOne({ where: whereCondition });
38
+ if (!value) {
39
+ throw new Error(`KPI value not found: kpi='${name}', group='${this.options.group ?? ''}', valueDate='${this.options.valueDate}'`);
40
+ }
41
+ return value.value;
42
+ }
43
+ }
44
+ }
45
+ exports.KpiValueProvider = KpiValueProvider;
46
+ //# sourceMappingURL=kpi-value-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kpi-value-provider.js","sourceRoot":"","sources":["../../server/controllers/kpi-value-provider.ts"],"names":[],"mappings":";;;AAAA,iDAAqD;AACrD,4CAAwC;AACxC,8DAAyD;AAGzD,MAAa,gBAAgB;IAC3B,YACU,OAKP;QALO,YAAO,GAAP,OAAO,CAKd;IACA,CAAC;IAEJ,KAAK,CAAC,GAAG,CAAC,IAAY;QACpB,MAAM,OAAO,GAAG,IAAA,qBAAa,EAAC,SAAG,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACnD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAA;QAC7F,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAA;QACnD,MAAM,SAAS,GAAG,IAAA,qBAAa,EAAC,oBAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAE1D,uCAAuC;QACvC,MAAM,cAAc,GAAQ;YAC1B,GAAG,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE;YACnB,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC/B,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE;SACtC,CAAA;QAED,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACjC,qCAAqC;YACrC,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC;gBACpC,KAAK,EAAE,cAAc;gBACrB,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;aAC7B,CAAC,CAAA;YACF,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,aAAa,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,kBAAkB,CAAC,CAAA;YAC3G,CAAC;YACD,OAAO,KAAK,CAAC,KAAK,CAAA;QACpB,CAAC;aAAM,CAAC;YACN,qBAAqB;YACrB,cAAc,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAA;YACjD,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;YAChE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,KAAK,CACb,6BAA6B,IAAI,aAAa,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,iBAAiB,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CACjH,CAAA;YACH,CAAC;YACD,OAAO,KAAK,CAAC,KAAK,CAAA;QACpB,CAAC;IACH,CAAC;CACF;AA7CD,4CA6CC","sourcesContent":["import { getRepository } from '@things-factory/shell'\nimport { Kpi } from '../service/kpi/kpi'\nimport { KpiValue } from '../service/kpi-value/kpi-value'\nimport { ValueProvider } from '../calculator/provider'\n\nexport class KpiValueProvider implements ValueProvider {\n constructor(\n private options: {\n valueDate: string\n group?: string\n domainId: string\n tx?: any\n }\n ) {}\n\n async get(name: string) {\n const kpiRepo = getRepository(Kpi, this.options.tx)\n const kpi = await kpiRepo.findOne({ where: { name, domain: { id: this.options.domainId } } })\n if (!kpi) throw new Error(`KPI not found: ${name}`)\n const valueRepo = getRepository(KpiValue, this.options.tx)\n\n // SINGLE 타입인 경우 valueDate 무시하고 최신 값 조회\n const whereCondition: any = {\n kpi: { id: kpi.id },\n group: this.options.group ?? '',\n domain: { id: this.options.domainId }\n }\n\n if (kpi.periodType === 'ALLTIME') {\n // ALLTIME 타입은 valueDate 무시하고 최신 값 사용\n const value = await valueRepo.findOne({\n where: whereCondition,\n order: { createdAt: 'DESC' }\n })\n if (!value) {\n throw new Error(`KPI value not found: kpi='${name}', group='${this.options.group ?? ''}' (ALLTIME type)`)\n }\n return value.value\n } else {\n // 기존 로직: 특정 날짜의 값 조회\n whereCondition.valueDate = this.options.valueDate\n const value = await valueRepo.findOne({ where: whereCondition })\n if (!value) {\n throw new Error(\n `KPI value not found: kpi='${name}', group='${this.options.group ?? ''}', valueDate='${this.options.valueDate}'`\n )\n }\n return value.value\n }\n }\n}\n"]}
@@ -9,7 +9,7 @@ export * from './kpi-metric/kpi-metric-type';
9
9
  export * from './kpi-metric-value/kpi-metric-value';
10
10
  export * from './kpi-metric-value/kpi-metric-value-type';
11
11
  export * from './kpi-alert';
12
- export declare const entities: (typeof import("./kpi-metric/kpi-metric").KpiMetric | typeof import("./kpi/kpi").Kpi | typeof import("./kpi/kpi-history").KpiHistory | typeof import("./kpi-category/kpi-category").KpiCategory | typeof import("./kpi-value/kpi-value").KpiValue | typeof import("./kpi-metric-value/kpi-metric-value").KpiMetricValue)[];
12
+ export declare const entities: (typeof import("./kpi-category/kpi-category").KpiCategory | typeof import("./kpi/kpi").Kpi | typeof import("./kpi-value/kpi-value").KpiValue | typeof import("./kpi-metric/kpi-metric").KpiMetric | typeof import("./kpi-metric-value/kpi-metric-value").KpiMetricValue | typeof import("./kpi/kpi-history").KpiHistory)[];
13
13
  export declare const schema: {
14
- resolverClasses: (typeof import("./kpi/kpi-query").KpiQuery | typeof import("./kpi/kpi-mutation").KpiMutation | typeof import("./kpi-category/kpi-category-query").KpiCategoryQuery | typeof import("./kpi-category/kpi-category-mutation").KpiCategoryMutation | typeof import("./kpi-value/kpi-value-query").KpiValueQuery | typeof import("./kpi-value/kpi-value-mutation").KpiValueMutation | typeof import("./kpi-metric/kpi-metric-query").KpiMetricQuery | typeof import("./kpi-metric/kpi-metric-mutation").KpiMetricMutation | typeof import("./kpi-metric-value/kpi-metric-value-query").KpiMetricValueQuery | typeof import("./kpi-metric-value/kpi-metric-value-mutation").KpiMetricValueMutation | typeof import("./kpi-alert/kpi-alert-query").KpiAlertQuery)[];
14
+ resolverClasses: (typeof import("./kpi-alert/kpi-alert-query").KpiAlertQuery | typeof import("./kpi/kpi-query").KpiQuery | typeof import("./kpi/kpi-mutation").KpiMutation | typeof import("./kpi-category/kpi-category-query").KpiCategoryQuery | typeof import("./kpi-category/kpi-category-mutation").KpiCategoryMutation | typeof import("./kpi-value/kpi-value-query").KpiValueQuery | typeof import("./kpi-value/kpi-value-mutation").KpiValueMutation | typeof import("./kpi-metric/kpi-metric-query").KpiMetricQuery | typeof import("./kpi-metric/kpi-metric-mutation").KpiMetricMutation | typeof import("./kpi-metric-value/kpi-metric-value-query").KpiMetricValueQuery | typeof import("./kpi-metric-value/kpi-metric-value-mutation").KpiMetricValueMutation)[];
15
15
  };
@@ -7,7 +7,7 @@ const kpi_metric_1 = require("../kpi-metric/kpi-metric");
7
7
  const kpi_value_1 = require("../kpi-value/kpi-value");
8
8
  const kpi_formula_service_1 = require("./kpi-formula.service");
9
9
  const aggregate_kpi_metric_1 = require("../kpi-metric/aggregate-kpi-metric");
10
- const kpi_value_grade_service_1 = require("../kpi-value/kpi-value-grade.service");
10
+ const kpi_value_score_service_1 = require("../kpi-value/kpi-value-score.service");
11
11
  /**
12
12
  * KPI 단위 집계/산식 자동화 함수
13
13
  * @param kpiId KPI ID
@@ -17,7 +17,7 @@ const kpi_value_grade_service_1 = require("../kpi-value/kpi-value-grade.service"
17
17
  */
18
18
  async function aggregateKpiValue(kpiId, domainId, context) {
19
19
  const tx = context.state?.tx || (0, shell_1.getRepository)(kpi_1.Kpi).manager;
20
- const gradeService = new kpi_value_grade_service_1.KpiValueGradeService();
20
+ const scoreService = new kpi_value_score_service_1.KpiValueScoreService();
21
21
  // 1. KPI 정보 조회
22
22
  const kpi = await (0, shell_1.getRepository)(kpi_1.Kpi).findOne({ where: { id: kpiId, domain: { id: domainId } } });
23
23
  if (!kpi)
@@ -80,8 +80,8 @@ async function aggregateKpiValue(kpiId, domainId, context) {
80
80
  entity.domain = kpi.domain;
81
81
  entity.creator = context.state?.user;
82
82
  entity.updater = context.state?.user;
83
- // 등급 자동 계산 및 저장
84
- await gradeService.calculateAndSaveGrade(entity, kpi);
83
+ // 성과 점수 자동 계산 및 저장
84
+ await scoreService.calculateAndSaveScore(entity, kpi);
85
85
  entity = await repo.save(entity);
86
86
  savedValues.push(entity);
87
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"aggregate-kpi.js","sourceRoot":"","sources":["../../../server/service/kpi/aggregate-kpi.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository } from '@things-factory/shell'\nimport { Kpi } from './kpi'\nimport { KpiMetric } from '../kpi-metric/kpi-metric'\nimport { KpiValue, KpiValueInputType } from '../kpi-value/kpi-value'\nimport { KpiFormulaService } from './kpi-formula.service'\nimport { aggregateKpiMetricValue } from '../kpi-metric/aggregate-kpi-metric'\nimport { KpiValueGradeService } from '../kpi-value/kpi-value-grade.service'\n\n/**\n * KPI 단위 집계/산식 자동화 함수\n * @param kpiId KPI ID\n * @param domainId 도메인 ID\n * @param context ResolverContext\n * @returns 저장된 KPI Value 배열\n */\nexport async function aggregateKpiValue(kpiId: string, domainId: string, context: ResolverContext) {\n const tx = context.state?.tx || getRepository(Kpi).manager\n const gradeService = new KpiValueGradeService()\n\n // 1. KPI 정보 조회\n const kpi = await getRepository(Kpi).findOne({ where: { id: kpiId, domain: { id: domainId } } })\n if (!kpi) throw new Error('KPI 정보 없음')\n if (!kpi.active) throw new Error('비활성화된 KPI')\n if (!kpi.formula) throw new Error('KPI formula 없음')\n\n // 2. formula 파싱 및 metric code별 값 집계\n const formulaService = new KpiFormulaService()\n const metricCodes = formulaService.extractMetricCodes(kpi.formula)\n const codeValueMap: Record<string, any[]> = {}\n for (const code of metricCodes) {\n // code로 metric 찾기\n const metric = await getRepository(KpiMetric).findOne({ where: { name: code, domain: { id: domainId } } })\n if (!metric) throw new Error(`KPI formula metric '${code}' not found`)\n codeValueMap[code] = await aggregateKpiMetricValue(metric.id, domainId, context)\n }\n // group/date/period별로 값 매핑(가장 최근 기준, group key 조합)\n const groupKey = v => [v.date, v.period, v.group].join('|')\n const groupMap: Record<string, any> = {}\n for (const code of metricCodes) {\n for (const v of codeValueMap[code]) {\n const key = groupKey(v)\n groupMap[key] = groupMap[key] || { ...v, _values: {} }\n groupMap[key]._values[code] = v.value\n }\n }\n // formula 계산 (js eval)\n const savedValues = []\n for (const key in groupMap) {\n const ctx = groupMap[key]._values\n let value = null\n try {\n value = Function(...Object.keys(ctx), `return (${kpi.formula})`)(...Object.values(ctx))\n } catch (e) {\n value = null\n }\n const valueDate = groupMap[key].date\n const group = groupMap[key].group\n const version = kpi.version || 1\n if (value == null || isNaN(value)) continue\n // upsert(동일 KPI, valueDate, group, version) 기준으로 저장\n const repo = getRepository(KpiValue, context.state?.tx)\n const existing = await repo.findOne({\n where: { kpi: { id: kpi.id }, valueDate, group, version, domain: { id: domainId } }\n })\n let entity = existing || repo.create()\n entity.kpi = kpi\n entity.kpiId = kpi.id\n entity.version = version\n entity.valueDate = valueDate\n entity.value = value\n entity.group = group\n entity.inputType = KpiValueInputType.AUTO\n entity.source = 'AUTO'\n entity.domain = kpi.domain\n entity.creator = context.state?.user\n entity.updater = context.state?.user\n\n // 등급 자동 계산 및 저장\n await gradeService.calculateAndSaveGrade(entity, kpi)\n\n entity = await repo.save(entity)\n savedValues.push(entity)\n }\n return savedValues\n}\n"]}
1
+ {"version":3,"file":"aggregate-kpi.js","sourceRoot":"","sources":["../../../server/service/kpi/aggregate-kpi.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository } from '@things-factory/shell'\nimport { Kpi } from './kpi'\nimport { KpiMetric } from '../kpi-metric/kpi-metric'\nimport { KpiValue, KpiValueInputType } from '../kpi-value/kpi-value'\nimport { KpiFormulaService } from './kpi-formula.service'\nimport { aggregateKpiMetricValue } from '../kpi-metric/aggregate-kpi-metric'\nimport { KpiValueScoreService } from '../kpi-value/kpi-value-score.service'\n\n/**\n * KPI 단위 집계/산식 자동화 함수\n * @param kpiId KPI ID\n * @param domainId 도메인 ID\n * @param context ResolverContext\n * @returns 저장된 KPI Value 배열\n */\nexport async function aggregateKpiValue(kpiId: string, domainId: string, context: ResolverContext) {\n const tx = context.state?.tx || getRepository(Kpi).manager\n const scoreService = new KpiValueScoreService()\n\n // 1. KPI 정보 조회\n const kpi = await getRepository(Kpi).findOne({ where: { id: kpiId, domain: { id: domainId } } })\n if (!kpi) throw new Error('KPI 정보 없음')\n if (!kpi.active) throw new Error('비활성화된 KPI')\n if (!kpi.formula) throw new Error('KPI formula 없음')\n\n // 2. formula 파싱 및 metric code별 값 집계\n const formulaService = new KpiFormulaService()\n const metricCodes = formulaService.extractMetricCodes(kpi.formula)\n const codeValueMap: Record<string, any[]> = {}\n for (const code of metricCodes) {\n // code로 metric 찾기\n const metric = await getRepository(KpiMetric).findOne({ where: { name: code, domain: { id: domainId } } })\n if (!metric) throw new Error(`KPI formula metric '${code}' not found`)\n codeValueMap[code] = await aggregateKpiMetricValue(metric.id, domainId, context)\n }\n // group/date/period별로 값 매핑(가장 최근 기준, group key 조합)\n const groupKey = v => [v.date, v.period, v.group].join('|')\n const groupMap: Record<string, any> = {}\n for (const code of metricCodes) {\n for (const v of codeValueMap[code]) {\n const key = groupKey(v)\n groupMap[key] = groupMap[key] || { ...v, _values: {} }\n groupMap[key]._values[code] = v.value\n }\n }\n // formula 계산 (js eval)\n const savedValues = []\n for (const key in groupMap) {\n const ctx = groupMap[key]._values\n let value = null\n try {\n value = Function(...Object.keys(ctx), `return (${kpi.formula})`)(...Object.values(ctx))\n } catch (e) {\n value = null\n }\n const valueDate = groupMap[key].date\n const group = groupMap[key].group\n const version = kpi.version || 1\n if (value == null || isNaN(value)) continue\n // upsert(동일 KPI, valueDate, group, version) 기준으로 저장\n const repo = getRepository(KpiValue, context.state?.tx)\n const existing = await repo.findOne({\n where: { kpi: { id: kpi.id }, valueDate, group, version, domain: { id: domainId } }\n })\n let entity = existing || repo.create()\n entity.kpi = kpi\n entity.kpiId = kpi.id\n entity.version = version\n entity.valueDate = valueDate\n entity.value = value\n entity.group = group\n entity.inputType = KpiValueInputType.AUTO\n entity.source = 'AUTO'\n entity.domain = kpi.domain\n entity.creator = context.state?.user\n entity.updater = context.state?.user\n\n // 성과 점수 자동 계산 및 저장\n await scoreService.calculateAndSaveScore(entity, kpi)\n\n entity = await repo.save(entity)\n savedValues.push(entity)\n }\n return savedValues\n}\n"]}