gabcares commited on
Commit
2354717
·
verified ·
1 Parent(s): 2f9d60c

Upload 6 files

Browse files
Files changed (6) hide show
  1. Dockerfile +25 -0
  2. assets/favicon.ico +0 -0
  3. main.py +177 -0
  4. requirements.txt +126 -0
  5. utils/__init__.py +0 -0
  6. utils/config.py +73 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11.9-slim
2
+
3
+ # Copy requirements file
4
+ COPY requirements.txt .
5
+
6
+ # Update pip
7
+ RUN pip --timeout=3000 install --no-cache-dir --upgrade pip
8
+
9
+ # Install dependecies
10
+ RUN pip --timeout=3000 install --no-cache-dir -r requirements.txt
11
+
12
+ # Make project directory
13
+ RUN mkdir -p /api/
14
+
15
+ # Set working directory
16
+ WORKDIR /api
17
+
18
+ # Copy API
19
+ COPY . .
20
+
21
+ # Expose app port
22
+ EXPOSE 7860
23
+
24
+ # Start application
25
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
assets/favicon.ico ADDED
main.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ from collections.abc import AsyncIterator
5
+ from contextlib import asynccontextmanager
6
+
7
+ from fastapi import FastAPI, Query
8
+ from fastapi.responses import FileResponse
9
+ from fastapi.staticfiles import StaticFiles
10
+ from fastapi_cache import FastAPICache
11
+ from fastapi_cache.backends.inmemory import InMemoryBackend
12
+ from fastapi_cache.coder import PickleCoder
13
+ from fastapi_cache.decorator import cache
14
+ import logging
15
+
16
+ from pydantic import BaseModel, Field
17
+ from typing import List, Union, Optional
18
+ from datetime import datetime
19
+
20
+ from sklearn.pipeline import Pipeline
21
+ import joblib
22
+
23
+ import pandas as pd
24
+
25
+ import httpx
26
+ from io import BytesIO
27
+
28
+
29
+ from utils.config import (
30
+ ONE_DAY_SEC,
31
+ ONE_WEEK_SEC,
32
+ ENV_PATH,
33
+ DESCRIPTION,
34
+ ALL_MODELS
35
+ )
36
+
37
+ load_dotenv(ENV_PATH)
38
+
39
+
40
+ @asynccontextmanager
41
+ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
42
+ FastAPICache.init(InMemoryBackend())
43
+ yield
44
+
45
+
46
+ # FastAPI Object
47
+ app = FastAPI(
48
+ title='Yassir Eta Prediction',
49
+ version='1.0.0',
50
+ description=DESCRIPTION,
51
+ lifespan=lifespan,
52
+ )
53
+
54
+ app.mount("/assets", StaticFiles(directory="assets"), name="assets")
55
+
56
+
57
+ @app.get('/favicon.ico', include_in_schema=False)
58
+ @cache(expire=ONE_WEEK_SEC, namespace='eta_favicon') # Cache for 1 week
59
+ async def favicon():
60
+ file_name = "favicon.ico"
61
+ file_path = os.path.join(app.root_path, "assets", file_name)
62
+ return FileResponse(path=file_path, headers={"Content-Disposition": "attachment; filename=" + file_name})
63
+
64
+
65
+ # API input features
66
+
67
+
68
+ class EtaFeatures(BaseModel):
69
+ timestamp: List[datetime] = Field(
70
+ description="Timestamp: Time that the trip was started")
71
+ origin_lat: List[float] = Field(
72
+ description="Origin_lat: Origin latitude (in degrees)")
73
+ origin_lon: List[float] = Field(
74
+ description="Origin_lon: Origin longitude (in degrees)")
75
+ destination_lat: List[float] = Field(
76
+ description="Destination_lat: Destination latitude (in degrees)")
77
+ destination_lon: List[float] = Field(
78
+ description="Destination_lon: Destination longitude (in degrees)")
79
+ trip_distance: List[float] = Field(
80
+ description="Trip_distance: Distance in meters on a driving route")
81
+
82
+
83
+ class Url(BaseModel):
84
+ url: str
85
+ pipeline_url: str
86
+
87
+
88
+ class ResultData(BaseModel):
89
+ prediction: List[float]
90
+
91
+
92
+ class PredictionResponse(BaseModel):
93
+ execution_msg: str
94
+ execution_code: int
95
+ result: ResultData
96
+
97
+
98
+ class ErrorResponse(BaseModel):
99
+ execution_msg: str
100
+ execution_code: int
101
+ error: Optional[str]
102
+
103
+
104
+ logging.basicConfig(level=logging.ERROR,
105
+ format='%(asctime)s - %(levelname)s - %(message)s')
106
+
107
+
108
+ # Load the model pipelines and encoder
109
+ # Cache for 1 day
110
+ @cache(expire=ONE_DAY_SEC, namespace='pipeline_resource', coder=PickleCoder)
111
+ async def load_pipeline(pipeline_url: Url) -> Pipeline:
112
+ async def url_to_data(url: Url):
113
+ async with httpx.AsyncClient() as client:
114
+ response = await client.get(url)
115
+ response.raise_for_status() # Ensure we catch any HTTP errors
116
+ # Convert response content to BytesIO object
117
+ data = BytesIO(response.content)
118
+ return data
119
+
120
+ pipeline = None
121
+ try:
122
+ pipeline: Pipeline = joblib.load(await url_to_data(pipeline_url))
123
+ except Exception as e:
124
+ logging.error(
125
+ "Omg, an error occurred in loading the pipeline resources: %s", e)
126
+ finally:
127
+ return pipeline
128
+
129
+
130
+ # Endpoints
131
+
132
+ # Status endpoint: check if api is online
133
+ @app.get('/')
134
+ @cache(expire=ONE_WEEK_SEC, namespace='eta_status_check') # Cache for 1 week
135
+ async def status_check():
136
+ return {"Status": "API is online..."}
137
+
138
+
139
+ @cache(expire=ONE_DAY_SEC, namespace='pipeline_regressor') # Cache for 1 day
140
+ async def pipeline_regressor(pipeline: Pipeline, data: EtaFeatures) -> Union[ErrorResponse, PredictionResponse]:
141
+ msg = 'Execution failed'
142
+ code = 0
143
+ output = ErrorResponse(**{'execution_msg': msg,
144
+ 'execution_code': code, 'error': None})
145
+
146
+ try:
147
+ # Create dataframe
148
+ df = pd.DataFrame.from_dict(data.__dict__)
149
+
150
+ # Make prediction
151
+ preds = pipeline.predict(df)
152
+ predictions = [float(pred) for pred in preds]
153
+
154
+ result = ResultData(**{"prediction": predictions})
155
+
156
+ msg = 'Execution was successful'
157
+ code = 1
158
+ output = PredictionResponse(
159
+ **{'execution_msg': msg,
160
+ 'execution_code': code, 'result': result}
161
+ )
162
+
163
+ except Exception as e:
164
+ error = f"Omg, pipeline regressor failure. {e}"
165
+ output = ErrorResponse(**{'execution_msg': msg,
166
+ 'execution_code': code, 'error': error})
167
+
168
+ finally:
169
+ return output
170
+
171
+
172
+ @app.post('/api/v1/eta/prediction', tags=['All Models'])
173
+ async def query_eta_prediction(data: EtaFeatures, model: str = Query('RandomForestRegressor', enum=list(ALL_MODELS.keys()))) -> Union[ErrorResponse, PredictionResponse]:
174
+ pipeline_url: Url = ALL_MODELS[model]
175
+ pipeline = await load_pipeline(pipeline_url)
176
+ output = await pipeline_regressor(pipeline, data)
177
+ return output
requirements.txt ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated-types==0.7.0
2
+ anyio==4.4.0
3
+ appdirs==1.4.4
4
+ asgiref==3.8.1
5
+ asttokens==2.4.1
6
+ async-timeout==4.0.3
7
+ attrs==24.1.0
8
+ branca==0.7.2
9
+ cattrs==23.2.3
10
+ certifi==2024.7.4
11
+ charset-normalizer==3.3.2
12
+ click==8.1.7
13
+ colorama==0.4.6
14
+ comm==0.2.2
15
+ contourpy==1.2.1
16
+ cycler==0.12.1
17
+ debugpy==1.8.2
18
+ decorator==5.1.1
19
+ exceptiongroup==1.2.2
20
+ executing==2.0.1
21
+ faicons==0.2.2
22
+ fastapi==0.112.0
23
+ fastapi-cache2==0.2.2
24
+ fastjsonschema==2.20.0
25
+ fonttools==4.53.1
26
+ geodatasets==2024.7.0
27
+ geographiclib==2.0
28
+ geojson==3.1.0
29
+ geopandas==1.0.1
30
+ geopy==2.4.1
31
+ h11==0.14.0
32
+ htmltools==0.5.3
33
+ httpcore==1.0.5
34
+ httpx==0.27.0
35
+ idna==3.7
36
+ importlib_metadata==8.2.0
37
+ # ipykernel==6.29.5
38
+ # ipyleaflet==0.19.2
39
+ # ipython==8.18.0
40
+ # ipywidgets==8.1.3
41
+ jedi==0.19.1
42
+ Jinja2==3.1.4
43
+ joblib==1.4.2
44
+ jsonschema==4.23.0
45
+ jsonschema-specifications==2023.12.1
46
+ # jupyter_client==8.6.2
47
+ # jupyter_core==5.7.2
48
+ # jupyter-leaflet==0.19.2
49
+ # jupyterlab_widgets==3.0.11
50
+ kiwisolver==1.4.5
51
+ linkify-it-py==2.0.3
52
+ markdown-it-py==3.0.0
53
+ MarkupSafe==2.1.5
54
+ # matplotlib==3.9.1
55
+ # matplotlib-inline==0.1.7
56
+ mdit-py-plugins==0.4.1
57
+ mdurl==0.1.2
58
+ # nbformat==5.10.4
59
+ nest_asyncio==1.6.0
60
+ numpy==1.26.4
61
+ packaging==24.1
62
+ pandas==2.2.2
63
+ parso==0.8.4
64
+ patsy==0.5.6
65
+ pendulum==3.0.0
66
+ pickleshare==0.7.5
67
+ pillow==10.4.0
68
+ # pip==24.0
69
+ platformdirs==4.2.2
70
+ # plotly==5.23.0
71
+ # plotly_calplot==0.1.20
72
+ pooch==1.8.2
73
+ prompt-toolkit==3.0.36
74
+ psutil==6.0.0
75
+ pure_eval==0.2.3
76
+ pydantic==2.8.2
77
+ pydantic_core==2.20.1
78
+ Pygments==2.18.0
79
+ PyJWT==2.9.0
80
+ pyogrio==0.9.0
81
+ pyparsing==3.1.2
82
+ pyproj==3.6.1
83
+ python-dateutil==2.9.0.post0
84
+ python-dotenv==1.0.1
85
+ python-multipart==0.0.9
86
+ pytz==2023.4
87
+ # pywin32==306
88
+ pyzmq==26.0.3
89
+ questionary==2.0.1
90
+ referencing==0.35.1
91
+ requests==2.32.3
92
+ requests-cache==1.2.1
93
+ rpds-py==0.19.1
94
+ rsconnect_python==1.24.0
95
+ scikit-learn==1.5.1
96
+ scipy==1.14.0
97
+ semver==2.13.0
98
+ setuptools==71.0.4
99
+ shapely==2.0.5
100
+ shiny==1.0.0
101
+ shinywidgets==0.3.2
102
+ six==1.16.0
103
+ sniffio==1.3.1
104
+ stack-data==0.6.2
105
+ starlette==0.37.2
106
+ statsmodels==0.14.2
107
+ tenacity==9.0.0
108
+ threadpoolctl==3.5.0
109
+ time-machine==2.15.0
110
+ tornado==6.4.1
111
+ traitlets==5.14.3
112
+ traittypes==0.2.1
113
+ typing_extensions==4.12.2
114
+ tzdata==2024.1
115
+ uc-micro-py==1.0.3
116
+ url-normalize==1.4.3
117
+ urllib3==2.2.2
118
+ uvicorn==0.30.4
119
+ watchfiles==0.22.0
120
+ wcwidth==0.2.13
121
+ websockets==12.0
122
+ wheel==0.43.0
123
+ widgetsnbextension==4.0.11
124
+ xgboost==2.1.1
125
+ xyzservices==2024.6.0
126
+ zipp==3.19.2
utils/__init__.py ADDED
File without changes
utils/config.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+ # ENV when using standalone uvicorn server running FastAPI in api directory
4
+ ENV_PATH = Path("../env/online.env")
5
+
6
+ ONE_DAY_SEC = 24*60*60
7
+
8
+ ONE_WEEK_SEC = ONE_DAY_SEC*7
9
+
10
+ PIPELINE_FUNCTION_URL = ""
11
+
12
+ RANDOM_FOREST_URL = "https://drive.google.com/uc?export=download&id=1t0RRzAbtW7Y1lAz4ddB5iY_0SIpfdHbB"
13
+
14
+ XGBOOST_URL = "https://raw.githubusercontent.com/valiantezabuku/Yassir-ETA-Prediction-Challenge-For-Azubian-Team-Curium/main/Models/XGBRegressor.joblib"
15
+
16
+ ADABOOST_URL = "https://raw.githubusercontent.com/valiantezabuku/Yassir-ETA-Prediction-Challenge-For-Azubian-Team-Curium/main/Models/AdaBoostRegressor.joblib"
17
+
18
+ GRADIENT_BOOST_URL = "https://raw.githubusercontent.com/valiantezabuku/Yassir-ETA-Prediction-Challenge-For-Azubian-Team-Curium/main/Models/GradientBoostingRegressor.joblib"
19
+
20
+ HISTGRADIENT_BOOST_URL = "https://raw.githubusercontent.com/valiantezabuku/Yassir-ETA-Prediction-Challenge-For-Azubian-Team-Curium/main/Models/HistGradientBoostingRegressor.joblib"
21
+
22
+ DECISION_TREE_URL = "https://raw.githubusercontent.com/valiantezabuku/Yassir-ETA-Prediction-Challenge-For-Azubian-Team-Curium/main/Models/DecisionTreeRegressor.joblib"
23
+
24
+ LINEAR_REG_URL = "https://raw.githubusercontent.com/valiantezabuku/Yassir-ETA-Prediction-Challenge-For-Azubian-Team-Curium/main/Models/LinearRegression.joblib"
25
+
26
+
27
+ ALL_MODELS = {
28
+ "AdaBoostRegressor": ADABOOST_URL,
29
+ "DecisionTreeRegressor": DECISION_TREE_URL,
30
+ "GradientBoostingRegressor": GRADIENT_BOOST_URL,
31
+ "HistGradientBoostingRegressor": HISTGRADIENT_BOOST_URL,
32
+ "LinearRegression": LINEAR_REG_URL,
33
+ "RandomForestRegressor": RANDOM_FOREST_URL,
34
+ "XGBRegressor": XGBOOST_URL
35
+ }
36
+
37
+ DESCRIPTION = """
38
+ This API accurately predicts the estimated time of arrival at the dropoff point for a single Yassir journey using `Random Forest model` and `XGBoost model`.\n
39
+
40
+ The models were trained on [The Yassir Eta datasets at Zindi Africa](https://zindi.africa/competitions/yassir-eta-prediction-challenge-for-azubian/data).\n
41
+
42
+ ### Features
43
+ `Timestamp:` Time that the trip was started\n
44
+ `Origin_lat:` Origin latitude (in degrees)\n
45
+ `Origin_lon:` Origin longitude (in degrees)\n
46
+ `Destination_lat:` Destination latitude (in degrees)\n
47
+ `Destination_lon:` Destination longitude (in degrees)\n
48
+ `Trip_distance:` Distance in meters on a driving route\n
49
+
50
+ #### Weather Features
51
+ Daily weather summaries, based on data from the ERA5 dataset.\n
52
+ `date:` ..\n
53
+ `dewpoint_2m_temperature:` ..\n
54
+ `maximum_2m_air_temperature:` ..\n
55
+ `mean_2m_air_temperature:` ..\n
56
+ `mean_sea_level_pressure:` ..\n
57
+ `minimum_2m_air_temperature:` ..\n
58
+ `surface_pressure:` ..\n
59
+ `total_precipitation:` ..\n
60
+ `u_component_of_wind_10m:` ..\n
61
+ `v_component_of_wind_10m:` ..\n
62
+
63
+ ### Results
64
+ **ETA prediction:** Estimated trip time in seconds\n
65
+
66
+
67
+ ### Explore the frontend data application
68
+ To explore the fontend application (built-with streamlit) click the link below.\n
69
+ 🚗[Yassir frontend](/https://hugginface-yassir)
70
+
71
+
72
+ Made with 💖 [Team Curium](#)
73
+ """