UNPKG

@modelx/modelx

Version:

Construct AI & ML models with JSON using Typescript & Tensorflow

742 lines (732 loc) 30.3 kB
import { ModelX, ModelTypes, getGeneratedStatefulFunction, sumPreviousRows, } from './model'; import { Dimensions, getIsOutlier, mockDates } from './constants'; import { DataSet, } from '@modelx/data/src/index'; import { Faker, getData, getDatum, timeseriesSort, getMockClassification, getMockRegression, getMockTimeseries, } from './util'; describe('Generated Functions', () => { describe('getGeneratedStatefulFunction', () => { it('should be a function', () => { expect(getGeneratedStatefulFunction).toHaveProperty('constructor'); }); it('should return a function', () => { const func = { variable_name: 'testFunction', function_body: 'return 3', }; const generatedFunc = getGeneratedStatefulFunction({ ...func, props: { Luxon: {}, ModelXData: {} }, function_name_prefix: 'custom_prefix_' }); expect(generatedFunc).toHaveProperty('constructor'); expect(generatedFunc(true)).toBe(3); }); it('should inject stateful information into the function execution context on this.props', () => { const func = { variable_name: 'testFunction', function_body: 'return 3+this.props.Luxon.someVal', }; const props = { Luxon: { someVal: 3 }, ModelXData: {} }; const generatedFunc = getGeneratedStatefulFunction({ ...func, props, function_name_prefix: 'custom_prefix_' }); expect(generatedFunc(true)).toBe(6); }); }); describe('sumPreviousRows', () => { it('should summarize data from previous rows', () => { const data = [ {label:'current', value:10}, {label:'prev 1', value:20}, {label:'prev 2', value:30}, {label:'prev 3', value:40}, {label:'prev 4', value:50}, {label:'prev 5', value:60}, {label:'prev 6', value:70}, {label:'prev 7', value:80}, {label:'prev 8', value:90}, {label:'prev 9', value:100}, ]; const property = 'value'; const prev2 = sumPreviousRows.call({ data, }, { property, offset: 1, rows: 2 }); expect(prev2).toBe(50); expect(sumPreviousRows.call({ data, }, { property, offset: 1, rows: 1 })).toBe(20); expect(sumPreviousRows.call({ data, }, { property, offset: 2, rows: 1 })).toBe(30); expect(sumPreviousRows.call({ data, }, { property, offset: 1, rows: 3 })).toBe(90); expect(() => { sumPreviousRows.call({ data, }, { property, offset: 0, rows: 3 }) }).toThrow('Offset must be larger'); }); }); }); describe('ModelX', () => { // beforeAll(async function () { // return true; // },5000); describe('constructor', () => { it('should export a named module class', () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, prediction_timeseries_date_format:'ff', }); expect(m1).toBeInstanceOf(ModelX); expect(m1).toHaveProperty('constructor'); }); }); describe('getTimeseriesDimension', () => { it('should return dimension and format if set correctly', () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, prediction_timeseries_date_format:'ff', }); const m2 = new ModelX({ model_type: ModelTypes.REGRESSION, dimension: Dimensions.MONTHLY, prediction_timeseries_date_format:'ff', }); // console.log({m1}) expect(m1.getTimeseriesDimension({dimension:Dimensions.YEARLY})).toMatchObject({ dimension: Dimensions.YEARLY, dateFormat: 'ff', }); expect(m2.getTimeseriesDimension({})).toMatchObject({ dimension: Dimensions.MONTHLY, dateFormat: 'ff', }); }); it('should return error without a date format ', () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, }); const m2 = new ModelX({ model_type: ModelTypes.REGRESSION, dimension: Dimensions.MONTHLY, }); // console.log({m1}) expect(() => { m1.getTimeseriesDimension({ }) }).toThrow(/Invalid timeseries dimension/); expect(() => { m2.getTimeseriesDimension({}); }).toThrow(/Invalid timeseries date format/); }); it('should return dimension and format from dataset', () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, prediction_timeseries_date_format:'ff', }); // console.log({ m1 }) const DataSetData = [{ dimension: 'monthly', }]; expect(m1.getTimeseriesDimension({ DataSetData, })).toMatchObject({ dimension: Dimensions.MONTHLY, dateFormat: 'ff', }); }); it('should calculate and return dimension and format from dataset', () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, prediction_timeseries_date_format:'iso', }); const DataSetData = [ { date:'2020-02-01', }, { date:'2020-03-01', }, { date:'2020-04-01', }, ]; expect(m1.getTimeseriesDimension({ DataSetData, })).toMatchObject({ dimension: Dimensions.MONTHLY, dateFormat: 'iso', }); const m2 = new ModelX({ model_type: ModelTypes.REGRESSION, }); const DataSetData2 = [ { date: new Date('2020-02-01'), }, { date: new Date('2020-02-02'), }, { date: new Date('2020-02-03'), }, ]; expect(m2.getTimeseriesDimension({ DataSetData:DataSetData2, })).toMatchObject({ dimension: Dimensions.DAILY, dateFormat: 'js', }); }); }); describe('getForecastDates', () => { it('should return range of dates', () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, prediction_timeseries_start_date: new Date('2020-04-01'), prediction_timeseries_end_date: new Date('2020-04-03'), dimension: Dimensions.DAILY, }); // console.log({m1}) const dates = m1.getForecastDates({ dimension: Dimensions.YEARLY }); expect(dates.length).toBe(3); }); it('should throw error with missing dimension', () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, prediction_timeseries_start_date: new Date('2020-04-01'), prediction_timeseries_end_date: new Date('2020-04-03'), }); expect(() => { m1.getForecastDates({}); }).toThrow('Forecasts require a timeseries dimension'); }); it('should throw error with missing dimension', () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, dimension: Dimensions.DAILY, }); expect(() => { m1.getForecastDates({}); }).toThrow('Start and End Forecast Dates are required'); }); }); describe('addMockData', () => { it('should add mock data', () => { const data = getData(2); const mockEncodedData = getData(1); const DS = new DataSet(data); const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, DataSet: DS, mockEncodedData, }); // console.log('BEFORE m1.DataSet.data', m1.DataSet.data); m1.addMockData(); // console.log('AFTER m1.DataSet.data', m1.DataSet.data); expect(m1.DataSet.data.length).toBe(data.length+mockEncodedData.length); }); it('should add mock data and mock dates', () => { const data = getData(2); const mockEncodedData = getData(1); const DS = new DataSet(data); const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, DataSet: DS, mockEncodedData, }); m1.addMockData({use_mock_dates:true}); expect(m1.DataSet.data.length).toBe(data.length+mockEncodedData.length+mockDates.length); }); it('should add mock dates', () => { const data = getData(2); const DS = new DataSet(data); const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, DataSet: DS, }); m1.addMockData({use_mock_dates:true}); expect(m1.DataSet.data.length).toBe(data.length+mockDates.length); }); }); describe('removeMockData', () => { it('should remove mock data', () => { const data = getData(2); const mockEncodedData = getData(1); const DS = new DataSet(data.concat(mockEncodedData)); const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, DataSet: DS, mockEncodedData, }); // console.log('BEFORE m1.DataSet.data', m1.DataSet.data); m1.removeMockData(); // console.log('AFTER m1.DataSet.data', m1.DataSet.data); expect(m1.DataSet.data.length).toBe(data.length); }); it('should remove mock data and mock dates', () => { const data = getData(2); const mockEncodedData = getData(1); const DS = new DataSet(data.concat(mockEncodedData, mockDates)); const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, DataSet: DS, mockEncodedData, }); m1.removeMockData({use_mock_dates:true}); expect(m1.DataSet.data.length).toBe(data.length); }); it('should remove mock dates', () => { const data = getData(2); const DS = new DataSet(data.concat(mockDates)); const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, DataSet: DS, }); m1.removeMockData({use_mock_dates:true}); expect(m1.DataSet.data.length).toBe(data.length); }); }); describe('getCrosstrainingData', () => { it('should return test and train data', () => { const data = getData(10); const DS = new DataSet(data); const train_size = 0.6; const dataTrainSize = Math.round(data.length * train_size); const dataTestSize = Math.round(data.length * (1-train_size)); const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, DataSet: DS, cross_validation_options: { train_size, } }); // console.log({ m1 }) const { test, train } = m1.getCrosstrainingData(); // console.log({ test, train }); expect(dataTrainSize).toBe(train.length); expect(dataTestSize).toBe(test.length); // m1.removeMockData({use_mock_dates:true}); // expect(m1.DataSet.data.length).toBe(data.length); }); it('should return test and train data sorted for timeseries. The split data should split original data in sorted order', () => { const data = getData(10).sort((a, b) => a.date.valueOf() - b.date.valueOf()); // console.log({ data }); const DS = new DataSet(data); const train_size = 0.8; const dataTrainSize = Math.round(data.length * train_size); const dataTestSize = Math.round(data.length * (1-train_size)); const m1 = new ModelX({ model_type: ModelTypes.TIMESERIES_REGRESSION_FORECAST, DataSet: DS, cross_validation_options: { train_size, } }); // console.log({ m1 }) const { test, train } = m1.getCrosstrainingData(); // console.log({ test, }); expect(dataTrainSize).toBe(train.length); expect(dataTestSize).toBe(test.length); expect(test[0].date).toBe(data[dataTrainSize].date); // m1.removeMockData({use_mock_dates:true}); // expect(m1.DataSet.data.length).toBe(data.length); }); }); describe('validateTrainingData', () => { it('should not throw error if input matrix is all numerical', () => { const inputMatrix = [ [1, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 1], ]; const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, x_indep_matrix_train: inputMatrix, }); expect(m1.validateTrainingData()).toBeTruthy; expect(m1.validateTrainingData({ inputMatrix, })).toBeTruthy; }); it('should throw errors if input matrix is not all numerical', () => { const inputMatrixUndef = [ [1, 0, 1, undefined], [0, 0, 0, 1], [1, 0, 0, 1], ]; const inputMatrixNaN = [ [1, 0, 1, NaN], [0, 0, 0, 1], [1, 0, 0, 1], ]; const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, }); expect(() => { m1.validateTrainingData({ inputMatrix: inputMatrixUndef, }) }).toThrow(); expect(() => { m1.validateTrainingData({ inputMatrix: inputMatrixNaN, }) }).toThrow(); }); }); describe('async getPredictionData', () => { const predictionMatrix = [ [1, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 1], ]; it('should get predictions from a promise', async () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, }); const getPredictionInputPromise = async function () { return predictionMatrix; } const predictionInputs = await m1.getPredictionData({ getPredictionInputPromise }); expect(predictionInputs).toBe(predictionMatrix); }); it('should return prediction inputs if not passed a custom function', async () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, prediction_inputs: predictionMatrix, }); const predictionInputs = await m1.getPredictionData(); expect(predictionInputs).toBe(predictionMatrix); }); }); describe('async validateTimeseriesData', () => { const timeseriesData = [ getDatum(new Date('2020-04-04T00:00:00.000Z')), getDatum(new Date('2020-04-05T00:00:00.000Z')), getDatum(new Date('2020-04-06T00:00:00.000Z')), getDatum(new Date('2020-04-07T00:00:00.000Z')), getDatum(new Date('2020-04-08T00:00:00.000Z')), getDatum(new Date('2020-04-09T00:00:00.000Z')), getDatum(new Date('2020-04-10T00:00:00.000Z')), getDatum(new Date('2020-04-11T00:00:00.000Z')), ].sort(timeseriesSort); // console.log({ timeseriesData }); it('should validate timeseries data that input predictions start within the timeseries range', async () => { const prediction_inputs = [ getDatum(new Date('2020-04-11T00:00:00.000Z')), getDatum(new Date('2020-04-12T00:00:00.000Z')), // getDatum(new Date('2020-04-13T00:00:00.000Z')), ].sort(timeseriesSort); const m1 = new ModelX({ model_type: ModelTypes.TIMESERIES_REGRESSION_FORECAST, DataSet: new DataSet([].concat(timeseriesData)), prediction_inputs, prediction_timeseries_start_date: prediction_inputs[0], }); m1.forecastDates = timeseriesData.map(t => t.date); const validatedTimeseriesData = await m1.validateTimeseriesData(); expect(validatedTimeseriesData).toBeTruthy; }); it('should throw an error if prediction inputs are not inclusive of forecastDates', async () => { const prediction_inputs = [ getDatum(new Date('2020-04-12T00:00:00.000Z')), getDatum(new Date('2020-04-13T00:00:00.000Z')), ].sort(timeseriesSort); const m1 = new ModelX({ model_type: ModelTypes.TIMESERIES_REGRESSION_FORECAST, DataSet: new DataSet([].concat(timeseriesData)), prediction_inputs, prediction_timeseries_start_date: prediction_inputs[0], }); m1.forecastDates = timeseriesData.map(t => t.date); expect(m1.validateTimeseriesData()).rejects.toThrow(/must be inclusive of forecastDates/); }); it('should fix prediction inputs to only predict inputs inclusive of forecastDates', async () => { const prediction_inputs = [ getDatum(new Date('2020-04-02T00:00:00.000Z')), getDatum(new Date('2020-04-03T00:00:00.000Z')), getDatum(new Date('2020-04-04T00:00:00.000Z')), getDatum(new Date('2020-04-05T00:00:00.000Z')), ].sort(timeseriesSort); const m1 = new ModelX({ model_type: ModelTypes.TIMESERIES_REGRESSION_FORECAST, DataSet: new DataSet([].concat(timeseriesData)), prediction_inputs, prediction_timeseries_start_date: prediction_inputs[0], }); m1.forecastDates = timeseriesData.map(t => t.date); const validatedTimeseriesData = await m1.validateTimeseriesData(); expect(validatedTimeseriesData.raw_prediction_inputs.length).toBe(2); const prediction_inputs2 = [ getDatum(new Date('2020-04-04T00:00:00.000Z')), getDatum(new Date('2020-04-05T00:00:00.000Z')), ].sort(timeseriesSort); const m2 = new ModelX({ model_type: ModelTypes.TIMESERIES_REGRESSION_FORECAST, DataSet: new DataSet([].concat(timeseriesData)), prediction_inputs: prediction_inputs2, prediction_timeseries_start_date: prediction_inputs2[0], }); m2.forecastDates = timeseriesData.map(t => t.date); const validatedTimeseriesData2 = await m2.validateTimeseriesData({ fixPredictionDates: false, }); expect(validatedTimeseriesData2.raw_prediction_inputs.length).toBe(2); const prediction_inputs3 = [ getDatum(new Date('2020-04-04T00:00:00.000Z')), getDatum(new Date('2020-04-05T00:00:00.000Z')), ].sort(timeseriesSort); const m3 = new ModelX({ model_type: ModelTypes.TIMESERIES_REGRESSION_FORECAST, DataSet: new DataSet([].concat(timeseriesData)), prediction_inputs: prediction_inputs3, }); m3.forecastDates = timeseriesData.map(t => t.date); const validatedTimeseriesData3 = await m2.validateTimeseriesData(); expect(validatedTimeseriesData3.raw_prediction_inputs.length).toBe(2); // console.log({ validatedTimeseriesData }); }); }); describe('async getDataSetProperties', () => { const timeseriesData = [ getDatum(new Date('2020-04-04T00:00:00.000Z')), getDatum(new Date('2020-04-05T00:00:00.000Z')), getDatum(new Date('2020-04-06T00:00:00.000Z')), getDatum(new Date('2020-04-07T00:00:00.000Z')), getDatum(new Date('2020-04-08T00:00:00.000Z')), getDatum(new Date('2020-04-09T00:00:00.000Z')), getDatum(new Date('2020-04-10T00:00:00.000Z')), getDatum(new Date('2020-04-11T00:00:00.000Z')), ].sort(timeseriesSort); it('should convert prediction next value functions ', async () => { const prediction_inputs_next_value_functions = [ { variable_name: 'type', function_body: 'return "deposit"', }, { variable_name: 'gen_type', function_body: 'return "deposit generated"', }, ]; const training_data_filter_function_body = 'return true;'; const m1 = new ModelX({ model_type: ModelTypes.TIMESERIES_REGRESSION_FORECAST, DataSet: new DataSet([].concat(timeseriesData)), prediction_inputs_next_value_functions, training_data_filter_function_body, // prediction_inputs, // prediction_timeseries_start_date: prediction_inputs[0], }); await m1.getDataSetProperties(); expect(m1.dimension).toBe(Dimensions.DAILY); expect(m1.training_data_filter_function).toHaveProperty('constructor'); expect(m1.prediction_inputs_next_value_function).toHaveProperty('constructor'); }); it('should create Forecast Dates ', async () => { const prediction_inputs = [ getDatum(new Date('2020-04-12T00:00:00.000Z')), getDatum(new Date('2020-04-13T00:00:00.000Z')), ].sort(timeseriesSort); const m1 = new ModelX({ model_type: ModelTypes.TIMESERIES_REGRESSION_FORECAST, DataSet: new DataSet([].concat(timeseriesData)), prediction_inputs, prediction_timeseries_start_date: prediction_inputs[0].date, prediction_timeseries_end_date: prediction_inputs[1].date, }); await m1.getDataSetProperties(); expect(m1.forecastDates.length).toBe(2); }); it('should create start and end prediction forecast Dates ', async () => { const prediction_inputs = [ getDatum(new Date('2020-04-12T00:00:00.000Z')), getDatum(new Date('2020-04-13T00:00:00.000Z')), ].sort(timeseriesSort); const m1 = new ModelX({ model_type: ModelTypes.TIMESERIES_REGRESSION_FORECAST, DataSet: new DataSet([].concat(timeseriesData)), prediction_inputs, }); await m1.getDataSetProperties(); expect(m1.prediction_timeseries_start_date).toBeInstanceOf(Date); expect(m1.prediction_timeseries_end_date).toBeInstanceOf(Date); // expect(m1.training_data_filter_function).toHaveProperty('constructor'); // expect(m1.prediction_inputs_next_value_function).toHaveProperty('constructor'); // m1.forecastDates = timeseriesData.map(t => t.date); // console.log({ m1 }); }); }); describe('async trainModel', () => { const timeseriesData = [ getDatum(new Date('2020-04-04T00:00:00.000Z'),{amount:407}), getDatum(new Date('2020-04-05T00:00:00.000Z'),{amount:408}), getDatum(new Date('2020-04-06T00:00:00.000Z'),{amount:409}), getDatum(new Date('2020-04-07T00:00:00.000Z'),{amount:410}), getDatum(new Date('2020-04-08T00:00:00.000Z'),{amount:411}), getDatum(new Date('2020-04-09T00:00:00.000Z'),{amount:412}), getDatum(new Date('2020-04-10T00:00:00.000Z'),{amount:413}), getDatum(new Date('2020-04-11T00:00:00.000Z'),{amount:414}), getDatum(new Date('2020-04-12T00:00:00.000Z'),{amount:415}), getDatum(new Date('2020-04-13T00:00:00.000Z'),{amount:416}), getDatum(new Date('2020-04-14T00:00:00.000Z'),{amount:417}), getDatum(new Date('2020-04-15T00:00:00.000Z'),{amount:418}), getDatum(new Date('2020-04-16T00:00:00.000Z'),{amount:419}), getDatum(new Date('2020-04-17T00:00:00.000Z'),{amount:420}), getDatum(new Date('2020-04-18T00:00:00.000Z'),{amount:421}), getDatum(new Date('2020-04-19T00:00:00.000Z'),{amount:422}), getDatum(new Date('2020-04-20T00:00:00.000Z'),{amount:423}), getDatum(new Date('2020-04-21T00:00:00.000Z'),{amount:424}), getDatum(new Date('2020-04-22T00:00:00.000Z'),{amount:425}), getDatum(new Date('2020-04-23T00:00:00.000Z'),{amount:426}), ].sort(timeseriesSort); const data = [ { input_1: 1, input_2: 10, input_3: 100, ignored_1: 1, output_1: 1000, output_2: 10000, }, { input_1: 2, input_2: 20, input_3: 200, ignored_1: 2, output_1: 2000, output_2: 20000, }, { input_1: 3, input_2: 30, input_3: 300, ignored_1: 3, output_1: 3000, output_2: 30000, }, { input_1: 4, input_2: 40, input_3: 400, ignored_1: 4, output_1: 4000, output_2: 40000, }, { input_1: 5, input_2: 50, input_3: 500, ignored_1: 5, output_1: 5000, output_2: 50000, }, { input_1: 6, input_2: 60, input_3: 600, ignored_1: 6, output_1: 6000, output_2: 60000, }, { input_1: 7, input_2: 70, input_3: 700, ignored_1: 7, output_1: 7000, output_2: 70000, }, { input_1: 8, input_2: 80, input_3: 800, ignored_1: 8, output_1: 8000, output_2: 80000, }, { input_1: 9, input_2: 90, input_3: 900, ignored_1: 9, output_1: 9000, output_2: 90000, }, ]; const independentVariables = [ 'input_1', 'input_2', 'input_3', ]; const dependentVariables = [ 'output_1', 'output_2', ]; const featureColumns = [].concat(independentVariables, dependentVariables); const training_feature_column_options = featureColumns .reduce((result, val) => { result[ val ] = ['scale', 'standard', ]; return result; }, {}); it('should throw an error if missing inputs and outputs', async () => { const m1 = new ModelX({ model_type: ModelTypes.REGRESSION, trainingData: data, }); const m2 = new ModelX({ model_type: ModelTypes.REGRESSION, trainingData: data, preprocessing_feature_column_options: { ignored_1:['scale', 'standard', ], }, x_independent_features: ['input_1'], }); expect(m1.trainModel()).rejects.toThrow(/Missing Inputs/); expect(m2.trainModel()).rejects.toThrow(/Missing Outputs/); }); it('should train a model', async () => { const m1 = new ModelX({ debug:false, model_type: ModelTypes.REGRESSION, trainingData: data, training_data_filter_function: function (datum, dataIndex) { if (datum.input_1 === 5) return false; return true; }, training_feature_column_options, x_independent_features: independentVariables, y_dependent_labels: dependentVariables, }); await m1.trainModel(); // console.log({ m1 }); expect(m1.Model.trained).toBe(true); expect(m1.Model.compiled).toBe(true); }, 120000); it('should train a forecast model', async () => { const independent_variables = [ 'year', 'month', 'day', 'late_payments', ]; const dependent_variables = [ 'amount', ]; const training_feature_column_options = { // amount: ['scale', 'standard',], year: ['onehot',], month: ['onehot',], day: ['onehot',], }; const m1 = new ModelX({ debug: false, use_mock_dates_to_fit_trainning_data: true, model_type: ModelTypes.TIMESERIES_REGRESSION_FORECAST, trainingData: timeseriesData, training_feature_column_options, independent_variables, dependent_variables, // x_independent_features: independentVariables, // y_dependent_labels: dependentVariables, }); await m1.trainModel(); // console.log({ m1 }); expect(m1.Model.trained).toBe(true); expect(m1.Model.compiled).toBe(true); expect(m1.x_raw_independent_features).toMatchObject(['year', 'month', 'day', 'late_payments']); expect(m1.y_raw_dependent_labels).toMatchObject(['amount']); expect(m1.preprocessing_feature_column_options).toMatchObject({ amount: ['median'] }); expect(m1.training_feature_column_options).toMatchObject({ year: ['onehot'], month: ['onehot'], day: ['onehot'], late_payments: ['label', { binary: true }], amount: ['scale', 'standard'] }); expect(m1.y_dependent_labels).toMatchObject(['amount']); // expect(m1.x_independent_features).toMatchObject(['year_2020', 'month_4', 'month_1', 'month_2', 'month_3', 'month_5', 'month_6', 'month_7', 'month_8', 'month_9', 'month_10', 'month_11', 'month_12', 'day_3', 'day_4', 'day_5', 'day_6', 'day_7', 'day_8', 'day_9', 'day_10', 'day_11', 'day_12', 'day_13', 'day_14', 'day_15', 'day_16', 'day_17', 'day_18', 'day_19', 'day_20', 'day_21', 'day_22', 'day_1', 'day_2', 'day_23', 'day_24', 'day_25', 'day_26', 'day_27', 'day_28', 'day_29', 'day_30', 'day_31', 'late_payments']); }, 120000); }); describe('async getTrainingData', () => { it('should do nothing if no training data is passed', async () => { const m1 = new ModelX({ debug:false, model_type: ModelTypes.REGRESSION, }); expect(m1.trainingData.length).toBe(0); await m1.getTrainingData(); expect(m1.trainingData.length).toBe(0); }); it('should accept traningData via options', async () => { const {data, }=getMockRegression(); const m1 = new ModelX({ debug:false, model_type: ModelTypes.REGRESSION, }); expect(m1.trainingData.length).toBe(0); await m1.getTrainingData({trainingData:data}); expect(m1.trainingData.length).toBe(data.length); }); it('should get trainingData via a getDataPromise function', async () => { const {data, }=getMockRegression(); const m1 = new ModelX({ debug:false, model_type: ModelTypes.REGRESSION, }); expect(m1.trainingData.length).toBe(0); async function getDataPromise() { return data; } await m1.getTrainingData({getDataPromise,}); expect(m1.trainingData.length).toBe(data.length); }); }); describe('async checkTrainingStatus', () => { it('should return true if model is already trained', async () => { const m1 = new ModelX({ debug:false, model_type: ModelTypes.REGRESSION, }); m1.status.trained = true; m1.getTrainingData = jest.fn(); m1.trainModel = jest.fn(); const trainingStatus = await m1.checkTrainingStatus(); expect(trainingStatus).toBe(true); expect(m1.getTrainingData).toBeCalledTimes(0); expect(m1.trainModel).toBeCalledTimes(0); }); it('should retrain on demand if model is not trained', async () => { const m1 = new ModelX({ debug:false, model_type: ModelTypes.REGRESSION, }); // m1.status.trained = true; m1.getTrainingData = jest.fn(); m1.trainModel = jest.fn(); const trainingStatus = await m1.checkTrainingStatus(); expect(trainingStatus).toBe(true); expect(m1.getTrainingData).toBeCalledTimes(1); expect(m1.trainModel).toBeCalledTimes(1); m1.status.trained = true; const trainingStatus2 = await m1.checkTrainingStatus(); expect(trainingStatus2).toBe(true); expect(m1.getTrainingData).toBeCalledTimes(1); expect(m1.trainModel).toBeCalledTimes(1); const trainingStatus3 = await m1.checkTrainingStatus({ retrain: true }); expect(trainingStatus3).toBe(true); expect(m1.getTrainingData).toBeCalledTimes(2); expect(m1.trainModel).toBeCalledTimes(2); }); }); });