diff --git a/src/controller/api/predictionController.ts b/src/controller/api/predictionController.ts index 46e36e3..12e2ec6 100644 --- a/src/controller/api/predictionController.ts +++ b/src/controller/api/predictionController.ts @@ -1,12 +1,12 @@ import { NextFunction, Request, Response } from "express"; import { TAPIResponse } from "../../types/core/http"; import createHttpError from "http-errors"; -import { selectAllProductNPrediction } from "../../repository/predictionRepository"; +import { selectAllProductNPrediction, selectDetailPrediction } from "../../repository/predictionRepository"; import { body, matchedData, param } from "express-validator"; import { makeAutoPrediction } from "../../services/predictionServices"; import expressValidatorErrorHandler from "../../middleware/expressValidatorErrorHandler"; import { IPredictionTable } from "../../types/db-model"; -import { preparePredictionData } from "../../services/prediction/prepareData"; +import { getPreparedCSVString, preparePredictionData } from "../../services/prediction/prepareData"; import { getPredictionModel } from "../../services/prediction/getPredictionModel"; import { shouldUseAutoModel } from "../../services/prediction/shouldUseAutoModel"; import { buildAutoPrediction } from "../../services/prediction/buildAutoPrediction"; @@ -14,6 +14,8 @@ import { savePredictionModel } from "../../services/prediction/savePredictionMod import { buildManualPrediction } from "../../services/prediction/buildManualPrediction"; import { persistPrediction } from "../../services/prediction/persistPrediction"; import { isErrorInstanceOfHttpError } from "../../utils/core/httpError"; +import { sendResponseError } from "../../utils/api/responseError"; +import { getTotalDaysInNextMonth } from "../../utils/core/date"; const periodArray = ['daily', 'weekly', 'monthly'] @@ -65,6 +67,7 @@ const filePrediction = [ value_column, prediction_period, date_regroup: record_period !== prediction_period, + future_step: 1 }) if (!pythonResponse?.mape || pythonResponse.mape > 50) { @@ -86,7 +89,7 @@ const filePrediction = [ message: 'Gagal melakukan prediksi. Silakan coba lagi atau cek data input.', }); } catch (error) { - next(createHttpError(500, "Internal Server Error", { cause: error })); + next(sendResponseError(error)) } } ] @@ -106,8 +109,7 @@ const getSavedPurchasePredictions = [ } res.json(result) } catch (error) { - console.log(error) - next(createHttpError(500, "Internal Server Error", { error })); + next(sendResponseError(error)); } } ] @@ -127,8 +129,7 @@ const getSavedSalePredictions = [ } res.json(result) } catch (error) { - console.log(error) - next(createHttpError(500, "Internal Server Error", { error })); + next(sendResponseError(error)); } } ] @@ -153,7 +154,7 @@ const purchasePrediction = [ let predictionResult if (shouldUseAutoModel(model, isExpired)) { predictionResult = await buildAutoPrediction(csv_string, prediction_period) - await savePredictionModel(model, predictionResult, product_id, prediction_period, source, req.user!.id) + await savePredictionModel(model, predictionResult, product_id, prediction_period, source, req.user!.id, 3) } else { const arimaModel: [number, number, number] = [model!.ar_p, model!.differencing_d, model!.ma_q] predictionResult = await buildManualPrediction(csv_string, prediction_period, arimaModel) @@ -163,9 +164,7 @@ const purchasePrediction = [ res.status(200).json({ success: true, data: finalResult }) } catch (err) { - if (isErrorInstanceOfHttpError(err)) - return next(err) - return next(createHttpError(500, 'Internal Server Error', { cause: err })) + next(sendResponseError(err)) } } ] @@ -190,7 +189,7 @@ const salesPrediction = [ let predictionResult if (shouldUseAutoModel(model, isExpired)) { predictionResult = await buildAutoPrediction(csv_string, prediction_period) - await savePredictionModel(model, predictionResult, product_id, prediction_period, source, req.user!.id) + await savePredictionModel(model, predictionResult, product_id, prediction_period, source, req.user!.id, 3) } else { const arimaModel: [number, number, number] = [model!.ar_p, model!.differencing_d, model!.ma_q] predictionResult = await buildManualPrediction(csv_string, prediction_period, arimaModel) @@ -200,9 +199,101 @@ const salesPrediction = [ res.status(200).json({ success: true, data: finalResult }) } catch (err) { - if (isErrorInstanceOfHttpError(err)) - return next(err) - return next(createHttpError(500, 'Internal Server Error', { cause: err })) + next(sendResponseError(err)) + } + } +] + +const smartPrediction = [ + param('product_id').exists().withMessage('product_id harus disediakan.') + .isInt({ gt: 0 }).withMessage('product_id harus berupa angka bulat positif.') + .toInt(), + + body('prediction_period') + .notEmpty().withMessage('prediction_period tidak boleh kosong.') + .isIn(['weekly', 'monthly']).withMessage('prediction_period harus bernilai "weekly" atau "monthly".'), + + body('prediction_source') + .notEmpty().withMessage('prediction_source tidak boleh kosong.') + .isIn(['sales', 'purchases']).withMessage('prediction_source harus bernilai "sales" atau "purchases".'), + + expressValidatorErrorHandler, + async (req: Request, res: Response, next: NextFunction) => { + try { + const { product_id, prediction_period, prediction_source } = matchedData(req) + const prediction_period_days = prediction_period === 'weekly' ? 7 : getTotalDaysInNextMonth(new Date()) + + const { + csv_string, data_freq + } = await getPreparedCSVString(product_id, prediction_period, prediction_source) + + const { model, isExpired } = await getPredictionModel( + product_id, + prediction_period, + prediction_source + ) + + let predictionResult + if (shouldUseAutoModel(model, isExpired)) { + predictionResult = await buildAutoPrediction( + csv_string, + prediction_period, + data_freq === 'daily' ? prediction_period_days : 1 + ) + await savePredictionModel( + model, + predictionResult, + product_id, + prediction_period, + prediction_source, + req.user!.id, + data_freq === 'daily' ? 1 : 3 + ) + } else { + const arimaModel: [number, number, number] = [model!.ar_p, model!.differencing_d, model!.ma_q] + predictionResult = await buildManualPrediction( + csv_string, + prediction_period, + arimaModel, + data_freq === 'daily' ? prediction_period_days : 1 + ) + } + + const finalResult = await persistPrediction( + predictionResult, + prediction_period, + prediction_source, + product_id, + req.user!.id + ) + + res.status(200).json({ success: true, data: finalResult }) + } catch (error) { + next(sendResponseError(error)) + } + } +] + +const predictionDetail = [ + body('product_id').exists().withMessage('product_id harus disediakan.') + .isInt({ gt: 0 }).withMessage('product_id harus berupa angka bulat positif.') + .toInt(), + body('prediction_period') + .notEmpty().withMessage('prediction_period tidak boleh kosong.') + .isIn(['weekly', 'monthly']).withMessage('prediction_period harus bernilai "weekly" atau "monthly".'), + body('source_type') + .notEmpty().withMessage('source_type tidak boleh kosong.') + .isIn(['sales', 'purchases']).withMessage('source_type harus bernilai "sales" atau "purchases".'), + expressValidatorErrorHandler, + async (req: Request, res: Response, next: NextFunction) => { + try { + const { product_id, prediction_period, source_type } = matchedData(req) + const detailPrediction = await selectDetailPrediction( + product_id, prediction_period, source_type + ) + res.status(200).json({ success: true, data: detailPrediction }) + } catch (err) { + next(sendResponseError(err)) } } ] @@ -212,5 +303,7 @@ export default { getSavedPurchasePredictions, getSavedSalePredictions, salesPrediction, - purchasePrediction + purchasePrediction, + predictionDetail, + smartPrediction } diff --git a/src/repository/analytics/latest-prediction.ts b/src/repository/analytics/latest-prediction.ts index 0cca54a..ecea30e 100644 --- a/src/repository/analytics/latest-prediction.ts +++ b/src/repository/analytics/latest-prediction.ts @@ -11,6 +11,7 @@ export function selectLatestPredictions( .join('product_categories as pc', 'pr.product_category_id', 'pc.id') .select( 'pr.product_name', + 'pr.stock', 'p.mape', 'p.prediction', 'pc.category_name', diff --git a/src/repository/predictionRepository.ts b/src/repository/predictionRepository.ts index 6c3bca0..79fdb77 100644 --- a/src/repository/predictionRepository.ts +++ b/src/repository/predictionRepository.ts @@ -1,5 +1,5 @@ import { db } from "../database/MySQL"; -import { IPredictionTable, IProductTable, ITransactionTable } from "../types/db-model"; +import { IPredictionModelTable, IPredictionTable, IProductTable, ITransactionTable } from "../types/db-model"; import { TGroupedProductSales } from "../types/db/product-sales"; import { selectProductsByUserId } from "./productsRepository"; @@ -86,4 +86,31 @@ export async function insertPrediction(data: Partial) { export function updatePrediction(id: IPredictionTable['id'], data: Partial) { return db('predictions').where({ id }).update(data) +} + +export function selectDetailPrediction( + product_id: IPredictionTable['product_id'], + period_type: IPredictionTable['period_type'], + prediction_source: IPredictionTable['prediction_source'], +) { + return db('products') + .leftJoin('predictions', function () { + this.on('predictions.product_id', '=', 'products.id') + .andOn(db.raw('predictions.period_type = ?', [period_type])) + .andOn(db.raw('predictions.prediction_source = ?', [prediction_source])) + .andOn(db.raw('predictions.expired >= ?', [new Date().toISOString()])); + }) + .where('products.id', product_id) + .select( + 'products.product_code', + 'products.product_name', + 'products.stock', + 'products.selling_price', + 'products.buying_price', + 'predictions.prediction', + 'predictions.lower_bound', + 'predictions.upper_bound', + 'predictions.mape', + 'predictions.rmse' + ).first(); } \ No newline at end of file diff --git a/src/repository/restockRepository.ts b/src/repository/transaction/purchase.ts similarity index 74% rename from src/repository/restockRepository.ts rename to src/repository/transaction/purchase.ts index 4893b85..1acfaf6 100644 --- a/src/repository/restockRepository.ts +++ b/src/repository/transaction/purchase.ts @@ -1,6 +1,6 @@ -import { db } from "../database/MySQL"; -import { IPurchaseTable } from "../types/db-model"; -import { getStartOfMonth, getStartOfWeek } from "../utils/core/date"; +import { db } from "../../database/MySQL"; +import { IPurchaseTable } from "../../types/db-model"; +import { getStartOfMonth, getStartOfWeek } from "../../utils/core/date"; export function insertsDataToRestock(data: Partial[]) { return db('restocks').insert(data) @@ -49,6 +49,27 @@ export const selectLastMonthRestockCount = (user_id: number) => { }>(); } +export function selectGroupedDailyPurchase( + productId: IPurchaseTable['product_id'], + endOfPeriod: 'last-week' | 'last-month' +) { + let startOfDay + if (endOfPeriod === 'last-month') + startOfDay = getStartOfMonth(new Date()).toISOString() + else (endOfPeriod === 'last-week') + startOfDay = getStartOfWeek(new Date()).toISOString() + + return db('restocks') + .select( + db.raw('DATE(buying_date) AS date'), + db.raw('SUM(amount) AS total_amount') + ) + .where('product_id', productId) + .andWhere('buying_date', '<', startOfDay) + .groupByRaw('DATE(buying_date)') + .orderBy('date'); +} + export function selectGroupedWeeklyPurchase(productId: IPurchaseTable['product_id']) { return db('restocks') .select( diff --git a/src/repository/transactionRepository.ts b/src/repository/transaction/sales.ts similarity index 74% rename from src/repository/transactionRepository.ts rename to src/repository/transaction/sales.ts index 8d4eb15..f7717f0 100644 --- a/src/repository/transactionRepository.ts +++ b/src/repository/transaction/sales.ts @@ -1,6 +1,6 @@ -import { db } from "../database/MySQL"; -import { ITransactionTable } from "../types/db-model"; -import { getStartOfMonth, getStartOfWeek } from "../utils/core/date"; +import { db } from "../../database/MySQL"; +import { ITransactionTable } from "../../types/db-model"; +import { getStartOfMonth, getStartOfWeek } from "../../utils/core/date"; export function insertsDataToTransaction(data: Partial[]) { return db('transactions').insert(data) @@ -49,6 +49,27 @@ export const selectCurrentMonthSales = async (user_id: number) => { }>(); } +export function selectGroupedDailySales( + productId: ITransactionTable['product_id'], + endOfPeriod: 'last-week' | 'last-month' +) { + let startOfDay + if (endOfPeriod === 'last-month') + startOfDay = getStartOfMonth(new Date()).toISOString() + else (endOfPeriod === 'last-week') + startOfDay = getStartOfWeek(new Date()).toISOString() + + return db('transactions') + .select( + db.raw('DATE(transaction_date) AS date'), + db.raw('SUM(amount) AS total_amount') + ) + .where('product_id', productId) + .andWhere('transaction_date', '<', startOfDay) + .groupByRaw('DATE(transaction_date)') + .orderBy('date'); +} + export function selectGroupedWeeklySales(productId: ITransactionTable['product_id']) { return db('transactions') .select( @@ -73,6 +94,7 @@ export function selectGroupedMonthlySales(productId: ITransactionTable['product_ .where('product_id', productId) .andWhere('transaction_date', '<', getStartOfMonth(new Date()).toISOString()) + .groupBy(['year', 'month']) .orderBy(['year', 'month']) } \ No newline at end of file diff --git a/src/routes/api/auth.ts b/src/routes/api/auth.ts index c07f9bd..5721101 100644 --- a/src/routes/api/auth.ts +++ b/src/routes/api/auth.ts @@ -1,6 +1,7 @@ import express from 'express' var router = express.Router(); import authRoute from '../../controller/api/authController'; +import authenticate from '../../middleware/authMiddleware'; router.post('/register', authRoute.register); router.post('/login', authRoute.login); @@ -10,6 +11,6 @@ router.patch('/forgot-password/:token', authRoute.forgotPasswordChangePassword); router.get('/verify/:token', authRoute.verify); router.post('/re-send-email-activation/:token', authRoute.resendEmailVerification); router.get('/refresh-token', authRoute.refreshToken); -router.get('/logout', authRoute.logout); +router.get('/logout', authenticate, authRoute.logout); export default router \ No newline at end of file diff --git a/src/routes/api/prediction.ts b/src/routes/api/prediction.ts index b2a82dc..8ddaf55 100644 --- a/src/routes/api/prediction.ts +++ b/src/routes/api/prediction.ts @@ -8,4 +8,6 @@ router.get('/saved-predictions/purchases/:period_type', authenticate, prediction router.get('/saved-predictions/sales/:period_type', authenticate, predictionController.getSavedSalePredictions) router.post('/purchase-prediction/:product_id', authenticate, predictionController.purchasePrediction) router.post('/sales-prediction/:product_id', authenticate, predictionController.salesPrediction) +router.post('/smart-prediction/:product_id', authenticate, predictionController.smartPrediction) +router.post('/detail-prediction', authenticate, predictionController.predictionDetail) export default router \ No newline at end of file diff --git a/src/services/dashboardServices.ts b/src/services/dashboardServices.ts index deddc23..5cfd88c 100644 --- a/src/services/dashboardServices.ts +++ b/src/services/dashboardServices.ts @@ -1,6 +1,6 @@ import { countProducts, selectLowStockProductCount } from "../repository/productsRepository" -import { selectLastMonthRestockCount, selectMonthlyRestockCount } from "../repository/restockRepository" -import { selectLastMonthSales, selectCurrentMonthSales } from "../repository/transactionRepository" +import { selectLastMonthRestockCount, selectMonthlyRestockCount } from "../repository/transaction/purchase" +import { selectCurrentMonthSales, selectLastMonthSales } from "../repository/transaction/sales" import { calculateGrowth } from '../utils/math/calculateGrowth' export async function getDashboardData(user_id: number) { diff --git a/src/services/prediction/buildAutoPrediction.ts b/src/services/prediction/buildAutoPrediction.ts index 9039b32..dae47fd 100644 --- a/src/services/prediction/buildAutoPrediction.ts +++ b/src/services/prediction/buildAutoPrediction.ts @@ -4,12 +4,14 @@ import { IPredictionTable } from "../../types/db-model" export async function buildAutoPrediction( csv_string: string, - prediction_period: IPredictionTable['period_type'] + prediction_period: IPredictionTable['period_type'], + future_step: number = 1 ) { const result = await makePrivateAutoPrediction({ csv_string, prediction_period, value_column: 'amount', + future_step }) if (!result) throw createHttpError(422, 'Prediksi gagal: model tidak mengembalikan hasil.') diff --git a/src/services/prediction/buildManualPrediction.ts b/src/services/prediction/buildManualPrediction.ts index 4cc1367..a3e98d9 100644 --- a/src/services/prediction/buildManualPrediction.ts +++ b/src/services/prediction/buildManualPrediction.ts @@ -5,13 +5,15 @@ import { IPredictionTable } from "../../types/db-model" export async function buildManualPrediction( csv_string: string, prediction_period: IPredictionTable['period_type'], - model: [number, number, number] + model: [number, number, number], + future_step: number = 1 ) { const result = await makePrivateManualPrediction({ csv_string, prediction_period, value_column: 'amount', - arima_model: model + arima_model: model, + future_step }) if (!result) throw createHttpError(422, 'Prediksi gagal: model tidak mengembalikan hasil.') diff --git a/src/services/prediction/persistPrediction.ts b/src/services/prediction/persistPrediction.ts index 80a75a9..abb407e 100644 --- a/src/services/prediction/persistPrediction.ts +++ b/src/services/prediction/persistPrediction.ts @@ -17,9 +17,9 @@ export async function persistPrediction( const record = { ...previous, - lower_bound: prediction_result.lower.map(v => Math.round(v))[0], - prediction: prediction_result.prediction.map(v => Math.round(v))[0], - upper_bound: prediction_result.upper.map(v => Math.round(v))[0], + lower_bound: prediction_result.lower.map(v => Math.round(v), 0), + prediction: prediction_result.prediction.map(v => Math.round(v)), + upper_bound: prediction_result.upper.map(v => Math.round(v)), period_type: prediction_period, prediction_source: source, product_id, diff --git a/src/services/prediction/prepareData.ts b/src/services/prediction/prepareData.ts index 07a919e..6a4f0c9 100644 --- a/src/services/prediction/prepareData.ts +++ b/src/services/prediction/prepareData.ts @@ -1,18 +1,27 @@ import createHttpError from 'http-errors' import papaparse from 'papaparse' -import { selectGroupedMonthlyPurchase, selectGroupedWeeklyPurchase } from '../../repository/restockRepository' import { selectDummy } from '../../repository/dummyRepository' +import { selectGroupedDailyPurchase, selectGroupedMonthlyPurchase, selectGroupedWeeklyPurchase } from '../../repository/transaction/purchase' +import { selectGroupedDailySales, selectGroupedMonthlySales, selectGroupedWeeklySales } from '../../repository/transaction/sales' export async function preparePredictionData(product_id: number, prediction_period: 'weekly' | 'monthly', source: 'purchases' | 'sales') { let groupedData if (prediction_period === 'weekly') { - groupedData = await selectGroupedWeeklyPurchase(product_id) - } else { - groupedData = await selectGroupedMonthlyPurchase(product_id) + if (source === 'purchases') { + groupedData = await selectGroupedWeeklyPurchase(product_id) + } else if (source === 'sales') { + groupedData = await selectGroupedWeeklySales(product_id) + } + } else if (prediction_period === 'monthly') { + if (source === 'purchases') { + groupedData = await selectGroupedMonthlyPurchase(product_id) + } else if (source === 'sales') { + groupedData = await selectGroupedMonthlySales(product_id) + } } if (!groupedData || groupedData.length === 0) { - throw createHttpError(404, `Minimal lakukan 1 ${source === 'purchases' ? 'pembelian' : 'penjualan'} product dan lakukan prediksi di bulan berikutnya`) + throw createHttpError(404, `Minimal lakukan 1 ${source === 'purchases' ? 'pembelian' : 'penjualan'} product dan lakukan prediksi di ${prediction_period === 'monthly' ? 'bulan' : 'minggu'} berikutnya`) } let data = groupedData.map(v => ({ amount: v.amount })) @@ -34,3 +43,58 @@ export async function preparePredictionData(product_id: number, prediction_perio const csv_string = papaparse.unparse(data) return csv_string } + +type PreparedDataReturnValue = { + data_freq: 'daily' | 'weekly' | 'monthly' + csv_string: string +} +export async function getPreparedCSVString( + product_id: number, + prediction_period: 'weekly' | 'monthly', + source: 'purchases' | 'sales' +): Promise { + const returnValue: PreparedDataReturnValue = { + data_freq: prediction_period, + csv_string: '' + } + + let groupedData + if (prediction_period === 'weekly') { + if (source === 'purchases') { + groupedData = await selectGroupedWeeklyPurchase(product_id) + } else { + groupedData = await selectGroupedWeeklySales(product_id) + } + } else { + if (source === 'purchases') { + groupedData = await selectGroupedMonthlyPurchase(product_id) + } else { + groupedData = await selectGroupedMonthlySales(product_id) + } + } + + if (groupedData.length < 10) { + if (source === 'purchases') { + groupedData = await selectGroupedDailyPurchase( + product_id, + prediction_period === 'weekly' ? 'last-week' : 'last-month' + ) + } else { + groupedData = await selectGroupedDailySales( + product_id, + prediction_period === 'weekly' ? 'last-week' : 'last-month' + ) + } + returnValue.data_freq = 'daily' + } + + if (groupedData.length < 30) { + throw createHttpError(422, `Minimal lakukan 30 transaksi ${source === 'sales' ? 'penjualan' : 'pembelian'} harian pada ${prediction_period === 'weekly' ? 'minggu' : 'bulan'} sebelumnya untuk dapat melakukan prediksi`) + } + + let data = groupedData.map(v => ({ amount: v.amount })) + + returnValue.csv_string = papaparse.unparse(data) + + return returnValue +} \ No newline at end of file diff --git a/src/services/prediction/savePredictionModel.ts b/src/services/prediction/savePredictionModel.ts index c8a69a5..fed925c 100644 --- a/src/services/prediction/savePredictionModel.ts +++ b/src/services/prediction/savePredictionModel.ts @@ -9,12 +9,13 @@ export async function savePredictionModel( product_id: IPredictionTable['product_id'], prediction_period: IPredictionTable['period_type'], source: IPredictionTable['prediction_source'], - user_id: IPredictionTable['user_id'] + user_id: IPredictionTable['user_id'], + expiration_interval: number ) { const [ar_p, differencing_d, ma_q] = prediction_result.arima_order const expired = prediction_period === 'monthly' ? - getExpiredDateFromMonth(new Date(), 3) : - getExpiredDateFromWeek(new Date(), 3) + getExpiredDateFromMonth(new Date(), expiration_interval) : + getExpiredDateFromWeek(new Date(), expiration_interval) if (!model?.id) { await insertPredictionModel({ diff --git a/src/services/restockServices.ts b/src/services/restockServices.ts index 0ebd421..bc09f10 100644 --- a/src/services/restockServices.ts +++ b/src/services/restockServices.ts @@ -1,4 +1,4 @@ -import { countRestocks, insertsDataToRestock, selectAllRestockHistory } from "../repository/restockRepository"; +import { countRestocks, insertsDataToRestock, selectAllRestockHistory } from "../repository/transaction/purchase"; import { IProductTable, IPurchaseTable } from "../types/db-model"; import { getProductByProductCodes, updateProductById } from "./productServices"; @@ -51,4 +51,4 @@ export async function showRestockHistory( } }; return -} \ No newline at end of file +} diff --git a/src/services/transactionServices.ts b/src/services/transactionServices.ts index 5f725a1..bfe65f6 100644 --- a/src/services/transactionServices.ts +++ b/src/services/transactionServices.ts @@ -1,4 +1,4 @@ -import { countSales, insertsDataToTransaction, selectAllSalesHistory } from "../repository/transactionRepository"; +import { countSales, insertsDataToTransaction, selectAllSalesHistory } from "../repository/transaction/sales"; import { IProductTable, ITransactionTable } from "../types/db-model"; import { getProductByProductCodes, updateProductById } from "./productServices"; @@ -21,7 +21,7 @@ export async function addTransactionRecords(data: { price: Number(p.selling_price), }) - const stockAfter = (Number(dataMatched?.amount) || 0) - p.stock + const stockAfter = p.stock - (Number(dataMatched?.amount) || 0) updateProductById(p.id, { stock: stockAfter < 1 ? 0 : stockAfter }) diff --git a/src/types/db-model.ts b/src/types/db-model.ts index b8995ce..d8fee37 100644 --- a/src/types/db-model.ts +++ b/src/types/db-model.ts @@ -59,9 +59,9 @@ export interface ITransactionTable { export interface IPredictionTable { id: number; - prediction: number; - lower_bound: number; - upper_bound: number; + prediction: number[]; + lower_bound: number[]; + upper_bound: number[]; rmse: number; mape: number; product_id: number; diff --git a/src/types/request/python-api.ts b/src/types/request/python-api.ts index ceafebb..bf30f10 100644 --- a/src/types/request/python-api.ts +++ b/src/types/request/python-api.ts @@ -4,6 +4,7 @@ type TPythonBasePredictionRequest = { value_column: string date_column?: string date_regroup?: boolean + future_step: number } type TPythonBasePredictionResponse = { diff --git a/src/utils/api/responseError.ts b/src/utils/api/responseError.ts new file mode 100644 index 0000000..8ee8802 --- /dev/null +++ b/src/utils/api/responseError.ts @@ -0,0 +1,19 @@ +import createHttpError, { HttpError } from "http-errors"; +import { isErrorInstanceOfHttpError } from "../core/httpError"; +import dotenv from 'dotenv' +dotenv.config() + +export function sendResponseError(error: unknown): HttpError { + let newError: HttpError; + if (isErrorInstanceOfHttpError(error)) { + newError = error; + } else { + newError = createHttpError(500, "Internal Server Error", { cause: error }); + } + + if (process.env.NODE_ENV === 'development') { + console.error(newError); + } + + return newError; +} \ No newline at end of file diff --git a/src/utils/core/date.ts b/src/utils/core/date.ts index f2163d8..3fc7dc7 100644 --- a/src/utils/core/date.ts +++ b/src/utils/core/date.ts @@ -25,4 +25,8 @@ export function getExpiredDateFromMonth(dateInput: string | Date, period = 1) { export function getExpiredDateFromWeek(dateInput: string | Date, period = 1) { return getStartOfWeek(dateInput).add(period, 'week').endOf('day').format('YYYY-MM-DD HH:mm:ss') +} + +export function getTotalDaysInNextMonth(dateInput: string | Date = new Date()) { + return getStartOfMonth(dateInput).add(1, 'month').daysInMonth() } \ No newline at end of file