@kognitivedev/model-flow 0.2.29

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.
@@ -0,0 +1,2 @@
1
+
2
+ $ tsc
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @kognitivedev/model-flow
2
+
3
+ ## 0.2.29
4
+
5
+ ### Patch Changes
6
+
7
+ - release
@@ -0,0 +1,54 @@
1
+ export type JsonObject = Record<string, unknown>;
2
+ export type ModelFlowTaskType = "classification" | "regression" | "ranking" | "extraction" | "generation";
3
+ export type ModelFlowNodeType = "data_source" | "query" | "join" | "snapshot" | "schema_profile" | "target" | "data_quality" | "leakage_check" | "cleaning" | "feature_transform" | "text_features" | "embedding" | "train_test_split" | "model_candidate" | "ensemble" | "validation" | "slice_metrics" | "prediction_preview" | "publish_contract" | "inference_transform" | "deploy_preview";
4
+ export interface ModelFlowObjective {
5
+ taskType: ModelFlowTaskType;
6
+ target?: string;
7
+ optimizationMetric?: string;
8
+ }
9
+ export interface ModelFlowNode {
10
+ id: string;
11
+ type: ModelFlowNodeType;
12
+ label?: string;
13
+ position?: {
14
+ x: number;
15
+ y: number;
16
+ };
17
+ config?: JsonObject;
18
+ }
19
+ export interface ModelFlowEdge {
20
+ id?: string;
21
+ from: string;
22
+ to: string;
23
+ label?: string;
24
+ config?: JsonObject;
25
+ }
26
+ export interface ModelFlowDefinition {
27
+ version: 1;
28
+ inputSchema?: JsonObject;
29
+ outputSchema?: JsonObject;
30
+ objective: ModelFlowObjective;
31
+ nodes: ModelFlowNode[];
32
+ edges: ModelFlowEdge[];
33
+ metadata?: JsonObject;
34
+ }
35
+ export interface ModelFlowValidationIssue {
36
+ severity: "error" | "warning" | "info";
37
+ scope: "graph" | "node" | "edge";
38
+ nodeId?: string;
39
+ edgeId?: string;
40
+ code: string;
41
+ message: string;
42
+ }
43
+ export interface ModelFlowRunEvent {
44
+ eventType: string;
45
+ nodeId?: string | null;
46
+ nodeType?: string | null;
47
+ stepIndex?: number | null;
48
+ payload?: JsonObject | null;
49
+ }
50
+ export declare const MODEL_FLOW_NODE_TYPES: ModelFlowNodeType[];
51
+ export declare function createEmptyModelFlowDefinition(): ModelFlowDefinition;
52
+ export declare function normalizeModelFlowDefinition(definition: Partial<ModelFlowDefinition> | null | undefined): ModelFlowDefinition;
53
+ export declare function validateModelFlowDefinition(definition: Partial<ModelFlowDefinition> | null | undefined): ModelFlowValidationIssue[];
54
+ export declare function hasBlockingModelFlowIssues(issues: ModelFlowValidationIssue[]): boolean;
package/dist/index.js ADDED
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MODEL_FLOW_NODE_TYPES = void 0;
4
+ exports.createEmptyModelFlowDefinition = createEmptyModelFlowDefinition;
5
+ exports.normalizeModelFlowDefinition = normalizeModelFlowDefinition;
6
+ exports.validateModelFlowDefinition = validateModelFlowDefinition;
7
+ exports.hasBlockingModelFlowIssues = hasBlockingModelFlowIssues;
8
+ exports.MODEL_FLOW_NODE_TYPES = [
9
+ "data_source",
10
+ "query",
11
+ "join",
12
+ "snapshot",
13
+ "schema_profile",
14
+ "target",
15
+ "data_quality",
16
+ "leakage_check",
17
+ "cleaning",
18
+ "feature_transform",
19
+ "text_features",
20
+ "embedding",
21
+ "train_test_split",
22
+ "model_candidate",
23
+ "ensemble",
24
+ "validation",
25
+ "slice_metrics",
26
+ "prediction_preview",
27
+ "publish_contract",
28
+ "inference_transform",
29
+ "deploy_preview",
30
+ ];
31
+ const NODE_TYPE_SET = new Set(exports.MODEL_FLOW_NODE_TYPES);
32
+ function createEmptyModelFlowDefinition() {
33
+ return {
34
+ version: 1,
35
+ inputSchema: { type: "object", properties: {}, required: [] },
36
+ outputSchema: { type: "object", properties: {}, required: [] },
37
+ objective: { taskType: "classification" },
38
+ nodes: [],
39
+ edges: [],
40
+ };
41
+ }
42
+ function normalizeModelFlowDefinition(definition) {
43
+ const fallback = createEmptyModelFlowDefinition();
44
+ const nodes = Array.isArray(definition === null || definition === void 0 ? void 0 : definition.nodes)
45
+ ? definition.nodes.filter(isModelFlowNode).map((node) => (Object.assign(Object.assign({}, node), { config: isRecord(node.config) ? node.config : {} })))
46
+ : [];
47
+ const edges = Array.isArray(definition === null || definition === void 0 ? void 0 : definition.edges)
48
+ ? definition.edges.filter(isModelFlowEdge)
49
+ : [];
50
+ const objective = isRecord(definition === null || definition === void 0 ? void 0 : definition.objective)
51
+ ? Object.assign(Object.assign({ taskType: isTaskType(definition.objective.taskType) ? definition.objective.taskType : fallback.objective.taskType }, (typeof definition.objective.target === "string" ? { target: definition.objective.target } : {})), (typeof definition.objective.optimizationMetric === "string" ? { optimizationMetric: definition.objective.optimizationMetric } : {})) : fallback.objective;
52
+ return Object.assign({ version: 1, inputSchema: isRecord(definition === null || definition === void 0 ? void 0 : definition.inputSchema) ? definition.inputSchema : fallback.inputSchema, outputSchema: isRecord(definition === null || definition === void 0 ? void 0 : definition.outputSchema) ? definition.outputSchema : fallback.outputSchema, objective,
53
+ nodes,
54
+ edges }, (isRecord(definition === null || definition === void 0 ? void 0 : definition.metadata) ? { metadata: definition.metadata } : {}));
55
+ }
56
+ function validateModelFlowDefinition(definition) {
57
+ var _a, _b;
58
+ const normalized = normalizeModelFlowDefinition(definition);
59
+ const issues = [];
60
+ const nodeIds = new Set();
61
+ const nodeTypes = new Set();
62
+ for (const node of normalized.nodes) {
63
+ if (nodeIds.has(node.id)) {
64
+ issues.push(nodeIssue("error", node.id, "duplicate_node_id", `Node id "${node.id}" is duplicated.`));
65
+ }
66
+ nodeIds.add(node.id);
67
+ nodeTypes.add(node.type);
68
+ }
69
+ for (const requiredType of ["data_source", "target", "model_candidate", "validation"]) {
70
+ if (!nodeTypes.has(requiredType)) {
71
+ issues.push(graphIssue("error", `missing_${requiredType}`, `Add a ${requiredType.replaceAll("_", " ")} node before publishing.`));
72
+ }
73
+ }
74
+ if (!((_a = normalized.objective) === null || _a === void 0 ? void 0 : _a.target)) {
75
+ const targetNode = normalized.nodes.find((node) => node.type === "target");
76
+ const targetColumn = typeof ((_b = targetNode === null || targetNode === void 0 ? void 0 : targetNode.config) === null || _b === void 0 ? void 0 : _b.targetColumn) === "string" ? targetNode.config.targetColumn : "";
77
+ if (!targetColumn) {
78
+ issues.push(graphIssue("error", "missing_target_column", "Choose the target column the model should predict."));
79
+ }
80
+ }
81
+ if (!nodeTypes.has("train_test_split")) {
82
+ issues.push(graphIssue("warning", "missing_holdout", "Add a train/test split so validation is not measured on training rows."));
83
+ }
84
+ if (!nodeTypes.has("publish_contract")) {
85
+ issues.push(graphIssue("info", "missing_publish_contract", "Add a publish contract node before exposing an inference API."));
86
+ }
87
+ for (const edge of normalized.edges) {
88
+ if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {
89
+ issues.push({
90
+ severity: "error",
91
+ scope: "edge",
92
+ edgeId: edge.id,
93
+ code: "broken_edge",
94
+ message: `Edge ${edge.from} -> ${edge.to} references a missing node.`,
95
+ });
96
+ }
97
+ }
98
+ return issues;
99
+ }
100
+ function hasBlockingModelFlowIssues(issues) {
101
+ return issues.some((issue) => issue.severity === "error");
102
+ }
103
+ function isModelFlowNode(value) {
104
+ if (!isRecord(value))
105
+ return false;
106
+ return typeof value.id === "string"
107
+ && typeof value.type === "string"
108
+ && NODE_TYPE_SET.has(value.type);
109
+ }
110
+ function isModelFlowEdge(value) {
111
+ if (!isRecord(value))
112
+ return false;
113
+ return typeof value.from === "string" && typeof value.to === "string";
114
+ }
115
+ function isTaskType(value) {
116
+ return value === "classification"
117
+ || value === "regression"
118
+ || value === "ranking"
119
+ || value === "extraction"
120
+ || value === "generation";
121
+ }
122
+ function isRecord(value) {
123
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
124
+ }
125
+ function graphIssue(severity, code, message) {
126
+ return { severity, scope: "graph", code, message };
127
+ }
128
+ function nodeIssue(severity, nodeId, code, message) {
129
+ return { severity, scope: "node", nodeId, code, message };
130
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@kognitivedev/model-flow",
3
+ "version": "0.2.29",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc -w --noCheck",
12
+ "test": "vitest run",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^20.0.0",
17
+ "typescript": "^5.0.0",
18
+ "vitest": "^3.0.0"
19
+ },
20
+ "description": "Shared model-flow graph contracts and validation for Kognitive",
21
+ "keywords": [
22
+ "kognitive",
23
+ "model-flow",
24
+ "ml",
25
+ "ai"
26
+ ],
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/kognitivedev/kognitive",
31
+ "directory": "packages/model-flow"
32
+ },
33
+ "homepage": "https://kognitive.dev"
34
+ }
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ createEmptyModelFlowDefinition,
4
+ hasBlockingModelFlowIssues,
5
+ normalizeModelFlowDefinition,
6
+ validateModelFlowDefinition,
7
+ } from "../index";
8
+
9
+ describe("model-flow shared contract", () => {
10
+ it("normalizes unknown definitions to a safe graph shape", () => {
11
+ const definition = normalizeModelFlowDefinition({ nodes: [{ id: "x", type: "not_real" } as never] });
12
+
13
+ expect(definition.version).toBe(1);
14
+ expect(definition.nodes).toEqual([]);
15
+ expect(definition.edges).toEqual([]);
16
+ });
17
+
18
+ it("validates publish-blocking model graph requirements", () => {
19
+ const issues = validateModelFlowDefinition(createEmptyModelFlowDefinition());
20
+
21
+ expect(hasBlockingModelFlowIssues(issues)).toBe(true);
22
+ expect(issues.map((issue) => issue.code)).toContain("missing_data_source");
23
+ expect(issues.map((issue) => issue.code)).toContain("missing_model_candidate");
24
+ });
25
+
26
+ it("accepts a complete minimal graph", () => {
27
+ const issues = validateModelFlowDefinition({
28
+ version: 1,
29
+ objective: { taskType: "classification", target: "churn_30d", optimizationMetric: "f1" },
30
+ nodes: [
31
+ { id: "source", type: "data_source" },
32
+ { id: "target", type: "target", config: { targetColumn: "churn_30d" } },
33
+ { id: "split", type: "train_test_split" },
34
+ { id: "model", type: "model_candidate" },
35
+ { id: "validate", type: "validation" },
36
+ { id: "contract", type: "publish_contract" },
37
+ ],
38
+ edges: [
39
+ { from: "source", to: "target" },
40
+ { from: "target", to: "split" },
41
+ { from: "split", to: "model" },
42
+ { from: "model", to: "validate" },
43
+ { from: "validate", to: "contract" },
44
+ ],
45
+ });
46
+
47
+ expect(hasBlockingModelFlowIssues(issues)).toBe(false);
48
+ });
49
+ });
50
+
package/src/index.ts ADDED
@@ -0,0 +1,235 @@
1
+ export type JsonObject = Record<string, unknown>;
2
+
3
+ export type ModelFlowTaskType =
4
+ | "classification"
5
+ | "regression"
6
+ | "ranking"
7
+ | "extraction"
8
+ | "generation";
9
+
10
+ export type ModelFlowNodeType =
11
+ | "data_source"
12
+ | "query"
13
+ | "join"
14
+ | "snapshot"
15
+ | "schema_profile"
16
+ | "target"
17
+ | "data_quality"
18
+ | "leakage_check"
19
+ | "cleaning"
20
+ | "feature_transform"
21
+ | "text_features"
22
+ | "embedding"
23
+ | "train_test_split"
24
+ | "model_candidate"
25
+ | "ensemble"
26
+ | "validation"
27
+ | "slice_metrics"
28
+ | "prediction_preview"
29
+ | "publish_contract"
30
+ | "inference_transform"
31
+ | "deploy_preview";
32
+
33
+ export interface ModelFlowObjective {
34
+ taskType: ModelFlowTaskType;
35
+ target?: string;
36
+ optimizationMetric?: string;
37
+ }
38
+
39
+ export interface ModelFlowNode {
40
+ id: string;
41
+ type: ModelFlowNodeType;
42
+ label?: string;
43
+ position?: { x: number; y: number };
44
+ config?: JsonObject;
45
+ }
46
+
47
+ export interface ModelFlowEdge {
48
+ id?: string;
49
+ from: string;
50
+ to: string;
51
+ label?: string;
52
+ config?: JsonObject;
53
+ }
54
+
55
+ export interface ModelFlowDefinition {
56
+ version: 1;
57
+ inputSchema?: JsonObject;
58
+ outputSchema?: JsonObject;
59
+ objective: ModelFlowObjective;
60
+ nodes: ModelFlowNode[];
61
+ edges: ModelFlowEdge[];
62
+ metadata?: JsonObject;
63
+ }
64
+
65
+ export interface ModelFlowValidationIssue {
66
+ severity: "error" | "warning" | "info";
67
+ scope: "graph" | "node" | "edge";
68
+ nodeId?: string;
69
+ edgeId?: string;
70
+ code: string;
71
+ message: string;
72
+ }
73
+
74
+ export interface ModelFlowRunEvent {
75
+ eventType: string;
76
+ nodeId?: string | null;
77
+ nodeType?: string | null;
78
+ stepIndex?: number | null;
79
+ payload?: JsonObject | null;
80
+ }
81
+
82
+ export const MODEL_FLOW_NODE_TYPES: ModelFlowNodeType[] = [
83
+ "data_source",
84
+ "query",
85
+ "join",
86
+ "snapshot",
87
+ "schema_profile",
88
+ "target",
89
+ "data_quality",
90
+ "leakage_check",
91
+ "cleaning",
92
+ "feature_transform",
93
+ "text_features",
94
+ "embedding",
95
+ "train_test_split",
96
+ "model_candidate",
97
+ "ensemble",
98
+ "validation",
99
+ "slice_metrics",
100
+ "prediction_preview",
101
+ "publish_contract",
102
+ "inference_transform",
103
+ "deploy_preview",
104
+ ];
105
+
106
+ const NODE_TYPE_SET = new Set<string>(MODEL_FLOW_NODE_TYPES);
107
+
108
+ export function createEmptyModelFlowDefinition(): ModelFlowDefinition {
109
+ return {
110
+ version: 1,
111
+ inputSchema: { type: "object", properties: {}, required: [] },
112
+ outputSchema: { type: "object", properties: {}, required: [] },
113
+ objective: { taskType: "classification" },
114
+ nodes: [],
115
+ edges: [],
116
+ };
117
+ }
118
+
119
+ export function normalizeModelFlowDefinition(definition: Partial<ModelFlowDefinition> | null | undefined): ModelFlowDefinition {
120
+ const fallback = createEmptyModelFlowDefinition();
121
+ const nodes = Array.isArray(definition?.nodes)
122
+ ? definition.nodes.filter(isModelFlowNode).map((node) => ({
123
+ ...node,
124
+ config: isRecord(node.config) ? node.config : {},
125
+ }))
126
+ : [];
127
+ const edges = Array.isArray(definition?.edges)
128
+ ? definition.edges.filter(isModelFlowEdge)
129
+ : [];
130
+ const objective = isRecord(definition?.objective)
131
+ ? {
132
+ taskType: isTaskType(definition.objective.taskType) ? definition.objective.taskType : fallback.objective.taskType,
133
+ ...(typeof definition.objective.target === "string" ? { target: definition.objective.target } : {}),
134
+ ...(typeof definition.objective.optimizationMetric === "string" ? { optimizationMetric: definition.objective.optimizationMetric } : {}),
135
+ }
136
+ : fallback.objective;
137
+
138
+ return {
139
+ version: 1,
140
+ inputSchema: isRecord(definition?.inputSchema) ? definition.inputSchema : fallback.inputSchema,
141
+ outputSchema: isRecord(definition?.outputSchema) ? definition.outputSchema : fallback.outputSchema,
142
+ objective,
143
+ nodes,
144
+ edges,
145
+ ...(isRecord(definition?.metadata) ? { metadata: definition.metadata } : {}),
146
+ };
147
+ }
148
+
149
+ export function validateModelFlowDefinition(definition: Partial<ModelFlowDefinition> | null | undefined): ModelFlowValidationIssue[] {
150
+ const normalized = normalizeModelFlowDefinition(definition);
151
+ const issues: ModelFlowValidationIssue[] = [];
152
+ const nodeIds = new Set<string>();
153
+ const nodeTypes = new Set<ModelFlowNodeType>();
154
+
155
+ for (const node of normalized.nodes) {
156
+ if (nodeIds.has(node.id)) {
157
+ issues.push(nodeIssue("error", node.id, "duplicate_node_id", `Node id "${node.id}" is duplicated.`));
158
+ }
159
+ nodeIds.add(node.id);
160
+ nodeTypes.add(node.type);
161
+ }
162
+
163
+ for (const requiredType of ["data_source", "target", "model_candidate", "validation"] as ModelFlowNodeType[]) {
164
+ if (!nodeTypes.has(requiredType)) {
165
+ issues.push(graphIssue("error", `missing_${requiredType}`, `Add a ${requiredType.replaceAll("_", " ")} node before publishing.`));
166
+ }
167
+ }
168
+
169
+ if (!normalized.objective?.target) {
170
+ const targetNode = normalized.nodes.find((node) => node.type === "target");
171
+ const targetColumn = typeof targetNode?.config?.targetColumn === "string" ? targetNode.config.targetColumn : "";
172
+ if (!targetColumn) {
173
+ issues.push(graphIssue("error", "missing_target_column", "Choose the target column the model should predict."));
174
+ }
175
+ }
176
+
177
+ if (!nodeTypes.has("train_test_split")) {
178
+ issues.push(graphIssue("warning", "missing_holdout", "Add a train/test split so validation is not measured on training rows."));
179
+ }
180
+
181
+ if (!nodeTypes.has("publish_contract")) {
182
+ issues.push(graphIssue("info", "missing_publish_contract", "Add a publish contract node before exposing an inference API."));
183
+ }
184
+
185
+ for (const edge of normalized.edges) {
186
+ if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {
187
+ issues.push({
188
+ severity: "error",
189
+ scope: "edge",
190
+ edgeId: edge.id,
191
+ code: "broken_edge",
192
+ message: `Edge ${edge.from} -> ${edge.to} references a missing node.`,
193
+ });
194
+ }
195
+ }
196
+
197
+ return issues;
198
+ }
199
+
200
+ export function hasBlockingModelFlowIssues(issues: ModelFlowValidationIssue[]): boolean {
201
+ return issues.some((issue) => issue.severity === "error");
202
+ }
203
+
204
+ function isModelFlowNode(value: unknown): value is ModelFlowNode {
205
+ if (!isRecord(value)) return false;
206
+ return typeof value.id === "string"
207
+ && typeof value.type === "string"
208
+ && NODE_TYPE_SET.has(value.type);
209
+ }
210
+
211
+ function isModelFlowEdge(value: unknown): value is ModelFlowEdge {
212
+ if (!isRecord(value)) return false;
213
+ return typeof value.from === "string" && typeof value.to === "string";
214
+ }
215
+
216
+ function isTaskType(value: unknown): value is ModelFlowTaskType {
217
+ return value === "classification"
218
+ || value === "regression"
219
+ || value === "ranking"
220
+ || value === "extraction"
221
+ || value === "generation";
222
+ }
223
+
224
+ function isRecord(value: unknown): value is JsonObject {
225
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
226
+ }
227
+
228
+ function graphIssue(severity: ModelFlowValidationIssue["severity"], code: string, message: string): ModelFlowValidationIssue {
229
+ return { severity, scope: "graph", code, message };
230
+ }
231
+
232
+ function nodeIssue(severity: ModelFlowValidationIssue["severity"], nodeId: string, code: string, message: string): ModelFlowValidationIssue {
233
+ return { severity, scope: "node", nodeId, code, message };
234
+ }
235
+
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "rootDir": "src",
6
+ "outDir": "dist",
7
+ "declaration": true,
8
+ "noEmit": false,
9
+ "incremental": false
10
+ },
11
+ "include": ["src"],
12
+ "exclude": ["src/__tests__"]
13
+ }
14
+