| import streamlit as st |
| import pandas as pd |
| import requests |
| from io import BytesIO |
|
|
| |
| st.set_page_config(layout="wide", page_title="Veille Sanitaire SCA - BuSCA") |
|
|
| |
| st.markdown(""" |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap'); |
| * { font-family: 'Roboto', sans-serif; } |
| [data-testid="stSidebar"] { |
| background-color: #2E3B4E; |
| color: #FFFFFF; |
| } |
| .stButton > button { |
| background-color: #1E88E5; |
| color: white; |
| border-radius: 5px; |
| } |
| .stButton > button:hover { |
| background-color: #1565C0; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| st.title("🔍 Veille Sanitaire BuSCA") |
| st.info("Les données sont chargées depuis la Plateforme SCA. Utilisez les filtres dans le menu latéral.") |
|
|
| |
| @st.cache_data(ttl=3600) |
| def load_data(): |
| |
| file_url = "https://www.plateforme-sca.fr/media/398/download" |
| |
| try: |
| |
| response = requests.get(file_url, headers={'User-Agent': 'Mozilla/5.0'}) |
| response.raise_for_status() |
| |
| df = pd.read_excel(BytesIO(response.content), engine='openpyxl') |
| |
| |
| df.columns = df.columns.str.strip().str.lower() |
| |
| |
| |
| rename_map = { |
| 'matrice': 'matrices', |
| 'danger': 'dangers' |
| } |
| df.rename(columns=rename_map, inplace=True) |
| |
| return df |
| except Exception as e: |
| st.error(f"Erreur critique lors du chargement des données : {e}") |
| return None |
|
|
| |
| df_full = load_data() |
|
|
| if df_full is None: |
| st.error("Impossible de continuer car les données n'ont pas pu être chargées.") |
| st.stop() |
|
|
| |
| COL_BUSCA = 'busca' |
| COL_TITRE = 'titre' |
| COL_MATRICE = 'matrices' |
| COL_DANGER = 'dangers' |
| COL_SECTION = 'section' |
| COL_TEXTE = 'texte' |
| COL_LIEN1 = 'lien' |
| COL_LIEN2 = 'lien2' |
|
|
| |
| essential_cols = [COL_BUSCA, COL_TITRE, COL_TEXTE, COL_MATRICE, COL_DANGER] |
| missing_cols = [col for col in essential_cols if col not in df_full.columns] |
| if missing_cols: |
| st.error(f"ERREUR : Les colonnes essentielles suivantes sont manquantes dans le fichier Excel : {', '.join(missing_cols)}") |
| st.write("Colonnes trouvées :", df_full.columns.tolist()) |
| st.stop() |
|
|
| |
| df_full = df_full.dropna(subset=[COL_BUSCA]) |
| |
| df_full[COL_BUSCA] = df_full[COL_BUSCA].astype(int) |
|
|
| |
| df_full = df_full.sort_values(by=COL_BUSCA, ascending=False) |
|
|
| |
| with st.sidebar: |
| st.header("🛠️ Filtres") |
| |
| if st.button("🔄 Rafraîchir les données"): |
| st.cache_data.clear() |
| st.rerun() |
|
|
| with st.expander("📌 Plage de numéros de BuSCA", expanded=True): |
| min_val = int(df_full[COL_BUSCA].min()) |
| max_val = int(df_full[COL_BUSCA].max()) |
| busca_range = st.slider("Numéros de BuSCA", min_val, max_val, (max_val - 20, max_val)) |
|
|
| with st.expander("🌍 Matrices"): |
| |
| unique_matrices = sorted(df_full[COL_MATRICE].fillna('Non spécifié').astype(str).unique()) |
| matrices = st.multiselect("Sélectionner les matrices", options=unique_matrices) |
|
|
| with st.expander("⚠️ Dangers"): |
| unique_dangers = sorted(df_full[COL_DANGER].fillna('Non spécifié').astype(str).unique()) |
| dangers = st.multiselect("Sélectionner les dangers", options=unique_dangers) |
|
|
| |
| with st.expander("🔎 Recherche par mots-clés"): |
| keywords = st.text_area("Mots-clés (séparés par des virgules)", placeholder="ex: listeria, lait, rappel...") |
|
|
| apply_filter = st.button("Appliquer les filtres", use_container_width=True) |
|
|
| |
| df_display = df_full.copy() |
|
|
| |
| |
| |
| |
| if apply_filter or True: |
| |
| df_display = df_display[ |
| (df_display[COL_BUSCA] >= busca_range[0]) & |
| (df_display[COL_BUSCA] <= busca_range[1]) |
| ] |
| |
| if matrices: |
| df_display = df_display[df_display[COL_MATRICE].astype(str).isin(matrices)] |
| if dangers: |
| df_display = df_display[df_display[COL_DANGER].astype(str).isin(dangers)] |
|
|
| |
| if keywords: |
| |
| keyword_list = [kw.strip().lower() for kw in keywords.split(',') if kw.strip()] |
| |
| |
| if keyword_list: |
| df_display = df_display[df_display.apply( |
| lambda row: any( |
| kw in str(row[COL_TITRE]).lower() or |
| kw in str(row[COL_TEXTE]).lower() or |
| kw in str(row[COL_DANGER]).lower() or |
| kw in str(row[COL_MATRICE]).lower() |
| for kw in keyword_list |
| ), |
| axis=1 |
| )] |
|
|
| |
| st.markdown(f"### 📑 Affichage de {len(df_display)} résultats") |
| if df_display.empty: |
| st.warning("Aucun bulletin ne correspond à vos critères de recherche.") |
| else: |
| for index, row in df_display.iterrows(): |
| titre = row.get(COL_TITRE, 'Titre manquant') |
| busca_num = row.get(COL_BUSCA, 'N/A') |
| |
| with st.expander(f"📄 **{titre}** (BuSCA n°{busca_num})"): |
| danger_val = row.get(COL_DANGER, 'N/A') |
| matrice_val = row.get(COL_MATRICE, 'N/A') |
| |
| st.markdown(f"**Danger :** `{danger_val}` | **Matrice :** `{matrice_val}`") |
| st.markdown("---") |
| |
| texte_content = str(row.get(COL_TEXTE, 'Texte manquant')).replace('\n', ' \n') |
| st.markdown(texte_content) |
| |
| st.markdown("---") |
| col1, col2 = st.columns(2) |
| with col1: |
| if pd.notna(row.get(COL_LIEN1)): |
| st.link_button("🔗 Lien Principal", row[COL_LIEN1]) |
| with col2: |
| if pd.notna(row.get(COL_LIEN2)): |
| st.link_button("🔗 Lien Secondaire", row[COL_LIEN2]) |