@things-factory/kpi 9.1.19 → 10.0.0-beta.2
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/client/pages/kpi/kpi-list-page.ts +339 -525
- package/client/pages/kpi/kpi-tree-page.ts +135 -207
- package/client/pages/kpi-metric/kpi-metric-list-page.ts +146 -226
- package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +187 -295
- package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +123 -194
- package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.ts +57 -91
- package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +180 -278
- package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +186 -286
- package/client/pages/kpi-value/kpi-value-editor-page.ts +189 -292
- package/client/pages/kpi-value/kpi-value-list-page.ts +170 -264
- package/dist-client/pages/kpi/kpi-list-page.d.ts +0 -6
- package/dist-client/pages/kpi/kpi-list-page.js +150 -282
- package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
- package/dist-client/pages/kpi/kpi-tree-page.d.ts +1 -7
- package/dist-client/pages/kpi/kpi-tree-page.js +76 -127
- package/dist-client/pages/kpi/kpi-tree-page.js.map +1 -1
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.d.ts +0 -6
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +62 -116
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -7
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +82 -140
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +0 -6
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +54 -98
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +1 -7
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +30 -57
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.d.ts +1 -7
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +91 -153
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.d.ts +0 -6
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +81 -155
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-editor-page.d.ts +1 -7
- package/dist-client/pages/kpi-value/kpi-value-editor-page.js +80 -136
- package/dist-client/pages/kpi-value/kpi-value-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +0 -6
- package/dist-client/pages/kpi-value/kpi-value-list-page.js +73 -134
- package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +18 -18
- package/client/tsconfig.json +0 -11
- package/dist-server/tsconfig.json +0 -10
- package/server/@types/index.d.ts +0 -11
- package/server/calculator/evaluator.ts +0 -45
- package/server/calculator/functions.ts +0 -67
- package/server/calculator/index.ts +0 -4
- package/server/calculator/parser.ts +0 -137
- package/server/calculator/provider.ts +0 -10
- package/server/controllers/index.ts +0 -2
- package/server/controllers/kpi-metric-value-provider.ts +0 -79
- package/server/controllers/kpi-value-provider.ts +0 -51
- package/server/index.ts +0 -6
- package/server/migrations/1752190849680-seed-kpi-metrics.ts +0 -124
- package/server/migrations/1752190849681-seed-kpi.ts +0 -356
- package/server/migrations/1752192090123-add-grades-to-kpi.ts +0 -67
- package/server/migrations/1752192090124-add-kpi-statistics.ts +0 -719
- package/server/migrations/1752192090128-seed-kpi-org-scope.ts +0 -132
- package/server/migrations/1752192090129-seed-kpi-values.ts +0 -207
- package/server/migrations/grade-data/x11-performance-table.json +0 -962
- package/server/migrations/grade-data/x12-performance-table.json +0 -611
- package/server/migrations/grade-data/x14-performance-table.json +0 -42
- package/server/migrations/grade-data/x21-performance-table.json +0 -889
- package/server/migrations/grade-data/x22-performance-table.json +0 -1064
- package/server/migrations/grade-data/x23-performance-table.json +0 -42
- package/server/migrations/grade-data/x31-performance-table.json +0 -644
- package/server/migrations/grade-data/x32-performance-table.json +0 -993
- package/server/migrations/grade-data/x33-performance-table.json +0 -195
- package/server/migrations/grade-data/x34-performance-table.json +0 -12
- package/server/migrations/grade-data/x35-performance-table.json +0 -42
- package/server/migrations/grade-data/x41-performance-table.json +0 -825
- package/server/migrations/grade-data/x42-performance-table.json +0 -786
- package/server/migrations/grade-data/x43-performance-table.json +0 -12
- package/server/migrations/grade-data/x44-performance-table.json +0 -42
- package/server/migrations/grade-data/x51-performance-table.json +0 -924
- package/server/migrations/grade-data/x52-performance-table.json +0 -42
- package/server/migrations/grade-data/x61-performance-table.json +0 -261
- package/server/migrations/grade-data/x62-performance-table.json +0 -42
- package/server/migrations/index.ts +0 -9
- package/server/migrations/seed-data/kpi-metrics-seed.json +0 -454
- package/server/migrations/seed-data/kpi-org-scope-seed.json +0 -1676
- package/server/migrations/seed-data/kpi-scopes-seed.json +0 -121
- package/server/migrations/seed-data/kpi-values-seed.json +0 -402
- package/server/migrations/seed-data/kpis-seed.json +0 -488
- package/server/migrations/seed-data/scope-definitions-seed.json +0 -90
- package/server/routes.ts +0 -81
- package/server/service/index.ts +0 -51
- package/server/service/kpi/aggregate-kpi.ts +0 -103
- package/server/service/kpi/event-subscriber.ts +0 -29
- package/server/service/kpi/index.ts +0 -9
- package/server/service/kpi/kpi-formula.service.ts +0 -164
- package/server/service/kpi/kpi-grade.types.ts +0 -28
- package/server/service/kpi/kpi-history.ts +0 -126
- package/server/service/kpi/kpi-mutation.ts +0 -553
- package/server/service/kpi/kpi-query.ts +0 -224
- package/server/service/kpi/kpi-type.ts +0 -151
- package/server/service/kpi/kpi.ts +0 -254
- package/server/service/kpi-alert/index.ts +0 -3
- package/server/service/kpi-alert/kpi-alert-query.ts +0 -59
- package/server/service/kpi-alert/kpi-alert-type.ts +0 -20
- package/server/service/kpi-metric/aggregate-kpi-metric.ts +0 -132
- package/server/service/kpi-metric/index.ts +0 -7
- package/server/service/kpi-metric/kpi-metric-mutation.ts +0 -309
- package/server/service/kpi-metric/kpi-metric-query.ts +0 -70
- package/server/service/kpi-metric/kpi-metric-type.ts +0 -111
- package/server/service/kpi-metric/kpi-metric.ts +0 -134
- package/server/service/kpi-metric-value/index.ts +0 -7
- package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +0 -270
- package/server/service/kpi-metric-value/kpi-metric-value-query.ts +0 -62
- package/server/service/kpi-metric-value/kpi-metric-value-type.ts +0 -82
- package/server/service/kpi-metric-value/kpi-metric-value.ts +0 -93
- package/server/service/kpi-org-scope/index.ts +0 -6
- package/server/service/kpi-org-scope/kpi-org-scope-mutation.ts +0 -173
- package/server/service/kpi-org-scope/kpi-org-scope-query.ts +0 -127
- package/server/service/kpi-org-scope/kpi-org-scope-type.ts +0 -68
- package/server/service/kpi-org-scope/kpi-org-scope.ts +0 -123
- package/server/service/kpi-scope/index.ts +0 -11
- package/server/service/kpi-scope/kpi-scope-mutation.ts +0 -129
- package/server/service/kpi-scope/kpi-scope-query.ts +0 -63
- package/server/service/kpi-scope/kpi-scope-type.ts +0 -96
- package/server/service/kpi-scope/kpi-scope.ts +0 -143
- package/server/service/kpi-statistic/index.ts +0 -7
- package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +0 -231
- package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +0 -410
- package/server/service/kpi-statistic/kpi-statistic-mutation.ts +0 -291
- package/server/service/kpi-statistic/kpi-statistic-query.ts +0 -146
- package/server/service/kpi-statistic/kpi-statistic-type.ts +0 -152
- package/server/service/kpi-statistic/kpi-statistic.ts +0 -199
- package/server/service/kpi-value/index.ts +0 -7
- package/server/service/kpi-value/kpi-value-mutation.ts +0 -432
- package/server/service/kpi-value/kpi-value-query.ts +0 -61
- package/server/service/kpi-value/kpi-value-score.service.ts +0 -106
- package/server/service/kpi-value/kpi-value-type.ts +0 -122
- package/server/service/kpi-value/kpi-value.ts +0 -160
- package/server/service/utils/value-date-util.ts +0 -119
- package/server/tsconfig.json +0 -10
- package/server/types/global.d.ts +0 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/kpi",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0-beta.2",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/index.js",
|
|
6
6
|
"things-factory": true,
|
|
@@ -28,22 +28,22 @@
|
|
|
28
28
|
"migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@operato/app": "^
|
|
32
|
-
"@operato/data-grist": "^
|
|
33
|
-
"@operato/dataset": "^
|
|
34
|
-
"@operato/ghost-print": "^
|
|
35
|
-
"@operato/graphql": "^
|
|
36
|
-
"@operato/grist-editor": "^
|
|
37
|
-
"@operato/i18n": "^
|
|
38
|
-
"@operato/layout": "^
|
|
39
|
-
"@operato/moment-timezone-es": "^
|
|
40
|
-
"@operato/p13n": "^
|
|
41
|
-
"@operato/shell": "^
|
|
42
|
-
"@operato/styles": "^
|
|
43
|
-
"@operato/utils": "^
|
|
44
|
-
"@things-factory/auth-base": "^
|
|
45
|
-
"@things-factory/dataset": "^
|
|
46
|
-
"@things-factory/shell": "^
|
|
31
|
+
"@operato/app": "^10.0.0-beta.1",
|
|
32
|
+
"@operato/data-grist": "^10.0.0-beta.1",
|
|
33
|
+
"@operato/dataset": "^10.0.0-beta.1",
|
|
34
|
+
"@operato/ghost-print": "^10.0.0-beta.1",
|
|
35
|
+
"@operato/graphql": "^10.0.0-beta.1",
|
|
36
|
+
"@operato/grist-editor": "^10.0.0-beta.1",
|
|
37
|
+
"@operato/i18n": "^10.0.0-beta.1",
|
|
38
|
+
"@operato/layout": "^10.0.0-beta.1",
|
|
39
|
+
"@operato/moment-timezone-es": "^10.0.0-beta.1",
|
|
40
|
+
"@operato/p13n": "^10.0.0-beta.1",
|
|
41
|
+
"@operato/shell": "^10.0.0-beta.1",
|
|
42
|
+
"@operato/styles": "^10.0.0-beta.1",
|
|
43
|
+
"@operato/utils": "^10.0.0-beta.1",
|
|
44
|
+
"@things-factory/auth-base": "^10.0.0-beta.2",
|
|
45
|
+
"@things-factory/dataset": "^10.0.0-beta.2",
|
|
46
|
+
"@things-factory/shell": "^10.0.0-beta.2"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "ea8a08ff802ae28a37fdda605b6356482e7a6faf"
|
|
49
49
|
}
|
package/client/tsconfig.json
DELETED
package/server/@types/index.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { FormulaNode } from './parser'
|
|
2
|
-
import type { FormulaFunctionMap } from './functions'
|
|
3
|
-
import type { ValueProvider } from './provider'
|
|
4
|
-
|
|
5
|
-
export interface EvalContext {
|
|
6
|
-
functions: FormulaFunctionMap
|
|
7
|
-
provider: ValueProvider
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// 비동기 evaluator만 유지, 함수명은 evaluateFormula로 단순화
|
|
11
|
-
export async function evaluateFormula(node: FormulaNode, ctx: EvalContext): Promise<any> {
|
|
12
|
-
switch (node.type) {
|
|
13
|
-
case 'number':
|
|
14
|
-
return node.value
|
|
15
|
-
case 'variable':
|
|
16
|
-
return await ctx.provider.get(node.name)
|
|
17
|
-
case 'function': {
|
|
18
|
-
const fn = ctx.functions[node.name]
|
|
19
|
-
if (!fn) throw new Error('Unknown function: ' + node.name)
|
|
20
|
-
const args = await Promise.all(node.args.map(arg => evaluateFormula(arg, ctx)))
|
|
21
|
-
return fn(...args)
|
|
22
|
-
}
|
|
23
|
-
case 'binary': {
|
|
24
|
-
const l = await evaluateFormula(node.left, ctx)
|
|
25
|
-
const r = await evaluateFormula(node.right, ctx)
|
|
26
|
-
switch (node.op) {
|
|
27
|
-
case '+':
|
|
28
|
-
return l + r
|
|
29
|
-
case '-':
|
|
30
|
-
return l - r
|
|
31
|
-
case '*':
|
|
32
|
-
return l * r
|
|
33
|
-
case '/':
|
|
34
|
-
return l / r
|
|
35
|
-
default:
|
|
36
|
-
throw new Error('Unknown operator: ' + node.op)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
case 'unary':
|
|
40
|
-
if (node.op === '-') return -(await evaluateFormula(node.arg, ctx))
|
|
41
|
-
throw new Error('Unknown unary operator: ' + node.op)
|
|
42
|
-
default:
|
|
43
|
-
throw new Error('Unknown node type: ' + (node as any).type)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
export type FormulaFunction = (...args: any[]) => any
|
|
2
|
-
export type FormulaFunctionMap = Record<string, FormulaFunction>
|
|
3
|
-
|
|
4
|
-
export const builtinFunctions: FormulaFunctionMap = {
|
|
5
|
-
sum: (...args: any[]) => args.reduce((a, b) => a + b, 0),
|
|
6
|
-
avg: (...args: any[]) => (args.length ? args.reduce((a, b) => a + b, 0) / args.length : 0),
|
|
7
|
-
min: (...args: any[]) => Math.min(...args),
|
|
8
|
-
max: (...args: any[]) => Math.max(...args),
|
|
9
|
-
round: (v: number, d: number = 0) => Number(v.toFixed(d)),
|
|
10
|
-
if: (cond: any, t: any, f: any) => (cond ? t : f),
|
|
11
|
-
// 성과 지수 계산을 위한 수치 적분 함수들
|
|
12
|
-
integrate: (func: Function, a: number, b: number, n: number = 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
|
-
|
|
22
|
-
beta_integrand: (t: number, alpha: number, beta: number) => {
|
|
23
|
-
// 베타 분포 피적분함수: t^(α-1) × (1-t)^(β-1)
|
|
24
|
-
return Math.pow(t, alpha - 1) * Math.pow(1 - t, beta - 1)
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
incomplete_beta: (x: number, alpha: number, beta: number) => {
|
|
28
|
-
// 불완전 베타 함수 계산
|
|
29
|
-
const func = (t: number) => Math.pow(t, alpha - 1) * Math.pow(1 - t, beta - 1)
|
|
30
|
-
const h = x / 1000
|
|
31
|
-
let sum = (func(0) + func(x)) / 2
|
|
32
|
-
for (let i = 1; i < 1000; i++) {
|
|
33
|
-
sum += func(i * h)
|
|
34
|
-
}
|
|
35
|
-
return sum * h
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
complete_beta: (alpha: number, beta: number) => {
|
|
39
|
-
// 완전 베타 함수 계산
|
|
40
|
-
const func = (t: number) => Math.pow(t, alpha - 1) * Math.pow(1 - t, beta - 1)
|
|
41
|
-
const h = 1 / 1000
|
|
42
|
-
let sum = (func(0) + func(1)) / 2
|
|
43
|
-
for (let i = 1; i < 1000; i++) {
|
|
44
|
-
sum += func(i * h)
|
|
45
|
-
}
|
|
46
|
-
return sum * h
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
performance_index: (x: number, alpha1: number, beta1: number, alpha2: number, beta2: number) => {
|
|
50
|
-
// 성과 지수 계산: 1 - (불완전 베타 / 완전 베타)
|
|
51
|
-
const numerator = builtinFunctions.incomplete_beta(x, alpha1, beta1)
|
|
52
|
-
const denominator = builtinFunctions.complete_beta(alpha2, beta2)
|
|
53
|
-
return 1 - numerator / denominator
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
// 지수 함수들
|
|
57
|
-
exp: (x: number) => Math.exp(x),
|
|
58
|
-
log: (x: number) => Math.log(x),
|
|
59
|
-
pow: (x: number, y: number) => Math.pow(x, y),
|
|
60
|
-
|
|
61
|
-
// 지수 감쇠 성과 지수
|
|
62
|
-
exponential_decay: (value: number, scale: number, power: number) => {
|
|
63
|
-
// exp(-(value / scale)^power)
|
|
64
|
-
return Math.exp(-Math.pow(value / scale, power))
|
|
65
|
-
}
|
|
66
|
-
// 필요시 확장
|
|
67
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
// KPI formula 파서: 수식 문자열을 AST로 변환
|
|
2
|
-
|
|
3
|
-
export type FormulaNode =
|
|
4
|
-
| { type: 'number'; value: number }
|
|
5
|
-
| { type: 'variable'; name: string }
|
|
6
|
-
| { type: 'function'; name: string; args: FormulaNode[] }
|
|
7
|
-
| { type: 'binary'; op: string; left: FormulaNode; right: FormulaNode }
|
|
8
|
-
| { type: 'unary'; op: string; arg: FormulaNode }
|
|
9
|
-
|
|
10
|
-
// 토큰 타입 정의
|
|
11
|
-
const TOKEN_REGEX = /\s*(\d+\.\d+|\d+|\[.*?\]|[A-Za-z_][A-Za-z0-9_]*|[+\-*/(),])/y
|
|
12
|
-
|
|
13
|
-
interface Token {
|
|
14
|
-
type: 'number' | 'variable' | 'function' | 'operator' | 'paren' | 'comma'
|
|
15
|
-
value: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function tokenize(formula: string): Token[] {
|
|
19
|
-
const tokens: Token[] = []
|
|
20
|
-
let m: RegExpExecArray | null
|
|
21
|
-
let lastIndex = 0
|
|
22
|
-
while ((m = TOKEN_REGEX.exec(formula))) {
|
|
23
|
-
lastIndex = TOKEN_REGEX.lastIndex
|
|
24
|
-
// m[0]은 공백 포함, m[1]은 capturing group (공백 제외)
|
|
25
|
-
const raw = m[1]
|
|
26
|
-
if (/^\d/.test(raw)) {
|
|
27
|
-
tokens.push({ type: 'number', value: raw })
|
|
28
|
-
} else if (/^\[.*\]$/.test(raw)) {
|
|
29
|
-
tokens.push({ type: 'variable', value: raw.slice(1, -1) })
|
|
30
|
-
} else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(raw)) {
|
|
31
|
-
tokens.push({ type: 'function', value: raw })
|
|
32
|
-
} else if (/^[+\-*/]$/.test(raw)) {
|
|
33
|
-
tokens.push({ type: 'operator', value: raw })
|
|
34
|
-
} else if (/^[(),]$/.test(raw)) {
|
|
35
|
-
if (raw === ',') tokens.push({ type: 'comma', value: raw })
|
|
36
|
-
else tokens.push({ type: 'paren', value: raw })
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (lastIndex < formula.length) {
|
|
40
|
-
throw new Error('Unexpected token at: ' + formula.slice(lastIndex))
|
|
41
|
-
}
|
|
42
|
-
return tokens
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 파서 구현 (재귀 하향, 연산자 우선순위, 함수/변수/숫자/괄호 지원)
|
|
46
|
-
export function parseFormula(formula: string): FormulaNode {
|
|
47
|
-
const tokens = tokenize(formula)
|
|
48
|
-
let pos = 0
|
|
49
|
-
|
|
50
|
-
function peek() {
|
|
51
|
-
return tokens[pos]
|
|
52
|
-
}
|
|
53
|
-
function next() {
|
|
54
|
-
return tokens[pos++]
|
|
55
|
-
}
|
|
56
|
-
function expect(type: string, value?: string) {
|
|
57
|
-
const t = next()
|
|
58
|
-
if (!t || t.type !== type || (value && t.value !== value))
|
|
59
|
-
throw new Error('Expected ' + type + (value ? ':' + value : ''))
|
|
60
|
-
return t
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function parsePrimary(): FormulaNode {
|
|
64
|
-
const t = peek()
|
|
65
|
-
if (!t) throw new Error('Unexpected end')
|
|
66
|
-
if (t.type === 'number') {
|
|
67
|
-
next()
|
|
68
|
-
return { type: 'number', value: parseFloat(t.value) }
|
|
69
|
-
}
|
|
70
|
-
if (t.type === 'variable') {
|
|
71
|
-
next()
|
|
72
|
-
return { type: 'variable', name: t.value }
|
|
73
|
-
}
|
|
74
|
-
if (t.type === 'function') {
|
|
75
|
-
// function 타입 토큰을 만났을 때, 다음 토큰이 '('인지 확인
|
|
76
|
-
const name = t.value
|
|
77
|
-
next()
|
|
78
|
-
|
|
79
|
-
// 다음 토큰이 '('이면 함수 호출, 아니면 변수로 처리
|
|
80
|
-
if (peek() && peek().type === 'paren' && peek().value === '(') {
|
|
81
|
-
// 함수 호출: func(expr, ...)
|
|
82
|
-
expect('paren', '(')
|
|
83
|
-
const args: FormulaNode[] = []
|
|
84
|
-
if ((peek() && peek().type !== 'paren') || (peek() && peek().value !== ')')) {
|
|
85
|
-
while (true) {
|
|
86
|
-
args.push(parseExpr())
|
|
87
|
-
if (peek() && peek().type === 'comma') {
|
|
88
|
-
next()
|
|
89
|
-
} else break
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
expect('paren', ')')
|
|
93
|
-
return { type: 'function', name, args }
|
|
94
|
-
} else {
|
|
95
|
-
// 변수로 처리
|
|
96
|
-
return { type: 'variable', name }
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (t.type === 'operator' && t.value === '-') {
|
|
100
|
-
next()
|
|
101
|
-
return { type: 'unary', op: '-', arg: parsePrimary() }
|
|
102
|
-
}
|
|
103
|
-
if (t.type === 'paren' && t.value === '(') {
|
|
104
|
-
next()
|
|
105
|
-
const node = parseExpr()
|
|
106
|
-
expect('paren', ')')
|
|
107
|
-
return node
|
|
108
|
-
}
|
|
109
|
-
throw new Error('Unexpected token: ' + t.type + ':' + t.value)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function parseMulDiv(): FormulaNode {
|
|
113
|
-
let node = parsePrimary()
|
|
114
|
-
while (peek() && peek().type === 'operator' && (peek().value === '*' || peek().value === '/')) {
|
|
115
|
-
const op = next().value
|
|
116
|
-
node = { type: 'binary', op, left: node, right: parsePrimary() }
|
|
117
|
-
}
|
|
118
|
-
return node
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function parseAddSub(): FormulaNode {
|
|
122
|
-
let node = parseMulDiv()
|
|
123
|
-
while (peek() && peek().type === 'operator' && (peek().value === '+' || peek().value === '-')) {
|
|
124
|
-
const op = next().value
|
|
125
|
-
node = { type: 'binary', op, left: node, right: parseMulDiv() }
|
|
126
|
-
}
|
|
127
|
-
return node
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function parseExpr(): FormulaNode {
|
|
131
|
-
return parseAddSub()
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const ast = parseExpr()
|
|
135
|
-
if (pos < tokens.length) throw new Error('Unexpected token at end: ' + JSON.stringify(tokens[pos]))
|
|
136
|
-
return ast
|
|
137
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { getRepository } from '@things-factory/shell'
|
|
2
|
-
import { KpiMetric } from '../service/kpi-metric/kpi-metric'
|
|
3
|
-
import { KpiMetricValue } from '../service/kpi-metric-value/kpi-metric-value'
|
|
4
|
-
import { ValueProvider } from '../calculator/provider'
|
|
5
|
-
import { getDefaultValueDate } from '../service/utils/value-date-util'
|
|
6
|
-
|
|
7
|
-
export class KpiMetricValueProvider implements ValueProvider {
|
|
8
|
-
constructor(
|
|
9
|
-
private options: {
|
|
10
|
-
valueDate: string
|
|
11
|
-
org?: string
|
|
12
|
-
domainId: string
|
|
13
|
-
tx?: any
|
|
14
|
-
}
|
|
15
|
-
) {}
|
|
16
|
-
|
|
17
|
-
async get(name: string) {
|
|
18
|
-
const metricRepo = getRepository(KpiMetric, this.options.tx)
|
|
19
|
-
const metric = await metricRepo.findOne({ where: { name, domain: { id: this.options.domainId } } })
|
|
20
|
-
if (!metric) throw new Error(`Metric not found: ${name}`)
|
|
21
|
-
|
|
22
|
-
// ALLTIME 타입인 경우 valueDate 무시하고 최신 값 조회
|
|
23
|
-
if (metric.periodType === 'ALLTIME') {
|
|
24
|
-
const valueRepo = getRepository(KpiMetricValue, this.options.tx)
|
|
25
|
-
const value = await valueRepo.findOne({
|
|
26
|
-
where: {
|
|
27
|
-
metric: { id: metric.id },
|
|
28
|
-
org: this.options.org ?? '',
|
|
29
|
-
domain: { id: this.options.domainId }
|
|
30
|
-
},
|
|
31
|
-
order: { createdAt: 'DESC' }
|
|
32
|
-
})
|
|
33
|
-
if (!value) {
|
|
34
|
-
throw new Error(`Metric value not found: metric='${name}', org='${this.options.org ?? ''}' (ALLTIME type)`)
|
|
35
|
-
}
|
|
36
|
-
return value.value
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// metric의 periodType에 맞게 valueDate 보정
|
|
40
|
-
let valueDate = this.options.valueDate
|
|
41
|
-
if (!valueDate) {
|
|
42
|
-
valueDate = getDefaultValueDate(metric.periodType, 'current')
|
|
43
|
-
} else {
|
|
44
|
-
// valueDate가 metric.periodType에 맞는 포맷인지 검사, 아니면 보정
|
|
45
|
-
// (간단히: 길이로 구분, 실제는 정규식 등으로 더 정교하게 가능)
|
|
46
|
-
if (metric.periodType === 'MONTH' && valueDate.length > 7) valueDate = valueDate.slice(0, 7)
|
|
47
|
-
if (metric.periodType === 'DAY' && valueDate.length > 10) valueDate = valueDate.slice(0, 10)
|
|
48
|
-
// 기타 periodType별 보정 추가 가능
|
|
49
|
-
}
|
|
50
|
-
const valueRepo = getRepository(KpiMetricValue, this.options.tx)
|
|
51
|
-
var value = await valueRepo.findOne({
|
|
52
|
-
where: {
|
|
53
|
-
metric: { id: metric.id },
|
|
54
|
-
valueDate,
|
|
55
|
-
org: this.options.org ?? '',
|
|
56
|
-
domain: { id: this.options.domainId }
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
if (!value) {
|
|
60
|
-
// 임시로 최신의 데이타 하나를 찾아오는 것으로 하자.
|
|
61
|
-
value = await valueRepo.findOne({
|
|
62
|
-
where: {
|
|
63
|
-
metric: { id: metric.id },
|
|
64
|
-
org: this.options.org ?? '',
|
|
65
|
-
domain: { id: this.options.domainId }
|
|
66
|
-
},
|
|
67
|
-
order: { valueDate: 'DESC' }
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
if (!value) {
|
|
71
|
-
throw new Error(
|
|
72
|
-
`Metric value not found: metric='${name}', org='${this.options.org ?? ''}', valueDate='${valueDate}', periodType='${metric.periodType}'`
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return value.value
|
|
78
|
-
}
|
|
79
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { getRepository } from '@things-factory/shell'
|
|
2
|
-
import { Kpi } from '../service/kpi/kpi'
|
|
3
|
-
import { KpiValue } from '../service/kpi-value/kpi-value'
|
|
4
|
-
import { ValueProvider } from '../calculator/provider'
|
|
5
|
-
|
|
6
|
-
export class KpiValueProvider implements ValueProvider {
|
|
7
|
-
constructor(
|
|
8
|
-
private options: {
|
|
9
|
-
valueDate: string
|
|
10
|
-
org?: string
|
|
11
|
-
domainId: string
|
|
12
|
-
tx?: any
|
|
13
|
-
}
|
|
14
|
-
) {}
|
|
15
|
-
|
|
16
|
-
async get(name: string) {
|
|
17
|
-
const kpiRepo = getRepository(Kpi, this.options.tx)
|
|
18
|
-
const kpi = await kpiRepo.findOne({ where: { name, domain: { id: this.options.domainId } } })
|
|
19
|
-
if (!kpi) throw new Error(`KPI not found: ${name}`)
|
|
20
|
-
const valueRepo = getRepository(KpiValue, this.options.tx)
|
|
21
|
-
|
|
22
|
-
// SINGLE 타입인 경우 valueDate 무시하고 최신 값 조회
|
|
23
|
-
const whereCondition: any = {
|
|
24
|
-
kpi: { id: kpi.id },
|
|
25
|
-
org: this.options.org ?? '',
|
|
26
|
-
domain: { id: this.options.domainId }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (kpi.periodType === 'ALLTIME') {
|
|
30
|
-
// ALLTIME 타입은 valueDate 무시하고 최신 값 사용
|
|
31
|
-
const value = await valueRepo.findOne({
|
|
32
|
-
where: whereCondition,
|
|
33
|
-
order: { createdAt: 'DESC' }
|
|
34
|
-
})
|
|
35
|
-
if (!value) {
|
|
36
|
-
throw new Error(`KPI value not found: kpi='${name}', org='${this.options.org ?? ''}' (ALLTIME type)`)
|
|
37
|
-
}
|
|
38
|
-
return value.value
|
|
39
|
-
} else {
|
|
40
|
-
// 기존 로직: 특정 날짜의 값 조회
|
|
41
|
-
whereCondition.valueDate = this.options.valueDate
|
|
42
|
-
const value = await valueRepo.findOne({ where: whereCondition })
|
|
43
|
-
if (!value) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`KPI value not found: kpi='${name}', org='${this.options.org ?? ''}', valueDate='${this.options.valueDate}'`
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
return value.value
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
package/server/index.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { MigrationInterface, QueryRunner } from 'typeorm'
|
|
2
|
-
import { Domain, getRepository } from '@things-factory/shell'
|
|
3
|
-
import { KpiPeriodType } from '../service/kpi/kpi'
|
|
4
|
-
import { KpiMetric, KpiMetricCollectType } from '../service/kpi-metric/kpi-metric'
|
|
5
|
-
import * as fs from 'fs'
|
|
6
|
-
import * as path from 'path'
|
|
7
|
-
|
|
8
|
-
interface KpiMetricSeedData {
|
|
9
|
-
name: string
|
|
10
|
-
description: string
|
|
11
|
-
unit?: string | null
|
|
12
|
-
source?: string | null
|
|
13
|
-
field_name?: string | null
|
|
14
|
-
active: boolean
|
|
15
|
-
schedule_id?: string | null
|
|
16
|
-
timezone: string
|
|
17
|
-
data_set_id?: string | null
|
|
18
|
-
period_type: string
|
|
19
|
-
collect_type: string
|
|
20
|
-
schedule?: string | null
|
|
21
|
-
created_at?: string
|
|
22
|
-
updated_at?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class SeedKpiMetrics1752190849680 implements MigrationInterface {
|
|
26
|
-
name = 'SeedKpiMetrics1752190849680'
|
|
27
|
-
|
|
28
|
-
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
29
|
-
try {
|
|
30
|
-
const repository = getRepository(KpiMetric)
|
|
31
|
-
const domainRepository = getRepository(Domain)
|
|
32
|
-
|
|
33
|
-
// SYSTEM 도메인 확인
|
|
34
|
-
const domain: Domain = await domainRepository.findOneBy({ name: 'SYSTEM' })
|
|
35
|
-
if (!domain) {
|
|
36
|
-
console.log('❌ SYSTEM domain not found')
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// JSON 파일 읽기
|
|
41
|
-
const seedFilePath = path.join(__dirname, 'seed-data', 'kpi-metrics-seed.json')
|
|
42
|
-
const seedData: KpiMetricSeedData[] = JSON.parse(fs.readFileSync(seedFilePath, 'utf8'))
|
|
43
|
-
|
|
44
|
-
console.log(`📊 Processing ${seedData.length} KPI metrics from JSON seed`)
|
|
45
|
-
|
|
46
|
-
for (const metricData of seedData) {
|
|
47
|
-
try {
|
|
48
|
-
const existingMetric = await repository.findOne({
|
|
49
|
-
where: { name: metricData.name, domain: { id: domain.id } }
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
if (existingMetric) {
|
|
53
|
-
console.log(`⚠️ KPI Metric already exists: ${metricData.name}`)
|
|
54
|
-
continue
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const metric = await repository.save({
|
|
58
|
-
name: metricData.name,
|
|
59
|
-
description: metricData.description,
|
|
60
|
-
unit: metricData.unit,
|
|
61
|
-
source: metricData.source,
|
|
62
|
-
fieldName: metricData.field_name,
|
|
63
|
-
active: metricData.active,
|
|
64
|
-
scheduleId: metricData.schedule_id,
|
|
65
|
-
timezone: metricData.timezone,
|
|
66
|
-
dataSetId: metricData.data_set_id,
|
|
67
|
-
periodType: KpiPeriodType[metricData.period_type as keyof typeof KpiPeriodType],
|
|
68
|
-
collectType: KpiMetricCollectType[metricData.collect_type as keyof typeof KpiMetricCollectType],
|
|
69
|
-
schedule: metricData.schedule,
|
|
70
|
-
domain
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
console.log(`✅ Created KPI Metric: ${metric.name}`)
|
|
74
|
-
} catch (error) {
|
|
75
|
-
console.error(`❌ Error creating KPI Metric "${metricData.name}":`, error)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
console.log(`🎉 Successfully processed KPI metrics from JSON seed`)
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error('❌ Error seeding KPI metrics from JSON:', error)
|
|
82
|
-
throw error
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
87
|
-
try {
|
|
88
|
-
const repository = getRepository(KpiMetric)
|
|
89
|
-
const domainRepository = getRepository(Domain)
|
|
90
|
-
|
|
91
|
-
const domain: Domain = await domainRepository.findOneBy({ name: 'SYSTEM' })
|
|
92
|
-
if (!domain) {
|
|
93
|
-
console.log('⚠️ SYSTEM domain not found. Nothing to delete.')
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// JSON 파일 읽기
|
|
98
|
-
const seedFilePath = path.join(__dirname, 'seed-data', 'kpi-metrics-seed.json')
|
|
99
|
-
const seedData: KpiMetricSeedData[] = JSON.parse(fs.readFileSync(seedFilePath, 'utf8'))
|
|
100
|
-
|
|
101
|
-
console.log(`🗑️ Cleaning up ${seedData.length} KPI metrics`)
|
|
102
|
-
|
|
103
|
-
for (const metricData of seedData.reverse()) {
|
|
104
|
-
try {
|
|
105
|
-
const metric = await repository.findOne({
|
|
106
|
-
where: { name: metricData.name, domain: { id: domain.id } }
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
if (metric) {
|
|
110
|
-
await repository.delete(metric.id)
|
|
111
|
-
console.log(`✅ Deleted KPI Metric: ${metricData.name}`)
|
|
112
|
-
}
|
|
113
|
-
} catch (error) {
|
|
114
|
-
console.error(`❌ Error deleting KPI Metric "${metricData.name}":`, error)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
console.log(`🎉 Successfully cleaned up KPI metrics`)
|
|
119
|
-
} catch (error) {
|
|
120
|
-
console.error('❌ Error cleaning up KPI metrics:', error)
|
|
121
|
-
throw error
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|