Create APPOK0503.py
Browse files- APPOK0503.py +184 -0
APPOK0503.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import requests
|
| 4 |
+
from io import BytesIO
|
| 5 |
+
import re
|
| 6 |
+
|
| 7 |
+
# 🖌️ Configurer le mode wide et le titre de la page
|
| 8 |
+
st.set_page_config(layout="wide", page_title="Veille Sanitaire SCA - BuSCA")
|
| 9 |
+
|
| 10 |
+
# 🖌️ Importer Google Fonts et personnaliser la sidebar
|
| 11 |
+
st.markdown("""
|
| 12 |
+
<style>
|
| 13 |
+
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap');
|
| 14 |
+
* { font-family: 'Roboto', sans-serif; }
|
| 15 |
+
/* Personnalisation de la sidebar */
|
| 16 |
+
[data-testid="stSidebar"] {
|
| 17 |
+
background-color: #2E3B4E; /* Fond sombre et lisible */
|
| 18 |
+
color: #FFFFFF; /* Texte blanc pour contraste */
|
| 19 |
+
width: 250px; /* Largeur légèrement augmentée */
|
| 20 |
+
border-right: 2px solid #1E88E5; /* Bordure droite bleue élégante */
|
| 21 |
+
}
|
| 22 |
+
/* Couleur des titres dans la sidebar */
|
| 23 |
+
[data-testid="stSidebar"] h1,
|
| 24 |
+
[data-testid="stSidebar"] h2,
|
| 25 |
+
[data-testid="stSidebar"] h3 {
|
| 26 |
+
color: #1E88E5; /* Bleu lisible pour les titres */
|
| 27 |
+
}
|
| 28 |
+
/* Personnalisation des boutons dans la sidebar */
|
| 29 |
+
.stButton > button {
|
| 30 |
+
background-color: #1E88E5; /* Bleu pour les boutons */
|
| 31 |
+
color: white;
|
| 32 |
+
border-radius: 5px;
|
| 33 |
+
padding: 3px 8px;
|
| 34 |
+
margin-bottom: 10px;
|
| 35 |
+
width: 100%; /* Largeur du bouton égale à celle de la sidebar */
|
| 36 |
+
}
|
| 37 |
+
.stButton > button:hover {
|
| 38 |
+
background-color: #1565C0; /* Bleu plus foncé au survol */
|
| 39 |
+
}
|
| 40 |
+
/* Personnalisation du bouton de la sidebar (❮) */
|
| 41 |
+
.css-1siy2j7, .css-18ni7ap {
|
| 42 |
+
color: #1E88E5; /* Change la couleur de l’icône ❮ */
|
| 43 |
+
}
|
| 44 |
+
.css-1siy2j7:hover, .css-18ni7ap:hover {
|
| 45 |
+
color: #1565C0; /* Couleur au survol de l’icône ❮ */
|
| 46 |
+
}
|
| 47 |
+
/* Liens dans la sidebar */
|
| 48 |
+
[data-testid="stSidebar"] a {
|
| 49 |
+
color: #90CAF9; /* Liens bleus clairs pour lisibilité */
|
| 50 |
+
text-decoration: none;
|
| 51 |
+
}
|
| 52 |
+
[data-testid="stSidebar"] a:hover {
|
| 53 |
+
text-decoration: underline;
|
| 54 |
+
}
|
| 55 |
+
/* Fond et bordures des expanders dans la sidebar */
|
| 56 |
+
[data-testid="stSidebar"] .streamlit-expander {
|
| 57 |
+
background-color: #374962; /* Fond sombre pour les expanders */
|
| 58 |
+
border: 1px solid #1E88E5; /* Bordure bleue pour les expanders */
|
| 59 |
+
}
|
| 60 |
+
[data-testid="stSidebar"] .streamlit-expanderHeader {
|
| 61 |
+
color: #FFFFFF; /* Texte blanc dans les titres d'expander */
|
| 62 |
+
}
|
| 63 |
+
/* Bannière personnalisée */
|
| 64 |
+
.banner {
|
| 65 |
+
background-image: url('https://github.com/M00N69/BUSCAR/blob/main/logo%2002%20copie.jpg?raw=true');
|
| 66 |
+
background-size: cover;
|
| 67 |
+
height: 120px; /* Hauteur de la bannière */
|
| 68 |
+
border-radius: 5px;
|
| 69 |
+
margin-top: -60px; /* Remonte la bannière plus haut dans l'écran */
|
| 70 |
+
margin-bottom: 20px;
|
| 71 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| 72 |
+
}
|
| 73 |
+
/* Liens par défaut dans le corps de la page */
|
| 74 |
+
a {
|
| 75 |
+
color: #1E88E5;
|
| 76 |
+
text-decoration: none;
|
| 77 |
+
}
|
| 78 |
+
a:hover {
|
| 79 |
+
text-decoration: underline;
|
| 80 |
+
}
|
| 81 |
+
</style>
|
| 82 |
+
<div class="banner"></div>
|
| 83 |
+
""", unsafe_allow_html=True)
|
| 84 |
+
|
| 85 |
+
# 🏷️ Titre principal et astuce d'utilisation
|
| 86 |
+
st.title("🔍 Veille SCA, Surveillance de la Chaine Alimentaire: BuSCA")
|
| 87 |
+
st.warning("👉 **Astuce :** Cliquez sur la flèche **<** en haut à gauche pour ouvrir/fermer la barre latérale et mieux voir les résultats !")
|
| 88 |
+
|
| 89 |
+
# 📦 Fonction pour charger les données avec cache
|
| 90 |
+
@st.cache_data
|
| 91 |
+
def load_data():
|
| 92 |
+
file_url = "https://www.plateforme-sca.fr/media/11/download"
|
| 93 |
+
try:
|
| 94 |
+
response = requests.get(file_url)
|
| 95 |
+
response.raise_for_status()
|
| 96 |
+
df = pd.read_excel(BytesIO(response.content), engine='openpyxl')
|
| 97 |
+
return df
|
| 98 |
+
except Exception as e:
|
| 99 |
+
st.error(f"Erreur lors du chargement des données : {e}")
|
| 100 |
+
return pd.DataFrame()
|
| 101 |
+
|
| 102 |
+
# 📦 Charger tous les BuSCA
|
| 103 |
+
df_full = load_data()
|
| 104 |
+
|
| 105 |
+
if df_full.empty:
|
| 106 |
+
st.write("Impossible de charger les données.")
|
| 107 |
+
else:
|
| 108 |
+
df_full.columns = df_full.columns.str.strip() # Supprimer les espaces dans les noms de colonnes
|
| 109 |
+
df_full = df_full.sort_values(by='BuSCA', ascending=False)
|
| 110 |
+
|
| 111 |
+
# 🌟 Appliquer par défaut le filtre sur les 5 derniers BuSCA
|
| 112 |
+
max_busca = int(df_full['BuSCA'].max())
|
| 113 |
+
min_busca = max_busca - 4 if max_busca > 4 else 1
|
| 114 |
+
df = df_full[(df_full['BuSCA'] >= min_busca) & (df_full['BuSCA'] <= max_busca)]
|
| 115 |
+
|
| 116 |
+
# 🌟 Menu latéral avec filtres complets
|
| 117 |
+
with st.sidebar:
|
| 118 |
+
st.header("🛠️ Filtres")
|
| 119 |
+
|
| 120 |
+
# 📌 Filtre par plage de numéros de BuSCA
|
| 121 |
+
with st.expander("📌 Plage de numéros de BuSCA"):
|
| 122 |
+
busca_range = st.slider("Numéros de BuSCA", min_value=int(df_full['BuSCA'].min()), max_value=int(df_full['BuSCA'].max()), value=(min_busca, max_busca))
|
| 123 |
+
|
| 124 |
+
# 📌 Autres filtres
|
| 125 |
+
with st.expander("🌍 Matrices"):
|
| 126 |
+
matrices = st.multiselect("Sélectionner les matrices", options=df_full['Matrice (catégories)'].unique())
|
| 127 |
+
|
| 128 |
+
with st.expander("⚠️ Dangers"):
|
| 129 |
+
dangers = st.multiselect("Sélectionner les dangers", options=df_full['Danger'].unique())
|
| 130 |
+
|
| 131 |
+
with st.expander("📂 Sections"):
|
| 132 |
+
sections = st.multiselect("Sélectionner les sections", options=df_full['Section'].unique())
|
| 133 |
+
|
| 134 |
+
with st.expander("🔎 Recherche par mots-clés"):
|
| 135 |
+
keywords = st.text_area("Mots-clés (séparés par des virgules)")
|
| 136 |
+
|
| 137 |
+
apply_filter = st.button("Appliquer les filtres", use_container_width=True)
|
| 138 |
+
|
| 139 |
+
# 🎯 Appliquer les filtres sur l'ensemble des BuSCA
|
| 140 |
+
if apply_filter:
|
| 141 |
+
df = df_full.copy() # Repartir sur toutes les données
|
| 142 |
+
with st.spinner('📊 Application des filtres...'):
|
| 143 |
+
df = df[(df['BuSCA'] >= busca_range[0]) & (df['BuSCA'] <= busca_range[1])]
|
| 144 |
+
if matrices:
|
| 145 |
+
df = df[df['Matrice (catégories)'].isin(matrices)]
|
| 146 |
+
if dangers:
|
| 147 |
+
df = df[df['Danger'].isin(dangers)]
|
| 148 |
+
if sections:
|
| 149 |
+
df = df[df['Section'].isin(sections)]
|
| 150 |
+
if keywords:
|
| 151 |
+
keyword_list = [kw.strip().lower() for kw in keywords.split(',')]
|
| 152 |
+
keyword_patterns = [re.compile(r'\b' + re.escape(kw) + r's?\b', re.IGNORECASE) for kw in keyword_list]
|
| 153 |
+
df = df[df.apply(lambda row: any(pattern.search(str(row)) for pattern in keyword_patterns), axis=1)]
|
| 154 |
+
st.success("Filtres appliqués avec succès !")
|
| 155 |
+
|
| 156 |
+
# 🗂️ Afficher les données avec `st.expander` optimisés
|
| 157 |
+
st.markdown("### 📑 Résultats filtrés")
|
| 158 |
+
for index, row in df.iterrows():
|
| 159 |
+
with st.expander(f"📄 {row['Titre']} (BuSCA n°{row['BuSCA']})"):
|
| 160 |
+
summary = row['Texte'][:200] + "..." if len(row['Texte']) > 200 else row['Texte']
|
| 161 |
+
st.markdown(f"**Résumé :** {summary}")
|
| 162 |
+
if len(row['Texte']) > 200:
|
| 163 |
+
st.markdown(f"**Texte complet :**\n{row['Texte']}")
|
| 164 |
+
|
| 165 |
+
# 🔗 Boutons pour les liens
|
| 166 |
+
link_col1, link_col2 = st.columns([1, 1])
|
| 167 |
+
with link_col1:
|
| 168 |
+
if pd.notna(row['Lien']):
|
| 169 |
+
st.markdown(f"[🔗 Lien 1]({row['Lien']})", unsafe_allow_html=True)
|
| 170 |
+
with link_col2:
|
| 171 |
+
if pd.notna(row['Lien2']):
|
| 172 |
+
st.markdown(f"[🔗 Lien 2]({row['Lien2']})", unsafe_allow_html=True)
|
| 173 |
+
|
| 174 |
+
# 🔗 Logo dans la barre latérale
|
| 175 |
+
st.sidebar.markdown(
|
| 176 |
+
"""
|
| 177 |
+
<div style="text-align: center;">
|
| 178 |
+
<a href="https://www.visipilot.com" target="_blank">
|
| 179 |
+
<img src="https://raw.githubusercontent.com/M00N69/RAPPELCONSO/main/logo%2004%20copie.jpg" alt="Visipilot Logo" style="width: 250px; margin-top: 20px;">
|
| 180 |
+
</a>
|
| 181 |
+
</div>
|
| 182 |
+
""", unsafe_allow_html=True
|
| 183 |
+
)
|
| 184 |
+
|