diff --git a/.env b/.env index 7389ee4..582b375 100644 --- a/.env +++ b/.env @@ -3,6 +3,7 @@ DB_HOST=162.19.78.131 DB_USER=excel DB_PASSWORD='%n#%3Lay1MPa$%kR^5@' DB_NAME=Acces +DB_NAME2=Sondes ADMIN_USER=Michel ADMIN_PASS_HASH='$2b$12$Dgv7jNLJuR.3hQminSVE9OP6hCSmW4nISArR3HF5LTPGFK0Zw29N2' diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 01a8cd9..a1dccde 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -5,7 +5,7 @@ mariadb true org.mariadb.jdbc.Driver - jdbc:mariadb://162.19.78.131:3306/Acces + jdbc:mariadb://162.19.78.131:3306/Sondes $ProjectFileDir$ diff --git a/app/Test_Mysql.py b/app/Test_Mysql.py deleted file mode 100644 index 45559f5..0000000 --- a/app/Test_Mysql.py +++ /dev/null @@ -1,11 +0,0 @@ -import mysql.connector, os -cnx = mysql.connector.connect( - host=os.getenv("DB_HOST"), - port=int(os.getenv("DB_PORT", "3306")), - user=os.getenv("DB_USER"), - password=os.getenv("DB_PASS"), - database=os.getenv("DB_NAME"), -) -print("OK, connecté !") -cnx.close() - diff --git a/app/injection_tables.py b/app/injection_tables.py new file mode 100644 index 0000000..dabda7d --- /dev/null +++ b/app/injection_tables.py @@ -0,0 +1,227 @@ +import os +import datetime as dt +import pandas as pd +import streamlit as st +from dotenv import load_dotenv +import mysql.connector as mc + +# ---------------------- +# Config de la page +# ---------------------- +st.set_page_config(page_title="Injection de données de test", page_icon="🧪", layout="centered") +st.title("🧪 Injecteur de relevés de test (Sondes)") +st.caption("Crée ~10 lignes au-dessus d'un seuil pour tester les alertes") + +# ---------------------- +# Connexion MySQL depuis .env +# ---------------------- +@st.cache_resource(show_spinner=False) +def get_connection(): + load_dotenv() + try: + cnx = mc.connect( + host=os.getenv("DB_HOST"), + user=os.getenv("DB_USER"), + password=os.getenv("DB_PASSWORD"), + database=os.getenv("DB_NAME2"), + autocommit=True, + ) + return cnx + except Exception as e: + st.error(f"Échec de connexion MySQL : {e}") + raise + +# ---------------------- +# Helpers : liste des sondes +# ---------------------- +@st.cache_data(ttl=60, show_spinner=False) +def list_sondes(site: str) -> list: + """ + Retourne la liste des sondes pour un site donné. + 1) Sondes actives dans Chambres_froides + 2) À défaut : DISTINCT Sonde dans la table de mesures {site} + Affiche des messages DEBUG en cas d'erreur ou de liste vide. + """ + cnx = get_connection() + cur = cnx.cursor() + + # 1) Chambres_froides + try: + cur.execute( + """ + SELECT Sonde + FROM `Chambres_froides` + WHERE Lieu = %s + AND (Etat = 'ON' OR Etat = 1) + ORDER BY Sonde + """, + (site,), + ) + rows = [r[0] for r in cur.fetchall()] + if rows: + st.info(f"[DEBUG] {len(rows)} sonde(s) trouvée(s) dans Chambres_froides pour Lieu='{site}'.") + return rows + else: + st.info(f"[DEBUG] Aucune sonde active dans Chambres_froides pour Lieu='{site}'.") + except Exception as e: + st.error(f"[DEBUG] Erreur Chambres_froides : {e}") + + # 2) Fallback : table de mesures `{site}` + try: + cur.execute(f"SELECT DISTINCT Sonde FROM `{site}` ORDER BY Sonde LIMIT 200") + rows = [r[0] for r in cur.fetchall()] + if rows: + st.info(f"[DEBUG] {len(rows)} sonde(s) trouvée(s) dans la table `{site}`.") + return rows + else: + st.info(f"[DEBUG] Aucune sonde trouvée dans la table `{site}`.") + except Exception as e: + st.error(f"[DEBUG] Erreur table `{site}` : {e}") + + return [] + +# ---------------------- +# Helper : récupérer Temp_Max pour une sonde donnée +# ---------------------- +@st.cache_data(ttl=60, show_spinner=False) +def get_temp_max(site: str, sonde: str): + """ + Retourne Temp_Max pour (site, sonde) depuis Chambres_froides. + Renvoie None si non trouvé, avec messages DEBUG. + """ + try: + cnx = get_connection() + cur = cnx.cursor() + cur.execute( + """ + SELECT Temp_Max + FROM `Chambres_froides` + WHERE Lieu = %s AND Sonde = %s + LIMIT 1 + """, + (site, sonde), + ) + row = cur.fetchone() + if row and row[0] is not None: + st.info(f"[DEBUG] Temp_Max trouvé pour {site}/{sonde} : {row[0]}°C.") + return float(row[0]) + else: + st.info(f"[DEBUG] Aucun Temp_Max trouvé dans Chambres_froides pour {site}/{sonde}.") + except Exception as e: + st.error(f"[DEBUG] Erreur get_temp_max : {e}") + return None + + return None + + +# ---------------------- +# UI paramètres +# ---------------------- +with st.sidebar: + st.header("Paramètres") + site = st.selectbox("Site (table)", ["Saclay", "Meudon"], index=0) + + # Sélecteur de sonde depuis la liste + options_sondes = list_sondes(site) + if not options_sondes: + st.warning("Aucune sonde trouvée pour ce site. Vous pouvez saisir un nom manuel.") + sonde = st.text_input("Nom de la sonde", value="TEST_Chambre1") + else: + sonde = st.selectbox("Sonde", options_sondes) + + st.subheader("Température") + # Auto-remplissage du seuil depuis la base et verrouillage par défaut + _temp_db = get_temp_max(site, sonde) + if _temp_db is None: + st.warning("Temp_Max introuvable en base ; valeur par défaut 6.0°C.") + _temp_db = 6.0 + allow_edit = st.checkbox("Autoriser la modification du seuil", value=False) + temp_max = st.number_input("Seuil (Temp_Max)", value=float(_temp_db), step=0.1, disabled=not allow_edit) + + delta = st.number_input("Delta au-dessus du seuil", value=1.0, step=0.1) + absolute_override = st.checkbox("Définir une température absolue à la place") + absolute_temp = st.number_input( + "Température absolue (si coché)", value=12.5, step=0.1, disabled=not absolute_override + ) + + st.subheader("Série temporelle") + rows = st.number_input("Nombre de points", min_value=1, max_value=200, value=10, step=1) + step_min = st.number_input("Pas (minutes)", min_value=1, max_value=120, value=5, step=1) + start_offset = st.number_input("Début : il y a (minutes)", min_value=0, max_value=1440, value=5, step=5) + + + st.markdown("---") + st.caption("Nettoyage rapide") + cleanup_scope = st.selectbox("Supprimer", ["Cette sonde", "Toutes les TEST_ des dernières 24h"]) + do_cleanup = st.button("🧹 Supprimer les données de test") +col1, col2 = st.columns(2) +# ---------------------- +# Actions +# ---------------------- +if col1.button("🚀 Injecter les données"): + try: + cnx = get_connection() + cur = cnx.cursor() + + # Calcul des timestamps et de la valeur + now = dt.datetime.now() + t0 = now - dt.timedelta(minutes=int(start_offset)) + if absolute_override: + value = float(absolute_temp) + else: + value = float(temp_max) + float(delta) + + # Préparation batch INSERT + sql = f"INSERT INTO `{site}` (Sonde, Temperature, Date) VALUES (%s,%s,%s)" + batch = [] + for i in range(int(rows)): + ts = t0 + dt.timedelta(minutes=i * int(step_min)) + batch.append((sonde, value, ts.strftime("%Y-%m-%d %H:%M:%S"))) + + cur.executemany(sql, batch) + st.success(f"{len(batch)} lignes insérées dans `{site}` pour **{sonde}** à **{value}°C**.") + + # Aperçu des données insérées + cur.execute( + f""" + SELECT Id, Sonde, Temperature, Date + FROM `{site}` + WHERE Sonde = %s AND Date >= %s AND Date <= %s + ORDER BY Date DESC + LIMIT 50 + """, + ( + sonde, + (t0 - dt.timedelta(minutes=1)).strftime("%Y-%m-%d %H:%M:%S"), + (t0 + dt.timedelta(minutes=int(rows)*int(step_min) + 1)).strftime("%Y-%m-%d %H:%M:%S"), + ), + ) + rows_preview = cur.fetchall() + if rows_preview: + df = pd.DataFrame(rows_preview, columns=["Id", "Sonde", "Temperature", "Date"]) + st.dataframe(df, use_container_width=True, hide_index=True) + else: + st.info("Aucune ligne trouvée pour l'aperçu (vérifiez les filtres/horaires).") + + except Exception as e: + st.error(f"Erreur lors de l'injection : {e}") + +# Nettoyage +def cleanup(): + cnx = get_connection() + cur = cnx.cursor() + if cleanup_scope == "Cette sonde": + cur.execute(f"DELETE FROM `{site}` WHERE Sonde = %s", (sonde,)) + st.success(f"Données supprimées pour la sonde **{sonde}** dans `{site}`.") + else: + cur.execute( + f"DELETE FROM `{site}` WHERE Sonde LIKE 'TEST\_%' ESCAPE '\\' AND Date >= NOW() - INTERVAL 1 DAY" + ) + st.success(f"Toutes les sondes **TEST_** des dernières 24h supprimées dans `{site}`.") + +if do_cleanup: + try: + cleanup() + except Exception as e: + st.error(f"Erreur de nettoyage : {e}") +