Spaces:
Sleeping
Sleeping
Commit ·
0ffaa2d
1
Parent(s): ddc29b3
Déploiement initial du dashboard Getaround Streamlit
Browse files- .gitattributes +4 -0
- Dockerfile +33 -0
- README.md +39 -4
- app.py +183 -0
- get_around_delay_analysis.xlsx +3 -0
- get_around_pricing_project.csv +3 -0
- getaround_EDA.ipynb +3 -0
- logo.png +3 -0
- requirements.txt +14 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.xlsx filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*.csv filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
*.ipynb filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
# Installer les dépendances système
|
| 5 |
+
RUN apt-get update && apt-get install -y \
|
| 6 |
+
build-essential \
|
| 7 |
+
libgl1-mesa-glx \
|
| 8 |
+
libglib2.0-0 \
|
| 9 |
+
curl \
|
| 10 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 11 |
+
|
| 12 |
+
# Mettre à jour pip + installer setuptools et wheel
|
| 13 |
+
RUN pip install --upgrade pip setuptools wheel
|
| 14 |
+
|
| 15 |
+
# Créer un dossier de travail
|
| 16 |
+
WORKDIR /app
|
| 17 |
+
|
| 18 |
+
# Copier les fichiers nécessaires
|
| 19 |
+
COPY requirements.txt ./
|
| 20 |
+
COPY app.py ./
|
| 21 |
+
COPY get_around_delay_analysis.xlsx ./
|
| 22 |
+
COPY get_around_pricing_project.csv ./
|
| 23 |
+
COPY logo.png ./
|
| 24 |
+
|
| 25 |
+
# Installer les dépendances Python (avec wheels)
|
| 26 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 27 |
+
|
| 28 |
+
# Exposer le port
|
| 29 |
+
EXPOSE 7860
|
| 30 |
+
|
| 31 |
+
# Lancer Streamlit
|
| 32 |
+
CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
| 33 |
+
|
README.md
CHANGED
|
@@ -1,11 +1,46 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
-
|
| 9 |
---
|
| 10 |
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Getaround Streamlit Dashboard
|
| 3 |
+
emoji: 🚗
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
+
app_port: 8501
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# 🚗 Dashboard Getaround
|
| 12 |
+
|
| 13 |
+
Bienvenue sur le tableau de bord interactif **Getaround** !
|
| 14 |
+
|
| 15 |
+
Ce dashboard Streamlit vous permet de :
|
| 16 |
+
|
| 17 |
+
### 📊 Analyses exploratoires (EDA)
|
| 18 |
+
- Explorer les **retards de location** et leur gravité.
|
| 19 |
+
- Visualiser l’impact des retards sur la **réservation suivante**.
|
| 20 |
+
- Analyser la **répartition des check-ins** et des états de location.
|
| 21 |
+
- Étudier la **distribution des prix** selon :
|
| 22 |
+
- le type de voiture 🚙
|
| 23 |
+
- le carburant ⛽
|
| 24 |
+
- la présence de Getaround Connect 🔗
|
| 25 |
+
- la puissance moteur ⚙️
|
| 26 |
+
|
| 27 |
+
### 🔮 Prédiction du prix
|
| 28 |
+
- Un formulaire intelligent permet d’estimer le prix journalier d’une voiture à partir de ses caractéristiques.
|
| 29 |
+
- L’estimation est faite via une API déployée dans un [Space Hugging Face dédié](https://huggingface.co/spaces/licorne2lc/get_around_api).
|
| 30 |
+
|
| 31 |
+
### 📎 Liens utiles
|
| 32 |
+
- 🔌 API utilisée pour les prédictions : [Voir la documentation Swagger](https://licorne2lc-get-around-api.hf.space/docs)
|
| 33 |
+
- 🧠 Notebook d’analyse : inclus dans la version locale
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
**Technologies** :
|
| 38 |
+
- Streamlit
|
| 39 |
+
- Plotly
|
| 40 |
+
- Seaborn & Matplotlib
|
| 41 |
+
- API REST (FastAPI)
|
| 42 |
+
- Docker
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
**Auteur** : [licorne2lc](https://huggingface.co/licorne2lc) • Projet encadré par Jedha Bootcamp
|
app.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import streamlit as st
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import matplotlib.pyplot as plt
|
| 5 |
+
import seaborn as sns
|
| 6 |
+
import requests
|
| 7 |
+
import plotly.express as px
|
| 8 |
+
from streamlit_option_menu import option_menu
|
| 9 |
+
from PIL import Image
|
| 10 |
+
|
| 11 |
+
# ========== CONFIGURATION ==========
|
| 12 |
+
st.set_page_config(page_title="Getaround Dashboard", layout="wide")
|
| 13 |
+
st.markdown("<h1 style='text-align: center;'>🚗 Getaround Dashboard</h1>", unsafe_allow_html=True)
|
| 14 |
+
|
| 15 |
+
# ========== LOGO ==========
|
| 16 |
+
try:
|
| 17 |
+
logo = Image.open("logo.png")
|
| 18 |
+
st.sidebar.image(logo, width=200)
|
| 19 |
+
except Exception:
|
| 20 |
+
st.sidebar.warning("Logo non chargé")
|
| 21 |
+
|
| 22 |
+
# ========== MENU ==========
|
| 23 |
+
with st.sidebar:
|
| 24 |
+
selected = option_menu(
|
| 25 |
+
"Menu principal",
|
| 26 |
+
["🏁 EDA delay", "📈 EDA pricing", "🔮 API prediction"],
|
| 27 |
+
icons=["hourglass-split", "bar-chart-line", "cpu"],
|
| 28 |
+
menu_icon="cast",
|
| 29 |
+
default_index=0,
|
| 30 |
+
)
|
| 31 |
+
st.markdown("---")
|
| 32 |
+
st.markdown("📄 [Espace API HuggingFace](https://huggingface.co/spaces/licorne2lc/get_around_api)")
|
| 33 |
+
st.markdown("🛠️ [Docs API /predict](https://licorne2lc-get-around-api.hf.space/docs)")
|
| 34 |
+
|
| 35 |
+
# ========== 1. EDA DELAY ==========
|
| 36 |
+
if selected == "🏁 EDA delay":
|
| 37 |
+
st.title("📊 Analyse des retards de Getaround")
|
| 38 |
+
try:
|
| 39 |
+
dataset = pd.read_excel("get_around_delay_analysis.xlsx")
|
| 40 |
+
|
| 41 |
+
dataset["delay_status"] = dataset["delay_at_checkout_in_minutes"].apply(
|
| 42 |
+
lambda x: "En avance" if x < 0 else "À l’heure" if x == 0 else "En retard"
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
def catégoriser_retard(x):
|
| 46 |
+
if x <= 0:
|
| 47 |
+
return "Pas de retard"
|
| 48 |
+
elif x <= 30:
|
| 49 |
+
return "< 30 min"
|
| 50 |
+
elif x <= 60:
|
| 51 |
+
return "30-60 min"
|
| 52 |
+
elif x <= 120:
|
| 53 |
+
return "1h-2h"
|
| 54 |
+
elif x <= 240:
|
| 55 |
+
return "2h-4h"
|
| 56 |
+
else:
|
| 57 |
+
return "> 4h"
|
| 58 |
+
|
| 59 |
+
retard_order = ["Pas de retard", "< 30 min", "30-60 min", "1h-2h", "2h-4h", "> 4h"]
|
| 60 |
+
dataset["retard_cat"] = dataset["delay_at_checkout_in_minutes"].apply(catégoriser_retard)
|
| 61 |
+
dataset["retard_cat"] = pd.Categorical(dataset["retard_cat"], categories=retard_order, ordered=True)
|
| 62 |
+
|
| 63 |
+
fig_checkin = px.pie(dataset, names="checkin_type", title="Répartition des types de check-in",
|
| 64 |
+
hole=0.4, color_discrete_sequence=px.colors.qualitative.Pastel, width=1000, height=400)
|
| 65 |
+
st.plotly_chart(fig_checkin)
|
| 66 |
+
|
| 67 |
+
fig_state = px.pie(dataset, names="state", title="Répartition des états de location",
|
| 68 |
+
hole=0.4, color_discrete_sequence=px.colors.qualitative.Set3, width=1000, height=400)
|
| 69 |
+
st.plotly_chart(fig_state)
|
| 70 |
+
|
| 71 |
+
fig_delay_status = px.pie(dataset, names="delay_status", title="Retards : avance / à l’heure / en retard",
|
| 72 |
+
hole=0.4, color_discrete_sequence=px.colors.qualitative.Vivid, width=1000, height=400)
|
| 73 |
+
st.plotly_chart(fig_delay_status)
|
| 74 |
+
|
| 75 |
+
filtered = dataset[(dataset["delay_at_checkout_in_minutes"] > 0) & (dataset["delay_at_checkout_in_minutes"] <= 1440)]
|
| 76 |
+
fig_delay_hist = px.histogram(filtered, x="delay_at_checkout_in_minutes", nbins=50,
|
| 77 |
+
title="Distribution des retards (0 à 1440 minutes)", width=1000, height=400)
|
| 78 |
+
st.plotly_chart(fig_delay_hist)
|
| 79 |
+
|
| 80 |
+
fig_retard_cat = px.bar(dataset["retard_cat"].value_counts().reindex(retard_order),
|
| 81 |
+
title="Gravité des retards", labels={"value": "Nombre", "index": "Catégorie"},
|
| 82 |
+
width=1000, height=400)
|
| 83 |
+
st.plotly_chart(fig_retard_cat)
|
| 84 |
+
|
| 85 |
+
dataset_for_join_B = dataset[["rental_id", "delay_at_checkout_in_minutes"]].rename(
|
| 86 |
+
columns={"rental_id": "rental_id_join", "delay_at_checkout_in_minutes": "delay_at_checkout_in_minutes_join"}
|
| 87 |
+
)
|
| 88 |
+
dataset_join = pd.merge(dataset, dataset_for_join_B,
|
| 89 |
+
left_on="previous_ended_rental_id", right_on="rental_id_join")
|
| 90 |
+
dataset_join["real_delay"] = dataset_join["time_delta_with_previous_rental_in_minutes"] - dataset_join["delay_at_checkout_in_minutes_join"]
|
| 91 |
+
dataset_join_clean = dataset_join.dropna(subset=["delay_at_checkout_in_minutes_join"])
|
| 92 |
+
dataset_join_clean["annulation_due_au_retard"] = dataset_join_clean.apply(
|
| 93 |
+
lambda row: "Oui" if row["state"] == "canceled" and row["real_delay"] < 0 else
|
| 94 |
+
"Annulée (autre raison)" if row["state"] == "canceled" else "Non", axis=1
|
| 95 |
+
)
|
| 96 |
+
fig_annul = px.pie(dataset_join_clean, names="annulation_due_au_retard",
|
| 97 |
+
title="Répartition des annulations dues au retard du locataire précédent",
|
| 98 |
+
hole=0.4, color_discrete_sequence=px.colors.qualitative.Bold, width=1000, height=400)
|
| 99 |
+
st.plotly_chart(fig_annul)
|
| 100 |
+
|
| 101 |
+
except FileNotFoundError:
|
| 102 |
+
st.error("❌ Fichier Excel non trouvé : get_around_delay_analysis.xlsx")
|
| 103 |
+
|
| 104 |
+
# ========== 2. EDA PRICING ==========
|
| 105 |
+
elif selected == "📈 EDA pricing":
|
| 106 |
+
st.title("💰 Analyse des prix de location Getaround")
|
| 107 |
+
try:
|
| 108 |
+
df_price = pd.read_csv("get_around_pricing_project.csv")
|
| 109 |
+
|
| 110 |
+
st.subheader("Distribution du prix journalier")
|
| 111 |
+
fig, ax = plt.subplots(figsize=(10, 4))
|
| 112 |
+
sns.histplot(df_price["rental_price_per_day"], bins=50, kde=True, ax=ax)
|
| 113 |
+
st.pyplot(fig)
|
| 114 |
+
|
| 115 |
+
st.subheader("Prix par type de carburant")
|
| 116 |
+
fig, ax = plt.subplots(figsize=(10, 4))
|
| 117 |
+
sns.boxplot(x="fuel", y="rental_price_per_day", data=df_price, ax=ax)
|
| 118 |
+
st.pyplot(fig)
|
| 119 |
+
|
| 120 |
+
st.subheader("Prix par type de voiture")
|
| 121 |
+
fig, ax = plt.subplots(figsize=(10, 4))
|
| 122 |
+
sns.boxplot(x="car_type", y="rental_price_per_day", data=df_price, ax=ax)
|
| 123 |
+
st.pyplot(fig)
|
| 124 |
+
|
| 125 |
+
st.subheader("Prix selon la présence de Getaround Connect")
|
| 126 |
+
fig, ax = plt.subplots(figsize=(10, 4))
|
| 127 |
+
sns.boxplot(x="has_getaround_connect", y="rental_price_per_day", data=df_price, ax=ax)
|
| 128 |
+
st.pyplot(fig)
|
| 129 |
+
|
| 130 |
+
st.subheader("Corrélation entre puissance moteur et prix journalier")
|
| 131 |
+
fig, ax = plt.subplots(figsize=(10, 4))
|
| 132 |
+
sns.scatterplot(x="engine_power", y="rental_price_per_day", data=df_price, hue="car_type", ax=ax)
|
| 133 |
+
st.pyplot(fig)
|
| 134 |
+
except FileNotFoundError:
|
| 135 |
+
st.error("❌ Fichier CSV non trouvé : get_around_pricing_project.csv")
|
| 136 |
+
|
| 137 |
+
# ========== 3. API PREDICTION ==========
|
| 138 |
+
elif selected == "🔮 API prediction":
|
| 139 |
+
st.title("🔮 Prédiction du prix de location")
|
| 140 |
+
st.markdown("Remplis les informations suivantes pour obtenir une estimation du prix")
|
| 141 |
+
|
| 142 |
+
with st.form("prediction_form"):
|
| 143 |
+
model_key = st.selectbox("Modèle de voiture", ["Peugeot", "Audi", "BMW"])
|
| 144 |
+
mileage = st.number_input("Kilométrage", value=50000)
|
| 145 |
+
engine_power = st.number_input("Puissance moteur", value=100)
|
| 146 |
+
fuel = st.selectbox("Type de carburant", ["diesel", "petrol", "electric", "hybrid"])
|
| 147 |
+
paint_color = st.selectbox("Couleur", ["black", "white", "grey", "blue", "red"])
|
| 148 |
+
car_type = st.selectbox("Type de voiture", ["sedan", "convertible", "suv", "coupe"])
|
| 149 |
+
private_parking_available = st.checkbox("Parking privé disponible", value=True)
|
| 150 |
+
has_gps = st.checkbox("GPS", value=True)
|
| 151 |
+
has_air_conditioning = st.checkbox("Climatisation", value=True)
|
| 152 |
+
automatic_car = st.checkbox("Boîte automatique", value=True)
|
| 153 |
+
has_getaround_connect = st.checkbox("Getaround Connect", value=True)
|
| 154 |
+
has_speed_regulator = st.checkbox("Régulateur de vitesse", value=True)
|
| 155 |
+
winter_tires = st.checkbox("Pneus hiver", value=True)
|
| 156 |
+
submitted = st.form_submit_button("📤 Envoyer")
|
| 157 |
+
|
| 158 |
+
if submitted:
|
| 159 |
+
data = {
|
| 160 |
+
"model_key": model_key,
|
| 161 |
+
"mileage": mileage,
|
| 162 |
+
"engine_power": engine_power,
|
| 163 |
+
"fuel": fuel,
|
| 164 |
+
"paint_color": paint_color,
|
| 165 |
+
"car_type": car_type,
|
| 166 |
+
"private_parking_available": private_parking_available,
|
| 167 |
+
"has_gps": has_gps,
|
| 168 |
+
"has_air_conditioning": has_air_conditioning,
|
| 169 |
+
"automatic_car": automatic_car,
|
| 170 |
+
"has_getaround_connect": has_getaround_connect,
|
| 171 |
+
"has_speed_regulator": has_speed_regulator,
|
| 172 |
+
"winter_tires": winter_tires,
|
| 173 |
+
}
|
| 174 |
+
api_url = "https://licorne2lc-get-around-api.hf.space/predict"
|
| 175 |
+
try:
|
| 176 |
+
response = requests.post(api_url, json=data)
|
| 177 |
+
if response.status_code == 200:
|
| 178 |
+
price = round(response.json()["prediction"], 2)
|
| 179 |
+
st.success(f"💰 Prix de location estimé : {price} € / jour")
|
| 180 |
+
else:
|
| 181 |
+
st.error(f"❌ Erreur {response.status_code} : {response.text}")
|
| 182 |
+
except Exception as e:
|
| 183 |
+
st.error(f"❌ Erreur lors de la requête : {e}")
|
get_around_delay_analysis.xlsx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a5c17402d38c9245f64a5139de72f671fcd518217afc2bcbc427b48f3c069263
|
| 3 |
+
size 889529
|
get_around_pricing_project.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cbc7e7e3e05837c21690ef49a1741c962ab79b1909df3c9bae883ead84f58c8f
|
| 3 |
+
size 408763
|
getaround_EDA.ipynb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8cb343ce7c1f0f2e5737fdbf797330d89359d9a9b80dea94060d79ae0456ffd8
|
| 3 |
+
size 2517879
|
logo.png
ADDED
|
Git LFS Details
|
requirements.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
bokeh==3.6.0
|
| 2 |
+
numpy==1.26.0
|
| 3 |
+
plotly==5.24.1
|
| 4 |
+
streamlit-lottie==0.0.5
|
| 5 |
+
|
| 6 |
+
streamlit==1.37.1
|
| 7 |
+
pandas==2.2.1
|
| 8 |
+
matplotlib==3.7.3
|
| 9 |
+
seaborn==0.13.2
|
| 10 |
+
requests==2.32.3
|
| 11 |
+
streamlit-option-menu==0.4.0
|
| 12 |
+
Pillow==10.3.0
|
| 13 |
+
pyarrow==16.1.0
|
| 14 |
+
openpyxl==3.1.2
|