POIDSBRIE / app.py
MMOON's picture
Update app.py
a94258d verified
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import plotly.figure_factory as ff
from datetime import datetime
import re
import io
def extract_numeric_value(value_str):
"""Extrait la valeur numérique en supprimant l'unité 'g' et les espaces."""
if isinstance(value_str, str):
match = re.search(r'(\d+\.?\d*)', value_str.strip())
return float(match.group(1)) if match else None
return value_str
def parse_lot_sections(content):
"""Divise le contenu en sections de lots."""
return [section.strip() for section in content.split('----------') if section.strip()]
def parse_lot_info(section):
"""Extrait les informations générales du lot."""
lines = section.split('\n')
lot_info = {
'lot': None,
'operateur': None,
'article': None,
'poids_cible': None,
'date': None,
'heure': None
}
for line in lines:
if 'LOT\t' in line:
lot_info['lot'] = line.split('\t')[1].strip()
elif 'OPERATEUR\t' in line:
lot_info['operateur'] = line.split('\t')[1].strip()
elif 'ARTICLE\t' in line:
lot_info['article'] = line.split('\t')[1].strip()
elif 'POIDS CIBLE\t' in line:
lot_info['poids_cible'] = extract_numeric_value(line.split('\t')[1])
elif 'DEBUT LOT' in line:
try:
date_line = next(l for l in lines[lines.index(line):] if re.match(r'\d{2}/\d{2}/\d{2}', l.strip()))
date_time = date_line.strip().split()
lot_info['date'] = datetime.strptime(date_time[0], '%d/%m/%y').strftime('%Y-%m-%d')
if len(date_time) > 1:
lot_info['heure'] = date_time[1]
except:
pass
return lot_info
def parse_weights(section):
"""Extrait les données de pesées individuelles."""
weights = []
lot_info = parse_lot_info(section)
lines = section.split('\n')
for line in lines:
if re.match(r'\s*\d+\t', line):
parts = line.split('\t')
if len(parts) >= 3:
weight = extract_numeric_value(parts[1])
target = lot_info['poids_cible']
weight_data = {
'identifiant_lot': f"{lot_info['lot']}_{lot_info['date']}",
'ligne': int(parts[0].strip()),
'poids': weight,
'tare': extract_numeric_value(parts[-1].split()[-2]) if 'TARE' in line else None,
'operateur': lot_info['operateur'],
'article': lot_info['article'],
'poids_cible': target,
'surpoids': weight - target if weight and target else None,
'surpoids_percentage': ((weight - target) / target * 100) if weight and target else None,
'date': lot_info['date'],
'heure': lot_info['heure']
}
weights.append(weight_data)
return weights
def create_weight_distribution_plot(weights_df):
"""Crée un graphique de distribution des poids par rapport au poids cible."""
fig = go.Figure()
for article in weights_df['article'].unique():
df_article = weights_df[weights_df['article'] == article]
fig.add_trace(go.Histogram(
x=df_article['surpoids_percentage'],
name=article,
opacity=0.75,
nbinsx=30
))
fig.update_layout(
title="Distribution des surpoids par article (%)",
xaxis_title="Surpoids (%)",
yaxis_title="Nombre de pesées",
barmode='overlay'
)
return fig
def create_boxplot_by_lot(weights_df):
"""Crée un box plot des poids par lot."""
fig = px.box(weights_df,
x='identifiant_lot',
y='surpoids',
color='article',
title="Distribution des surpoids par lot",
labels={'surpoids': 'Surpoids (g)',
'identifiant_lot': 'Lot'})
fig.update_layout(xaxis_tickangle=-45)
return fig
def create_trend_plot(weights_df):
"""Crée un graphique de tendance des surpoids dans le temps."""
# Calcul de la moyenne mobile
df_sorted = weights_df.sort_values(['date', 'heure'])
df_sorted['moving_avg'] = df_sorted.groupby('article')['surpoids'].transform(
lambda x: x.rolling(window=10, min_periods=1).mean()
)
fig = px.line(df_sorted,
x=df_sorted.index,
y='moving_avg',
color='article',
title="Évolution des surpoids dans le temps (moyenne mobile)",
labels={'moving_avg': 'Surpoids moyen (g)',
'index': 'Séquence de pesée'})
return fig
def create_heatmap(weights_df):
"""Crée une heatmap des surpoids moyens par article et par lot."""
pivot_table = pd.pivot_table(weights_df,
values='surpoids',
index='article',
columns='identifiant_lot',
aggfunc='mean')
fig = px.imshow(pivot_table,
labels=dict(x="Lot", y="Article", color="Surpoids moyen (g)"),
aspect="auto",
title="Heatmap des surpoids moyens par lot et article")
return fig
def create_control_chart(weights_df):
"""Crée une carte de contrôle des poids."""
df_sorted = weights_df.sort_values(['date', 'heure'])
# Calcul des limites de contrôle
mean = df_sorted['poids'].mean()
std = df_sorted['poids'].std()
ucl = mean + 3 * std
lcl = mean - 3 * std
fig = go.Figure()
# Ajout des points de mesure
fig.add_trace(go.Scatter(
x=df_sorted.index,
y=df_sorted['poids'],
mode='markers+lines',
name='Poids'
))
# Ajout des lignes de contrôle
fig.add_hline(y=mean, line_dash="dash", line_color="green", annotation_text="Moyenne")
fig.add_hline(y=ucl, line_dash="dash", line_color="red", annotation_text="UCL")
fig.add_hline(y=lcl, line_dash="dash", line_color="red", annotation_text="LCL")
fig.update_layout(
title="Carte de contrôle des poids",
xaxis_title="Séquence de pesée",
yaxis_title="Poids (g)"
)
return fig
def analyze_overweight_costs(weights_df):
"""Analyse les coûts liés aux surpoids."""
analysis = weights_df.groupby('article').agg({
'surpoids': ['mean', 'std', 'sum'],
'poids_cible': 'first',
'poids': 'count'
}).round(2)
analysis.columns = ['Surpoids moyen (g)', 'Écart-type surpoids (g)',
'Surpoids total (g)', 'Poids cible (g)', 'Nombre de pesées']
analysis['Surpoids moyen (%)'] = (analysis['Surpoids moyen (g)'] /
analysis['Poids cible (g)'] * 100).round(2)
return analysis
def process_file(content):
"""Traite le fichier complet et retourne les DataFrames."""
sections = parse_lot_sections(content)
# Traitement des données
all_weights = []
all_summaries = []
for section in sections:
if section and 'LOT\t' in section:
weights = parse_weights(section)
if weights:
all_weights.extend(weights)
# Création des DataFrames
weights_df = pd.DataFrame(all_weights)
return weights_df
def main():
st.set_page_config(page_title="Analyse des pesées", layout="wide")
st.title("Analyse des données de pesée")
uploaded_file = st.file_uploader("Choisir un fichier", type=['txt'])
if uploaded_file is not None:
content = uploaded_file.read().decode('utf-8')
try:
weights_df = process_file(content)
# Analyse des surpoids
st.header("Analyse des surpoids")
# Métriques clés
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Surpoids moyen", f"{weights_df['surpoids'].mean():.2f}g")
with col2:
st.metric("Surpoids médian", f"{weights_df['surpoids'].median():.2f}g")
with col3:
st.metric("Écart-type surpoids", f"{weights_df['surpoids'].std():.2f}g")
# Graphiques
st.subheader("Distribution des surpoids")
st.plotly_chart(create_weight_distribution_plot(weights_df), use_container_width=True)
st.subheader("Analyse par lot")
st.plotly_chart(create_boxplot_by_lot(weights_df), use_container_width=True)
st.subheader("Évolution temporelle")
st.plotly_chart(create_trend_plot(weights_df), use_container_width=True)
st.subheader("Carte de contrôle")
st.plotly_chart(create_control_chart(weights_df), use_container_width=True)
# Analyse des coûts
st.header("Analyse détaillée par article")
analysis_df = analyze_overweight_costs(weights_df)
st.dataframe(analysis_df)
# Heatmap
st.subheader("Heatmap des surpoids")
st.plotly_chart(create_heatmap(weights_df), use_container_width=True)
# Export Excel
output = io.BytesIO()
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
weights_df.to_excel(writer, sheet_name='Données détaillées', index=False)
analysis_df.to_excel(writer, sheet_name='Analyse surpoids')
output.seek(0)
st.download_button(
label="Télécharger l'analyse Excel",
data=output,
file_name="analyse_surpoids.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
except Exception as e:
st.error(f"Une erreur s'est produite lors du traitement du fichier : {str(e)}")
if __name__ == '__main__':
main()