Correction bugs mineurs Domo91
This commit is contained in:
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
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
BIN
app/assets/qr_domo91.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
243
app/domo91.py
243
app/domo91.py
@@ -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 l’application de supervision.")
|
st.write("Bienvenue sur l’application 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,9 +112,10 @@ 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
|
||||||
@@ -157,45 +175,51 @@ 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_date_site (DateJour, Site), \
|
||||||
KEY idx_journal_statut (Statut)
|
KEY idx_journal_statut (Statut)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) 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, \
|
||||||
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, \
|
||||||
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) 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)",
|
||||||
@@ -205,7 +229,8 @@ 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
|
||||||
|
ON Sondes.Journal_Erreurs
|
||||||
FOR EACH ROW
|
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);
|
||||||
@@ -213,7 +238,8 @@ def ensure_schema():
|
|||||||
""",
|
""",
|
||||||
"""
|
"""
|
||||||
CREATE TRIGGER trg_je_bu
|
CREATE TRIGGER trg_je_bu
|
||||||
BEFORE UPDATE ON Sondes.Journal_Erreurs
|
BEFORE UPDATE
|
||||||
|
ON Sondes.Journal_Erreurs
|
||||||
FOR EACH ROW
|
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);
|
||||||
@@ -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,10 +599,21 @@ 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, \
|
||||||
|
Sonde, \
|
||||||
|
DateJour, \
|
||||||
|
Type, \
|
||||||
|
Source_Id, \
|
||||||
|
Resume,
|
||||||
|
Statut, \
|
||||||
|
Priorite, \
|
||||||
|
Assignation, \
|
||||||
|
Commentaire, \
|
||||||
|
Tag
|
||||||
FROM Sondes.Journal_Erreurs
|
FROM Sondes.Journal_Erreurs
|
||||||
WHERE Site=%s AND DateJour=%s;
|
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))
|
||||||
@@ -572,14 +631,13 @@ def upsert_journal(rows: list[dict]):
|
|||||||
(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)
|
||||||
@@ -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,8 +905,8 @@ 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)
|
||||||
@@ -911,14 +945,11 @@ if st.session_state.get("authenticated"):
|
|||||||
|
|
||||||
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"])
|
||||||
styled_df = df_sonde.style.map(surlignage_temp, subset=["Temperature"])
|
|
||||||
st.dataframe(styled_df, use_container_width=True)
|
st.dataframe(styled_df, use_container_width=True)
|
||||||
|
|
||||||
st.subheader("📈 Évolution de la température")
|
st.subheader("📈 Évolution de la température")
|
||||||
@@ -931,12 +962,11 @@ if st.session_state.get("authenticated"):
|
|||||||
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
|
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
|
||||||
ax.legend()
|
ax.legend()
|
||||||
st.pyplot(fig)
|
st.pyplot(fig)
|
||||||
|
else:
|
||||||
|
st.info("Aucun relevé dans la tranche horaire sélectionnée.")
|
||||||
else:
|
else:
|
||||||
st.info("Aucun relevé pour cette date.")
|
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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user