@hazeljs/ml 0.2.4 → 0.3.1
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/dist/evaluation/metrics.service.test.js +288 -0
- package/dist/experiments/__tests__/experiment.decorator.test.d.ts +2 -0
- package/dist/experiments/__tests__/experiment.decorator.test.d.ts.map +1 -0
- package/dist/experiments/__tests__/experiment.decorator.test.js +121 -0
- package/dist/experiments/__tests__/experiment.service.test.d.ts +2 -0
- package/dist/experiments/__tests__/experiment.service.test.d.ts.map +1 -0
- package/dist/experiments/__tests__/experiment.service.test.js +460 -0
- package/dist/experiments/experiment.decorator.d.ts +44 -0
- package/dist/experiments/experiment.decorator.d.ts.map +1 -0
- package/dist/experiments/experiment.decorator.js +51 -0
- package/dist/experiments/experiment.service.d.ts +42 -0
- package/dist/experiments/experiment.service.d.ts.map +1 -0
- package/dist/experiments/experiment.service.js +355 -0
- package/dist/experiments/experiment.types.d.ts +60 -0
- package/dist/experiments/experiment.types.d.ts.map +1 -0
- package/dist/experiments/experiment.types.js +5 -0
- package/dist/experiments/index.d.ts +9 -0
- package/dist/experiments/index.d.ts.map +1 -0
- package/dist/experiments/index.js +16 -0
- package/dist/features/__tests__/feature-view.decorator.test.d.ts +2 -0
- package/dist/features/__tests__/feature-view.decorator.test.d.ts.map +1 -0
- package/dist/features/__tests__/feature-view.decorator.test.js +168 -0
- package/dist/features/__tests__/feature.decorator.test.d.ts +2 -0
- package/dist/features/__tests__/feature.decorator.test.d.ts.map +1 -0
- package/dist/features/__tests__/feature.decorator.test.js +167 -0
- package/dist/features/feature-store.service.d.ts +59 -0
- package/dist/features/feature-store.service.d.ts.map +1 -0
- package/dist/features/feature-store.service.js +197 -0
- package/dist/features/feature-view.decorator.d.ts +52 -0
- package/dist/features/feature-view.decorator.d.ts.map +1 -0
- package/dist/features/feature-view.decorator.js +54 -0
- package/dist/features/feature.decorator.d.ts +42 -0
- package/dist/features/feature.decorator.d.ts.map +1 -0
- package/dist/features/feature.decorator.js +49 -0
- package/dist/features/feature.types.d.ts +93 -0
- package/dist/features/feature.types.d.ts.map +1 -0
- package/dist/features/feature.types.js +5 -0
- package/dist/features/index.d.ts +12 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +29 -0
- package/dist/features/offline-store.d.ts +40 -0
- package/dist/features/offline-store.d.ts.map +1 -0
- package/dist/features/offline-store.js +215 -0
- package/dist/features/online-store.d.ts +45 -0
- package/dist/features/online-store.d.ts.map +1 -0
- package/dist/features/online-store.js +139 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -1
- package/dist/monitoring/__tests__/drift.service.test.d.ts +2 -0
- package/dist/monitoring/__tests__/drift.service.test.d.ts.map +1 -0
- package/dist/monitoring/__tests__/drift.service.test.js +362 -0
- package/dist/monitoring/__tests__/monitor.service.test.d.ts +2 -0
- package/dist/monitoring/__tests__/monitor.service.test.d.ts.map +1 -0
- package/dist/monitoring/__tests__/monitor.service.test.js +360 -0
- package/dist/monitoring/drift.service.d.ts +68 -0
- package/dist/monitoring/drift.service.d.ts.map +1 -0
- package/dist/monitoring/drift.service.js +360 -0
- package/dist/monitoring/drift.types.d.ts +44 -0
- package/dist/monitoring/drift.types.d.ts.map +1 -0
- package/dist/monitoring/drift.types.js +5 -0
- package/dist/monitoring/index.d.ts +10 -0
- package/dist/monitoring/index.d.ts.map +1 -0
- package/dist/monitoring/index.js +13 -0
- package/dist/monitoring/monitor.service.d.ts +79 -0
- package/dist/monitoring/monitor.service.d.ts.map +1 -0
- package/dist/monitoring/monitor.service.js +192 -0
- package/dist/training/trainer.service.test.js +105 -0
- package/package.json +2 -2
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Experiment Service - ML experiment tracking and management
|
|
4
|
+
*/
|
|
5
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
6
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
7
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
8
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
9
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ExperimentService = void 0;
|
|
16
|
+
const core_1 = require("@hazeljs/core");
|
|
17
|
+
const core_2 = __importDefault(require("@hazeljs/core"));
|
|
18
|
+
const fs_1 = require("fs");
|
|
19
|
+
const path_1 = require("path");
|
|
20
|
+
function generateId() {
|
|
21
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
22
|
+
}
|
|
23
|
+
let ExperimentService = class ExperimentService {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.experiments = new Map();
|
|
26
|
+
this.runs = new Map();
|
|
27
|
+
this.config = { storage: 'memory' };
|
|
28
|
+
this.storageDir = './experiments';
|
|
29
|
+
}
|
|
30
|
+
configure(config) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
if (config.file) {
|
|
33
|
+
this.storageDir = config.file.directory;
|
|
34
|
+
}
|
|
35
|
+
core_2.default.debug('ExperimentService configured', { storage: config.storage });
|
|
36
|
+
}
|
|
37
|
+
// ─── Experiments ───────────────────────────────────────────────────────────
|
|
38
|
+
createExperiment(name, options = {}) {
|
|
39
|
+
const experiment = {
|
|
40
|
+
id: generateId(),
|
|
41
|
+
name,
|
|
42
|
+
description: options.description,
|
|
43
|
+
tags: options.tags,
|
|
44
|
+
createdAt: new Date(),
|
|
45
|
+
updatedAt: new Date(),
|
|
46
|
+
};
|
|
47
|
+
this.experiments.set(experiment.id, experiment);
|
|
48
|
+
if (this.config.storage === 'file') {
|
|
49
|
+
this.saveExperimentToFile(experiment);
|
|
50
|
+
}
|
|
51
|
+
core_2.default.debug(`Created experiment: ${name} (${experiment.id})`);
|
|
52
|
+
return experiment;
|
|
53
|
+
}
|
|
54
|
+
getExperiment(id) {
|
|
55
|
+
if (this.config.storage === 'file') {
|
|
56
|
+
return this.loadExperimentFromFile(id);
|
|
57
|
+
}
|
|
58
|
+
return this.experiments.get(id);
|
|
59
|
+
}
|
|
60
|
+
listExperiments() {
|
|
61
|
+
if (this.config.storage === 'file') {
|
|
62
|
+
return this.loadAllExperimentsFromFile();
|
|
63
|
+
}
|
|
64
|
+
return Array.from(this.experiments.values());
|
|
65
|
+
}
|
|
66
|
+
deleteExperiment(id) {
|
|
67
|
+
// Delete associated runs first
|
|
68
|
+
const runsToDelete = Array.from(this.runs.values()).filter((r) => r.experimentId === id);
|
|
69
|
+
for (const run of runsToDelete) {
|
|
70
|
+
this.deleteRun(run.id);
|
|
71
|
+
}
|
|
72
|
+
const deleted = this.experiments.delete(id);
|
|
73
|
+
if (deleted && this.config.storage === 'file') {
|
|
74
|
+
const expDir = (0, path_1.join)(this.storageDir, id);
|
|
75
|
+
if ((0, fs_1.existsSync)(expDir)) {
|
|
76
|
+
// Delete all files in experiment directory
|
|
77
|
+
const files = (0, fs_1.readdirSync)(expDir);
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
(0, fs_1.unlinkSync)((0, path_1.join)(expDir, file));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
core_2.default.debug(`Deleted experiment: ${id}`);
|
|
84
|
+
return deleted;
|
|
85
|
+
}
|
|
86
|
+
// ─── Runs ──────────────────────────────────────────────────────────────────
|
|
87
|
+
startRun(experimentId, options = {}) {
|
|
88
|
+
const run = {
|
|
89
|
+
id: generateId(),
|
|
90
|
+
experimentId,
|
|
91
|
+
name: options.name,
|
|
92
|
+
status: 'running',
|
|
93
|
+
params: options.params ?? {},
|
|
94
|
+
metrics: {},
|
|
95
|
+
artifacts: [],
|
|
96
|
+
startTime: new Date(),
|
|
97
|
+
tags: options.tags,
|
|
98
|
+
};
|
|
99
|
+
this.runs.set(run.id, run);
|
|
100
|
+
if (this.config.storage === 'file') {
|
|
101
|
+
this.saveRunToFile(run);
|
|
102
|
+
}
|
|
103
|
+
core_2.default.debug(`Started run: ${run.id} for experiment: ${experimentId}`);
|
|
104
|
+
return run;
|
|
105
|
+
}
|
|
106
|
+
endRun(runId, status = 'completed') {
|
|
107
|
+
const run = this.runs.get(runId);
|
|
108
|
+
if (!run) {
|
|
109
|
+
throw new Error(`Run not found: ${runId}`);
|
|
110
|
+
}
|
|
111
|
+
run.status = status;
|
|
112
|
+
run.endTime = new Date();
|
|
113
|
+
run.durationMs = run.endTime.getTime() - run.startTime.getTime();
|
|
114
|
+
if (this.config.storage === 'file') {
|
|
115
|
+
this.saveRunToFile(run);
|
|
116
|
+
}
|
|
117
|
+
core_2.default.debug(`Ended run: ${runId} with status: ${status}`);
|
|
118
|
+
return run;
|
|
119
|
+
}
|
|
120
|
+
getRun(id) {
|
|
121
|
+
if (this.config.storage === 'file') {
|
|
122
|
+
return this.loadRunFromFile(id);
|
|
123
|
+
}
|
|
124
|
+
return this.runs.get(id);
|
|
125
|
+
}
|
|
126
|
+
listRuns(experimentId) {
|
|
127
|
+
if (this.config.storage === 'file') {
|
|
128
|
+
const all = this.loadAllRunsFromFile();
|
|
129
|
+
if (experimentId) {
|
|
130
|
+
return all.filter((r) => r.experimentId === experimentId);
|
|
131
|
+
}
|
|
132
|
+
return all;
|
|
133
|
+
}
|
|
134
|
+
const runs = Array.from(this.runs.values());
|
|
135
|
+
if (experimentId) {
|
|
136
|
+
return runs.filter((r) => r.experimentId === experimentId);
|
|
137
|
+
}
|
|
138
|
+
return runs;
|
|
139
|
+
}
|
|
140
|
+
deleteRun(id) {
|
|
141
|
+
const run = this.runs.get(id);
|
|
142
|
+
if (!run)
|
|
143
|
+
return false;
|
|
144
|
+
// Delete artifacts
|
|
145
|
+
for (const artifact of run.artifacts) {
|
|
146
|
+
this.deleteArtifactFile(artifact);
|
|
147
|
+
}
|
|
148
|
+
const deleted = this.runs.delete(id);
|
|
149
|
+
if (deleted && this.config.storage === 'file') {
|
|
150
|
+
const runPath = (0, path_1.join)(this.storageDir, run.experimentId, `run-${id}.json`);
|
|
151
|
+
if ((0, fs_1.existsSync)(runPath)) {
|
|
152
|
+
(0, fs_1.unlinkSync)(runPath);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return deleted;
|
|
156
|
+
}
|
|
157
|
+
// ─── Metrics ───────────────────────────────────────────────────────────────
|
|
158
|
+
logMetric(runId, key, value) {
|
|
159
|
+
const run = this.runs.get(runId);
|
|
160
|
+
if (!run) {
|
|
161
|
+
throw new Error(`Run not found: ${runId}`);
|
|
162
|
+
}
|
|
163
|
+
run.metrics[key] = value;
|
|
164
|
+
if (this.config.storage === 'file') {
|
|
165
|
+
this.saveRunToFile(run);
|
|
166
|
+
}
|
|
167
|
+
core_2.default.debug(`Logged metric: ${key}=${value} for run: ${runId}`);
|
|
168
|
+
}
|
|
169
|
+
logMetrics(runId, metrics) {
|
|
170
|
+
for (const [key, value] of Object.entries(metrics)) {
|
|
171
|
+
this.logMetric(runId, key, value);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
getBestRun(experimentId, metric, mode = 'max') {
|
|
175
|
+
const runs = this.listRuns(experimentId).filter((r) => r.metrics[metric] !== undefined);
|
|
176
|
+
if (runs.length === 0)
|
|
177
|
+
return undefined;
|
|
178
|
+
return runs.reduce((best, current) => {
|
|
179
|
+
const bestValue = best.metrics[metric];
|
|
180
|
+
const currentValue = current.metrics[metric];
|
|
181
|
+
if (mode === 'max') {
|
|
182
|
+
return currentValue > bestValue ? current : best;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
return currentValue < bestValue ? current : best;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// ─── Artifacts ───────────────────────────────────────────────────────────────
|
|
190
|
+
logArtifact(runId, name, type, content, metadata) {
|
|
191
|
+
const run = this.runs.get(runId);
|
|
192
|
+
if (!run) {
|
|
193
|
+
throw new Error(`Run not found: ${runId}`);
|
|
194
|
+
}
|
|
195
|
+
const artifact = {
|
|
196
|
+
id: generateId(),
|
|
197
|
+
runId,
|
|
198
|
+
name,
|
|
199
|
+
type,
|
|
200
|
+
path: '', // Will be set based on storage
|
|
201
|
+
size: Buffer.byteLength(content),
|
|
202
|
+
metadata,
|
|
203
|
+
createdAt: new Date(),
|
|
204
|
+
};
|
|
205
|
+
if (this.config.storage === 'file') {
|
|
206
|
+
const artifactPath = (0, path_1.join)(this.storageDir, run.experimentId, runId, `${name}.${this.getArtifactExtension(type)}`);
|
|
207
|
+
artifact.path = artifactPath;
|
|
208
|
+
// Ensure directory exists
|
|
209
|
+
const dir = (0, path_1.join)(this.storageDir, run.experimentId, runId);
|
|
210
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
211
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
212
|
+
}
|
|
213
|
+
(0, fs_1.writeFileSync)(artifactPath, content);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// In-memory: store content in metadata (not ideal for large files)
|
|
217
|
+
artifact.path = `memory://${artifact.id}`;
|
|
218
|
+
artifact.metadata = { ...artifact.metadata, _content: content.toString('base64') };
|
|
219
|
+
}
|
|
220
|
+
run.artifacts.push(artifact);
|
|
221
|
+
if (this.config.storage === 'file') {
|
|
222
|
+
this.saveRunToFile(run);
|
|
223
|
+
}
|
|
224
|
+
core_2.default.debug(`Logged artifact: ${name} for run: ${runId}`);
|
|
225
|
+
return artifact;
|
|
226
|
+
}
|
|
227
|
+
getArtifact(runId, artifactId) {
|
|
228
|
+
const run = this.runs.get(runId);
|
|
229
|
+
if (!run)
|
|
230
|
+
return undefined;
|
|
231
|
+
return run.artifacts.find((a) => a.id === artifactId);
|
|
232
|
+
}
|
|
233
|
+
// ─── Comparison ─────────────────────────────────────────────────────────────
|
|
234
|
+
compareRuns(runIds) {
|
|
235
|
+
return runIds
|
|
236
|
+
.map((id) => this.getRun(id))
|
|
237
|
+
.filter((run) => run !== undefined && run.status === 'completed')
|
|
238
|
+
.map((run) => ({
|
|
239
|
+
runId: run.id,
|
|
240
|
+
params: run.params,
|
|
241
|
+
metrics: run.metrics,
|
|
242
|
+
durationMs: run.durationMs ?? 0,
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
// ─── File Storage Helpers ────────────────────────────────────────────────────
|
|
246
|
+
saveExperimentToFile(experiment) {
|
|
247
|
+
const expDir = (0, path_1.join)(this.storageDir, experiment.id);
|
|
248
|
+
if (!(0, fs_1.existsSync)(expDir)) {
|
|
249
|
+
(0, fs_1.mkdirSync)(expDir, { recursive: true });
|
|
250
|
+
}
|
|
251
|
+
const path = (0, path_1.join)(expDir, 'experiment.json');
|
|
252
|
+
(0, fs_1.writeFileSync)(path, JSON.stringify(experiment, null, 2));
|
|
253
|
+
}
|
|
254
|
+
loadExperimentFromFile(id) {
|
|
255
|
+
const path = (0, path_1.join)(this.storageDir, id, 'experiment.json');
|
|
256
|
+
if (!(0, fs_1.existsSync)(path))
|
|
257
|
+
return undefined;
|
|
258
|
+
const content = (0, fs_1.readFileSync)(path, 'utf-8');
|
|
259
|
+
const exp = JSON.parse(content);
|
|
260
|
+
exp.createdAt = new Date(exp.createdAt);
|
|
261
|
+
exp.updatedAt = new Date(exp.updatedAt);
|
|
262
|
+
return exp;
|
|
263
|
+
}
|
|
264
|
+
loadAllExperimentsFromFile() {
|
|
265
|
+
if (!(0, fs_1.existsSync)(this.storageDir))
|
|
266
|
+
return [];
|
|
267
|
+
const experiments = [];
|
|
268
|
+
const dirs = (0, fs_1.readdirSync)(this.storageDir, { withFileTypes: true });
|
|
269
|
+
for (const dir of dirs) {
|
|
270
|
+
if (dir.isDirectory()) {
|
|
271
|
+
const exp = this.loadExperimentFromFile(dir.name);
|
|
272
|
+
if (exp)
|
|
273
|
+
experiments.push(exp);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return experiments;
|
|
277
|
+
}
|
|
278
|
+
saveRunToFile(run) {
|
|
279
|
+
const expDir = (0, path_1.join)(this.storageDir, run.experimentId);
|
|
280
|
+
if (!(0, fs_1.existsSync)(expDir)) {
|
|
281
|
+
(0, fs_1.mkdirSync)(expDir, { recursive: true });
|
|
282
|
+
}
|
|
283
|
+
const path = (0, path_1.join)(expDir, `run-${run.id}.json`);
|
|
284
|
+
(0, fs_1.writeFileSync)(path, JSON.stringify(run, null, 2));
|
|
285
|
+
}
|
|
286
|
+
loadRunFromFile(id) {
|
|
287
|
+
// Find the run file in any experiment directory
|
|
288
|
+
if (!(0, fs_1.existsSync)(this.storageDir))
|
|
289
|
+
return undefined;
|
|
290
|
+
const dirs = (0, fs_1.readdirSync)(this.storageDir, { withFileTypes: true });
|
|
291
|
+
for (const dir of dirs) {
|
|
292
|
+
if (dir.isDirectory()) {
|
|
293
|
+
const path = (0, path_1.join)(this.storageDir, dir.name, `run-${id}.json`);
|
|
294
|
+
if ((0, fs_1.existsSync)(path)) {
|
|
295
|
+
const content = (0, fs_1.readFileSync)(path, 'utf-8');
|
|
296
|
+
const run = JSON.parse(content);
|
|
297
|
+
run.startTime = new Date(run.startTime);
|
|
298
|
+
if (run.endTime)
|
|
299
|
+
run.endTime = new Date(run.endTime);
|
|
300
|
+
for (const artifact of run.artifacts) {
|
|
301
|
+
artifact.createdAt = new Date(artifact.createdAt);
|
|
302
|
+
}
|
|
303
|
+
return run;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
loadAllRunsFromFile() {
|
|
310
|
+
if (!(0, fs_1.existsSync)(this.storageDir))
|
|
311
|
+
return [];
|
|
312
|
+
const runs = [];
|
|
313
|
+
const dirs = (0, fs_1.readdirSync)(this.storageDir, { withFileTypes: true });
|
|
314
|
+
for (const dir of dirs) {
|
|
315
|
+
if (dir.isDirectory()) {
|
|
316
|
+
const expDir = (0, path_1.join)(this.storageDir, dir.name);
|
|
317
|
+
const files = (0, fs_1.readdirSync)(expDir);
|
|
318
|
+
for (const file of files) {
|
|
319
|
+
if (file.startsWith('run-') && file.endsWith('.json')) {
|
|
320
|
+
const runId = file.replace('run-', '').replace('.json', '');
|
|
321
|
+
const run = this.loadRunFromFile(runId);
|
|
322
|
+
if (run)
|
|
323
|
+
runs.push(run);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return runs;
|
|
329
|
+
}
|
|
330
|
+
deleteArtifactFile(artifact) {
|
|
331
|
+
if (artifact.path.startsWith('file://') || !artifact.path.startsWith('memory://')) {
|
|
332
|
+
if ((0, fs_1.existsSync)(artifact.path)) {
|
|
333
|
+
(0, fs_1.unlinkSync)(artifact.path);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
getArtifactExtension(type) {
|
|
338
|
+
switch (type) {
|
|
339
|
+
case 'model':
|
|
340
|
+
return 'bin';
|
|
341
|
+
case 'plot':
|
|
342
|
+
return 'png';
|
|
343
|
+
case 'log':
|
|
344
|
+
return 'log';
|
|
345
|
+
case 'data':
|
|
346
|
+
return 'json';
|
|
347
|
+
default:
|
|
348
|
+
return 'txt';
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
exports.ExperimentService = ExperimentService;
|
|
353
|
+
exports.ExperimentService = ExperimentService = __decorate([
|
|
354
|
+
(0, core_1.Service)()
|
|
355
|
+
], ExperimentService);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hazeljs/ml - Experiment Tracking Types
|
|
3
|
+
*/
|
|
4
|
+
export interface Experiment {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
tags?: string[];
|
|
9
|
+
createdAt: Date;
|
|
10
|
+
updatedAt: Date;
|
|
11
|
+
}
|
|
12
|
+
export interface Run {
|
|
13
|
+
id: string;
|
|
14
|
+
experimentId: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
status: 'running' | 'completed' | 'failed' | 'aborted';
|
|
17
|
+
params: Record<string, unknown>;
|
|
18
|
+
metrics: Record<string, number>;
|
|
19
|
+
artifacts: Artifact[];
|
|
20
|
+
startTime: Date;
|
|
21
|
+
endTime?: Date;
|
|
22
|
+
durationMs?: number;
|
|
23
|
+
tags?: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface Artifact {
|
|
26
|
+
id: string;
|
|
27
|
+
runId: string;
|
|
28
|
+
name: string;
|
|
29
|
+
type: 'model' | 'plot' | 'log' | 'data' | 'other';
|
|
30
|
+
path: string;
|
|
31
|
+
size?: number;
|
|
32
|
+
metadata?: Record<string, unknown>;
|
|
33
|
+
createdAt: Date;
|
|
34
|
+
}
|
|
35
|
+
export interface ExperimentConfig {
|
|
36
|
+
storage: 'file' | 'postgres' | 'memory';
|
|
37
|
+
file?: {
|
|
38
|
+
directory: string;
|
|
39
|
+
};
|
|
40
|
+
postgres?: {
|
|
41
|
+
host: string;
|
|
42
|
+
port: number;
|
|
43
|
+
database: string;
|
|
44
|
+
user: string;
|
|
45
|
+
password: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export interface ExperimentQuery {
|
|
49
|
+
name?: string;
|
|
50
|
+
tags?: string[];
|
|
51
|
+
createdAfter?: Date;
|
|
52
|
+
createdBefore?: Date;
|
|
53
|
+
}
|
|
54
|
+
export interface RunComparison {
|
|
55
|
+
runId: string;
|
|
56
|
+
params: Record<string, unknown>;
|
|
57
|
+
metrics: Record<string, number>;
|
|
58
|
+
durationMs: number;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=experiment.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"experiment.types.d.ts","sourceRoot":"","sources":["../../src/experiments/experiment.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IACvD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;IACxC,IAAI,CAAC,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,aAAa,CAAC,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hazeljs/ml - Experiment Tracking
|
|
3
|
+
*
|
|
4
|
+
* Export all experiment tracking components
|
|
5
|
+
*/
|
|
6
|
+
export type { Experiment as ExperimentType, Run, Artifact, ExperimentConfig, ExperimentQuery, RunComparison, } from './experiment.types';
|
|
7
|
+
export { ExperimentService } from './experiment.service';
|
|
8
|
+
export { Experiment, getExperimentMetadata, hasExperimentMetadata, type ExperimentOptions, type ExperimentMetadata, } from './experiment.decorator';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/experiments/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EACV,UAAU,IAAI,cAAc,EAC5B,GAAG,EACH,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,aAAa,GACd,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @hazeljs/ml - Experiment Tracking
|
|
4
|
+
*
|
|
5
|
+
* Export all experiment tracking components
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.hasExperimentMetadata = exports.getExperimentMetadata = exports.Experiment = exports.ExperimentService = void 0;
|
|
9
|
+
// Services
|
|
10
|
+
var experiment_service_1 = require("./experiment.service");
|
|
11
|
+
Object.defineProperty(exports, "ExperimentService", { enumerable: true, get: function () { return experiment_service_1.ExperimentService; } });
|
|
12
|
+
// Decorators
|
|
13
|
+
var experiment_decorator_1 = require("./experiment.decorator");
|
|
14
|
+
Object.defineProperty(exports, "Experiment", { enumerable: true, get: function () { return experiment_decorator_1.Experiment; } });
|
|
15
|
+
Object.defineProperty(exports, "getExperimentMetadata", { enumerable: true, get: function () { return experiment_decorator_1.getExperimentMetadata; } });
|
|
16
|
+
Object.defineProperty(exports, "hasExperimentMetadata", { enumerable: true, get: function () { return experiment_decorator_1.hasExperimentMetadata; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-view.decorator.test.d.ts","sourceRoot":"","sources":["../../../src/features/__tests__/feature-view.decorator.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
const feature_view_decorator_1 = require("../feature-view.decorator");
|
|
10
|
+
describe('FeatureView decorator', () => {
|
|
11
|
+
it('should attach metadata to class', () => {
|
|
12
|
+
let UserFeatures = class UserFeatures {
|
|
13
|
+
};
|
|
14
|
+
UserFeatures = __decorate([
|
|
15
|
+
(0, feature_view_decorator_1.FeatureView)({
|
|
16
|
+
name: 'user-features',
|
|
17
|
+
entities: ['user'],
|
|
18
|
+
description: 'User feature view',
|
|
19
|
+
})
|
|
20
|
+
], UserFeatures);
|
|
21
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(UserFeatures);
|
|
22
|
+
expect(metadata).toBeDefined();
|
|
23
|
+
expect(metadata?.name).toBe('user-features');
|
|
24
|
+
expect(metadata?.entities).toEqual(['user']);
|
|
25
|
+
expect(metadata?.description).toBe('User feature view');
|
|
26
|
+
});
|
|
27
|
+
it('should default online to true', () => {
|
|
28
|
+
let TestFeatures = class TestFeatures {
|
|
29
|
+
};
|
|
30
|
+
TestFeatures = __decorate([
|
|
31
|
+
(0, feature_view_decorator_1.FeatureView)({ name: 'test', entities: ['user'] })
|
|
32
|
+
], TestFeatures);
|
|
33
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
34
|
+
expect(metadata?.online).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('should default offline to true', () => {
|
|
37
|
+
let TestFeatures = class TestFeatures {
|
|
38
|
+
};
|
|
39
|
+
TestFeatures = __decorate([
|
|
40
|
+
(0, feature_view_decorator_1.FeatureView)({ name: 'test', entities: ['user'] })
|
|
41
|
+
], TestFeatures);
|
|
42
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
43
|
+
expect(metadata?.offline).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
it('should allow setting online to false', () => {
|
|
46
|
+
let TestFeatures = class TestFeatures {
|
|
47
|
+
};
|
|
48
|
+
TestFeatures = __decorate([
|
|
49
|
+
(0, feature_view_decorator_1.FeatureView)({ name: 'test', entities: ['user'], online: false })
|
|
50
|
+
], TestFeatures);
|
|
51
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
52
|
+
expect(metadata?.online).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
it('should allow setting offline to false', () => {
|
|
55
|
+
let TestFeatures = class TestFeatures {
|
|
56
|
+
};
|
|
57
|
+
TestFeatures = __decorate([
|
|
58
|
+
(0, feature_view_decorator_1.FeatureView)({ name: 'test', entities: ['user'], offline: false })
|
|
59
|
+
], TestFeatures);
|
|
60
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
61
|
+
expect(metadata?.offline).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
it('should handle multiple entities', () => {
|
|
64
|
+
let TestFeatures = class TestFeatures {
|
|
65
|
+
};
|
|
66
|
+
TestFeatures = __decorate([
|
|
67
|
+
(0, feature_view_decorator_1.FeatureView)({ name: 'test', entities: ['user', 'product', 'session'] })
|
|
68
|
+
], TestFeatures);
|
|
69
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
70
|
+
expect(metadata?.entities).toEqual(['user', 'product', 'session']);
|
|
71
|
+
});
|
|
72
|
+
it('should handle optional ttl', () => {
|
|
73
|
+
let TestFeatures = class TestFeatures {
|
|
74
|
+
};
|
|
75
|
+
TestFeatures = __decorate([
|
|
76
|
+
(0, feature_view_decorator_1.FeatureView)({ name: 'test', entities: ['user'], ttl: 3600 })
|
|
77
|
+
], TestFeatures);
|
|
78
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
79
|
+
expect(metadata?.ttl).toBe(3600);
|
|
80
|
+
});
|
|
81
|
+
it('should handle batch source', () => {
|
|
82
|
+
let TestFeatures = class TestFeatures {
|
|
83
|
+
};
|
|
84
|
+
TestFeatures = __decorate([
|
|
85
|
+
(0, feature_view_decorator_1.FeatureView)({
|
|
86
|
+
name: 'test',
|
|
87
|
+
entities: ['user'],
|
|
88
|
+
source: { type: 'batch', config: { path: '/data/features' } },
|
|
89
|
+
})
|
|
90
|
+
], TestFeatures);
|
|
91
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
92
|
+
expect(metadata?.source?.type).toBe('batch');
|
|
93
|
+
expect(metadata?.source?.config).toEqual({ path: '/data/features' });
|
|
94
|
+
});
|
|
95
|
+
it('should handle stream source', () => {
|
|
96
|
+
let TestFeatures = class TestFeatures {
|
|
97
|
+
};
|
|
98
|
+
TestFeatures = __decorate([
|
|
99
|
+
(0, feature_view_decorator_1.FeatureView)({
|
|
100
|
+
name: 'test',
|
|
101
|
+
entities: ['user'],
|
|
102
|
+
source: { type: 'stream', config: { topic: 'features' } },
|
|
103
|
+
})
|
|
104
|
+
], TestFeatures);
|
|
105
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
106
|
+
expect(metadata?.source?.type).toBe('stream');
|
|
107
|
+
});
|
|
108
|
+
it('should handle request source', () => {
|
|
109
|
+
let TestFeatures = class TestFeatures {
|
|
110
|
+
};
|
|
111
|
+
TestFeatures = __decorate([
|
|
112
|
+
(0, feature_view_decorator_1.FeatureView)({
|
|
113
|
+
name: 'test',
|
|
114
|
+
entities: ['user'],
|
|
115
|
+
source: { type: 'request' },
|
|
116
|
+
})
|
|
117
|
+
], TestFeatures);
|
|
118
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
119
|
+
expect(metadata?.source?.type).toBe('request');
|
|
120
|
+
});
|
|
121
|
+
it('should handle source without config', () => {
|
|
122
|
+
let TestFeatures = class TestFeatures {
|
|
123
|
+
};
|
|
124
|
+
TestFeatures = __decorate([
|
|
125
|
+
(0, feature_view_decorator_1.FeatureView)({
|
|
126
|
+
name: 'test',
|
|
127
|
+
entities: ['user'],
|
|
128
|
+
source: { type: 'batch' },
|
|
129
|
+
})
|
|
130
|
+
], TestFeatures);
|
|
131
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
132
|
+
expect(metadata?.source?.type).toBe('batch');
|
|
133
|
+
expect(metadata?.source?.config).toBeUndefined();
|
|
134
|
+
});
|
|
135
|
+
it('should handle features without optional fields', () => {
|
|
136
|
+
let TestFeatures = class TestFeatures {
|
|
137
|
+
};
|
|
138
|
+
TestFeatures = __decorate([
|
|
139
|
+
(0, feature_view_decorator_1.FeatureView)({ name: 'test', entities: ['user'] })
|
|
140
|
+
], TestFeatures);
|
|
141
|
+
const metadata = (0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures);
|
|
142
|
+
expect(metadata?.description).toBeUndefined();
|
|
143
|
+
expect(metadata?.ttl).toBeUndefined();
|
|
144
|
+
expect(metadata?.source).toBeUndefined();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('hasFeatureViewMetadata', () => {
|
|
148
|
+
it('should return true for decorated class', () => {
|
|
149
|
+
let TestFeatures = class TestFeatures {
|
|
150
|
+
};
|
|
151
|
+
TestFeatures = __decorate([
|
|
152
|
+
(0, feature_view_decorator_1.FeatureView)({ name: 'test', entities: ['user'] })
|
|
153
|
+
], TestFeatures);
|
|
154
|
+
expect((0, feature_view_decorator_1.hasFeatureViewMetadata)(TestFeatures)).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
it('should return false for non-decorated class', () => {
|
|
157
|
+
class TestFeatures {
|
|
158
|
+
}
|
|
159
|
+
expect((0, feature_view_decorator_1.hasFeatureViewMetadata)(TestFeatures)).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe('getFeatureViewMetadata', () => {
|
|
163
|
+
it('should return undefined for non-decorated class', () => {
|
|
164
|
+
class TestFeatures {
|
|
165
|
+
}
|
|
166
|
+
expect((0, feature_view_decorator_1.getFeatureViewMetadata)(TestFeatures)).toBeUndefined();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature.decorator.test.d.ts","sourceRoot":"","sources":["../../../src/features/__tests__/feature.decorator.test.ts"],"names":[],"mappings":""}
|