@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.
Files changed (140) hide show
  1. package/client/pages/kpi/kpi-list-page.ts +339 -525
  2. package/client/pages/kpi/kpi-tree-page.ts +135 -207
  3. package/client/pages/kpi-metric/kpi-metric-list-page.ts +146 -226
  4. package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +187 -295
  5. package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +123 -194
  6. package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.ts +57 -91
  7. package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +180 -278
  8. package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +186 -286
  9. package/client/pages/kpi-value/kpi-value-editor-page.ts +189 -292
  10. package/client/pages/kpi-value/kpi-value-list-page.ts +170 -264
  11. package/dist-client/pages/kpi/kpi-list-page.d.ts +0 -6
  12. package/dist-client/pages/kpi/kpi-list-page.js +150 -282
  13. package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
  14. package/dist-client/pages/kpi/kpi-tree-page.d.ts +1 -7
  15. package/dist-client/pages/kpi/kpi-tree-page.js +76 -127
  16. package/dist-client/pages/kpi/kpi-tree-page.js.map +1 -1
  17. package/dist-client/pages/kpi-metric/kpi-metric-list-page.d.ts +0 -6
  18. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +62 -116
  19. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
  20. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -7
  21. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +82 -140
  22. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
  23. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +0 -6
  24. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +54 -98
  25. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
  26. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +1 -7
  27. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +30 -57
  28. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
  29. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.d.ts +1 -7
  30. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +91 -153
  31. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js.map +1 -1
  32. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.d.ts +0 -6
  33. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +81 -155
  34. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js.map +1 -1
  35. package/dist-client/pages/kpi-value/kpi-value-editor-page.d.ts +1 -7
  36. package/dist-client/pages/kpi-value/kpi-value-editor-page.js +80 -136
  37. package/dist-client/pages/kpi-value/kpi-value-editor-page.js.map +1 -1
  38. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +0 -6
  39. package/dist-client/pages/kpi-value/kpi-value-list-page.js +73 -134
  40. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
  41. package/dist-client/tsconfig.tsbuildinfo +1 -1
  42. package/dist-server/service/index.d.ts +1 -1
  43. package/dist-server/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +18 -18
  45. package/client/tsconfig.json +0 -11
  46. package/dist-server/tsconfig.json +0 -10
  47. package/server/@types/index.d.ts +0 -11
  48. package/server/calculator/evaluator.ts +0 -45
  49. package/server/calculator/functions.ts +0 -67
  50. package/server/calculator/index.ts +0 -4
  51. package/server/calculator/parser.ts +0 -137
  52. package/server/calculator/provider.ts +0 -10
  53. package/server/controllers/index.ts +0 -2
  54. package/server/controllers/kpi-metric-value-provider.ts +0 -79
  55. package/server/controllers/kpi-value-provider.ts +0 -51
  56. package/server/index.ts +0 -6
  57. package/server/migrations/1752190849680-seed-kpi-metrics.ts +0 -124
  58. package/server/migrations/1752190849681-seed-kpi.ts +0 -356
  59. package/server/migrations/1752192090123-add-grades-to-kpi.ts +0 -67
  60. package/server/migrations/1752192090124-add-kpi-statistics.ts +0 -719
  61. package/server/migrations/1752192090128-seed-kpi-org-scope.ts +0 -132
  62. package/server/migrations/1752192090129-seed-kpi-values.ts +0 -207
  63. package/server/migrations/grade-data/x11-performance-table.json +0 -962
  64. package/server/migrations/grade-data/x12-performance-table.json +0 -611
  65. package/server/migrations/grade-data/x14-performance-table.json +0 -42
  66. package/server/migrations/grade-data/x21-performance-table.json +0 -889
  67. package/server/migrations/grade-data/x22-performance-table.json +0 -1064
  68. package/server/migrations/grade-data/x23-performance-table.json +0 -42
  69. package/server/migrations/grade-data/x31-performance-table.json +0 -644
  70. package/server/migrations/grade-data/x32-performance-table.json +0 -993
  71. package/server/migrations/grade-data/x33-performance-table.json +0 -195
  72. package/server/migrations/grade-data/x34-performance-table.json +0 -12
  73. package/server/migrations/grade-data/x35-performance-table.json +0 -42
  74. package/server/migrations/grade-data/x41-performance-table.json +0 -825
  75. package/server/migrations/grade-data/x42-performance-table.json +0 -786
  76. package/server/migrations/grade-data/x43-performance-table.json +0 -12
  77. package/server/migrations/grade-data/x44-performance-table.json +0 -42
  78. package/server/migrations/grade-data/x51-performance-table.json +0 -924
  79. package/server/migrations/grade-data/x52-performance-table.json +0 -42
  80. package/server/migrations/grade-data/x61-performance-table.json +0 -261
  81. package/server/migrations/grade-data/x62-performance-table.json +0 -42
  82. package/server/migrations/index.ts +0 -9
  83. package/server/migrations/seed-data/kpi-metrics-seed.json +0 -454
  84. package/server/migrations/seed-data/kpi-org-scope-seed.json +0 -1676
  85. package/server/migrations/seed-data/kpi-scopes-seed.json +0 -121
  86. package/server/migrations/seed-data/kpi-values-seed.json +0 -402
  87. package/server/migrations/seed-data/kpis-seed.json +0 -488
  88. package/server/migrations/seed-data/scope-definitions-seed.json +0 -90
  89. package/server/routes.ts +0 -81
  90. package/server/service/index.ts +0 -51
  91. package/server/service/kpi/aggregate-kpi.ts +0 -103
  92. package/server/service/kpi/event-subscriber.ts +0 -29
  93. package/server/service/kpi/index.ts +0 -9
  94. package/server/service/kpi/kpi-formula.service.ts +0 -164
  95. package/server/service/kpi/kpi-grade.types.ts +0 -28
  96. package/server/service/kpi/kpi-history.ts +0 -126
  97. package/server/service/kpi/kpi-mutation.ts +0 -553
  98. package/server/service/kpi/kpi-query.ts +0 -224
  99. package/server/service/kpi/kpi-type.ts +0 -151
  100. package/server/service/kpi/kpi.ts +0 -254
  101. package/server/service/kpi-alert/index.ts +0 -3
  102. package/server/service/kpi-alert/kpi-alert-query.ts +0 -59
  103. package/server/service/kpi-alert/kpi-alert-type.ts +0 -20
  104. package/server/service/kpi-metric/aggregate-kpi-metric.ts +0 -132
  105. package/server/service/kpi-metric/index.ts +0 -7
  106. package/server/service/kpi-metric/kpi-metric-mutation.ts +0 -309
  107. package/server/service/kpi-metric/kpi-metric-query.ts +0 -70
  108. package/server/service/kpi-metric/kpi-metric-type.ts +0 -111
  109. package/server/service/kpi-metric/kpi-metric.ts +0 -134
  110. package/server/service/kpi-metric-value/index.ts +0 -7
  111. package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +0 -270
  112. package/server/service/kpi-metric-value/kpi-metric-value-query.ts +0 -62
  113. package/server/service/kpi-metric-value/kpi-metric-value-type.ts +0 -82
  114. package/server/service/kpi-metric-value/kpi-metric-value.ts +0 -93
  115. package/server/service/kpi-org-scope/index.ts +0 -6
  116. package/server/service/kpi-org-scope/kpi-org-scope-mutation.ts +0 -173
  117. package/server/service/kpi-org-scope/kpi-org-scope-query.ts +0 -127
  118. package/server/service/kpi-org-scope/kpi-org-scope-type.ts +0 -68
  119. package/server/service/kpi-org-scope/kpi-org-scope.ts +0 -123
  120. package/server/service/kpi-scope/index.ts +0 -11
  121. package/server/service/kpi-scope/kpi-scope-mutation.ts +0 -129
  122. package/server/service/kpi-scope/kpi-scope-query.ts +0 -63
  123. package/server/service/kpi-scope/kpi-scope-type.ts +0 -96
  124. package/server/service/kpi-scope/kpi-scope.ts +0 -143
  125. package/server/service/kpi-statistic/index.ts +0 -7
  126. package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +0 -231
  127. package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +0 -410
  128. package/server/service/kpi-statistic/kpi-statistic-mutation.ts +0 -291
  129. package/server/service/kpi-statistic/kpi-statistic-query.ts +0 -146
  130. package/server/service/kpi-statistic/kpi-statistic-type.ts +0 -152
  131. package/server/service/kpi-statistic/kpi-statistic.ts +0 -199
  132. package/server/service/kpi-value/index.ts +0 -7
  133. package/server/service/kpi-value/kpi-value-mutation.ts +0 -432
  134. package/server/service/kpi-value/kpi-value-query.ts +0 -61
  135. package/server/service/kpi-value/kpi-value-score.service.ts +0 -106
  136. package/server/service/kpi-value/kpi-value-type.ts +0 -122
  137. package/server/service/kpi-value/kpi-value.ts +0 -160
  138. package/server/service/utils/value-date-util.ts +0 -119
  139. package/server/tsconfig.json +0 -10
  140. package/server/types/global.d.ts +0 -8
@@ -1,63 +0,0 @@
1
- import { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'
2
- import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
3
- import { User } from '@things-factory/auth-base'
4
- import type ResolverContext from '@things-factory/auth-base'
5
- import { KpiScope } from './kpi-scope'
6
- import { KpiScopeList } from './kpi-scope-type'
7
-
8
- @Resolver(KpiScope)
9
- export class KpiScopeQuery {
10
- @Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
11
- @Query(returns => KpiScope!, { nullable: true, description: 'Fetch a scope dimension definition by ID' })
12
- async kpiScope(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<KpiScope> {
13
- const { domain } = context.state
14
-
15
- return await getRepository(KpiScope).findOne({
16
- where: { domain: { id: domain.id }, id }
17
- })
18
- }
19
-
20
- @Query(returns => KpiScopeList, { description: 'Fetch multiple scope dimension definitions' })
21
- @Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
22
- async kpiScopes(
23
- @Args(type => ListParam) params: ListParam,
24
- @Ctx() context: ResolverContext
25
- ): Promise<KpiScopeList> {
26
- const { domain } = context.state
27
-
28
- const queryBuilder = getQueryBuilderFromListParams({
29
- domain,
30
- params,
31
- repository: await getRepository(KpiScope),
32
- searchables: ['name', 'description'],
33
- filtersMap: {
34
- level: { columnName: 'level' },
35
- scopeType: { columnName: 'scopeType' },
36
- parentLevel: { columnName: 'parentLevel' },
37
- active: { columnName: 'active' },
38
- includeInStatistics: { columnName: 'includeInStatistics' },
39
- showInDashboard: { columnName: 'showInDashboard' }
40
- }
41
- })
42
-
43
- const [items, total] = await queryBuilder.getManyAndCount()
44
-
45
- return { items, total }
46
- }
47
-
48
-
49
- @FieldResolver(type => Domain)
50
- async domain(@Root() kpiScope: KpiScope): Promise<Domain> {
51
- return kpiScope.domainId && (await getRepository(Domain).findOneBy({ id: kpiScope.domainId }))
52
- }
53
-
54
- @FieldResolver(type => User)
55
- async updater(@Root() kpiScope: KpiScope): Promise<User> {
56
- return kpiScope.updaterId && (await getRepository(User).findOneBy({ id: kpiScope.updaterId }))
57
- }
58
-
59
- @FieldResolver(type => User)
60
- async creator(@Root() kpiScope: KpiScope): Promise<User> {
61
- return kpiScope.creatorId && (await getRepository(User).findOneBy({ id: kpiScope.creatorId }))
62
- }
63
- }
@@ -1,96 +0,0 @@
1
- import { ScalarObject } from '@things-factory/shell'
2
- import { ObjectType, Field, InputType, ID, Int } from 'type-graphql'
3
- import { KpiScope, ScopeType } from './kpi-scope'
4
-
5
- @InputType()
6
- export class NewKpiScope {
7
- @Field(type => Int, { description: 'Scope level (1-5) corresponding to scope01-scope05 in KpiOrgScope' })
8
- level: number
9
-
10
- @Field({ description: 'Display name for this scope dimension (e.g., "지역", "회사", "프로젝트규모")' })
11
- name: string
12
-
13
- @Field({ nullable: true, description: 'Detailed description of what this scope dimension represents' })
14
- description?: string
15
-
16
- @Field(type => ScopeType, { description: 'Type category of this scope dimension' })
17
- scopeType: ScopeType
18
-
19
- @Field(type => Int, { nullable: true, description: 'Parent scope level if this forms a hierarchy' })
20
- parentLevel?: number
21
-
22
- @Field(type => Int, { description: 'Order/priority for display and processing', defaultValue: 1 })
23
- displayOrder: number
24
-
25
- @Field(type => [String], { nullable: true, description: 'List of valid values for this scope' })
26
- validValues?: string[]
27
-
28
- @Field({ nullable: true, description: 'Regex pattern for validating values in this scope dimension' })
29
- validationPattern?: string
30
-
31
- @Field({ description: 'Whether this scope definition is active and should be used', defaultValue: true })
32
- active: boolean
33
-
34
- @Field({ description: 'Whether this scope should be included in statistical calculations', defaultValue: true })
35
- includeInStatistics: boolean
36
-
37
- @Field({ description: 'Whether this scope should be displayed in dashboard visualizations', defaultValue: false })
38
- showInDashboard: boolean
39
-
40
- @Field(type => ScalarObject, { nullable: true, description: 'Additional metadata for this scope dimension' })
41
- metadata?: any
42
- }
43
-
44
- @InputType()
45
- export class KpiScopePatch {
46
- @Field(type => ID, { nullable: true })
47
- id?: string
48
-
49
- @Field(type => Int, { description: 'Scope level (1-5) corresponding to scope01-scope05 in KpiOrgScope' })
50
- level: number
51
-
52
- @Field({ description: 'Display name for this scope dimension (e.g., "지역", "회사", "프로젝트규모")' })
53
- name: string
54
-
55
- @Field({ nullable: true, description: 'Detailed description of what this scope dimension represents' })
56
- description?: string
57
-
58
- @Field(type => ScopeType, { description: 'Type category of this scope dimension' })
59
- scopeType: ScopeType
60
-
61
- @Field(type => Int, { nullable: true, description: 'Parent scope level if this forms a hierarchy' })
62
- parentLevel?: number
63
-
64
- @Field(type => Int, { description: 'Order/priority for display and processing', defaultValue: 1 })
65
- displayOrder: number
66
-
67
- @Field(type => [String], { nullable: true, description: 'List of valid values for this scope' })
68
- validValues?: string[]
69
-
70
- @Field({ nullable: true, description: 'Regex pattern for validating values in this scope dimension' })
71
- validationPattern?: string
72
-
73
- @Field({ description: 'Whether this scope definition is active and should be used', defaultValue: true })
74
- active: boolean
75
-
76
- @Field({ description: 'Whether this scope should be included in statistical calculations', defaultValue: true })
77
- includeInStatistics: boolean
78
-
79
- @Field({ description: 'Whether this scope should be displayed in dashboard visualizations', defaultValue: false })
80
- showInDashboard: boolean
81
-
82
- @Field(type => ScalarObject, { nullable: true, description: 'Additional metadata for this scope dimension' })
83
- metadata?: any
84
-
85
- @Field({ nullable: true })
86
- cuFlag?: string
87
- }
88
-
89
- @ObjectType()
90
- export class KpiScopeList {
91
- @Field(type => [KpiScope])
92
- items: KpiScope[]
93
-
94
- @Field()
95
- total: number
96
- }
@@ -1,143 +0,0 @@
1
- import {
2
- CreateDateColumn,
3
- UpdateDateColumn,
4
- Entity,
5
- Index,
6
- Column,
7
- RelationId,
8
- ManyToOne,
9
- PrimaryGeneratedColumn
10
- } from 'typeorm'
11
- import { ObjectType, Field, ID, registerEnumType, Int } from 'type-graphql'
12
-
13
- import { Domain, ScalarObject } from '@things-factory/shell'
14
- import { User } from '@things-factory/auth-base'
15
-
16
- export enum ScopeType {
17
- GEOGRAPHIC = 'GEOGRAPHIC', // 지리적 차원 (지역, 시도, 구군)
18
- ORGANIZATIONAL = 'ORGANIZATIONAL', // 조직 차원 (회사, 부서, 팀)
19
- BUSINESS = 'BUSINESS', // 비즈니스 차원 (규모, 유형, 등급)
20
- TECHNICAL = 'TECHNICAL', // 기술적 차원 (공종, 기술, 방법)
21
- TEMPORAL = 'TEMPORAL', // 시간적 차원 (분기, 년도, 기간)
22
- CUSTOM = 'CUSTOM' // 사용자 정의
23
- }
24
-
25
- registerEnumType(ScopeType, {
26
- name: 'ScopeType',
27
- description: 'Types of organizational scope dimensions'
28
- })
29
-
30
- @Entity()
31
- @Index('ix_kpi_scope_domain', (kpiScope: KpiScope) => [kpiScope.domain])
32
- @Index('ix_kpi_scope_level', (kpiScope: KpiScope) => [kpiScope.domain, kpiScope.level])
33
- @ObjectType({
34
- description: 'Defines the meaning and metadata for each scope level (scope01~05) in KpiOrgScope'
35
- })
36
- export class KpiScope {
37
- @PrimaryGeneratedColumn('uuid')
38
- @Field(type => ID)
39
- readonly id: string
40
-
41
- // 스코프 레벨 (1-5)
42
- @Column({ type: 'int' })
43
- @Field(type => Int, { description: 'Scope level (1-5) corresponding to scope01-scope05 in KpiOrgScope' })
44
- level: number
45
-
46
- // 스코프 차원 이름
47
- @Column()
48
- @Field({ description: 'Display name for this scope dimension (e.g., "지역", "회사", "프로젝트규모")' })
49
- name: string
50
-
51
- // 스코프 차원 설명
52
- @Column({ nullable: true })
53
- @Field({ nullable: true, description: 'Detailed description of what this scope dimension represents' })
54
- description?: string
55
-
56
- // 스코프 타입 (지리적, 조직적, 비즈니스 등)
57
- @Column()
58
- @Field(type => ScopeType, { description: 'Type category of this scope dimension' })
59
- scopeType: ScopeType
60
-
61
- // 계층 정보
62
- @Column({ nullable: true })
63
- @Field(type => Int, { nullable: true, description: 'Parent scope level if this forms a hierarchy' })
64
- parentLevel?: number
65
-
66
- @Column({ default: 1 })
67
- @Field(type => Int, { description: 'Order/priority for display and processing' })
68
- displayOrder: number
69
-
70
- // 유효한 값들 (선택사항)
71
- @Column('simple-json', { nullable: true })
72
- @Field(type => ScalarObject, {
73
- nullable: true,
74
- description: 'List of valid values for this scope (e.g., ["서울", "부산", "대구"] for regions)'
75
- })
76
- validValues?: string[]
77
-
78
- // 정규표현식 검증 (선택사항)
79
- @Column({ nullable: true })
80
- @Field({
81
- nullable: true,
82
- description: 'Regex pattern for validating values in this scope dimension'
83
- })
84
- validationPattern?: string
85
-
86
- // 활성화 여부
87
- @Column({ default: true })
88
- @Field({ description: 'Whether this scope definition is active and should be used' })
89
- active: boolean
90
-
91
- // 통계 계산에 사용 여부
92
- @Column({ default: true })
93
- @Field({ description: 'Whether this scope should be included in statistical calculations' })
94
- includeInStatistics: boolean
95
-
96
- // 대시보드에 표시 여부
97
- @Column({ default: false })
98
- @Field({ description: 'Whether this scope should be displayed in dashboard visualizations' })
99
- showInDashboard: boolean
100
-
101
- // 메타데이터
102
- @Column('simple-json', { nullable: true })
103
- @Field(type => ScalarObject, {
104
- nullable: true,
105
- description: 'Additional metadata for this scope dimension (colors, icons, etc.)'
106
- })
107
- metadata?: {
108
- color?: string
109
- icon?: string
110
- unit?: string
111
- [key: string]: any
112
- }
113
-
114
- // === 표준 필드들 ===
115
- @ManyToOne(type => Domain)
116
- @Field({ nullable: true, description: 'Domain this scope definition belongs to' })
117
- domain?: Domain
118
-
119
- @RelationId((kpiScope: KpiScope) => kpiScope.domain)
120
- domainId?: string
121
-
122
- @CreateDateColumn()
123
- @Field({ nullable: true })
124
- createdAt?: Date
125
-
126
- @UpdateDateColumn()
127
- @Field({ nullable: true })
128
- updatedAt?: Date
129
-
130
- @ManyToOne(type => User, { nullable: true })
131
- @Field(type => User, { nullable: true })
132
- creator?: User
133
-
134
- @RelationId((kpiScope: KpiScope) => kpiScope.creator)
135
- creatorId?: string
136
-
137
- @ManyToOne(type => User, { nullable: true })
138
- @Field(type => User, { nullable: true })
139
- updater?: User
140
-
141
- @RelationId((kpiScope: KpiScope) => kpiScope.updater)
142
- updaterId?: string
143
- }
@@ -1,7 +0,0 @@
1
- import { KpiStatistic } from './kpi-statistic.js'
2
- import { KpiStatisticQuery } from './kpi-statistic-query.js'
3
- import { KpiStatisticMutation } from './kpi-statistic-mutation.js'
4
-
5
- export const entities = [KpiStatistic]
6
- export const resolvers = [KpiStatisticQuery, KpiStatisticMutation]
7
- export const subscribers = []
@@ -1,231 +0,0 @@
1
- import { getRepository } from '@things-factory/shell'
2
- import type ResolverContext from '@things-factory/auth-base'
3
- import { KpiStatisticCalculationService } from './kpi-statistic-calculation.service'
4
- import { KpiPeriodType } from '../kpi/kpi'
5
- import { Domain } from '@things-factory/shell'
6
-
7
- /**
8
- * KPI 통계 배치 계산 서비스
9
- * 정기적으로 실행하여 KpiValue 데이터를 집계해서 KpiStatistic을 생성/업데이트
10
- */
11
- export class KpiStatisticBatchService {
12
-
13
- /**
14
- * 일일 배치: 어제 날짜의 모든 통계 계산
15
- */
16
- static async runDailyBatch(domainId?: string): Promise<void> {
17
- console.log('[KPI Statistics] Starting daily batch calculation...')
18
-
19
- const yesterday = new Date()
20
- yesterday.setDate(yesterday.getDate() - 1)
21
- const valueDate = yesterday.toISOString().split('T')[0] // YYYY-MM-DD
22
-
23
- await this.calculateForAllDomains(KpiPeriodType.DAY, valueDate, domainId)
24
-
25
- console.log(`[KPI Statistics] Daily batch completed for ${valueDate}`)
26
- }
27
-
28
- /**
29
- * 월간 배치: 지난 달의 모든 통계 계산
30
- */
31
- static async runMonthlyBatch(domainId?: string): Promise<void> {
32
- console.log('[KPI Statistics] Starting monthly batch calculation...')
33
-
34
- const lastMonth = new Date()
35
- lastMonth.setMonth(lastMonth.getMonth() - 1)
36
- const valueDate = lastMonth.toISOString().substring(0, 7) // YYYY-MM
37
-
38
- await this.calculateForAllDomains(KpiPeriodType.MONTH, valueDate, domainId)
39
-
40
- console.log(`[KPI Statistics] Monthly batch completed for ${valueDate}`)
41
- }
42
-
43
- /**
44
- * 분기 배치: 지난 분기의 모든 통계 계산
45
- */
46
- static async runQuarterlyBatch(domainId?: string): Promise<void> {
47
- console.log('[KPI Statistics] Starting quarterly batch calculation...')
48
-
49
- const now = new Date()
50
- const currentQuarter = Math.floor(now.getMonth() / 3) + 1
51
- const lastQuarter = currentQuarter === 1 ? 4 : currentQuarter - 1
52
- const year = currentQuarter === 1 ? now.getFullYear() - 1 : now.getFullYear()
53
- const valueDate = `${year}-Q${lastQuarter}`
54
-
55
- await this.calculateForAllDomains(KpiPeriodType.QUARTER, valueDate, domainId)
56
-
57
- console.log(`[KPI Statistics] Quarterly batch completed for ${valueDate}`)
58
- }
59
-
60
- /**
61
- * 연간 배치: 작년의 모든 통계 계산
62
- */
63
- static async runYearlyBatch(domainId?: string): Promise<void> {
64
- console.log('[KPI Statistics] Starting yearly batch calculation...')
65
-
66
- const lastYear = new Date().getFullYear() - 1
67
- const valueDate = lastYear.toString()
68
-
69
- await this.calculateForAllDomains(KpiPeriodType.YEAR, valueDate, domainId)
70
-
71
- console.log(`[KPI Statistics] Yearly batch completed for ${valueDate}`)
72
- }
73
-
74
- /**
75
- * 대시보드용 지역별 통계 계산
76
- */
77
- static async runDashboardBatch(
78
- periodType: KpiPeriodType = KpiPeriodType.MONTH,
79
- valueDate?: string,
80
- domainId?: string
81
- ): Promise<void> {
82
- console.log('[KPI Statistics] Starting dashboard batch calculation...')
83
-
84
- if (!valueDate) {
85
- const now = new Date()
86
- if (periodType === KpiPeriodType.MONTH) {
87
- now.setMonth(now.getMonth() - 1)
88
- valueDate = now.toISOString().substring(0, 7) // YYYY-MM
89
- } else {
90
- now.setDate(now.getDate() - 1)
91
- valueDate = now.toISOString().split('T')[0] // YYYY-MM-DD
92
- }
93
- }
94
-
95
- await this.calculateForAllDomains(periodType, valueDate, domainId)
96
-
97
- console.log(`[KPI Statistics] Dashboard batch completed for ${valueDate} (${periodType})`)
98
- }
99
-
100
- /**
101
- * 모든 도메인에 대해 통계 계산
102
- */
103
- private static async calculateForAllDomains(
104
- periodType: KpiPeriodType,
105
- valueDate: string,
106
- domainId?: string
107
- ): Promise<void> {
108
- try {
109
- const domains = domainId
110
- ? await getRepository(Domain).find({ where: { id: domainId } })
111
- : await getRepository(Domain).find()
112
-
113
- for (const domain of domains) {
114
- try {
115
- await this.calculateForDomain(domain, periodType, valueDate)
116
- } catch (error) {
117
- console.error(`[KPI Statistics] Domain ${domain.name} calculation failed, continuing with next domain:`, error)
118
- // 개별 도메인 실패가 전체 배치를 중단시키지 않도록 계속 진행
119
- continue
120
- }
121
- }
122
- } catch (error) {
123
- console.error('[KPI Statistics] Batch calculation failed:', error)
124
- throw error
125
- }
126
- }
127
-
128
- /**
129
- * 특정 도메인에 대해 통계 계산
130
- */
131
- private static async calculateForDomain(
132
- domain: Domain,
133
- periodType: KpiPeriodType,
134
- valueDate: string
135
- ): Promise<void> {
136
- console.log(`[KPI Statistics] Calculating for domain: ${domain.name} (${domain.id})`)
137
-
138
- // 가짜 ResolverContext 생성 (배치 작업용)
139
- const context: ResolverContext = {
140
- state: {
141
- domain,
142
- user: { id: 'system', name: 'System Batch' }, // 시스템 사용자
143
- tx: null // 트랜잭션 없음 (각 계산마다 개별 트랜잭션)
144
- }
145
- } as any
146
-
147
- try {
148
- const statistics = await KpiStatisticCalculationService.calculateAllStatistics(
149
- periodType,
150
- valueDate,
151
- context
152
- )
153
-
154
- console.log(`[KPI Statistics] Generated ${statistics.length} statistics for domain ${domain.name}`)
155
-
156
- // 성공 로깅
157
- this.logBatchResult(domain.id, periodType, valueDate, statistics.length, 'SUCCESS')
158
-
159
- } catch (error) {
160
- console.error(`[KPI Statistics] Failed to calculate for domain ${domain.name}:`, error)
161
-
162
- // 실패 로깅
163
- this.logBatchResult(domain.id, periodType, valueDate, 0, 'FAILED', error.message)
164
-
165
- // 에러를 던져서 상위에서 처리
166
- throw error
167
- }
168
- }
169
-
170
- /**
171
- * 배치 실행 결과 로깅
172
- */
173
- private static logBatchResult(
174
- domainId: string,
175
- periodType: KpiPeriodType,
176
- valueDate: string,
177
- statisticsCount: number,
178
- status: 'SUCCESS' | 'FAILED',
179
- errorMessage?: string
180
- ): void {
181
- const logData = {
182
- timestamp: new Date().toISOString(),
183
- domainId,
184
- periodType,
185
- valueDate,
186
- statisticsCount,
187
- status,
188
- errorMessage
189
- }
190
-
191
- // 실제 운영에서는 로깅 시스템에 저장
192
- console.log('[KPI Statistics Batch Log]', JSON.stringify(logData))
193
- }
194
-
195
- /**
196
- * 배치 작업 스케줄링 설정 예시
197
- */
198
- static setupBatchSchedule(): void {
199
- // 실제 구현시에는 cron 등의 스케줄러 사용
200
- console.log('[KPI Statistics] Batch schedule setup example:')
201
- console.log('Daily batch: Run at 02:00 AM every day')
202
- console.log('Monthly batch: Run at 03:00 AM on 1st of every month')
203
- console.log('Quarterly batch: Run at 04:00 AM on 1st of every quarter')
204
- console.log('Yearly batch: Run at 05:00 AM on January 1st')
205
-
206
- /*
207
- // Node-cron 사용 예시:
208
- import cron from 'node-cron'
209
-
210
- // 매일 새벽 2시
211
- cron.schedule('0 2 * * *', () => {
212
- this.runDailyBatch()
213
- })
214
-
215
- // 매월 1일 새벽 3시
216
- cron.schedule('0 3 1 * *', () => {
217
- this.runMonthlyBatch()
218
- })
219
-
220
- // 매 분기 첫날 새벽 4시 (1, 4, 7, 10월)
221
- cron.schedule('0 4 1 1,4,7,10 *', () => {
222
- this.runQuarterlyBatch()
223
- })
224
-
225
- // 매년 1월 1일 새벽 5시
226
- cron.schedule('0 5 1 1 *', () => {
227
- this.runYearlyBatch()
228
- })
229
- */
230
- }
231
- }