@things-factory/mlops 9.1.19

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 (94) hide show
  1. package/README.md +341 -0
  2. package/client/bootstrap.ts +41 -0
  3. package/client/index.ts +2 -0
  4. package/client/pages/ml-deployment/ml-deployment-list-page.ts +46 -0
  5. package/client/pages/ml-model/ml-model-list-page.ts +46 -0
  6. package/client/pages/ml-pipeline/ml-pipeline-list-page.ts +46 -0
  7. package/client/pages/mlops-dashboard/mlops-dashboard-page.ts +90 -0
  8. package/client/route.ts +19 -0
  9. package/client/tsconfig.json +12 -0
  10. package/dist-client/bootstrap.d.ts +1 -0
  11. package/dist-client/bootstrap.js +40 -0
  12. package/dist-client/bootstrap.js.map +1 -0
  13. package/dist-client/index.d.ts +2 -0
  14. package/dist-client/index.js +3 -0
  15. package/dist-client/index.js.map +1 -0
  16. package/dist-client/pages/ml-deployment/ml-deployment-list-page.d.ts +5 -0
  17. package/dist-client/pages/ml-deployment/ml-deployment-list-page.js +49 -0
  18. package/dist-client/pages/ml-deployment/ml-deployment-list-page.js.map +1 -0
  19. package/dist-client/pages/ml-model/ml-model-list-page.d.ts +5 -0
  20. package/dist-client/pages/ml-model/ml-model-list-page.js +49 -0
  21. package/dist-client/pages/ml-model/ml-model-list-page.js.map +1 -0
  22. package/dist-client/pages/ml-pipeline/ml-pipeline-list-page.d.ts +5 -0
  23. package/dist-client/pages/ml-pipeline/ml-pipeline-list-page.js +49 -0
  24. package/dist-client/pages/ml-pipeline/ml-pipeline-list-page.js.map +1 -0
  25. package/dist-client/pages/mlops-dashboard/mlops-dashboard-page.d.ts +5 -0
  26. package/dist-client/pages/mlops-dashboard/mlops-dashboard-page.js +93 -0
  27. package/dist-client/pages/mlops-dashboard/mlops-dashboard-page.js.map +1 -0
  28. package/dist-client/route.d.ts +1 -0
  29. package/dist-client/route.js +17 -0
  30. package/dist-client/route.js.map +1 -0
  31. package/dist-client/tsconfig.tsbuildinfo +1 -0
  32. package/dist-server/index.d.ts +2 -0
  33. package/dist-server/index.js +6 -0
  34. package/dist-server/index.js.map +1 -0
  35. package/dist-server/routes.d.ts +5 -0
  36. package/dist-server/routes.js +12 -0
  37. package/dist-server/routes.js.map +1 -0
  38. package/dist-server/service/index.d.ts +8 -0
  39. package/dist-server/service/index.js +31 -0
  40. package/dist-server/service/index.js.map +1 -0
  41. package/dist-server/service/ml-deployment/index.d.ts +3 -0
  42. package/dist-server/service/ml-deployment/index.js +8 -0
  43. package/dist-server/service/ml-deployment/index.js.map +1 -0
  44. package/dist-server/service/ml-deployment/ml-deployment.d.ts +48 -0
  45. package/dist-server/service/ml-deployment/ml-deployment.js +153 -0
  46. package/dist-server/service/ml-deployment/ml-deployment.js.map +1 -0
  47. package/dist-server/service/ml-model/index.d.ts +5 -0
  48. package/dist-server/service/ml-model/index.js +11 -0
  49. package/dist-server/service/ml-model/index.js.map +1 -0
  50. package/dist-server/service/ml-model/ml-model-mutation.d.ts +24 -0
  51. package/dist-server/service/ml-model/ml-model-mutation.js +177 -0
  52. package/dist-server/service/ml-model/ml-model-mutation.js.map +1 -0
  53. package/dist-server/service/ml-model/ml-model-query.d.ts +23 -0
  54. package/dist-server/service/ml-model/ml-model-query.js +123 -0
  55. package/dist-server/service/ml-model/ml-model-query.js.map +1 -0
  56. package/dist-server/service/ml-model/ml-model-types.d.ts +27 -0
  57. package/dist-server/service/ml-model/ml-model-types.js +105 -0
  58. package/dist-server/service/ml-model/ml-model-types.js.map +1 -0
  59. package/dist-server/service/ml-model/ml-model.d.ts +52 -0
  60. package/dist-server/service/ml-model/ml-model.js +161 -0
  61. package/dist-server/service/ml-model/ml-model.js.map +1 -0
  62. package/dist-server/service/ml-pipeline/index.d.ts +3 -0
  63. package/dist-server/service/ml-pipeline/index.js +8 -0
  64. package/dist-server/service/ml-pipeline/index.js.map +1 -0
  65. package/dist-server/service/ml-pipeline/ml-pipeline.d.ts +50 -0
  66. package/dist-server/service/ml-pipeline/ml-pipeline.js +163 -0
  67. package/dist-server/service/ml-pipeline/ml-pipeline.js.map +1 -0
  68. package/dist-server/service/ml-pipeline-run/index.d.ts +3 -0
  69. package/dist-server/service/ml-pipeline-run/index.js +8 -0
  70. package/dist-server/service/ml-pipeline-run/index.js.map +1 -0
  71. package/dist-server/service/ml-pipeline-run/ml-pipeline-run.d.ts +40 -0
  72. package/dist-server/service/ml-pipeline-run/ml-pipeline-run.js +136 -0
  73. package/dist-server/service/ml-pipeline-run/ml-pipeline-run.js.map +1 -0
  74. package/dist-server/tsconfig.json +11 -0
  75. package/dist-server/tsconfig.tsbuildinfo +1 -0
  76. package/package.json +52 -0
  77. package/server/index.ts +3 -0
  78. package/server/routes.ts +13 -0
  79. package/server/service/index.ts +29 -0
  80. package/server/service/ml-deployment/index.ts +6 -0
  81. package/server/service/ml-deployment/ml-deployment.ts +136 -0
  82. package/server/service/ml-model/index.ts +9 -0
  83. package/server/service/ml-model/ml-model-mutation.ts +171 -0
  84. package/server/service/ml-model/ml-model-query.ts +97 -0
  85. package/server/service/ml-model/ml-model-types.ts +71 -0
  86. package/server/service/ml-model/ml-model.ts +143 -0
  87. package/server/service/ml-pipeline/index.ts +6 -0
  88. package/server/service/ml-pipeline/ml-pipeline.ts +144 -0
  89. package/server/service/ml-pipeline-run/index.ts +6 -0
  90. package/server/service/ml-pipeline-run/ml-pipeline-run.ts +118 -0
  91. package/server/tsconfig.json +11 -0
  92. package/things-factory.config.js +13 -0
  93. package/translations/en.json +30 -0
  94. package/translations/ko.json +30 -0
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@things-factory/mlops",
3
+ "version": "9.1.19",
4
+ "main": "dist-server/index.js",
5
+ "browser": "dist-client/index.js",
6
+ "things-factory": true,
7
+ "license": "MIT",
8
+ "author": "Things-Factory Team",
9
+ "description": "MLOps module - ML lifecycle orchestration platform for Things-Factory",
10
+ "publishConfig": {
11
+ "access": "public",
12
+ "@things-factory:registry": "https://registry.npmjs.org"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/hatiolab/things-factory.git",
17
+ "directory": "packages/mlops"
18
+ },
19
+ "scripts": {
20
+ "build": "npm run build:server && npm run build:client",
21
+ "copy:files:client": "copyfiles -e \"./client/**/*.{ts,js,json}\" -u 1 \"./client/**/*\" dist-client",
22
+ "copy:files:server": "copyfiles -e \"./server/**/*.{ts,js}\" -u 1 \"./server/**/*\" dist-server",
23
+ "build:client": "npm run copy:files:client && npx tsc --p ./client/tsconfig.json",
24
+ "build:server": "npm run copy:files:server && npx tsc --p ./server/tsconfig.json",
25
+ "clean:client": "npx rimraf dist-client",
26
+ "clean:server": "npx rimraf dist-server",
27
+ "clean": "npm run clean:server && npm run clean:client",
28
+ "migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration"
29
+ },
30
+ "dependencies": {
31
+ "@operato/app": "^9.0.0",
32
+ "@operato/data-grist": "^9.0.0",
33
+ "@operato/graphql": "^9.0.0",
34
+ "@operato/i18n": "^9.0.0",
35
+ "@operato/layout": "^9.0.0",
36
+ "@operato/shell": "^9.0.0",
37
+ "@things-factory/auth-base": "^9.1.19",
38
+ "@things-factory/integration-label-studio": "^9.1.19",
39
+ "@things-factory/integration-mlflow": "^9.1.19",
40
+ "@things-factory/shell": "^9.1.19"
41
+ },
42
+ "keywords": [
43
+ "things-factory",
44
+ "mlops",
45
+ "machine-learning",
46
+ "ml-platform",
47
+ "model-management",
48
+ "mlflow",
49
+ "label-studio"
50
+ ],
51
+ "gitHead": "078438034dbe19915108e89ff24024f7044a85a9"
52
+ }
@@ -0,0 +1,3 @@
1
+ export * from './service/index.js'
2
+
3
+ import './routes.js'
@@ -0,0 +1,13 @@
1
+ /**
2
+ * MLOps Module Routes
3
+ *
4
+ * Register additional routes if needed (beyond GraphQL)
5
+ */
6
+
7
+ // Example: Health check endpoint, webhooks, etc.
8
+ // import Koa from 'koa'
9
+ // import Router from 'koa-router'
10
+
11
+ // process.on('bootstrap-module-domain-private-route', (app: Koa, router: Router) => {
12
+ // // Add custom routes here
13
+ // })
@@ -0,0 +1,29 @@
1
+ /* EXPORT ENTITY TYPES */
2
+ export * from './ml-model/ml-model'
3
+ export * from './ml-pipeline/ml-pipeline'
4
+ export * from './ml-pipeline-run/ml-pipeline-run'
5
+ export * from './ml-deployment/ml-deployment'
6
+
7
+ /* IMPORT ENTITIES AND RESOLVERS */
8
+ import { entities as MLModelEntities, resolvers as MLModelResolvers } from './ml-model'
9
+ import { entities as MLPipelineEntities, resolvers as MLPipelineResolvers } from './ml-pipeline'
10
+ import { entities as MLPipelineRunEntities, resolvers as MLPipelineRunResolvers } from './ml-pipeline-run'
11
+ import { entities as MLDeploymentEntities, resolvers as MLDeploymentResolvers } from './ml-deployment'
12
+
13
+ export const entities = [
14
+ /* ENTITIES */
15
+ ...MLModelEntities,
16
+ ...MLPipelineEntities,
17
+ ...MLPipelineRunEntities,
18
+ ...MLDeploymentEntities
19
+ ]
20
+
21
+ export const schema = {
22
+ resolverClasses: [
23
+ /* RESOLVER CLASSES */
24
+ ...MLModelResolvers,
25
+ ...MLPipelineResolvers,
26
+ ...MLPipelineRunResolvers,
27
+ ...MLDeploymentResolvers
28
+ ]
29
+ }
@@ -0,0 +1,6 @@
1
+ import { MLDeployment } from './ml-deployment.js'
2
+
3
+ export const entities = [MLDeployment]
4
+ export const resolvers = []
5
+
6
+ // export * from './ml-deployment'
@@ -0,0 +1,136 @@
1
+ import {
2
+ CreateDateColumn,
3
+ UpdateDateColumn,
4
+ DeleteDateColumn,
5
+ Entity,
6
+ Index,
7
+ Column,
8
+ RelationId,
9
+ ManyToOne,
10
+ PrimaryGeneratedColumn
11
+ } from 'typeorm'
12
+ import { ObjectType, Field, Int, ID, registerEnumType } from 'type-graphql'
13
+ import { Domain } from '@things-factory/shell'
14
+ import { User } from '@things-factory/auth-base'
15
+ import { MLModel } from '../ml-model/ml-model'
16
+
17
+ /**
18
+ * Deployment Status
19
+ */
20
+ export enum DeploymentStatus {
21
+ DEPLOYING = 'DEPLOYING',
22
+ ACTIVE = 'ACTIVE',
23
+ FAILED = 'FAILED',
24
+ STOPPED = 'STOPPED'
25
+ }
26
+
27
+ registerEnumType(DeploymentStatus, {
28
+ name: 'DeploymentStatus',
29
+ description: 'Model deployment status'
30
+ })
31
+
32
+ /**
33
+ * Deployment Environment
34
+ */
35
+ export enum DeploymentEnvironment {
36
+ DEVELOPMENT = 'DEVELOPMENT',
37
+ STAGING = 'STAGING',
38
+ PRODUCTION = 'PRODUCTION'
39
+ }
40
+
41
+ registerEnumType(DeploymentEnvironment, {
42
+ name: 'DeploymentEnvironment',
43
+ description: 'Deployment environment'
44
+ })
45
+
46
+ /**
47
+ * ML Deployment Entity
48
+ *
49
+ * Tracks model deployments to various environments
50
+ */
51
+ @Entity('ml_deployments')
52
+ @Index('ix_ml_deployment_0', ['domain', 'model'])
53
+ @Index('ix_ml_deployment_1', ['environment', 'status'])
54
+ @ObjectType({ description: 'ML Model deployment configuration' })
55
+ export class MLDeployment {
56
+ @PrimaryGeneratedColumn('uuid')
57
+ @Field(type => ID, { description: 'Deployment ID' })
58
+ id: string
59
+
60
+ @ManyToOne(() => MLModel)
61
+ @Field(type => MLModel, { description: 'Deployed model' })
62
+ model: MLModel
63
+
64
+ @RelationId((deployment: MLDeployment) => deployment.model)
65
+ modelId: string
66
+
67
+ @Column({ nullable: true, comment: 'Deployment name' })
68
+ @Field({ description: 'Deployment name' })
69
+ name: string
70
+
71
+ @Column({ type: 'varchar', default: DeploymentEnvironment.DEVELOPMENT, comment: 'Environment' })
72
+ @Field(type => DeploymentEnvironment, { description: 'Deployment environment' })
73
+ environment: DeploymentEnvironment
74
+
75
+ @Column({ type: 'varchar', default: DeploymentStatus.DEPLOYING, comment: 'Status' })
76
+ @Field(type => DeploymentStatus, { description: 'Current deployment status' })
77
+ status: DeploymentStatus
78
+
79
+ @Column({ nullable: true, comment: 'Endpoint URL' })
80
+ @Field({ nullable: true, description: 'API endpoint URL' })
81
+ endpoint?: string
82
+
83
+ @Column({ type: 'simple-json', nullable: true, comment: 'Scaling configuration' })
84
+ @Field(type => String, { nullable: true, description: 'Scaling config as JSON' })
85
+ scalingConfig?: string
86
+
87
+ @Column({ type: 'simple-json', nullable: true, comment: 'Resource allocation' })
88
+ @Field(type => String, { nullable: true, description: 'Resource config as JSON' })
89
+ resources?: string
90
+
91
+ @Column({ type: 'timestamp', nullable: true, comment: 'Deployment time' })
92
+ @Field({ nullable: true, description: 'Deployed at' })
93
+ deployedAt?: Date
94
+
95
+ @Column({ type: 'int', default: 0, comment: 'Total prediction count' })
96
+ @Field(type => Int, { description: 'Total predictions served' })
97
+ predictionCount: number
98
+
99
+ @Column({ type: 'float', nullable: true, comment: 'Average latency (ms)' })
100
+ @Field({ nullable: true, description: 'Average latency in ms' })
101
+ avgLatency?: number
102
+
103
+ @Column({ type: 'float', nullable: true, comment: 'Error rate' })
104
+ @Field({ nullable: true, description: 'Error rate (0-1)' })
105
+ errorRate?: number
106
+
107
+ @Column({ type: 'simple-json', nullable: true, comment: 'Deployment metadata' })
108
+ @Field(type => String, { nullable: true, description: 'Metadata as JSON' })
109
+ metadata?: string
110
+
111
+ @ManyToOne(() => Domain)
112
+ @Field({ nullable: true, description: 'Domain' })
113
+ domain?: Domain
114
+
115
+ @RelationId((deployment: MLDeployment) => deployment.domain)
116
+ domainId?: string
117
+
118
+ @ManyToOne(() => User, { nullable: true })
119
+ @Field(type => User, { nullable: true, description: 'Deployed by' })
120
+ deployedBy?: User
121
+
122
+ @RelationId((deployment: MLDeployment) => deployment.deployedBy)
123
+ deployedById?: string
124
+
125
+ @CreateDateColumn()
126
+ @Field({ description: 'Created at' })
127
+ createdAt: Date
128
+
129
+ @UpdateDateColumn()
130
+ @Field({ description: 'Updated at' })
131
+ updatedAt: Date
132
+
133
+ @DeleteDateColumn()
134
+ @Field({ nullable: true, description: 'Deleted at' })
135
+ deletedAt?: Date
136
+ }
@@ -0,0 +1,9 @@
1
+ import { MLModel } from './ml-model'
2
+ import { MLModelQuery } from './ml-model-query'
3
+ import { MLModelMutation } from './ml-model-mutation'
4
+
5
+ export const entities = [MLModel]
6
+ export const resolvers = [MLModelQuery, MLModelMutation]
7
+
8
+ // export * from './ml-model'
9
+ // export * from './ml-model-types'
@@ -0,0 +1,171 @@
1
+ import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'
2
+ import { getRepository } from 'typeorm'
3
+ import { MLModel, ModelStage } from './ml-model'
4
+ import { RegisterMLModelInput, UpdateMLModelInput, PromoteModelInput } from './ml-model-types'
5
+
6
+ @Resolver(MLModel)
7
+ export class MLModelMutation {
8
+ /**
9
+ * Register new ML model
10
+ */
11
+ @Mutation(returns => MLModel, {
12
+ description: 'Register new ML model in the system'
13
+ })
14
+ @Directive('@privilege(category: "mlops", privilege: "mutation")')
15
+ async registerMLModel(@Arg('input') input: RegisterMLModelInput, @Ctx() context: ResolverContext): Promise<MLModel> {
16
+ const { domain, user } = context.state
17
+
18
+ // Check if model with same name and version exists
19
+ const existing = await getRepository(MLModel).findOne({
20
+ where: {
21
+ domain: { id: domain.id },
22
+ name: input.name,
23
+ version: input.version
24
+ }
25
+ })
26
+
27
+ if (existing) {
28
+ throw new Error(`Model ${input.name} version ${input.version} already exists`)
29
+ }
30
+
31
+ const model = getRepository(MLModel).create({
32
+ ...input,
33
+ stage: input.stage || ModelStage.NONE,
34
+ domain,
35
+ creator: user,
36
+ updater: user
37
+ })
38
+
39
+ return await getRepository(MLModel).save(model)
40
+ }
41
+
42
+ /**
43
+ * Update ML model
44
+ */
45
+ @Mutation(returns => MLModel, {
46
+ description: 'Update ML model information'
47
+ })
48
+ @Directive('@privilege(category: "mlops", privilege: "mutation")')
49
+ async updateMLModel(
50
+ @Arg('id') id: string,
51
+ @Arg('input') input: UpdateMLModelInput,
52
+ @Ctx() context: ResolverContext
53
+ ): Promise<MLModel> {
54
+ const { domain, user } = context.state
55
+
56
+ const model = await getRepository(MLModel).findOne({
57
+ where: { id, domain: { id: domain.id } }
58
+ })
59
+
60
+ if (!model) {
61
+ throw new Error(`Model ${id} not found`)
62
+ }
63
+
64
+ Object.assign(model, input, { updater: user })
65
+
66
+ return await getRepository(MLModel).save(model)
67
+ }
68
+
69
+ /**
70
+ * Promote model to target stage
71
+ */
72
+ @Mutation(returns => MLModel, {
73
+ description: 'Promote model to target deployment stage (Staging/Production)'
74
+ })
75
+ @Directive('@privilege(category: "mlops", privilege: "mutation")')
76
+ async promoteMLModel(@Arg('input') input: PromoteModelInput, @Ctx() context: ResolverContext): Promise<MLModel> {
77
+ const { domain, user } = context.state
78
+
79
+ const model = await getRepository(MLModel).findOne({
80
+ where: { id: input.modelId, domain: { id: domain.id } }
81
+ })
82
+
83
+ if (!model) {
84
+ throw new Error(`Model ${input.modelId} not found`)
85
+ }
86
+
87
+ // Validate stage transition
88
+ if (model.stage === ModelStage.ARCHIVED) {
89
+ throw new Error('Cannot promote archived model')
90
+ }
91
+
92
+ if (input.targetStage === ModelStage.NONE) {
93
+ throw new Error('Cannot promote to None stage')
94
+ }
95
+
96
+ // If promoting to Production, demote other Production models with same name
97
+ if (input.targetStage === ModelStage.PRODUCTION) {
98
+ await getRepository(MLModel).update(
99
+ {
100
+ domain: { id: domain.id },
101
+ name: model.name,
102
+ stage: ModelStage.PRODUCTION
103
+ },
104
+ {
105
+ stage: ModelStage.STAGING
106
+ }
107
+ )
108
+ }
109
+
110
+ model.stage = input.targetStage
111
+ model.updater = user
112
+
113
+ return await getRepository(MLModel).save(model)
114
+ }
115
+
116
+ /**
117
+ * Archive ML model
118
+ */
119
+ @Mutation(returns => Boolean, {
120
+ description: 'Archive ML model (soft delete)'
121
+ })
122
+ @Directive('@privilege(category: "mlops", privilege: "mutation")')
123
+ async archiveMLModel(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
124
+ const { domain } = context.state
125
+
126
+ const model = await getRepository(MLModel).findOne({
127
+ where: { id, domain: { id: domain.id } }
128
+ })
129
+
130
+ if (!model) {
131
+ throw new Error(`Model ${id} not found`)
132
+ }
133
+
134
+ if (model.stage === ModelStage.PRODUCTION) {
135
+ throw new Error('Cannot archive production model. Demote it first.')
136
+ }
137
+
138
+ model.stage = ModelStage.ARCHIVED
139
+
140
+ await getRepository(MLModel).save(model)
141
+
142
+ return true
143
+ }
144
+
145
+ /**
146
+ * Delete ML model
147
+ */
148
+ @Mutation(returns => Boolean, {
149
+ description: 'Delete ML model permanently'
150
+ })
151
+ @Directive('@privilege(category: "mlops", privilege: "mutation")')
152
+ async deleteMLModel(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
153
+ const { domain } = context.state
154
+
155
+ const model = await getRepository(MLModel).findOne({
156
+ where: { id, domain: { id: domain.id } }
157
+ })
158
+
159
+ if (!model) {
160
+ throw new Error(`Model ${id} not found`)
161
+ }
162
+
163
+ if (model.stage === ModelStage.PRODUCTION) {
164
+ throw new Error('Cannot delete production model')
165
+ }
166
+
167
+ await getRepository(MLModel).softDelete({ id })
168
+
169
+ return true
170
+ }
171
+ }
@@ -0,0 +1,97 @@
1
+ import { Resolver, Query, Arg, Ctx, Directive } from 'type-graphql'
2
+ import { getRepository, In } from 'typeorm'
3
+ import { MLModel, ModelStage } from './ml-model'
4
+
5
+ @Resolver(MLModel)
6
+ export class MLModelQuery {
7
+ /**
8
+ * Get all ML models
9
+ */
10
+ @Query(returns => [MLModel], {
11
+ description: 'Get all ML models in the system'
12
+ })
13
+ @Directive('@privilege(category: "mlops", privilege: "query")')
14
+ async mlModels(@Ctx() context: ResolverContext): Promise<MLModel[]> {
15
+ const { domain } = context.state
16
+
17
+ return await getRepository(MLModel).find({
18
+ where: { domain: { id: domain.id } },
19
+ order: { createdAt: 'DESC' },
20
+ relations: ['creator', 'updater']
21
+ })
22
+ }
23
+
24
+ /**
25
+ * Get ML model by ID
26
+ */
27
+ @Query(returns => MLModel, {
28
+ description: 'Get ML model by ID',
29
+ nullable: true
30
+ })
31
+ @Directive('@privilege(category: "mlops", privilege: "query")')
32
+ async mlModel(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<MLModel | undefined> {
33
+ const { domain } = context.state
34
+
35
+ return await getRepository(MLModel).findOne({
36
+ where: { id, domain: { id: domain.id } },
37
+ relations: ['creator', 'updater']
38
+ })
39
+ }
40
+
41
+ /**
42
+ * Get models by stage
43
+ */
44
+ @Query(returns => [MLModel], {
45
+ description: 'Get models by deployment stage'
46
+ })
47
+ @Directive('@privilege(category: "mlops", privilege: "query")')
48
+ async mlModelsByStage(
49
+ @Arg('stage', type => ModelStage) stage: ModelStage,
50
+ @Ctx() context: ResolverContext
51
+ ): Promise<MLModel[]> {
52
+ const { domain } = context.state
53
+
54
+ return await getRepository(MLModel).find({
55
+ where: { domain: { id: domain.id }, stage },
56
+ order: { updatedAt: 'DESC' },
57
+ relations: ['creator', 'updater']
58
+ })
59
+ }
60
+
61
+ /**
62
+ * Get models by name (all versions)
63
+ */
64
+ @Query(returns => [MLModel], {
65
+ description: 'Get all versions of a model by name'
66
+ })
67
+ @Directive('@privilege(category: "mlops", privilege: "query")')
68
+ async mlModelVersions(@Arg('name') name: string, @Ctx() context: ResolverContext): Promise<MLModel[]> {
69
+ const { domain } = context.state
70
+
71
+ return await getRepository(MLModel).find({
72
+ where: { domain: { id: domain.id }, name },
73
+ order: { createdAt: 'DESC' },
74
+ relations: ['creator', 'updater']
75
+ })
76
+ }
77
+
78
+ /**
79
+ * Get models by Label Studio project
80
+ */
81
+ @Query(returns => [MLModel], {
82
+ description: 'Get models associated with Label Studio project'
83
+ })
84
+ @Directive('@privilege(category: "mlops", privilege: "query")')
85
+ async mlModelsByLabelStudioProject(
86
+ @Arg('projectId', type => Number) projectId: number,
87
+ @Ctx() context: ResolverContext
88
+ ): Promise<MLModel[]> {
89
+ const { domain } = context.state
90
+
91
+ return await getRepository(MLModel).find({
92
+ where: { domain: { id: domain.id }, labelStudioProjectId: projectId },
93
+ order: { createdAt: 'DESC' },
94
+ relations: ['creator', 'updater']
95
+ })
96
+ }
97
+ }
@@ -0,0 +1,71 @@
1
+ import { InputType, Field, Int } from 'type-graphql'
2
+ import { ModelStage, ModelFramework } from './ml-model'
3
+
4
+ @InputType({ description: 'Input for registering ML model' })
5
+ export class RegisterMLModelInput {
6
+ @Field({ description: 'Model name' })
7
+ name: string
8
+
9
+ @Field({ description: 'Model version' })
10
+ version: string
11
+
12
+ @Field({ nullable: true, description: 'Model description' })
13
+ description?: string
14
+
15
+ @Field(type => ModelStage, { nullable: true, description: 'Initial stage' })
16
+ stage?: ModelStage
17
+
18
+ @Field(type => ModelFramework, { nullable: true, description: 'ML framework' })
19
+ framework?: ModelFramework
20
+
21
+ @Field({ nullable: true, description: 'Model metrics as JSON string' })
22
+ metrics?: string
23
+
24
+ @Field({ nullable: true, description: 'Model metadata as JSON string' })
25
+ metadata?: string
26
+
27
+ @Field({ nullable: true, description: 'Artifact URI' })
28
+ artifactUri?: string
29
+
30
+ @Field({ nullable: true, description: 'MLflow run ID' })
31
+ mlflowRunId?: string
32
+
33
+ @Field({ nullable: true, description: 'MLflow experiment ID' })
34
+ mlflowExperimentId?: string
35
+
36
+ @Field(type => Int, { nullable: true, description: 'Label Studio project ID' })
37
+ labelStudioProjectId?: number
38
+
39
+ @Field({ nullable: true, description: 'Tags as JSON array string' })
40
+ tags?: string
41
+ }
42
+
43
+ @InputType({ description: 'Input for updating ML model' })
44
+ export class UpdateMLModelInput {
45
+ @Field({ nullable: true, description: 'Model description' })
46
+ description?: string
47
+
48
+ @Field(type => ModelStage, { nullable: true, description: 'Stage' })
49
+ stage?: ModelStage
50
+
51
+ @Field({ nullable: true, description: 'Model metrics as JSON string' })
52
+ metrics?: string
53
+
54
+ @Field({ nullable: true, description: 'Model metadata as JSON string' })
55
+ metadata?: string
56
+
57
+ @Field({ nullable: true, description: 'Tags as JSON array string' })
58
+ tags?: string
59
+ }
60
+
61
+ @InputType({ description: 'Input for promoting model stage' })
62
+ export class PromoteModelInput {
63
+ @Field({ description: 'Model ID to promote' })
64
+ modelId: string
65
+
66
+ @Field(type => ModelStage, { description: 'Target stage' })
67
+ targetStage: ModelStage
68
+
69
+ @Field({ nullable: true, description: 'Promotion notes' })
70
+ notes?: string
71
+ }