Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
ENTRYPOINT ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
56 changes: 56 additions & 0 deletions app/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import io

import pandas as pd
from fastapi import APIRouter, File, HTTPException, UploadFile
from fastapi.responses import StreamingResponse
from pydantic import BaseModel

from app.model_configurator import ModelConfigurator

router = APIRouter()
configurator = ModelConfigurator()


class ConfigRequest(BaseModel):
trend_models: list[str]
seasonal_models: list[str]


class FilePathRequest(BaseModel):
path: str


@router.post("/configure")
def configure(request: ConfigRequest):
try:
configurator.set_config(request.trend_models, request.seasonal_models)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
return {"message": "Configuration saved"}


@router.post("/train")
def train(file: UploadFile = File(...)):
df = pd.read_csv(file.file, parse_dates=["date"], index_col=False)
try:
configurator.fit_model(df)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
return {"message": f"Model training was successful with configs {configurator.config_names}"}


@router.post("/predict")
def predict(file: UploadFile = File(...)):
df = pd.read_csv(file.file, parse_dates=["date"], index_col=False)
try:
prediction = configurator.predict(df)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
csv_buffer = io.StringIO()
prediction.to_csv(csv_buffer, index=False)
csv_buffer.seek(0)
return StreamingResponse(
content=csv_buffer,
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=predictions.csv"},
)
11 changes: 11 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from fastapi import FastAPI

from app.api import router

app = FastAPI(
title="Time Series Forecasting API",
description="API for configuring, training, and predicting time series models",
version="1.0.0",
)

app.include_router(router, prefix="/api")
32 changes: 32 additions & 0 deletions app/model_configurator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Optional

import pandas as pd

from configs.models_collector import CONFIG_TYPE, ModelsCollector
from configs.models_configs import ModelsConfigs
from src.models.time_series_model import TimeSeriesModel


class ModelConfigurator:
def __init__(self) -> None:
self._trend_config: Optional[CONFIG_TYPE] = None
self._seasonal_config: Optional[CONFIG_TYPE] = None
self._model: Optional[TimeSeriesModel] = None
self._collector: ModelsCollector = ModelsCollector(ModelsConfigs)
self.config_names: Optional[dict] = None

def set_config(self, trend_models: list[str], seasonal_models: list[str]) -> None:
self._trend_config = self._collector.get_configs(trend_models)
self._seasonal_config = self._collector.get_configs(seasonal_models)
self.config_names = {"trend_models": trend_models, "seasonal_models": seasonal_models}

def fit_model(self, X: pd.DataFrame) -> None:
if self._trend_config is None or self._seasonal_config is None:
raise ValueError("Configs unsetted")
model = TimeSeriesModel(self._trend_config, self._seasonal_config)
self._model = model.fit(X)

def predict(self, X: pd.DataFrame) -> pd.DataFrame:
if self._model is None:
raise ValueError("Unfitted")
return self._model.predict(X)
6 changes: 4 additions & 2 deletions configs/models_collector.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Any
from typing import Any, TypeVar

CONFIG_TYPE = TypeVar("CONFIG_TYPE", bound=list[tuple[Any, list[Any]]])


class ModelsCollector:
Expand All @@ -23,7 +25,7 @@ def __init__(self, models_config: dict[str, tuple[Any, list[Any]]]) -> None:
"""
self.models_config = models_config

def get_configs(self, models_names: list[str]) -> list[tuple[Any, list[Any]]]:
def get_configs(self, models_names: list[str]) -> CONFIG_TYPE:
"""
Retrieves unique configurations for the specified model names.

Expand Down
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ xgboost >= 3.0.0
workalendar >= 17.0.0
catboost >= 1.2.8
statsmodels >= 0.14.4
fastapi>=0.115.12
uvicorn >= 0.32.1
pydantic >= 2.9.2
python-multipart>=0.0.20
3 changes: 2 additions & 1 deletion src/special_preprocessing/date_transformers/series_comp.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def fit(self, X: pd.DataFrame, y: Optional[pd.DataFrame] = None) -> "GroupByDate
return self

def transform(self, X: pd.DataFrame) -> pd.DataFrame:
special_columns = ["discount", "price", "discount.1", "key", "date", "ship"]
special_columns = ["discount", "price", "discount.1", "key", "date", "ship", "discount"]
other = [col for col in X.columns if col not in special_columns]
new_data = pd.DataFrame()
keys = X["key"].unique()
Expand All @@ -23,6 +23,7 @@ def transform(self, X: pd.DataFrame) -> pd.DataFrame:
"ship": "sum",
"discount.1": "mean",
"price": "mean",
"discount": "max",
}
)
new_data = pd.concat([new_data, grouped], ignore_index=True)
Expand Down
8 changes: 4 additions & 4 deletions src/special_preprocessing/first_special_pipeline/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder

from src.special_preprocessing.date_transformers.features_extraction import FeatureExtractionTransformer
from src.special_preprocessing.date_transformers.series_comp import DateRangeFilledTransformer, GroupByDateTransformer
from src.special_preprocessing.date_transformers.series_decomposition import Separation, SeriesDecompositionTransformer
from src.special_preprocessing.first_special_pipeline.preprocessing import (
from ..date_transformers.features_extraction import FeatureExtractionTransformer
from ..date_transformers.series_comp import DateRangeFilledTransformer, GroupByDateTransformer
from ..date_transformers.series_decomposition import Separation, SeriesDecompositionTransformer
from ..first_special_pipeline.preprocessing import (
ChangeTypesTransformer,
DropDuplicatesTransformer,
KeyIndexTransformer,
Expand Down