@things-factory/kpi 9.1.8 → 9.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/kpi",
3
- "version": "9.1.8",
3
+ "version": "9.1.13",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -41,9 +41,9 @@
41
41
  "@operato/shell": "^9.0.0",
42
42
  "@operato/styles": "^9.0.0",
43
43
  "@operato/utils": "^9.0.0",
44
- "@things-factory/auth-base": "^9.1.0",
45
- "@things-factory/dataset": "^9.1.8",
46
- "@things-factory/shell": "^9.1.0"
44
+ "@things-factory/auth-base": "^9.1.13",
45
+ "@things-factory/dataset": "^9.1.13",
46
+ "@things-factory/shell": "^9.1.13"
47
47
  },
48
- "gitHead": "f57b6d95e097f5ba9d2dc653390f869a10243a89"
48
+ "gitHead": "a65dd907befaf3a422aa0250d6096b86d151d46e"
49
49
  }
@@ -21,7 +21,8 @@ function tokenize(formula: string): Token[] {
21
21
  let lastIndex = 0
22
22
  while ((m = TOKEN_REGEX.exec(formula))) {
23
23
  lastIndex = TOKEN_REGEX.lastIndex
24
- const [raw] = m
24
+ // m[0] 공백 포함, m[1]은 capturing group (공백 제외)
25
+ const raw = m[1]
25
26
  if (/^\d/.test(raw)) {
26
27
  tokens.push({ type: 'number', value: raw })
27
28
  } else if (/^\[.*\]$/.test(raw)) {
@@ -71,21 +72,29 @@ export function parseFormula(formula: string): FormulaNode {
71
72
  return { type: 'variable', name: t.value }
72
73
  }
73
74
  if (t.type === 'function') {
74
- // 함수 호출: func(expr, ...)
75
+ // function 타입 토큰을 만났을 때, 다음 토큰이 '('인지 확인
75
76
  const name = t.value
76
77
  next()
77
- expect('paren', '(')
78
- const args: FormulaNode[] = []
79
- if ((peek() && peek().type !== 'paren') || (peek() && peek().value !== ')')) {
80
- while (true) {
81
- args.push(parseExpr())
82
- if (peek() && peek().type === 'comma') {
83
- next()
84
- } else break
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
+ }
85
91
  }
92
+ expect('paren', ')')
93
+ return { type: 'function', name, args }
94
+ } else {
95
+ // 변수로 처리
96
+ return { type: 'variable', name }
86
97
  }
87
- expect('paren', ')')
88
- return { type: 'function', name, args }
89
98
  }
90
99
  if (t.type === 'operator' && t.value === '-') {
91
100
  next()
@@ -344,14 +344,17 @@ export class KpiValueMutation {
344
344
  relations: ['kpi', 'kpiOrgScope']
345
345
  })
346
346
  if (!kpiValue) throw new Error('KPI Value not found')
347
- const kpi = kpiValue.kpi || (await kpiRepo.findOne({ where: { id: kpiValue.kpiId, domain: { id: domain.id } } }))
347
+ // 항상 최신 버전의 KPI를 조회하여 사용
348
+ const kpi = await kpiRepo.findOne({
349
+ where: { id: kpiValue.kpiId, domain: { id: domain.id } },
350
+ order: { version: 'DESC' }
351
+ })
348
352
  if (!kpi) throw new Error('KPI 정보 없음')
349
353
  if (!kpi.formula) throw new Error('KPI formula 없음')
350
354
  const periodType = kpi.periodType || KpiPeriodType.DAY
351
355
  const valueDate = kpiValue.valueDate
352
356
  const kpiOrgScope = kpiValue.kpiOrgScope
353
357
  const org = kpiOrgScope?.entityName || kpiOrgScope?.org || 'unknown' // KpiOrgScope에서 org 정보 추출
354
- const version = kpiValue.version
355
358
 
356
359
  // 2. formula에서 metric code 추출
357
360
  const metricCodes = (kpi.formula.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || []).filter(
@@ -415,8 +418,9 @@ export class KpiValueMutation {
415
418
  const kpiValueResult = await evaluateFormula(ast, evalContext)
416
419
  if (kpiValueResult == null || isNaN(kpiValueResult)) throw new Error('KPI formula 결과값 없음')
417
420
 
418
- // 5. KPI Value 업데이트
421
+ // 5. KPI Value 업데이트 (최신 버전으로)
419
422
  kpiValue.value = kpiValueResult
423
+ kpiValue.version = kpi.version || 1
420
424
  kpiValue.updater = user
421
425
 
422
426
  // 6. 성과 점수 자동 계산 및 저장
@@ -33,11 +33,8 @@ export class KpiValueScoreService {
33
33
  if (result !== null && result !== undefined && !isNaN(result)) {
34
34
  const performanceScore = Number(result)
35
35
 
36
- // score를 0~1 범위로 제한
37
- const clampedScore = Math.max(0, Math.min(1, performanceScore))
38
-
39
36
  return {
40
- score: clampedScore,
37
+ score: performanceScore,
41
38
  calculationMethod: 'formula'
42
39
  }
43
40
  }
@@ -65,11 +62,8 @@ export class KpiValueScoreService {
65
62
  return null
66
63
  }
67
64
 
68
- // score를 0~1 범위로 제한
69
- const clampedScore = Math.max(0, Math.min(1, grade.score))
70
-
71
65
  return {
72
- score: clampedScore,
66
+ score: grade.score,
73
67
  calculationMethod: 'lookup'
74
68
  }
75
69
  }
@@ -77,7 +77,7 @@ export class KpiValue {
77
77
  @Column('float', { nullable: true })
78
78
  @Field(type => Float, {
79
79
  nullable: true,
80
- description: 'Performance score calculated from KPI value using scoreFormula or grades lookup table. Range: 0-1.'
80
+ description: 'Performance score calculated from KPI value using scoreFormula or grades lookup table.'
81
81
  })
82
82
  score?: number
83
83