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}")
+