klakenyuo commited on
Commit
c034281
·
1 Parent(s): e5080f8
.DS_Store CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
 
Dockerfile → Dockerfile.api RENAMED
@@ -1,20 +1,11 @@
1
- # Utilise une image officielle Python
2
  FROM python:3.10-slim
3
 
4
- # Définir le répertoire de travail
5
  WORKDIR /code
6
-
7
- # Copier les fichiers de dépendances
8
- COPY requirements.txt .
9
-
10
- # Installer les dépendances
11
  RUN pip install --no-cache-dir -r requirements.txt
12
 
13
- # Copier tout le code dans le container
14
- COPY . .
15
 
16
- # Exposer le port attendu par Hugging Face Spaces
17
  EXPOSE 7860
18
-
19
- # Commande pour lancer l'API FastAPI via Uvicorn
20
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
1
  FROM python:3.10-slim
2
 
 
3
  WORKDIR /code
4
+ COPY api/requirements.txt .
 
 
 
 
5
  RUN pip install --no-cache-dir -r requirements.txt
6
 
7
+ COPY api /code
8
+ COPY model /code/model
9
 
 
10
  EXPOSE 7860
 
 
11
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
Dockerfile.dashboard ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+ RUN pip install streamlit pandas numpy
5
+
6
+ COPY dashboard /app
7
+ COPY model /app/model
8
+
9
+ EXPOSE 8501
10
+ CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
README.md CHANGED
@@ -1,10 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Get Around Api
3
- emoji: 👀
4
- colorFrom: purple
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
+ # Getaround Pricing & Delay Prediction
2
+
3
+ Ce projet regroupe deux modules principaux :
4
+ - 🔮 Une **API FastAPI** pour la prédiction du prix d'une voiture en fonction de ses caractéristiques.
5
+ - 📊 Un **Dashboard Streamlit** pour visualiser les retards de retour de véhicule et explorer les données.
6
+
7
+ ---
8
+
9
+ ## 🚀 Lancer l'application avec Docker
10
+
11
+ ### 1. Prérequis
12
+ - Docker et Docker Compose installés
13
+
14
+ ### 2. Lancer l'application
15
+
16
+ ```bash
17
+ docker-compose up --build
18
+ ```
19
+
20
+ ---
21
+
22
+ ## 📂 Architecture du projet
23
+
24
+ ```
25
+ getaround-docker/
26
+ ├── api/ # API FastAPI (endpoint /predict)
27
+ ├── dashboard/ # Application Streamlit
28
+ ├── model/ # Modèles entraînés
29
+ ├── data/ # Données Excel et CSV
30
+ ├── Dockerfile.api # Image de l'API
31
+ ├── Dockerfile.dashboard # Image du dashboard
32
+ └── docker-compose.yml # Orchestration Docker
33
+ ```
34
+
35
  ---
36
+
37
+ ## 🌐 Accès aux interfaces
38
+
39
+ - 📊 Dashboard : [http://localhost:8501](http://localhost:8501)
40
+ - 🧠 API Swagger : [http://localhost:7860/docs](http://localhost:7860/docs)
41
+
42
+ ---
43
+
44
+ ## 📁 Dossier `data/`
45
+
46
+ Ce dossier contient :
47
+ - `delay_analysis.csv` et `.xlsx` : données d’analyse de retards
48
+ - `pricing_project.csv` : dataset utilisé pour entraîner le modèle de prédiction
49
+
50
+ ---
51
+
52
+ ## 🧠 À propos de l'API
53
+
54
+ **Endpoint principal :**
55
+ ```
56
+ POST /predict
57
+ ```
58
+
59
+ **Exemple de payload JSON :**
60
+ ```json
61
+ {
62
+ "model_key": "renault",
63
+ "mileage": 45000,
64
+ "engine_power": 90,
65
+ "fuel": "diesel",
66
+ "paint_color": "grey",
67
+ "car_type": "hatchback",
68
+ "private_parking_available": true,
69
+ "has_gps": true,
70
+ "has_air_conditioning": true,
71
+ "automatic_car": false,
72
+ "has_getaround_connect": true,
73
+ "has_speed_regulator": true,
74
+ "winter_tires": false
75
+ }
76
+ ```
77
+
78
  ---
79
 
80
+ ## 👤 Auteur
81
+ **Gilles AKAKPO**
82
+ Projet réalisé dans le cadre d’une étude de cas sur la plateforme Getaround.
app.py → api/app.py RENAMED
File without changes
requirements.txt → api/requirements.txt RENAMED
@@ -5,4 +5,3 @@ pandas
5
  numpy
6
  joblib
7
  python-multipart
8
- mlflow
 
5
  numpy
6
  joblib
7
  python-multipart
 
dashboard/app.py ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import plotly.graph_objects as go
5
+ import numpy as np
6
+ import joblib
7
+ from typing import Dict, List, Any
8
+ import logging
9
+
10
+ # Configuration de la page Streamlit
11
+ st.set_page_config(
12
+ page_title='GetAround project',
13
+ page_icon='🚗',
14
+ layout="wide",
15
+ initial_sidebar_state="auto",
16
+ menu_items=None
17
+ )
18
+
19
+ # Configuration du logging
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Constantes
24
+ DATA_FILES = {
25
+ 'pricing': '../data/pricing_project.csv',
26
+ 'delay': '../data/delay_analysis.csv'
27
+ }
28
+
29
+ # Fonctions utilitaires
30
+ def load_data(file_path: str, sep: str = ',') -> pd.DataFrame:
31
+ """
32
+ Charge les données depuis un fichier CSV.
33
+
34
+ Args:
35
+ file_path (str): Chemin du fichier CSV
36
+ sep (str): Séparateur utilisé dans le fichier CSV
37
+
38
+ Returns:
39
+ pd.DataFrame: DataFrame contenant les données
40
+ """
41
+ try:
42
+ return pd.read_csv(file_path, sep=sep)
43
+ except Exception as e:
44
+ logger.error(f"Erreur lors du chargement des données: {e}")
45
+ st.error(f"Erreur lors du chargement des données: {e}")
46
+ return pd.DataFrame()
47
+
48
+ def display_metrics(df_delay: pd.DataFrame, df_pricing: pd.DataFrame, col: st.columns) -> None:
49
+ """
50
+ Affiche les métriques principales dans les colonnes spécifiées.
51
+
52
+ Args:
53
+ df_delay (pd.DataFrame): DataFrame contenant les données de retard
54
+ df_pricing (pd.DataFrame): DataFrame contenant les données de prix
55
+ col (st.columns): Colonnes Streamlit pour l'affichage
56
+ """
57
+ nb_rentals = len(df_delay)
58
+
59
+ with col[0]:
60
+ st.metric(
61
+ label="Nombres de voitures dans le parc :",
62
+ value=df_delay['car_id'].nunique()
63
+ )
64
+ connect_percentage = round(
65
+ len(df_pricing[df_pricing['has_getaround_connect'] == True]) / len(df_pricing) * 100
66
+ )
67
+ st.metric(
68
+ label="Pourcentage de voitures équipées 'Connect' :",
69
+ value=f"{connect_percentage} %"
70
+ )
71
+
72
+ with col[2]:
73
+ st.metric(
74
+ label="Nombres de locations :",
75
+ value=nb_rentals
76
+ )
77
+ connect_rentals_percentage = round(
78
+ len(df_delay[df_delay['checkin_type'] == 'connect']) / nb_rentals * 100
79
+ )
80
+ st.metric(
81
+ label="Pourcentage de location via 'Connect' :",
82
+ value=f"{connect_rentals_percentage} %"
83
+ )
84
+
85
+ with col[1]:
86
+ delay_percentage = round(
87
+ len(df_delay[df_delay['delay_at_checkout_in_minutes'] > 0]) / nb_rentals * 100
88
+ )
89
+ st.metric(
90
+ label="Pourcentage de locations rendues avec retard :",
91
+ value=f"{delay_percentage} %"
92
+ )
93
+ cancel_percentage = round(
94
+ len(df_delay[df_delay['state'] == 'canceled']) / nb_rentals * 100
95
+ )
96
+ st.metric(
97
+ label="Pourcentage de locations annulées :",
98
+ value=f"{cancel_percentage} %"
99
+ )
100
+
101
+ def main_page() -> None:
102
+ """Page d'accueil de l'application."""
103
+ st.markdown("# Accueil")
104
+ st.sidebar.markdown("# Accueil")
105
+ st.header('Statistiques')
106
+
107
+ # Chargement des données
108
+ dataset_pricing = load_data(DATA_FILES['pricing'])
109
+ dataset_delay = load_data(DATA_FILES['delay'], sep=';')
110
+
111
+ if dataset_pricing.empty or dataset_delay.empty:
112
+ st.error("Impossible de charger les données. Veuillez vérifier les fichiers.")
113
+ return
114
+
115
+ # Affichage des métriques
116
+ main_metrics_cols = st.columns([33, 33, 34])
117
+ display_metrics(dataset_delay, dataset_pricing, main_metrics_cols)
118
+
119
+ # Footer
120
+ st.markdown("---")
121
+ footer = """
122
+ <style>
123
+ .footer {
124
+ position: fixed;
125
+ left: 0;
126
+ bottom: 0;
127
+ width: 100%;
128
+ background-color: transparent;
129
+ color: white;
130
+ text-align: center;
131
+ }
132
+ </style>
133
+ """
134
+ st.markdown(footer, unsafe_allow_html=True)
135
+
136
+ def page2() -> None:
137
+ """Page d'analyse des retards."""
138
+ st.title("Dashboard : Analyse d'un jeu de données de GetAround 🚗💲")
139
+ st.markdown("""
140
+ Voici quelques informations clefs pour comprendre la dynamique des retards lors des réservations
141
+ sur GetAround 🚗, ainsi que leur impact sur les locations, et donc sur le chiffre d'affaire
142
+ potentiel de GetAround 🚗.
143
+ """)
144
+ st.markdown("---")
145
+
146
+ # Chargement des données
147
+ dataset_delay = load_data(DATA_FILES['delay'], sep=';')
148
+ if dataset_delay.empty:
149
+ st.error("Impossible de charger les données. Veuillez vérifier les fichiers.")
150
+ return
151
+
152
+ # Partie 1: Overview des retards
153
+ st.subheader("Partie 1 : Overview des retards")
154
+ main_metrics_cols_1 = st.columns([34, 33, 33])
155
+
156
+ with main_metrics_cols_1[0]:
157
+ # Graphique des retards
158
+ ended_rentals = dataset_delay[dataset_delay["state"] == "ended"]
159
+ labels = ["A l'heure ou en avance", 'En retard']
160
+ values = [
161
+ len(ended_rentals[ended_rentals["delay_at_checkout_in_minutes"] <= 0]),
162
+ len(ended_rentals[ended_rentals["delay_at_checkout_in_minutes"] > 0])
163
+ ]
164
+ fig = px.pie(
165
+ names=labels,
166
+ values=values,
167
+ title="Part des retards dans les réservations abouties"
168
+ )
169
+ st.plotly_chart(fig, use_container_width=True)
170
+
171
+ with main_metrics_cols_1[1]:
172
+ # Distribution des retards
173
+ delayed_rentals = ended_rentals[ended_rentals["delay_at_checkout_in_minutes"] > 0]
174
+ fig2 = px.histogram(
175
+ delayed_rentals,
176
+ x="delay_at_checkout_in_minutes",
177
+ range_x=[0, 12*60],
178
+ title="Distribution des retards en minutes",
179
+ labels={"delay_at_checkout_in_minutes": "Retard au checkout (mn)"}
180
+ )
181
+ st.plotly_chart(fig2, use_container_width=True)
182
+
183
+ with main_metrics_cols_1[2]:
184
+ # Métriques des retards
185
+ moyenne_retard = delayed_rentals["delay_at_checkout_in_minutes"].median()
186
+ st.metric(
187
+ label="Retard médian : ",
188
+ value=f"{round(moyenne_retard, 2)} minutes"
189
+ )
190
+ retard_une_h = 100 * (
191
+ len(delayed_rentals[delayed_rentals["delay_at_checkout_in_minutes"] > 60]) /
192
+ len(ended_rentals)
193
+ )
194
+ st.metric(
195
+ label="Retard supérieur à 1h :",
196
+ value=f"{round(retard_une_h, 2)} %"
197
+ )
198
+
199
+ # Partie 2: Analyse des délais
200
+ st.markdown("---")
201
+ st.subheader("Partie 2 : Impact des délais entre locations")
202
+ main_metrics_cols_2 = st.columns([70, 30])
203
+
204
+ with main_metrics_cols_2[0]:
205
+ with st.spinner('Chargement...'):
206
+ # Préparation des données
207
+ dataset_delay.dropna(subset=['delay_at_checkout_in_minutes'], inplace=True)
208
+ dataset_delay = dataset_delay.reset_index(drop=True)
209
+ dataset_delay['delay_problem'] = (
210
+ dataset_delay['delay_at_checkout_in_minutes'] -
211
+ dataset_delay['time_delta_with_previous_rental_in_minutes']
212
+ )
213
+
214
+ # Calcul des statistiques par seuil
215
+ def compute_stats_threshold(delay_tresh: int, check_type: str) -> int:
216
+ mask = (
217
+ (dataset_delay['delay_problem'] > delay_tresh) &
218
+ (dataset_delay['checkin_type'] == check_type)
219
+ )
220
+ return dataset_delay[mask].count()[0]
221
+
222
+ # Calcul des ratios de locations perdues
223
+ nb_rent_connect = dataset_delay[dataset_delay['checkin_type'] == 'connect'].count()[0]
224
+ nb_rent_mobile = dataset_delay[dataset_delay['checkin_type'] == 'mobile'].count()[0]
225
+
226
+ results = {
227
+ 'Threshold (min)': range(400),
228
+ 'Rent_lost_mobile(%)': [],
229
+ 'Rent_lost_connect(%)': []
230
+ }
231
+
232
+ for i in range(400):
233
+ results['Rent_lost_mobile(%)'].append(
234
+ compute_stats_threshold(i, 'mobile') / nb_rent_mobile * 100
235
+ )
236
+ results['Rent_lost_connect(%)'].append(
237
+ compute_stats_threshold(i, 'connect') / nb_rent_connect * 100
238
+ )
239
+
240
+ df_delay_stat_treshold = pd.DataFrame(results)
241
+
242
+ # Affichage du graphique
243
+ st.line_chart(
244
+ data=df_delay_stat_treshold,
245
+ x='Threshold (min)',
246
+ y=["Rent_lost_mobile(%)", 'Rent_lost_connect(%)'],
247
+ use_container_width=True
248
+ )
249
+
250
+ # Sélecteur de délai
251
+ delay = st.slider(
252
+ 'Quel délai en deux locations (en minutes) :',
253
+ 0, 400, 60
254
+ )
255
+
256
+ with main_metrics_cols_2[1]:
257
+ # Affichage des métriques de délai
258
+ st.metric(
259
+ label=f"Pourcentage de location perdue sur mobile pour un délai de {delay} minutes :",
260
+ value=f"{round(df_delay_stat_treshold.iloc[delay][1], 2)} %"
261
+ )
262
+ st.metric(
263
+ label=f"Pourcentage de location perdue sur l'app pour un délai de {delay} minutes :",
264
+ value=f"{round(df_delay_stat_treshold.iloc[delay][2], 2)} %"
265
+ )
266
+
267
+ def predict_price(values: List[Any]) -> float:
268
+ """
269
+ Prédit le prix de location d'un véhicule.
270
+
271
+ Args:
272
+ values (List[Any]): Liste des caractéristiques du véhicule
273
+
274
+ Returns:
275
+ float: Prix prédit
276
+ """
277
+ try:
278
+ predict_array = np.zeros((1, 13))
279
+ im_df = pd.DataFrame(
280
+ predict_array,
281
+ columns=[
282
+ 'model_key', 'mileage', 'engine_power', 'fuel', 'paint_color',
283
+ 'car_type', 'private_parking_available', 'has_gps',
284
+ 'has_air_conditioning', 'automatic_car', 'has_getaround_connect',
285
+ 'has_speed_regulator', 'winter_tires'
286
+ ]
287
+ )
288
+ im_df[0:1] = values
289
+
290
+ loaded_model = joblib.load('../model/finalized_model.sav')
291
+ pipeline = joblib.load('../model/finalized_prepoc.sav')
292
+
293
+ result = loaded_model.predict(pipeline.transform(im_df))
294
+ return result[0]
295
+ except Exception as e:
296
+ logger.error(f"Erreur lors de la prédiction: {e}")
297
+ st.error(f"Erreur lors de la prédiction: {e}")
298
+ return 0.0
299
+
300
+ def page3() -> None:
301
+ """Page de prédiction des prix."""
302
+ st.markdown("# Prédiction")
303
+ st.sidebar.markdown("# Prédiction 🎉")
304
+ st.markdown("**Veuillez entrer les informations concernant votre véhicule :**")
305
+
306
+ # Chargement des données
307
+ dataset_pricing = load_data(DATA_FILES['pricing'])
308
+ if dataset_pricing.empty:
309
+ st.error("Impossible de charger les données. Veuillez vérifier les fichiers.")
310
+ return
311
+
312
+ # Formulaire de saisie
313
+ col1, col2 = st.columns(2)
314
+
315
+ with col1:
316
+ marque = st.selectbox(
317
+ 'Marque :',
318
+ tuple(dataset_pricing['model_key'].unique())
319
+ )
320
+ kil = st.number_input(
321
+ "Entrer le kilométrage :",
322
+ 1, 1000000, 150000, 10
323
+ )
324
+ puissance = st.number_input(
325
+ "Entrer la puissance du véhicule (en CV) :",
326
+ 40, 400, 100, 1
327
+ )
328
+ energie = st.selectbox(
329
+ 'Carburant :',
330
+ tuple(dataset_pricing['fuel'].unique())
331
+ )
332
+ couleur = st.selectbox(
333
+ 'Couleur du véhicule :',
334
+ tuple(dataset_pricing['paint_color'].unique())
335
+ )
336
+ car_type = st.selectbox(
337
+ 'Type de véhicule :',
338
+ tuple(dataset_pricing['car_type'].unique())
339
+ )
340
+
341
+ with col2:
342
+ # Options booléennes
343
+ options = {
344
+ 'parking': 'Place de parking privée',
345
+ 'gps': 'GPS intégré',
346
+ 'ac': 'Climatisation',
347
+ 'auto': 'Boîte automatique',
348
+ 'gac': 'GetAround Connect',
349
+ 'speed': 'Régulateur de vitesse',
350
+ 'hiver': 'Pneus hiver'
351
+ }
352
+
353
+ values = {}
354
+ for key, label in options.items():
355
+ values[key] = st.selectbox(label + ' :', ('Yes', 'No')) == 'Yes'
356
+
357
+ # Bouton de prédiction
358
+ if st.button("Predict"):
359
+ list_values = [
360
+ marque, int(kil), int(puissance), energie, couleur, car_type,
361
+ values['parking'], values['gps'], values['ac'], values['auto'],
362
+ values['gac'], values['speed'], values['hiver']
363
+ ]
364
+
365
+ result = predict_price(list_values)
366
+ if result > 0:
367
+ st.success(
368
+ f"Le montant de location à la journée de votre véhicule "
369
+ f"s'élève à {result:.2f} €"
370
+ )
371
+
372
+ # Configuration des pages
373
+ page_names_to_funcs = {
374
+ "Accueil": main_page,
375
+ "Dashboard": page2,
376
+ "Prédiction": page3,
377
+ }
378
+
379
+ # Sélection et affichage de la page
380
+ selected_page = st.sidebar.selectbox(
381
+ "Selectionner une page :",
382
+ page_names_to_funcs.keys()
383
+ )
384
+ page_names_to_funcs[selected_page]()
data/.DS_Store ADDED
Binary file (6.15 kB). View file
 
data/delay_analysis.csv ADDED
The diff for this file is too large to render. See raw diff
 
data/pricing_project.csv ADDED
The diff for this file is too large to render. See raw diff
 
docker-compose.yml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: "3.8"
2
+
3
+ services:
4
+ api:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile.api
8
+ ports:
9
+ - "7860:7860"
10
+
11
+ dashboard:
12
+ build:
13
+ context: .
14
+ dockerfile: Dockerfile.dashboard
15
+ ports:
16
+ - "8501:8501"