Correction bugs mineurs Domo91

This commit is contained in:
2025-12-16 10:26:11 +01:00
parent b4c2ca8400
commit 92b57df303
4 changed files with 204 additions and 173 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 746 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
app/assets/QR_Domo91FR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
app/assets/qr_domo91.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -4,12 +4,12 @@ import random
import traceback import traceback
from datetime import datetime, date, time from datetime import datetime, date, time
from contextlib import closing from contextlib import closing
import bcrypt import bcrypt
import matplotlib.dates as mdates import matplotlib.dates as mdates
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import mysql.connector import mysql.connector
import pandas as pd import pandas as pd
pd.set_option("future.no_silent_downcasting", True) pd.set_option("future.no_silent_downcasting", True)
import streamlit as st import streamlit as st
from PIL import Image from PIL import Image
@@ -17,7 +17,6 @@ from dotenv import find_dotenv, load_dotenv
from fpdf import FPDF from fpdf import FPDF
from streamlit_autorefresh import st_autorefresh from streamlit_autorefresh import st_autorefresh
# ========================================================= # =========================================================
# Config de page # Config de page
# ========================================================= # =========================================================
@@ -32,7 +31,6 @@ st.sidebar.image(logo, use_container_width=True)
st.title("📊 Domo91 - Surveillance des sondes") st.title("📊 Domo91 - Surveillance des sondes")
st.write("Bienvenue sur lapplication de supervision.") st.write("Bienvenue sur lapplication de supervision.")
# ========================================================= # =========================================================
# ENV & DB # ENV & DB
# ========================================================= # =========================================================
@@ -62,6 +60,24 @@ def assert_site_ok(site_name: str):
if site_name not in SITES_AUTORISES: if site_name not in SITES_AUTORISES:
raise ValueError(f"Site invalide: {site_name}") raise ValueError(f"Site invalide: {site_name}")
def get_chambres_froides_actives(conn, site: str):
"""Retourne la liste des chambres froides actives (Etat=ON) pour un site, avec leur Temp_Max."""
assert_site_ok(site)
with closing(conn.cursor(dictionary=True)) as cur:
cur.execute(
"""
SELECT Sonde, Temp_Max
FROM Sondes.Chambres_froides
WHERE Lieu = %s
AND UPPER(Etat) = 'ON'
ORDER BY Sonde
""",
(site,),
)
return cur.fetchall()
# ========================================================= # =========================================================
# Session state # Session state
# ========================================================= # =========================================================
@@ -76,6 +92,7 @@ for key, default in {
}.items(): }.items():
st.session_state.setdefault(key, default) st.session_state.setdefault(key, default)
# ========================================================= # =========================================================
# Sécurité mots de passe # Sécurité mots de passe
# ========================================================= # =========================================================
@@ -95,10 +112,11 @@ def fetch_gyro(site_name: str):
q = """ q = """
SELECT Etat, `Date` SELECT Etat, `Date`
FROM Sondes.v_gyro_last FROM Sondes.v_gyro_last
WHERE Lieu = %s AND Sonde = 'Gyro' WHERE Lieu = %s \
AND Sonde = 'Gyro'
ORDER BY `Date` DESC ORDER BY `Date` DESC
LIMIT 1 LIMIT 1 \
""" """
with closing(get_connection()) as cnx, closing(cnx.cursor(dictionary=True)) as cur: with closing(get_connection()) as cnx, closing(cnx.cursor(dictionary=True)) as cur:
cur.execute(q, (site_name,)) # <-- FIX cur.execute(q, (site_name,)) # <-- FIX
row = cur.fetchone() row = cur.fetchone()
@@ -157,46 +175,52 @@ def render_gyro_badge(site_name: str, stale_after_min: int = 10):
# ========================================================= # =========================================================
def ensure_schema(): def ensure_schema():
ddl_generated = """ ddl_generated = """
CREATE TABLE IF NOT EXISTS Journal_Erreurs ( CREATE TABLE IF NOT EXISTS Journal_Erreurs \
Id INT AUTO_INCREMENT PRIMARY KEY, ( \
Site VARCHAR(50) NOT NULL, Id INT AUTO_INCREMENT PRIMARY KEY, \
Sonde VARCHAR(100) NOT NULL, Site VARCHAR(50) NOT NULL, \
DateJour DATE NOT NULL, Sonde VARCHAR(100) NOT NULL, \
Type VARCHAR(30) NOT NULL, DateJour DATE NOT NULL, \
Source_Id INT NULL, Type VARCHAR(30) NOT NULL, \
Source_Id_norm INT GENERATED ALWAYS AS (COALESCE(Source_Id, 0)) STORED, Source_Id INT NULL, \
Resume TEXT NOT NULL, Source_Id_norm INT GENERATED ALWAYS AS (COALESCE(Source_Id, 0)) STORED, \
Statut VARCHAR(20) NOT NULL DEFAULT 'Nouveau', Resume TEXT NOT NULL, \
Priorite TINYINT NOT NULL DEFAULT 3, Statut VARCHAR(20) NOT NULL DEFAULT 'Nouveau', \
Assignation VARCHAR(100) NULL, Priorite TINYINT NOT NULL DEFAULT 3, \
Commentaire TEXT NULL, Assignation VARCHAR(100) NULL, \
Tag VARCHAR(50) NULL, Commentaire TEXT NULL, \
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, Tag VARCHAR(50) NULL, \
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, \
UNIQUE KEY uk_site_sonde_date_type (Site, Sonde, DateJour, Type, Source_Id_norm), UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \
KEY idx_journal_date_site (DateJour, Site), UNIQUE KEY uk_site_sonde_date_type (Site, Sonde, DateJour, Type, Source_Id_norm), \
KEY idx_journal_statut (Statut) KEY idx_journal_date_site (DateJour, Site), \
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; KEY idx_journal_statut (Statut)
""" ) ENGINE = InnoDB \
DEFAULT CHARSET = utf8mb4 \
COLLATE = utf8mb4_unicode_ci; \
"""
ddl_fallback = """ ddl_fallback = """
CREATE TABLE IF NOT EXISTS Journal_Erreurs ( CREATE TABLE IF NOT EXISTS Journal_Erreurs \
Id INT AUTO_INCREMENT PRIMARY KEY, ( \
Site VARCHAR(50) NOT NULL, Id INT AUTO_INCREMENT PRIMARY KEY, \
Sonde VARCHAR(100) NOT NULL, Site VARCHAR(50) NOT NULL, \
DateJour DATE NOT NULL, Sonde VARCHAR(100) NOT NULL, \
Type VARCHAR(30) NOT NULL, DateJour DATE NOT NULL, \
Source_Id INT NULL, Type VARCHAR(30) NOT NULL, \
Source_Id_norm INT NULL DEFAULT 0, Source_Id INT NULL, \
Resume TEXT NOT NULL, Source_Id_norm INT NULL DEFAULT 0, \
Statut VARCHAR(20) NOT NULL DEFAULT 'Nouveau', Resume TEXT NOT NULL, \
Priorite TINYINT NOT NULL DEFAULT 3, Statut VARCHAR(20) NOT NULL DEFAULT 'Nouveau', \
Assignation VARCHAR(100) NULL, Priorite TINYINT NOT NULL DEFAULT 3, \
Commentaire TEXT NULL, Assignation VARCHAR(100) NULL, \
Tag VARCHAR(50) NULL, Commentaire TEXT NULL, \
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, Tag VARCHAR(50) NULL, \
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, \
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
""" ) ENGINE = InnoDB \
DEFAULT CHARSET = utf8mb4 \
COLLATE = utf8mb4_unicode_ci; \
"""
idxs_fallback = [ idxs_fallback = [
"CREATE UNIQUE INDEX IF NOT EXISTS uk_site_sonde_date_type ON Sondes.Journal_Erreurs (Site, Sonde, DateJour, Type, Source_Id_norm)", "CREATE UNIQUE INDEX IF NOT EXISTS uk_site_sonde_date_type ON Sondes.Journal_Erreurs (Site, Sonde, DateJour, Type, Source_Id_norm)",
"CREATE INDEX IF NOT EXISTS idx_journal_date_site ON Sondes.Journal_Erreurs (DateJour, Site)", "CREATE INDEX IF NOT EXISTS idx_journal_date_site ON Sondes.Journal_Erreurs (DateJour, Site)",
@@ -205,18 +229,20 @@ def ensure_schema():
triggers_fallback = [ triggers_fallback = [
""" """
CREATE TRIGGER trg_je_bi CREATE TRIGGER trg_je_bi
BEFORE INSERT ON Sondes.Journal_Erreurs BEFORE INSERT
FOR EACH ROW ON Sondes.Journal_Erreurs
FOR EACH ROW
BEGIN BEGIN
SET NEW.Source_Id_norm = IFNULL(NEW.Source_Id, 0); SET NEW.Source_Id_norm = IFNULL(NEW.Source_Id, 0);
END END
""", """,
""" """
CREATE TRIGGER trg_je_bu CREATE TRIGGER trg_je_bu
BEFORE UPDATE ON Sondes.Journal_Erreurs BEFORE UPDATE
FOR EACH ROW ON Sondes.Journal_Erreurs
FOR EACH ROW
BEGIN BEGIN
SET NEW.Source_Id_norm = IFNULL(NEW.Source_Id, 0); SET NEW.Source_Id_norm = IFNULL(NEW.Source_Id, 0);
END END
""", """,
] ]
@@ -251,7 +277,6 @@ try:
except Exception as e: except Exception as e:
st.warning(f"Init schéma Journal_Erreurs : {e}") st.warning(f"Init schéma Journal_Erreurs : {e}")
# ========================================================= # =========================================================
# Connexion utilisateur # Connexion utilisateur
# ========================================================= # =========================================================
@@ -259,7 +284,21 @@ if not st.session_state.get("authenticated", False):
login = st.sidebar.text_input("Nom d'utilisateur") login = st.sidebar.text_input("Nom d'utilisateur")
password = st.sidebar.text_input("Mot de passe", type="password") password = st.sidebar.text_input("Mot de passe", type="password")
if st.sidebar.button("Se connecter"): # --- Bouton connexion ---
clicked = st.sidebar.button("Se connecter")
# --- QR code SOUS le bouton ---
st.sidebar.markdown("---")
st.sidebar.caption("Accès rapide depuis un smartphone :")
qr_path = os.path.join(BASE_DIR, "assets", "QR_Domo91FR.png")
if os.path.exists(qr_path):
st.sidebar.image(qr_path, width=180)
else:
st.sidebar.error(f"QR code introuvable : {qr_path}")
# --- Traitement de la connexion ---
if clicked:
try: try:
with closing(get_connection()) as conn, closing(conn.cursor(dictionary=True)) as cursor: with closing(get_connection()) as conn, closing(conn.cursor(dictionary=True)) as cursor:
cursor.execute( cursor.execute(
@@ -310,12 +349,21 @@ else:
st.rerun() st.rerun()
# =========================================================
# PDF
# =========================================================
# ========================================================= # =========================================================
# PDF # PDF
# ========================================================= # =========================================================
def generer_pdf(site_name: str, date_str: str, periode: str): def generer_pdf(site_name: str, date_str: str, periode: str):
"""
Génère un PDF de relevés + alertes pour un site et une date.
- site_name : nom de table (Saclay/Meudon)
- date_str : YYYY-MM-DD
- periode : libellé de tranche horaire
"""
assert_site_ok(site_name) assert_site_ok(site_name)
st.info(f"Génération du rapport PDF pour {site} à la date {date_str} ({periode})") st.info(f"Génération du rapport PDF pour {site_name} à la date {date_str} ({periode})")
plages = { plages = {
"Toute la journée": (time(0, 0), time(23, 59)), "Toute la journée": (time(0, 0), time(23, 59)),
@@ -326,8 +374,9 @@ def generer_pdf(site_name: str, date_str: str, periode: str):
try: try:
with closing(get_connection()) as conn, closing(conn.cursor(dictionary=True)) as cur: with closing(get_connection()) as conn, closing(conn.cursor(dictionary=True)) as cur:
# Mesures
cur.execute( cur.execute(
f"SELECT Sonde, Date, Temperature FROM `{site}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", f"SELECT Sonde, Date, Temperature FROM `{site_name}` WHERE DATE(Date) = %s ORDER BY Sonde, Date",
(date_str,), (date_str,),
) )
rows = cur.fetchall() rows = cur.fetchall()
@@ -351,7 +400,8 @@ def generer_pdf(site_name: str, date_str: str, periode: str):
df_sonde = df[df["Sonde"] == sonde] df_sonde = df[df["Sonde"] == sonde]
releves[sonde] = list(zip(df_sonde["Heure"], df_sonde["Temperature"])) releves[sonde] = list(zip(df_sonde["Heure"], df_sonde["Temperature"]))
table_alertes = f"Alertes_{site}" # Alertes du jour
table_alertes = f"Alertes_{site_name}"
cur.execute( cur.execute(
f"SELECT Sonde, Debut_defaut, Etat FROM `{table_alertes}` WHERE DATE(Debut_defaut) = %s", f"SELECT Sonde, Debut_defaut, Etat FROM `{table_alertes}` WHERE DATE(Debut_defaut) = %s",
(date_str,), (date_str,),
@@ -363,15 +413,11 @@ def generer_pdf(site_name: str, date_str: str, periode: str):
self.set_font("Arial", "B", 14) self.set_font("Arial", "B", 14)
self.cell(0, 10, "Rapport de surveillance des sondes", ln=1, align="C") self.cell(0, 10, "Rapport de surveillance des sondes", ln=1, align="C")
self.set_font("Arial", "", 12) self.set_font("Arial", "", 12)
self.cell(0, 8, f"Site : {getattr(self, 'site_name', '')}", ln=1, align="C")
self.cell(0, 8, f"Date : {date_str}", ln=1, align="C") self.cell(0, 8, f"Date : {date_str}", ln=1, align="C")
self.cell(0, 8, f"Période : {getattr(self, 'periode', '')}", ln=1, align="C") self.cell(0, 8, f"Période : {getattr(self, 'periode', '')}", ln=1, align="C")
self.ln(4) self.ln(4)
def site_info(self, site_name):
self.set_font("Arial", "B", 12)
self.cell(0, 8, f"Site : {site_name}", ln=1)
self.ln(2)
def releves_section(self, data): def releves_section(self, data):
self.set_font("Arial", "B", 12) self.set_font("Arial", "B", 12)
self.cell(0, 8, "Relevés de température", ln=1) self.cell(0, 8, "Relevés de température", ln=1)
@@ -405,6 +451,8 @@ def generer_pdf(site_name: str, date_str: str, periode: str):
h2, t2 = col2[i] h2, t2 = col2[i]
self.cell(40, 6, h2, border=1) self.cell(40, 6, h2, border=1)
self.cell(30, 6, f"{t2:.2f}", border=1) self.cell(30, 6, f"{t2:.2f}", border=1)
else:
self.cell(70, 6, "", border=0)
self.ln() self.ln()
self.ln(3) self.ln(3)
@@ -419,15 +467,15 @@ def generer_pdf(site_name: str, date_str: str, periode: str):
self.cell(0, 6, f"{a['Sonde']} - {a['Debut_defaut']} - {a['Etat']}", ln=1) self.cell(0, 6, f"{a['Sonde']} - {a['Debut_defaut']} - {a['Etat']}", ln=1)
pdf = RapportPDF() pdf = RapportPDF()
pdf.site_name = site_name
pdf.periode = periode pdf.periode = periode
pdf.add_page() pdf.add_page()
pdf.site_info(site)
pdf.releves_section(releves) pdf.releves_section(releves)
pdf.alertes_section(alertes) pdf.alertes_section(alertes)
output_dir = "../PDF" output_dir = os.path.join(BASE_DIR, "PDF")
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
file_name = f"rapport_{site}_{date_str}.pdf" file_name = f"rapport_{site_name}_{date_str}.pdf"
output_path = os.path.join(output_dir, file_name) output_path = os.path.join(output_dir, file_name)
pdf.output(output_path) pdf.output(output_path)
@@ -551,11 +599,22 @@ def load_anomalies_auto(site_name: str, jour: date):
def load_journal_existants(site: str, jour: date): def load_journal_existants(site: str, jour: date):
assert_site_ok(site) assert_site_ok(site)
q = """ q = """
SELECT Id, Site, Sonde, DateJour, Type, Source_Id, Resume, SELECT Id, \
Statut, Priorite, Assignation, Commentaire, Tag Site, \
FROM Sondes.Journal_Erreurs Sonde, \
WHERE Site=%s AND DateJour=%s; DateJour, \
""" Type, \
Source_Id, \
Resume,
Statut, \
Priorite, \
Assignation, \
Commentaire, \
Tag
FROM Sondes.Journal_Erreurs
WHERE Site = %s \
AND DateJour = %s; \
"""
with closing(get_connection()) as cnx, closing(cnx.cursor(dictionary=True)) as cur: with closing(get_connection()) as cnx, closing(cnx.cursor(dictionary=True)) as cur:
cur.execute(q, (site, jour)) cur.execute(q, (site, jour))
rows = cur.fetchall() rows = cur.fetchall()
@@ -568,19 +627,18 @@ def upsert_journal(rows: list[dict]):
if not rows: if not rows:
return return
q_insert = """ q_insert = """
INSERT INTO Sondes.Journal_Erreurs INSERT INTO Sondes.Journal_Erreurs
(Site, Sonde, DateJour, Type, Source_Id, Resume, Statut, Priorite, Assignation, Commentaire, Tag) (Site, Sonde, DateJour, Type, Source_Id, Resume, Statut, Priorite, Assignation, Commentaire, Tag)
VALUES (%(Site)s, %(Sonde)s, %(DateJour)s, %(Type)s, %(Source_Id)s, %(Resume)s, VALUES (%(Site)s, %(Sonde)s, %(DateJour)s, %(Type)s, %(Source_Id)s, %(Resume)s,
%(Statut)s, %(Priorite)s, %(Assignation)s, %(Commentaire)s, %(Tag)s) %(Statut)s, %(Priorite)s, %(Assignation)s, %(Commentaire)s, %(Tag)s)
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE Resume=VALUES(Resume), \
Resume=VALUES(Resume), Statut=VALUES(Statut), \
Statut=VALUES(Statut), Priorite=VALUES(Priorite), \
Priorite=VALUES(Priorite), Assignation=VALUES(Assignation), \
Assignation=VALUES(Assignation), Commentaire=VALUES(Commentaire), \
Commentaire=VALUES(Commentaire), Tag=VALUES(Tag), \
Tag=VALUES(Tag), UpdatedAt=CURRENT_TIMESTAMP; \
UpdatedAt=CURRENT_TIMESTAMP; """
"""
with closing(get_connection()) as cnx, closing(cnx.cursor()) as cur: with closing(get_connection()) as cnx, closing(cnx.cursor()) as cur:
cur.executemany(q_insert, rows) cur.executemany(q_insert, rows)
cnx.commit() cnx.commit()
@@ -629,11 +687,11 @@ def page_journal_erreurs():
df_saved["Source_Id_norm"] = df_saved["Source_Id"].apply(_source_norm) df_saved["Source_Id_norm"] = df_saved["Source_Id"].apply(_source_norm)
df_saved["Key"] = ( df_saved["Key"] = (
df_saved["Site"].astype(str) + "|" + df_saved["Site"].astype(str) + "|" +
df_saved["Sonde"].astype(str) + "|" + df_saved["Sonde"].astype(str) + "|" +
df_saved["DateJour"].astype(str) + "|" + df_saved["DateJour"].astype(str) + "|" +
df_saved["Type"].astype(str) + "|" + df_saved["Type"].astype(str) + "|" +
df_saved["Source_Id_norm"].astype(str) df_saved["Source_Id_norm"].astype(str)
) )
for c in ["Statut", "Priorite", "Assignation", "Commentaire", "Tag"]: for c in ["Statut", "Priorite", "Assignation", "Commentaire", "Tag"]:
if c not in df_saved.columns: if c not in df_saved.columns:
@@ -641,11 +699,11 @@ def page_journal_erreurs():
# base Key (toujours) # base Key (toujours)
base["Key"] = ( base["Key"] = (
base["Site"].astype(str) + "|" + base["Site"].astype(str) + "|" +
base["Sonde"].astype(str) + "|" + base["Sonde"].astype(str) + "|" +
base["DateJour"].astype(str) + "|" + base["DateJour"].astype(str) + "|" +
base["Type"].astype(str) + "|" + base["Type"].astype(str) + "|" +
base["Source_Id_norm"].astype(str) base["Source_Id_norm"].astype(str)
) )
df = base.merge( df = base.merge(
@@ -798,7 +856,6 @@ if st.session_state.get("authenticated"):
try: try:
# Site imposé ou sélection admin # Site imposé ou sélection admin
if st.session_state.get("role") == "superviseur": if st.session_state.get("role") == "superviseur":
# sélection possible
if site_actuel not in SITES_LISTE: if site_actuel not in SITES_LISTE:
site_actuel = SITES_LISTE[0] site_actuel = SITES_LISTE[0]
site_actuel = st.selectbox( site_actuel = st.selectbox(
@@ -827,41 +884,18 @@ if st.session_state.get("authenticated"):
df_sonde = pd.DataFrame() df_sonde = pd.DataFrame()
seuil_temp = 10.0 seuil_temp = 10.0
sonde_choisie = None sonde_choisie = None
with closing(get_connection()) as conn, closing(conn.cursor(dictionary=True)) as cursor:
cursor.execute(
"""
SELECT Sonde, Temp_Max
FROM Sondes.Chambres_froides
WHERE Lieu = %s
AND UPPER(Etat) = 'ON'
ORDER BY Sonde
""",
(site_actuel,),
)
rows_mesures = [] # important pour éviter NameError
with closing(get_connection()) as conn, closing(conn.cursor(dictionary=True)) as cursor:
cursor.execute(
"""
SELECT Sonde, Temp_Max
FROM Sondes.Chambres_froides
WHERE Lieu = %s
AND UPPER(Etat) = 'ON'
ORDER BY Sonde
""",
(site_actuel,),
)
cfg_on = cursor.fetchall()
# 1) Charger config (sondes ON + seuils) puis mesures du jour
with closing(get_connection()) as conn:
cfg_on = get_chambres_froides_actives(conn, site_actuel)
sondes_on = [r["Sonde"] for r in cfg_on] sondes_on = [r["Sonde"] for r in cfg_on]
seuils_on = {r["Sonde"]: float(r["Temp_Max"]) for r in cfg_on} seuils_on = {r["Sonde"]: float(r["Temp_Max"]) for r in cfg_on}
# IMPORTANT: test avant la requête mesures
if not sondes_on: if not sondes_on:
st.warning("Aucune sonde active (Etat=ON) dans Chambres_froides pour ce site.") st.warning("Aucune sonde active (Etat=ON) dans Chambres_froides pour ce site.")
st.stop() st.stop()
with closing(get_connection()) as conn, closing(conn.cursor(dictionary=True)) as cursor: with closing(conn.cursor(dictionary=True)) as cur_mesures:
placeholders = ", ".join(["%s"] * len(sondes_on)) placeholders = ", ".join(["%s"] * len(sondes_on))
q = f""" q = f"""
SELECT Sonde, Date, Temperature SELECT Sonde, Date, Temperature
@@ -871,72 +905,68 @@ if st.session_state.get("authenticated"):
ORDER BY Sonde, Date DESC ORDER BY Sonde, Date DESC
""" """
params = [date_selectionnee.strftime("%Y-%m-%d")] + sondes_on params = [date_selectionnee.strftime("%Y-%m-%d")] + sondes_on
cursor.execute(q, params) cur_mesures.execute(q, params)
rows_mesures = cursor.fetchall() rows_mesures = cur_mesures.fetchall()
if rows_mesures: if rows_mesures:
df = pd.DataFrame(rows_mesures) df = pd.DataFrame(rows_mesures)
df["Date"] = pd.to_datetime(df["Date"]) df["Date"] = pd.to_datetime(df["Date"])
sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes_on) sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes_on)
df_sonde = df[df["Sonde"] == sonde_choisie].copy() df_sonde = df[df["Sonde"] == sonde_choisie].copy()
df_sonde["Heure"] = df_sonde["Date"].dt.hour df_sonde["Heure"] = df_sonde["Date"].dt.hour
tranche = st.radio( tranche = st.radio(
"🕒 Tranche horaire :", "🕒 Tranche horaire :",
["Toute la journée", "Matin (6h-12h)", "Après-midi (12h-18h)", "Nuit (18h-6h)"], ["Toute la journée", "Matin (6h-12h)", "Après-midi (12h-18h)", "Nuit (18h-6h)"],
)
st.session_state["selected_periode"] = tranche
if st.button("🧾 Générer le PDF du jour"):
generer_pdf(
site_actuel,
date_selectionnee.strftime("%Y-%m-%d"),
st.session_state.get("selected_periode", "Toute la journée"),
) )
st.session_state["selected_periode"] = tranche
if st.button("🧾 Générer le PDF du jour"): # Filtre tranche
generer_pdf( if tranche == "Matin (6h-12h)":
site_actuel, df_sonde = df_sonde[(df_sonde["Heure"] >= 6) & (df_sonde["Heure"] < 12)]
date_selectionnee.strftime("%Y-%m-%d"), elif tranche == "Après-midi (12h-18h)":
st.session_state.get("selected_periode", "Toute la journée"), df_sonde = df_sonde[(df_sonde["Heure"] >= 12) & (df_sonde["Heure"] < 18)]
) elif tranche == "Nuit (18h-6h)":
df_sonde = df_sonde[(df_sonde["Heure"] >= 18) | (df_sonde["Heure"] < 6)]
# Filtre tranche seuil_temp = seuils_on.get(sonde_choisie, 10.0)
if tranche == "Matin (6h-12h)":
df_sonde = df_sonde[(df_sonde["Heure"] >= 6) & (df_sonde["Heure"] < 12)]
elif tranche == "Après-midi (12h-18h)":
df_sonde = df_sonde[(df_sonde["Heure"] >= 12) & (df_sonde["Heure"] < 18)]
elif tranche == "Nuit (18h-6h)":
df_sonde = df_sonde[(df_sonde["Heure"] >= 18) | (df_sonde["Heure"] < 6)]
seuil_temp = seuils_on.get(sonde_choisie, 10.0) if not df_sonde.empty:
st.subheader("📊 Tableau des relevés")
if not df_sonde.empty:
st.subheader("📊 Tableau des relevés")
def surlignage_temp(val): def surlignage_temp(val):
try: try:
if float(val) > seuil_temp: return "color: red; font-weight: bold" if float(val) > seuil_temp else ""
return "color: red; font-weight: bold" except (ValueError, TypeError):
except Exception:
pass
return "" return ""
styled_df = df_sonde.style.applymap(surlignage_temp, subset=["Temperature"])
st.dataframe(styled_df, use_container_width=True)
styled_df = df_sonde.style.map(surlignage_temp, subset=["Temperature"]) st.subheader("📈 Évolution de la température")
st.dataframe(styled_df, use_container_width=True) fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(df_sonde["Date"], df_sonde["Temperature"], marker="o")
st.subheader("📈 Évolution de la température") ax.axhline(seuil_temp, linestyle="--", label=f"Seuil {seuil_temp}°C")
fig, ax = plt.subplots(figsize=(10, 4)) ax.set_xlabel("Heure")
ax.plot(df_sonde["Date"], df_sonde["Temperature"], marker="o") ax.set_ylabel("Température (°C)")
ax.axhline(seuil_temp, linestyle="--", label=f"Seuil {seuil_temp}°C") ax.set_title(f"{sonde_choisie} - {date_selectionnee.strftime('%d/%m/%Y')}")
ax.set_xlabel("Heure") ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
ax.set_ylabel("Température (°C)") ax.legend()
ax.set_title(f"{sonde_choisie} - {date_selectionnee.strftime('%d/%m/%Y')}") st.pyplot(fig)
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
ax.legend()
st.pyplot(fig)
else: else:
st.info("Aucun relevé pour cette date.") st.info("Aucun relevé dans la tranche horaire sélectionnée.")
else:
st.info("Aucun relevé pour cette date.")
if not sondes_on:
st.warning("Aucune sonde active (Etat=ON) dans Chambres_froides pour ce site.")
st.stop()
except Exception as e: except Exception as e:
st.error(f"Erreur : {e}") st.error(f"Erreur : {e}")
st.text(traceback.format_exc()) st.text(traceback.format_exc())
@@ -991,7 +1021,8 @@ if st.session_state.get("authenticated"):
st.session_state["refresh_admin"] = random.randint(0, 9999) st.session_state["refresh_admin"] = random.randint(0, 9999)
try: try:
with closing(get_connection()) as conn_admin, closing(conn_admin.cursor(dictionary=True)) as cursor_admin: with closing(get_connection()) as conn_admin, closing(
conn_admin.cursor(dictionary=True)) as cursor_admin:
cursor_admin.execute("SELECT * FROM Sondes.Chambres_froides WHERE Lieu = %s", (site,)) cursor_admin.execute("SELECT * FROM Sondes.Chambres_froides WHERE Lieu = %s", (site,))
chambres = cursor_admin.fetchall() chambres = cursor_admin.fetchall()