Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,63 +1,40 @@
|
|
1 |
import gradio as gr
|
2 |
import pandas as pd
|
3 |
-
import numpy as np
|
4 |
import matplotlib.pyplot as plt
|
5 |
|
6 |
-
|
7 |
-
MOIS = ['Janv.', 'Fev.', 'Mars', 'Avr.', 'Mai', 'Juin', 'Juil.', 'Août', 'Sept.', 'Oct.', 'Nov.', 'Dec.']
|
8 |
|
9 |
-
# =========================
|
10 |
-
# Onglet 1 : ancien graphe
|
11 |
-
# =========================
|
12 |
def generer_graphique(file, mois):
|
13 |
-
|
14 |
-
|
15 |
-
fusionne par 'Standard', calcule la moyenne et colorise selon la valeur.
|
16 |
-
"""
|
17 |
-
try:
|
18 |
-
# Lecture des deux feuilles historiques
|
19 |
-
sheet1 = pd.read_excel(file.name, sheet_name='CDT Assemblage', skiprows=4)
|
20 |
-
sheet2 = pd.read_excel(file.name, sheet_name='CDT PE', skiprows=4)
|
21 |
-
except Exception as e:
|
22 |
-
raise gr.Error(f"Impossible de lire les feuilles 'CDT Assemblage' et 'CDT PE'. Détails: {e}")
|
23 |
|
24 |
-
def extraire(df,
|
25 |
-
# 3e colonne = standards dans ta structure existante
|
26 |
standards = df.iloc[:, 2]
|
27 |
-
|
28 |
-
|
29 |
-
# on tente une correspondance souple
|
30 |
-
candidats = [c for c in df.columns if str(c).strip().lower() == mois_col.strip().lower()]
|
31 |
-
if not candidats:
|
32 |
-
raise gr.Error(f"Colonne mois '{mois_col}' introuvable dans la feuille.\nColonnes vues : {list(df.columns)}")
|
33 |
-
mois_col = candidats[0]
|
34 |
-
valeurs = df[mois_col]
|
35 |
-
df_filtre = pd.DataFrame({'Standard': standards, 'Valeur': valeurs}).dropna()
|
36 |
exclure = ['Objectifs', 'Résultats', 'mat>', 'mat> B']
|
37 |
-
|
38 |
-
return df_filtre[mask]
|
39 |
|
40 |
df1 = extraire(sheet1, mois)
|
41 |
df2 = extraire(sheet2, mois)
|
42 |
merged = pd.merge(df1, df2, on='Standard', suffixes=('_1', '_2'))
|
43 |
merged['Moyenne'] = merged[['Valeur_1', 'Valeur_2']].mean(axis=1)
|
44 |
|
45 |
-
# Respecter l'ordre d'origine des standards de la première feuille
|
46 |
ordre = sheet1.iloc[:, 2].dropna().tolist()
|
47 |
-
|
48 |
-
merged = merged.set_index('Standard').loc[
|
49 |
|
50 |
-
#
|
51 |
-
|
|
|
52 |
if val < 3:
|
53 |
-
|
54 |
elif val < 6.5:
|
55 |
-
|
56 |
elif val < 9.2:
|
57 |
-
|
58 |
else:
|
59 |
-
|
60 |
-
couleurs = [couleur(v) for v in merged['Moyenne']]
|
61 |
|
62 |
fig, ax = plt.subplots(figsize=(12, 6))
|
63 |
ax.bar(merged['Standard'], merged['Moyenne'], color=couleurs)
|
@@ -66,187 +43,18 @@ def generer_graphique(file, mois):
|
|
66 |
ax.set_ylabel("Moyenne")
|
67 |
plt.xticks(rotation=45, ha='right')
|
68 |
plt.tight_layout()
|
69 |
-
return fig
|
70 |
-
|
71 |
-
# ===========================================
|
72 |
-
# Onglet 2 : nouvelles courbes d'évolution
|
73 |
-
# ===========================================
|
74 |
-
def _find_header_row(df_raw, mois_labels):
|
75 |
-
"""
|
76 |
-
Trouve la ligne d'en-tête qui contient un max de libellés de mois.
|
77 |
-
Retourne l'index de cette ligne ou None.
|
78 |
-
"""
|
79 |
-
best_idx, best_cnt = None, -1
|
80 |
-
mois_lower = [m.strip().lower() for m in mois_labels]
|
81 |
-
for i in range(min(200, len(df_raw))): # pas besoin de balayer toute la feuille
|
82 |
-
row_vals = [str(x).strip().lower() for x in df_raw.iloc[i, :].tolist()]
|
83 |
-
cnt = sum(m in row_vals for m in mois_lower)
|
84 |
-
if cnt > best_cnt:
|
85 |
-
best_cnt, best_idx = cnt, i
|
86 |
-
# on considère acceptable si >= 6 mois trouvés
|
87 |
-
return best_idx if best_cnt >= 6 else None
|
88 |
-
|
89 |
-
def _prep_synthese(df_raw):
|
90 |
-
"""
|
91 |
-
Nettoie la feuille 'Synthèse usine' en détectant l'en-tête (mois),
|
92 |
-
puis renvoie (df, mois_trouves, cols_avant_mois)
|
93 |
-
- df : dataframe propre (lignes de données sous l'en-tête)
|
94 |
-
- mois_trouves : liste des mois présents dans les colonnes
|
95 |
-
- cols_avant_mois : noms des colonnes "descriptives" avant les colonnes mois
|
96 |
-
"""
|
97 |
-
hdr_idx = _find_header_row(df_raw, MOIS)
|
98 |
-
if hdr_idx is None:
|
99 |
-
raise gr.Error("Impossible d'identifier l'en-tête des mois dans 'Synthèse usine'.")
|
100 |
-
header = df_raw.iloc[hdr_idx].tolist()
|
101 |
-
df = df_raw.iloc[hdr_idx+1:].copy()
|
102 |
-
df.columns = [str(c).strip() if str(c) != 'nan' else f"col_{i}" for i, c in enumerate(header)]
|
103 |
-
|
104 |
-
# Harmoniser les mois ('Août' vs 'Aout', etc.)
|
105 |
-
colmap = {}
|
106 |
-
for c in df.columns:
|
107 |
-
c_norm = str(c).strip()
|
108 |
-
if c_norm.lower() == 'aout':
|
109 |
-
c_norm = 'Août'
|
110 |
-
colmap[c] = c_norm
|
111 |
-
df.rename(columns=colmap, inplace=True)
|
112 |
-
|
113 |
-
mois_trouves = [m for m in MOIS if m in df.columns]
|
114 |
-
if len(mois_trouves) < 6:
|
115 |
-
raise gr.Error(f"Colonnes mois insuffisantes détectées. Trouvées: {mois_trouves}")
|
116 |
-
|
117 |
-
# Colonnes descriptives = toutes les colonnes avant les mois
|
118 |
-
first_mois_idx = min(df.columns.get_loc(m) for m in mois_trouves)
|
119 |
-
cols_avant_mois = df.columns[:first_mois_idx].tolist()
|
120 |
-
# Nettoyage : forward-fill sur colonnes descriptives (souvent structure en lignes regroupées)
|
121 |
-
for c in cols_avant_mois:
|
122 |
-
df[c] = df[c].replace({np.nan: None})
|
123 |
-
df[c] = df[c].ffill()
|
124 |
-
|
125 |
-
return df, mois_trouves, cols_avant_mois
|
126 |
-
|
127 |
-
def _pick_best_column_for(labels_df, candidates, keywords):
|
128 |
-
"""
|
129 |
-
Parmi les colonnes 'candidates', trouve celle qui matche le mieux 'keywords'
|
130 |
-
(tous les mots-clés doivent apparaître, insensibles à la casse).
|
131 |
-
"""
|
132 |
-
best_col, best_hits = None, -1
|
133 |
-
kws = [k.lower() for k in keywords]
|
134 |
-
for c in candidates:
|
135 |
-
vals = labels_df[c].astype(str).str.lower()
|
136 |
-
hits = vals.apply(lambda x: all(k in x for k in kws)).sum()
|
137 |
-
if hits > best_hits:
|
138 |
-
best_hits, best_col = hits, c
|
139 |
-
return best_col
|
140 |
-
|
141 |
-
def _extract_series(df, mois_cols, cols_avant_mois, kpi_keywords, type_keywords):
|
142 |
-
"""
|
143 |
-
Extrait une série (sur 12 mois) pour un couple (KPI, TYPE).
|
144 |
-
- kpi_keywords : ex. ['cdt', 'assemblage']
|
145 |
-
- type_keywords : ex. ['obj'] ou ['act'] (gère 'act'/'act.')
|
146 |
-
Retourne un pd.Series indexé par mois_cols (peut contenir NaN).
|
147 |
-
"""
|
148 |
-
# On cherche sur les colonnes descriptives où se trouvent KPI et TYPE
|
149 |
-
kpi_col = _pick_best_column_for(df, cols_avant_mois, kpi_keywords)
|
150 |
-
type_col = _pick_best_column_for(df, cols_avant_mois, type_keywords)
|
151 |
-
|
152 |
-
if kpi_col is None:
|
153 |
-
raise gr.Error(f"Impossible de localiser le KPI {' '.join(kpi_keywords)} dans 'Synthèse usine'.")
|
154 |
-
if type_col is None:
|
155 |
-
# parfois 'Obj/Act' est dans la même colonne que le KPI ou dans une 2e colonne voisine
|
156 |
-
# on tente alors dans kpi_col
|
157 |
-
type_col = kpi_col
|
158 |
-
|
159 |
-
# Filtrage
|
160 |
-
mask_kpi = df[kpi_col].astype(str).str.lower().apply(lambda x: all(k in x for k in [k.lower() for k in kpi_keywords]))
|
161 |
-
# gérer 'act' ou 'act.' et 'obj'
|
162 |
-
type_norm = [t.lower().rstrip('.') for t in type_keywords]
|
163 |
-
mask_type = df[type_col].astype(str).str.lower().apply(lambda x: any(t in x.rstrip('.') for t in type_norm))
|
164 |
-
|
165 |
-
sub = df[mask_kpi & mask_type]
|
166 |
-
if sub.empty:
|
167 |
-
# fallback: si pas trouvé, tenter recherche large en concaténant les colonnes descriptives
|
168 |
-
concat_desc = df[cols_avant_mois].astype(str).agg(' '.join, axis=1).str.lower()
|
169 |
-
mask_all = concat_desc.apply(lambda x: all(k in x for k in [k.lower() for k in kpi_keywords])) & \
|
170 |
-
concat_desc.apply(lambda x: any(t in x for t in type_norm))
|
171 |
-
sub = df[mask_all]
|
172 |
-
|
173 |
-
if sub.empty:
|
174 |
-
# on renvoie une série NaN pour ne pas casser le tracé global
|
175 |
-
return pd.Series([np.nan]*len(mois_cols), index=mois_cols, dtype=float)
|
176 |
|
177 |
-
# Si plusieurs lignes matchent, on prend la première non vide sur les mois (ou la moyenne)
|
178 |
-
# Ici, on prend la moyenne par mois pour être robuste
|
179 |
-
series_vals = sub[mois_cols].apply(pd.to_numeric, errors='coerce').mean(axis=0)
|
180 |
-
return series_vals
|
181 |
-
|
182 |
-
def courbes_evolution(file):
|
183 |
-
"""
|
184 |
-
Nouvelle fonctionnalité : lit 'Synthèse usine' -> tableau '#@mat B AOS par regroupement de ligne'
|
185 |
-
puis trace 6 courbes :
|
186 |
-
- CDT Assemblage (OBJ, ACT)
|
187 |
-
- CDT PE (OBJ, ACT)
|
188 |
-
- Moyenne OBJ (entre les 2 OBJ)
|
189 |
-
- Moyenne ACT (entre les 2 ACT)
|
190 |
-
"""
|
191 |
-
try:
|
192 |
-
df_raw = pd.read_excel(file.name, sheet_name='Synthèse usine', header=None)
|
193 |
-
except Exception as e:
|
194 |
-
raise gr.Error(f"Impossible de lire la feuille 'Synthèse usine'. Détails: {e}")
|
195 |
-
|
196 |
-
df, mois_cols, cols_desc = _prep_synthese(df_raw)
|
197 |
-
|
198 |
-
# Extraire les 4 séries principales
|
199 |
-
asm_obj = _extract_series(df, mois_cols, cols_desc, kpi_keywords=['cdt', 'assemblage'], type_keywords=['obj'])
|
200 |
-
asm_act = _extract_series(df, mois_cols, cols_desc, kpi_keywords=['cdt', 'assemblage'], type_keywords=['act'])
|
201 |
-
pe_obj = _extract_series(df, mois_cols, cols_desc, kpi_keywords=['cdt', 'pe'], type_keywords=['obj'])
|
202 |
-
pe_act = _extract_series(df, mois_cols, cols_desc, kpi_keywords=['cdt', 'pe'], type_keywords=['act'])
|
203 |
-
|
204 |
-
# Moyennes OBJ/ACT entre Assemblage et PE
|
205 |
-
mean_obj = pd.concat([asm_obj, pe_obj], axis=1).mean(axis=1)
|
206 |
-
mean_act = pd.concat([asm_act, pe_act], axis=1).mean(axis=1)
|
207 |
-
|
208 |
-
# Tracé
|
209 |
-
fig, ax = plt.subplots(figsize=(12, 6))
|
210 |
-
x = np.arange(len(mois_cols))
|
211 |
-
|
212 |
-
# 4 courbes + 2 moyennes
|
213 |
-
ax.plot(x, asm_obj.values, marker='o', label='CDT Assemblage - OBJ')
|
214 |
-
ax.plot(x, asm_act.values, marker='o', label='CDT Assemblage - ACT')
|
215 |
-
ax.plot(x, pe_obj.values, marker='o', label='CDT PE - OBJ')
|
216 |
-
ax.plot(x, pe_act.values, marker='o', label='CDT PE - ACT')
|
217 |
-
ax.plot(x, mean_obj.values, marker='D', linestyle='--', label='Moyenne OBJ (Asm+PE)')
|
218 |
-
ax.plot(x, mean_act.values, marker='D', linestyle='--', label='Moyenne ACT (Asm+PE)')
|
219 |
-
|
220 |
-
ax.set_xticks(x)
|
221 |
-
ax.set_xticklabels(mois_cols, rotation=0)
|
222 |
-
ax.set_xlabel("Mois")
|
223 |
-
ax.set_ylabel("Valeur")
|
224 |
-
ax.set_title("Évolution mensuelle – CDT Assemblage & CDT PE (OBJ/ACT) + Moyennes")
|
225 |
-
ax.grid(True, which='both', linestyle=':', linewidth=0.8)
|
226 |
-
ax.legend(loc='best', ncol=2)
|
227 |
-
plt.tight_layout()
|
228 |
return fig
|
229 |
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
with gr.Tab("Courbes d’évolution (nouveau)"):
|
244 |
-
in_file2 = gr.File(label="Fichier Excel (.xlsx)", file_types=[".xlsx"])
|
245 |
-
out_plot2 = gr.Plot(label="Évolution mensuelle")
|
246 |
-
btn2 = gr.Button("Tracer")
|
247 |
-
btn2.click(fn=courbes_evolution, inputs=[in_file2], outputs=out_plot2)
|
248 |
-
|
249 |
-
# Pour HF Spaces / Docker : ne pas ouvrir de navigateur
|
250 |
-
if __name__ == "__main__":
|
251 |
-
demo.launch(server_name="0.0.0.0", server_port=7860)
|
252 |
-
|
|
|
1 |
import gradio as gr
|
2 |
import pandas as pd
|
|
|
3 |
import matplotlib.pyplot as plt
|
4 |
|
5 |
+
mois_disponibles = ['Janv.', 'Fev.', 'Mars', 'Avr.', 'Mai', 'Juin', 'Juil.', 'Août', 'Sept.', 'Oct.', 'Nov.', 'Dec.']
|
|
|
6 |
|
|
|
|
|
|
|
7 |
def generer_graphique(file, mois):
|
8 |
+
sheet1 = pd.read_excel(file.name, sheet_name='CDT Assemblage', skiprows=4)
|
9 |
+
sheet2 = pd.read_excel(file.name, sheet_name='CDT PE', skiprows=4)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
+
def extraire(df, mois):
|
|
|
12 |
standards = df.iloc[:, 2]
|
13 |
+
valeurs = df[mois]
|
14 |
+
df_filtré = pd.DataFrame({'Standard': standards, 'Valeur': valeurs}).dropna()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
exclure = ['Objectifs', 'Résultats', 'mat>', 'mat> B']
|
16 |
+
return df_filtré[~df_filtré['Standard'].astype(str).str.contains('|'.join(exclure), case=False)]
|
|
|
17 |
|
18 |
df1 = extraire(sheet1, mois)
|
19 |
df2 = extraire(sheet2, mois)
|
20 |
merged = pd.merge(df1, df2, on='Standard', suffixes=('_1', '_2'))
|
21 |
merged['Moyenne'] = merged[['Valeur_1', 'Valeur_2']].mean(axis=1)
|
22 |
|
|
|
23 |
ordre = sheet1.iloc[:, 2].dropna().tolist()
|
24 |
+
ordre_filtré = [s for s in ordre if s in merged['Standard'].values]
|
25 |
+
merged = merged.set_index('Standard').loc[ordre_filtré].reset_index()
|
26 |
|
27 |
+
# Définir les couleurs selon les valeurs
|
28 |
+
couleurs = []
|
29 |
+
for val in merged['Moyenne']:
|
30 |
if val < 3:
|
31 |
+
couleurs.append('red')
|
32 |
elif val < 6.5:
|
33 |
+
couleurs.append('yellow')
|
34 |
elif val < 9.2:
|
35 |
+
couleurs.append('green')
|
36 |
else:
|
37 |
+
couleurs.append('skyblue')
|
|
|
38 |
|
39 |
fig, ax = plt.subplots(figsize=(12, 6))
|
40 |
ax.bar(merged['Standard'], merged['Moyenne'], color=couleurs)
|
|
|
43 |
ax.set_ylabel("Moyenne")
|
44 |
plt.xticks(rotation=45, ha='right')
|
45 |
plt.tight_layout()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
return fig
|
48 |
|
49 |
+
interface = gr.Interface(
|
50 |
+
fn=generer_graphique,
|
51 |
+
inputs=[
|
52 |
+
gr.File(label="Fichier Excel (.xlsx)", file_types=[".xlsx"]),
|
53 |
+
gr.Dropdown(choices=mois_disponibles, label="Mois")
|
54 |
+
],
|
55 |
+
outputs=gr.Plot(label="Graphique"),
|
56 |
+
title="Analyse de Maturité par Standard",
|
57 |
+
description="Chargez un fichier Excel, sélectionnez un mois, et visualisez la moyenne des scores."
|
58 |
+
)
|
59 |
+
|
60 |
+
interface.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|