Consolidation de Domo91 et cosmétique
This commit is contained in:
150
app/domo91.py
150
app/domo91.py
@@ -14,6 +14,7 @@ pd.set_option("future.no_silent_downcasting", True)
|
||||
import streamlit as st
|
||||
from dotenv import find_dotenv, load_dotenv
|
||||
from fpdf import FPDF
|
||||
from streamlit_autorefresh import st_autorefresh
|
||||
|
||||
# =========================================================
|
||||
# Config de page
|
||||
@@ -35,6 +36,7 @@ db_config = {
|
||||
"password": os.getenv("DB_PASS"),
|
||||
"database": os.getenv("DB_NAME"),
|
||||
"autocommit": False,
|
||||
"consume_results": True,
|
||||
}
|
||||
|
||||
# Roissy n'existe pas actuellement => on garde Saclay / Meudon
|
||||
@@ -46,10 +48,9 @@ def get_connection():
|
||||
return mysql.connector.connect(**db_config)
|
||||
|
||||
|
||||
def assert_site_ok(site: str):
|
||||
if site not in SITES_AUTORISES:
|
||||
raise ValueError(f"Site invalide: {site}")
|
||||
|
||||
def assert_site_ok(site_name: str):
|
||||
if site_name not in SITES_AUTORISES:
|
||||
raise ValueError(f"Site invalide: {site_name}")
|
||||
|
||||
# =========================================================
|
||||
# Session state
|
||||
@@ -79,9 +80,8 @@ def verifier_password(input_password: str, hash_en_base: str) -> bool:
|
||||
# =========================================================
|
||||
# Gyro: lecture + badge
|
||||
# =========================================================
|
||||
def fetch_gyro(site: str):
|
||||
"""Retourne (etat, ts) depuis la vue v_gyro_last pour le site donné."""
|
||||
assert_site_ok(site)
|
||||
def fetch_gyro(site_name: str):
|
||||
assert_site_ok(site_name)
|
||||
q = """
|
||||
SELECT Etat, `Date`
|
||||
FROM Sondes.v_gyro_last
|
||||
@@ -90,7 +90,7 @@ def fetch_gyro(site: str):
|
||||
LIMIT 1
|
||||
"""
|
||||
with closing(get_connection()) as cnx, closing(cnx.cursor(dictionary=True)) as cur:
|
||||
cur.execute(q, (site,))
|
||||
cur.execute(q, (site_name,)) # <-- FIX
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
return None, None
|
||||
@@ -99,9 +99,9 @@ def fetch_gyro(site: str):
|
||||
return etat, ts
|
||||
|
||||
|
||||
def render_gyro_badge(site: str, stale_after_min: int = 10):
|
||||
def render_gyro_badge(site_name: str, stale_after_min: int = 10):
|
||||
"""Affiche un voyant Gyro (vert/rouge/orange) + fraîcheur des données."""
|
||||
etat, ts = fetch_gyro(site)
|
||||
etat, ts = fetch_gyro(site_name)
|
||||
|
||||
if etat in ("ON", "1"):
|
||||
color, label = "#ef4444", "GYRO ON"
|
||||
@@ -303,8 +303,8 @@ else:
|
||||
# =========================================================
|
||||
# PDF
|
||||
# =========================================================
|
||||
def generer_pdf(site: str, date_str: str, periode: str):
|
||||
assert_site_ok(site)
|
||||
def generer_pdf(site_name: str, date_str: str, periode: str):
|
||||
assert_site_ok(site_name)
|
||||
st.info(f"Génération du rapport PDF pour {site} à la date {date_str} ({periode})")
|
||||
|
||||
plages = {
|
||||
@@ -467,9 +467,9 @@ def load_alertes(site: str, jour: date):
|
||||
return pd.DataFrame(rows, columns=cols) if rows else pd.DataFrame(columns=cols)
|
||||
|
||||
|
||||
def load_anomalies_auto(site: str, jour: date):
|
||||
assert_site_ok(site)
|
||||
table_mesures = site
|
||||
def load_anomalies_auto(site_name: str, jour: date):
|
||||
assert_site_ok(site_name)
|
||||
table_mesures = site_name
|
||||
gap_threshold_min = 20
|
||||
jump_deg = 10
|
||||
min_phys, max_phys = -60, 120
|
||||
@@ -530,7 +530,7 @@ def load_anomalies_auto(site: str, jour: date):
|
||||
|
||||
ORDER BY Sonde;
|
||||
"""
|
||||
params = (jour, site, jour, site, jour, site, jour)
|
||||
params = (jour, site_name, jour, site_name, jour, site_name, jour)
|
||||
with closing(get_connection()) as cnx, closing(cnx.cursor(dictionary=True)) as cur:
|
||||
cur.execute(q, params)
|
||||
rows = cur.fetchall()
|
||||
@@ -783,6 +783,7 @@ if st.session_state.get("authenticated"):
|
||||
date_selectionnee = st.session_state.get("selected_date", date.today())
|
||||
|
||||
# ------------------ Accueil ------------------
|
||||
rows_mesures = []
|
||||
if onglet_selectionne == "Accueil":
|
||||
try:
|
||||
# Site imposé ou sélection admin
|
||||
@@ -804,7 +805,7 @@ if st.session_state.get("authenticated"):
|
||||
# Voyant Gyro
|
||||
st.subheader(f"🚨 Statut Gyro — {site_actuel}")
|
||||
try:
|
||||
st.autorefresh(interval=30000, key="gyro_autorefresh")
|
||||
st_autorefresh(interval=30_000, key="gyro_autorefresh")
|
||||
except Exception:
|
||||
pass
|
||||
render_gyro_badge(site_actuel)
|
||||
@@ -813,24 +814,61 @@ if st.session_state.get("authenticated"):
|
||||
date_selectionnee = st.date_input("📅 Date du relevé", value=date_selectionnee)
|
||||
st.session_state["selected_date"] = date_selectionnee
|
||||
|
||||
rows = []
|
||||
df_sonde = pd.DataFrame()
|
||||
seuil_temp = 10.0
|
||||
sonde_choisie = None
|
||||
|
||||
with closing(get_connection()) as conn, closing(conn.cursor(dictionary=True)) as cursor:
|
||||
cursor.execute(
|
||||
f"SELECT * FROM `{site_actuel}` WHERE DATE(Date) = %s ORDER BY Sonde, Date DESC",
|
||||
(date_selectionnee.strftime("%Y-%m-%d"),),
|
||||
"""
|
||||
SELECT Sonde, Temp_Max
|
||||
FROM Sondes.Chambres_froides
|
||||
WHERE Lieu = %s
|
||||
AND UPPER(Etat) = 'ON'
|
||||
ORDER BY Sonde
|
||||
""",
|
||||
(site_actuel,),
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
rows_mesures = [] # important pour éviter NameError
|
||||
|
||||
if rows:
|
||||
df = pd.DataFrame(rows)
|
||||
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()
|
||||
|
||||
sondes_on = [r["Sonde"] 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:
|
||||
st.warning("Aucune sonde active (Etat=ON) dans Chambres_froides pour ce site.")
|
||||
st.stop()
|
||||
|
||||
with closing(get_connection()) as conn, closing(conn.cursor(dictionary=True)) as cursor:
|
||||
placeholders = ", ".join(["%s"] * len(sondes_on))
|
||||
q = f"""
|
||||
SELECT Sonde, Date, Temperature
|
||||
FROM `{site_actuel}`
|
||||
WHERE DATE(Date) = %s
|
||||
AND Sonde IN ({placeholders})
|
||||
ORDER BY Sonde, Date DESC
|
||||
"""
|
||||
params = [date_selectionnee.strftime("%Y-%m-%d")] + sondes_on
|
||||
cursor.execute(q, params)
|
||||
rows_mesures = cursor.fetchall()
|
||||
|
||||
if rows_mesures:
|
||||
df = pd.DataFrame(rows_mesures)
|
||||
df["Date"] = pd.to_datetime(df["Date"])
|
||||
sondes = sorted(df["Sonde"].unique())
|
||||
sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes)
|
||||
|
||||
sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes_on)
|
||||
df_sonde = df[df["Sonde"] == sonde_choisie].copy()
|
||||
df_sonde["Heure"] = df_sonde["Date"].dt.hour
|
||||
|
||||
@@ -855,42 +893,40 @@ if st.session_state.get("authenticated"):
|
||||
elif tranche == "Nuit (18h-6h)":
|
||||
df_sonde = df_sonde[(df_sonde["Heure"] >= 18) | (df_sonde["Heure"] < 6)]
|
||||
|
||||
# Seuil
|
||||
cursor.execute(
|
||||
"SELECT Temp_Max FROM Sondes.Chambres_froides WHERE Lieu = %s AND Sonde = %s",
|
||||
(site_actuel, sonde_choisie),
|
||||
)
|
||||
seuil = cursor.fetchone()
|
||||
if seuil and seuil.get("Temp_Max") is not None:
|
||||
seuil_temp = float(seuil["Temp_Max"])
|
||||
seuil_temp = seuils_on.get(sonde_choisie, 10.0)
|
||||
|
||||
if rows and 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):
|
||||
try:
|
||||
if float(val) > seuil_temp:
|
||||
return "color: red; font-weight: bold"
|
||||
except Exception:
|
||||
pass
|
||||
return ""
|
||||
|
||||
styled_df = df_sonde.style.map(surlignage_temp, subset=["Temperature"])
|
||||
st.dataframe(styled_df, use_container_width=True)
|
||||
def surlignage_temp(val):
|
||||
try:
|
||||
if float(val) > seuil_temp:
|
||||
return "color: red; font-weight: bold"
|
||||
except Exception:
|
||||
pass
|
||||
return ""
|
||||
|
||||
st.subheader("📈 Évolution de la température")
|
||||
fig, ax = plt.subplots(figsize=(10, 4))
|
||||
ax.plot(df_sonde["Date"], df_sonde["Temperature"], marker="o")
|
||||
ax.axhline(seuil_temp, linestyle="--", label=f"Seuil {seuil_temp}°C")
|
||||
ax.set_xlabel("Heure")
|
||||
ax.set_ylabel("Température (°C)")
|
||||
ax.set_title(f"{sonde_choisie} - {date_selectionnee.strftime('%d/%m/%Y')}")
|
||||
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
|
||||
ax.legend()
|
||||
st.pyplot(fig)
|
||||
elif not rows:
|
||||
st.info("Aucun relevé pour cette date.")
|
||||
|
||||
styled_df = df_sonde.style.map(surlignage_temp, subset=["Temperature"])
|
||||
st.dataframe(styled_df, use_container_width=True)
|
||||
|
||||
st.subheader("📈 Évolution de la température")
|
||||
fig, ax = plt.subplots(figsize=(10, 4))
|
||||
ax.plot(df_sonde["Date"], df_sonde["Temperature"], marker="o")
|
||||
ax.axhline(seuil_temp, linestyle="--", label=f"Seuil {seuil_temp}°C")
|
||||
ax.set_xlabel("Heure")
|
||||
ax.set_ylabel("Température (°C)")
|
||||
ax.set_title(f"{sonde_choisie} - {date_selectionnee.strftime('%d/%m/%Y')}")
|
||||
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
|
||||
ax.legend()
|
||||
st.pyplot(fig)
|
||||
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:
|
||||
st.error(f"Erreur : {e}")
|
||||
st.text(traceback.format_exc())
|
||||
|
||||
Reference in New Issue
Block a user