Files
Gestion_sondes/Outils/Injection_tests.py

237 lines
8.0 KiB
Python

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_PASS"),
database=os.getenv("DB_NAME"),
autocommit=True,
)
return cnx
except Exception as e:
st.error(f"Échec de connexion MySQL : {e}")
raise
# ----------------------
# Helpers : liste des sondes actives et hors entretien
# ----------------------
@st.cache_data(ttl=60, show_spinner=False)
def list_sondes(site: str) -> list:
"""Retourne la liste des sondes actives (Etat=ON) et non en entretien pour le site.
Essaie d'abord monitor_{site}, puis Chambres_froides, sinon fallback via la table de mesures.
"""
cnx = get_connection()
cur = cnx.cursor()
# 1) monitor_{site}
try:
cur.execute(
f"""
SELECT Sonde
FROM `monitor_{site}`
WHERE (Etat='ON' OR Etat=1)
AND ( (Maintenance='OFF') OR (Maintenance=0) OR (Maintenance IS NULL) )
ORDER BY Sonde
"""
)
rows = [r[0] for r in cur.fetchall()]
if rows:
return rows
except Exception:
pass
# 2) Chambres_froides
try:
cur.execute(
"""
SELECT Sonde
FROM `Chambres_froides`
WHERE Lieu=%s AND (Etat='ON' OR Etat=1)
AND ( (Maintenance='OFF') OR (Maintenance=0) OR (Maintenance IS NULL) )
ORDER BY Sonde
""",
(site,)
)
rows = [r[0] for r in cur.fetchall()]
if rows:
return rows
except Exception:
pass
# 3) Fallback : dernier état via table de mesures (distinct)
try:
cur.execute(
f"SELECT DISTINCT Sonde FROM `{site}` ORDER BY Sonde LIMIT 200"
)
return [r[0] for r in cur.fetchall()]
except Exception:
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) en cherchant d'abord dans monitor_{site}, puis Chambres_froides.
Renvoie None si non trouvé."""
try:
cnx = get_connection()
cur = cnx.cursor()
# 1) monitor_{site}
try:
cur.execute(
f"SELECT Temp_Max FROM `monitor_{site}` WHERE Sonde=%s LIMIT 1",
(sonde,)
)
row = cur.fetchone()
if row and row[0] is not None:
return float(row[0])
except Exception:
pass
# 2) Chambres_froides
try:
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:
return float(row[0])
except Exception:
pass
except Exception:
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 active / hors entretien
options_sondes = list_sondes(site)
if not options_sondes:
st.warning("Aucune sonde active trouvée (ou table monitor introuvable). Vous pouvez saisir un nom manuel.")
sonde = st.text_input("Nom de la sonde", value="TEST_Chambre1")
else:
sonde = st.selectbox("Sonde (actives, hors entretien)", 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=45, 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}")