exlive / app.py
nsaintsever's picture
Update app.py
7c67a2b verified
raw
history blame contribute delete
9.06 kB
import streamlit as st
import requests
import pandas as pd
import plotly.express as px
from bs4 import BeautifulSoup
import random
import time
# Configuration de l'interface Streamlit
st.set_page_config(page_title="Wine prices", layout="wide")
# 🟢 Initialisation du session state
if "selected_wines" not in st.session_state:
st.session_state.selected_wines = []
dark_mode_css = """
<style>
body {
background-color: #0e1117;
color: white;
}
.stApp {
background-color: #0e1117;
}
.stSidebar {
background-color: #161a25;
}
.stTextInput, .stSelectbox, .stButton, .stDataFrame, .stTable {
background-color: #21262d;
color: white;
}
.stPlotlyChart {
background-color: transparent;
}
</style>
"""
st.markdown(dark_mode_css, unsafe_allow_html=True)
# Charger le fichier Excel contenant la correspondance LWIN7 -> Nom du vin
@st.cache_data
def load_wine_data():
return pd.read_excel("LWINdatabase.xlsx") # Assure-toi d'avoir ce fichier avec LWIN7, DISPLAY_NAME
df_wines = load_wine_data()
# Sélection du vin par l'utilisateur
def select_wine():
return st.sidebar.selectbox("Sélectionnez un vin :", df_wines["DISPLAY_NAME"].unique())
# Génération du LWIN11
def generate_lwin11(lwin7, vintage):
return str(lwin7) + str(vintage)
# Récupération du taux de change GBP/EUR depuis Boursorama
def get_exchange_rate():
url = "https://www.boursorama.com/bourse/devises/taux-de-change-livresterling-euro-GBP-EUR/"
response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
if response.status_code == 200:
soup = BeautifulSoup(response.text, "html.parser")
rate_tag = soup.find("span", class_="c-instrument c-instrument--last")
if rate_tag:
rate = rate_tag.text.replace(",", ".") # Convertir en format float
return float(rate)
st.error("Impossible de récupérer le taux de change GBP/EUR.")
return 1.2 # Valeur par défaut
cookies_list = [
{'_vinous_session' : "BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiJWQ4MDUyOTllZGNkZDE5YTUyZWE3OTU0NzZjMTRjMDk1BjsAVEkiGXdhcmRlbi51c2VyLnVzZXIua2V5BjsAVFsHWwZpAq5mSSIiJDJhJDEwJFdTaU9uamkydDBNN3RqMG01YXRJVS4GOwBUSSIdd2FyZGVuLnVzZXIudXNlci5zZXNzaW9uBjsAVHsGSSIUbGFzdF9yZXF1ZXN0X2F0BjsAVGwrB5mlAGg%3D--7777dea1a4299409509b5af6636777dd9f84f0b7"}, # via Max
]
def get_vinous_data(lwin11, display_name):
headers = {"User-Agent": "Mozilla/5.0", "Accept": "application/json"}
current_cookie = random.choice(cookies_list)
url_prices = f"https://vinous.com/api/v2/wine/{lwin11}/livex_price_history"
response_prices = requests.get(url_prices, headers=headers, cookies=current_cookie)
time.sleep(0.5)
df_prices = pd.DataFrame()
if response_prices.status_code == 200:
try:
data_prices = response_prices.json().get("livex_price_history", [])
if not data_prices:
st.warning(f"🚨 Malheureusement, le vin **{display_name} ({lwin11})** n'est pas disponible.")
return pd.DataFrame()
df_prices = pd.DataFrame(data_prices)
except Exception as e:
st.error(f"⚠️ Erreur lors du traitement des données pour **{display_name} ({lwin11})**.")
return pd.DataFrame()
else:
st.warning(f"🚨 Impossible de récupérer les prix pour **{display_name} ({lwin11})**. Code erreur: {response_prices.status_code}")
return pd.DataFrame()
return df_prices
# Convertir les prix en EUR et diviser par 12 pour obtenir le prix par bouteille
def convert_price_per_bottle(df_prices, exchange_rate):
df_prices["date_string"] = pd.to_datetime(df_prices["date_string"], format="%Y-%m-%d")
df_prices["value"] = df_prices["value"].astype(float)
df_prices["price_per_bottle"] = (df_prices["value"] * exchange_rate) / 12
return df_prices
def check_wine_availability(lwin11):
"""Teste la disponibilité d'un vin/millésime via un appel API."""
headers = {"User-Agent": "Mozilla/5.0", "Accept": "application/json"}
current_cookie = random.choice(cookies_list)
url = f"https://vinous.com/api/v2/wine/{lwin11}/livex_price_history"
response = requests.get(url, headers=headers, cookies=current_cookie)
if response.status_code == 200:
try:
data = response.json()
if "error" in data:
return False # Vin non disponible
return True # Vin disponible
except:
return False # Erreur JSON
return False # Code erreur autre que 200
# Sélection du vin par l'utilisateur
st.sidebar.title("Filtres")
# 🟢 Sélection de la région
selected_region = st.sidebar.selectbox("Sélectionnez une région :", df_wines["REGION"].dropna().unique())
# 🟢 Filtrer les vins selon la région sélectionnée
filtered_wines = df_wines[df_wines["REGION"] == selected_region]
# 🟢 Sélection **multiple** de vins parmi les vins de la région
selected_wines = st.sidebar.multiselect("Sélectionnez un ou plusieurs vins :", filtered_wines["DISPLAY_NAME"].unique())
# 🟢 Filtrer les millésimes selon les vins sélectionnés
if selected_wines:
filtered_vintages = sorted(range(1925, 2026), reverse=True)
selected_vintages = st.sidebar.multiselect("Sélectionnez un ou plusieurs millésimes :", filtered_vintages)
else:
selected_vintages = []
# 🟢 Générer les LWIN11 pour chaque combinaison vin-millésime
wine_selections = []
for selected_wine in selected_wines:
wine_data = filtered_wines[filtered_wines["DISPLAY_NAME"] == selected_wine].iloc[0]
lwin7 = wine_data["LWIN"]
for vintage in selected_vintages:
lwin11 = f"{lwin7}{vintage}"
wine_selections.append((selected_wine, vintage, lwin11))
# 🟢 Vérifier la disponibilité des vins/millésimes avant ajout au graphique
# 🟢 Vérifier la disponibilité des vins/millésimes avant ajout au graphique
available_wines = []
for wine_name, vintage, lwin11 in wine_selections:
if check_wine_availability(lwin11): # Vérifie si le vin/millésime est dispo via API
available_wines.append((wine_name, vintage, lwin11))
else:
st.sidebar.warning(f"⚠️ {wine_name} ({vintage}) n'est pas disponible.")
# 🟢 Bouton pour ajouter au graphique les vins/millésimes disponibles (évite les doublons)
if available_wines and st.sidebar.button("Ajouter au graphique"):
for wine in available_wines:
if wine not in st.session_state.selected_wines: # Ajout uniquement si non présent
st.session_state.selected_wines.append(wine)
# 🟢 Bouton pour réinitialiser le graphique
if st.sidebar.button("Reset graphique"):
st.session_state.selected_wines = []
# Récupération et affichage des prix
if st.session_state.selected_wines:
fig = px.line(
title="Évolution des Prix",
color_discrete_sequence=px.colors.qualitative.Safe
)
# Récupérer le taux de change une seule fois
exchange_rate = get_exchange_rate()
all_dates = [] # Stocke toutes les dates pour fixer l'axe X
for wine_name, vintage, lwin11 in st.session_state.selected_wines:
df_prices = get_vinous_data(lwin11, wine_name)
if not df_prices.empty:
df_prices = convert_price_per_bottle(df_prices, exchange_rate)
all_dates.extend(df_prices["date_string"]) # Ajoute toutes les dates au pool global
df_prices["tooltip"] = df_prices.apply(
lambda row: f"<b>Prix:</b> {row['price_per_bottle']:.2f} EUR<br><b>Date:</b> {row['date_string']}",
axis=1
)
last_price = df_prices["price_per_bottle"].iloc[-1]
fig.add_scatter(
x=df_prices["date_string"],
y=df_prices["price_per_bottle"],
mode="lines+markers",
name=f"{wine_name} ({vintage}) - {last_price:.2f} EUR",
text=df_prices["tooltip"],
hoverinfo="text"
)
# Récupérer la plage complète des dates pour fixer l'axe X
if all_dates:
min_date = min(all_dates)
max_date = max(all_dates)
fig.update_layout(
height=600,
legend=dict(
orientation="h",
yanchor="bottom", y=-0.3,
xanchor="center", x=0.5
),
xaxis=dict(
tickangle=45,
tickmode="array",
tickvals=pd.date_range(
start=min_date,
end=max_date,
freq='Q'
).strftime('%Y-%m').tolist(),
tickformat="%Y-%m",
range=[min_date, max_date] # Fixe la plage de l'axe X
),
margin=dict(l=20, r=20, t=50, b=100),
)
st.plotly_chart(fig, use_container_width=True)