POMPOL / appmistral.py
MMOON's picture
Create appmistral.py
c5940bb verified
import streamlit as st
import pandas as pd
import numpy as np
from scipy import stats
from scipy.interpolate import interp1d
from typing import Tuple, Optional, Dict, Any, List
import plotly.graph_objects as go
import plotly.express as px
import io
import xlsxwriter
from PIL import Image
import statsmodels.api as sm
from statsmodels.formula.api import ols
# Classe personnalisée pour les erreurs de validation
class ValidationError(Exception):
pass
# Fonction pour créer un template Excel pour l'import des données
def create_excel_template() -> bytes:
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output)
# Formats Excel
header_format = workbook.add_format({
'bold': True,
'bg_color': '#4B8BBE',
'font_color': 'white',
'border': 1,
'align': 'center'
})
data_format = workbook.add_format({
'border': 1,
'align': 'center'
})
# Feuille Pesées
ws_pesees = workbook.add_worksheet("Pesées")
# En-têtes
headers = ["Date", "Heure", "Lot", "Tare (g)", "Poids Brut (g)", "Remarques"]
for col, header in enumerate(headers):
ws_pesees.write(0, col, header, header_format)
ws_pesees.set_column(col, col, 15) # Largeur de colonne
# Données exemple
example_data = [
["2024-01-01", "08:00", "LOT001", 10.5, 510.5, ""],
["2024-01-01", "09:00", "LOT001", 10.4, 509.8, ""],
["2024-01-01", "10:00", "LOT001", 10.6, 511.2, ""],
]
for row, data in enumerate(example_data, start=1):
for col, value in enumerate(data):
ws_pesees.write(row, col, value, data_format)
# Feuille Instructions
ws_instructions = workbook.add_worksheet("Instructions")
instructions = [
["Guide d'utilisation du fichier de pesées"],
[""],
["1. Format des colonnes:"],
[" - Date: AAAA-MM-JJ"],
[" - Heure: HH:MM"],
[" - Lot: Texte libre"],
[" - Tare (g): Nombre décimal"],
[" - Poids Brut (g): Nombre décimal"],
[" - Remarques: Texte libre optionnel"],
[""],
["2. Consignes importantes:"],
[" - Ne pas modifier les en-têtes"],
[" - Remplir toutes les colonnes obligatoires"],
[" - Un minimum de 5 mesures est recommandé"],
[" - Vérifier la cohérence des unités (grammes)"],
[" - Pour les produits avec signe 'e', respecter les exigences additionnelles"],
]
workbook.close()
output.seek(0)
return output.getvalue()
# Fonction pour déterminer l'erreur maximale tolérée (EMT)
def get_emt(qn: float) -> float:
"""
Détermine l'erreur maximale tolérée selon l'annexe 5 du guide DGCCRF.
Args:
qn: Quantité nominale en grammes ou millilitres
Returns:
EMT en grammes ou millilitres
"""
if qn >= 5 and qn <= 50:
return qn * 0.09
elif qn > 50 and qn <= 100:
return 4.5
elif qn > 100 and qn <= 200:
return qn * 0.045
elif qn > 200 and qn <= 300:
return 9
elif qn > 300 and qn <= 500:
return qn * 0.03
elif qn > 500 and qn <= 1000:
return 15
elif qn > 1000 and qn <= 10000:
return qn * 0.015
elif qn > 10000 and qn <= 15000:
return 150
elif qn > 15000:
return qn * 0.01
else:
raise ValueError(f"Quantité nominale {qn} hors plage valide (doit être ≥ 5).")
# Classe pour effectuer les calculs métrologiques
class MetrologicalCalculations:
def __init__(self, qn, emt=None, surpoids_max=0, n=5, frequence=1.0, use_e_sign=False):
"""
Initialise les calculs métrologiques.
Args:
qn: Quantité nominale
emt: Erreur maximale tolérée (calculée automatiquement si None)
surpoids_max: Tolérance supérieure acceptée
n: Taille d'échantillon pour les contrôles
frequence: Fréquence d'échantillonnage (par heure)
use_e_sign: True si le produit porte le signe 'e'
"""
self.qn = qn
self.emt = emt if emt is not None else get_emt(qn)
self.surpoids_max = surpoids_max
self.n = n
self.frequence = frequence
self.use_e_sign = use_e_sign
def calculate_pol(self):
"""
Calcule la Période Opérationnelle Limite (POl)
selon la section VII.B.2.3 du guide DGCCRF.
"""
base_pol = 4 # 4 heures comme référence
qn_factor = min(2.0, max(0.5, self.qn / 1000))
emt_factor = min(1.5, max(0.5, self.emt / (self.qn * 0.01)))
n_factor = min(1.5, max(0.5, self.n / 5))
pol_adjusted = base_pol * qn_factor * emt_factor * n_factor
return pol_adjusted * self.frequence
def calculate_pom(self, sigma_0):
"""
Calcule la Période Opérationnelle Moyenne (POM)
selon la section VII.B.2.3 du guide DGCCRF.
Args:
sigma_0: Écart-type du processus
Returns:
Valeur POM
"""
# Détermination du seuil de centrage ms
if sigma_0 <= self.emt / 2.05:
ms = self.qn
else:
ms = self.qn - self.emt + 2.05 * sigma_0
# Calcul de QC (quantité cible)
qc = ms + self.surpoids_max
# Calcul du delta
delta = (qc - (self.qn - self.emt + 2.05 * sigma_0)) / sigma_0
# Calcul du delta_sqrt_n
delta_sqrt_n = delta * np.sqrt(self.n)
# Facteur de fréquence
freq_factor = max(0.5, min(2.0, 1 / self.frequence))
# Calcul POM - Approximation simplifiée
# (Une implémentation complète nécessiterait des tables de calcul détaillées)
if delta_sqrt_n <= 0.5:
return 100 * freq_factor # Valeur très élevée
elif delta_sqrt_n <= 1.0:
return 15 * freq_factor
elif delta_sqrt_n <= 1.5:
return 8 * freq_factor
elif delta_sqrt_n <= 2.0:
return 5 * freq_factor
elif delta_sqrt_n <= 2.5:
return 3 * freq_factor
elif delta_sqrt_n <= 3.0:
return 2 * freq_factor
else:
return 1 * freq_factor
def evaluate_sampling_plan(self, sigma_0):
"""
Évalue l'efficacité du plan d'échantillonnage.
Args:
sigma_0: Écart-type du processus
Returns:
Dictionnaire avec les résultats d'évaluation
"""
pol = self.calculate_pol()
pom = self.calculate_pom(sigma_0)
is_valid = pom <= pol
recommendations = []
if not is_valid:
if pom > pol * 2:
recommendations.append("🚨 Plan d'échantillonnage très insuffisant")
recommendations.append(f"- Augmenter la fréquence à au moins {max(2, self.frequence * 2):.1f} contrôles/heure")
recommendations.append(f"- Augmenter la taille d'échantillon à {max(5, self.n + 2)} unités minimum")
else:
recommendations.append("⚠️ Plan d'échantillonnage légèrement insuffisant")
recommendations.append(f"- Envisager d'augmenter la fréquence à {max(1, self.frequence * 1.5):.1f} contrôles/heure")
return {
'is_valid': is_valid,
'pom': pom,
'pol': pol,
'recommendations': recommendations
}
def analyze_tare_variability(self, tare_values):
"""
Analyse la variabilité de la tare selon la section VI.A du guide DGCCRF.
Args:
tare_values: Liste des valeurs de tare
Returns:
Dictionnaire avec les résultats de l'analyse
"""
tare_mean = np.mean(tare_values)
tare_std = np.std(tare_values, ddof=1)
# Règle de la section VI.A: contrôle avec tare individuelle
# si écart-type > EMT/5
use_individual_tare = tare_std > (self.emt / 5)
return {
'mean': tare_mean,
'std': tare_std,
'use_individual_tare': use_individual_tare,
'threshold': self.emt / 5
}
def calculate_threshold_centering(self, sigma_0):
"""
Calcule le seuil de centrage (ms) selon la section V du guide DGCCRF.
Args:
sigma_0: Écart-type du processus
Returns:
Seuil de centrage (ms)
"""
# Vérification si σ0 ≤ E/2.05
if sigma_0 <= self.emt / 2.05:
return self.qn
else:
# Si le processus est peu précis, un surdosage est nécessaire
return self.qn - self.emt + 2.05 * sigma_0
def calculate_target_quantity(self, sigma_0, lot_size=None):
"""
Calcule la quantité cible (QC) selon la section VII.B.2.1 du guide DGCCRF.
Args:
sigma_0: Écart-type du processus
lot_size: Taille du lot (pour critère des superdéfectueux)
Returns:
Quantité cible (QC)
"""
ms = self.calculate_threshold_centering(sigma_0)
# Si produit avec signe "e", vérifier critère des superdéfectueux
if self.use_e_sign and lot_size:
# Facteur U selon effectif du lot (section V.B)
if lot_size <= 1000:
u_value = 3.09
elif lot_size <= 10000:
u_value = 3.71
else:
u_value = 4.26
# Calcul pour critère superdéfectueux
ms_super = self.qn - 2 * self.emt + u_value * sigma_0
# Prendre la valeur la plus contraignante
ms = max(ms, ms_super)
# Ajouter facteur pour efficacité de la carte de contrôle
k_factor = 0.5 # Valeur typique pour cartes de contrôle efficaces
return ms + k_factor * sigma_0
def calculate_capability_indices(self, data, qn=None, emt=None):
"""
Calcule les indices de capabilité du processus.
Args:
data: Liste des poids nets
qn: Quantité nominale (utilise self.qn si None)
emt: Erreur maximale tolérée (utilise self.emt si None)
Returns:
Dictionnaire avec les indices de capabilité
"""
if qn is None:
qn = self.qn
if emt is None:
emt = self.emt
mean = np.mean(data)
std = np.std(data, ddof=1)
# Cp: rapport entre la tolérance et la dispersion naturelle
cp = emt / (3 * std)
# Cpk: indice tenant compte du centrage du processus
cpk_inf = (mean - (qn - emt)) / (3 * std)
cpk_sup = ((qn + emt) - mean) / (3 * std)
cpk = min(cpk_inf, cpk_sup)
# Interprétation selon standards industriels
process_centered = abs(mean - qn) <= emt/4
return {
'mean': mean,
'std': std,
'cp': cp,
'cpk': cpk,
'process_centered': process_centered,
'capable': cp >= 1.33,
'centered_capable': cpk >= 1.33
}
def analyze_lot_conformity(self, net_weights):
"""
Analyse la conformité du lot selon les 3 critères du guide DGCCRF.
Args:
net_weights: Liste des poids nets
Returns:
Dictionnaire avec les résultats d'analyse
"""
# Statistiques de base
mean = np.mean(net_weights)
std = np.std(net_weights, ddof=1) # Calcul de l'écart-type
# 1. Critère de la moyenne
mean_conformity = mean >= self.qn
# 2. Critère des défectueux
defectives = [w for w in net_weights if (self.qn - w) > self.emt]
defective_rate = len(defectives) / len(net_weights) * 100
defective_conformity = defective_rate <= 2.0 # Maximum 2% selon guide
# 3. Critère des superdéfectueux (pour produits avec "e")
super_defectives = [w for w in net_weights if (self.qn - w) > 2 * self.emt]
super_defective_conformity = len(super_defectives) == 0 if self.use_e_sign else True
# Calcul du seuil de centrage
ms = self.calculate_threshold_centering(std)
# Quantité cible recommandée
qc = self.calculate_target_quantity(std, len(net_weights))
# Test de normalité
stat, p_value = stats.shapiro(net_weights)
is_normal = p_value > 0.05
return {
'mean': mean,
'std': std, # Ajoutez l'écart-type ici
'min': min(net_weights),
'max': max(net_weights),
'criteria': {
'mean': mean_conformity,
'defectives': defective_conformity,
'super_defectives': super_defective_conformity
},
'defectives': {
'count': len(defectives),
'rate': defective_rate,
'values': defectives if len(defectives) < 10 else defectives[:10]
},
'super_defectives': {
'count': len(super_defectives),
'values': super_defectives
},
'threshold_centering': ms,
'target_quantity': qc,
'overall_conformity': mean_conformity and defective_conformity and super_defective_conformity,
'normality_test': {
'is_normal': is_normal,
'p_value': p_value
}
}
# Fonction d'affichage de l'aide
def show_help():
"""Affiche l'aide contextuelle"""
st.sidebar.markdown("""
## 💡 Aide rapide
### Paramètres essentiels
- **Quantité Nominale**: Poids cible du produit
- **EMT**: Erreur maximale tolérée
- **Surpoids max**: Tolérance supérieure acceptée
- **Signe e**: Apposé pour garantir la conformité métrologique
### Procédure d'utilisation
1. Remplir les paramètres
2. Télécharger le template Excel
3. Compléter les données
4. Importer le fichier
5. Analyser les résultats
### Interprétation des résultats
- **Conformité moyenne**: Moyenne ≥ QN
- **Conformité défectueux**: Défectueux ≤ 2%
- **Cp > 1.33**: Processus capable
- **Cpk > 1.33**: Processus centré et capable
- **POM ≤ POL**: Plan d'échantillonnage efficace
### Critères DGCCRF (Décret 78-166)
- **Critère de la moyenne**: Le contenu effectif des préemballages du lot ne doit pas être inférieur, en moyenne, à la quantité nominale.
- **Critère des défectueux**: Le taux de préemballages défectueux doit être inférieur à 2%.
- **Critère des superdéfectueux**: Aucun préemballage ne doit présenter un manquant supérieur à 2× l'EMT (pour produits avec signe "e").
### Besoin d'aide ?
[Guide DGCCRF](https://www.economie.gouv.fr/dgccrf/controle-des-preemballages)
""")
# Fonction d'affichage de la documentation détaillée
def show_detailed_documentation():
"""Affiche la documentation détaillée dans un expander"""
with st.expander("📚 Guide simplifié du contrôle métrologique des préemballages"):
st.markdown("""
## Guide simplifié du contrôle métrologique des préemballages
### 1. Cadre réglementaire et références
#### 1.1 Textes réglementaires
- **Décret n°78-166** du 31 janvier 1978 : Ce décret fixe les règles de contrôle des produits préemballés, comme les produits alimentaires.
- **Directive 76/211/CEE** : Cette directive européenne concerne le poids ou le volume indiqué sur les emballages des produits.
- **Guide DGCCRF** : Guide officiel pour vérifier que les emballages respectent les poids ou volumes annoncés.
#### 1.2 Normes applicables
- **ISO 2859** : Règles pour vérifier si un lot de produits est conforme à l'aide de quelques échantillons.
- **ISO 3951** : Règles pour contrôler la qualité des produits en mesurant précisément certains paramètres (comme le poids).
### 2. Définitions simples et concepts clés
#### 2.1 Les principaux paramètres du contrôle
- **Quantité nominale (Qn)** : C'est le poids ou volume indiqué sur l'emballage (par exemple, 500 g).
- **Erreur maximale tolérée (T)** : Il est normal qu'il y ait de légères différences de poids entre les emballages. L'erreur maximale tolérée (ou EMT) est la marge d'erreur autorisée par la loi.
- **Qn - T** : C'est le poids minimum acceptable pour chaque produit. Si un produit est en dessous de ce poids, il est considéré comme non conforme.
- **Ms (Moyenne minimale acceptable)** : C'est le poids moyen minimum que doit atteindre l'ensemble des produits d'un lot pour être jugé conforme.
#### 2.2 Comment calculer la moyenne minimale (Ms)
Pour vérifier si un lot est conforme, nous devons calculer la **moyenne minimale acceptable (Ms)**. Voici comment elle est calculée :
```plaintext
Si les variations de poids (appelées écart-type) sont faibles : Ms = Qn
Si les variations de poids sont importantes : Ms = Qn - T + 2.05 * écart-type
```
**Exemple** : Si la quantité nominale (Qn) est 500 g et l'erreur tolérée (T) est 15 g, et que l'écart-type (σ₀) est faible, la moyenne acceptable du lot sera simplement 500 g.
### 3. Calcul du POM et POL (Période Opérationnelle)
#### 3.1 Qu'est-ce que le POM et POL ?
Le POM (Période Opérationnelle Moyenne) et le POL (Période Opérationnelle Limite) sont des indicateurs qui permettent de savoir combien d'échantillons vous devez prélever en moyenne pour détecter un problème de poids dans un lot de produits. Pour que le contrôle soit efficace, il faut que POM ≤ POL.
#### 3.2 Comment calculer le POM ?
Pour calculer le POM, il faut utiliser une méthode mathématique qui tient compte de la variation de poids dans le lot. Le calcul se fait en plusieurs étapes :
- **Étape 1** : Calculer le **delta** (c'est la différence entre la cible de production et le poids minimal acceptable).
```plaintext
delta = (Ms + surpoids_max - (Qn - T + 2.05 * écart-type)) / écart-type
```
- **Étape 2** : Calculer le **delta_sqrt_n** qui tient compte de la taille de l'échantillon (combien de produits on pèse).
```plaintext
delta_sqrt_n = delta * √n
```
### 4. Analyse des résultats (vérification des données)
#### 4.1 Test de normalité (Shapiro-Wilk)
Avant de valider le contrôle, on vérifie si les poids des produits sont "répartis normalement". Ce test permet de savoir si les poids suivent une distribution habituelle. Si le test dit "oui", on peut utiliser les calculs habituels pour valider le contrôle.
#### 4.2 Indices de capabilité (Cp et Cpk)
Les indices de capabilité permettent de savoir si le processus de production est capable de fabriquer des produits qui respectent les limites légales de poids.
- **Cp** : Il mesure la capacité du processus à respecter l'erreur maximale tolérée (T).
- **Cpk** : Il mesure si le processus est bien centré par rapport à la quantité nominale (Qn).
```plaintext
Cp = T / (6 * écart-type)
Cpk = min((USL - moyenne du processus)/(3 * écart-type), (moyenne du processus - LSL)/(3 * écart-type))
Où :
- **USL** (Upper Specification Limit) = Qn + T (limite supérieure de poids)
- **LSL** (Lower Specification Limit) = Qn - T (limite inférieure de poids)
```
### 5. Critères de validation du lot
#### 5.1 Règle pour valider ou rejeter le lot
Une fois le POM calculé, on le compare au POl (la période opérationnelle limite). Le POl dépend de la fréquence des prélèvements.
```plaintext
Si POM ≤ POl : Le contrôle est validé (le lot est conforme).
Si POM > POl : Le contrôle est insuffisant (il faut prélever plus d'échantillons ou revoir le processus).
```
#### 5.2 Interprétation des indices Cp et Cpk
- **Cp ≥ 1.33** : Le processus est capable de respecter les tolérances.
- **Cpk ≥ 1.33** : Le processus est bien centré et les produits sont conformes.
### 6. Limites et précautions
#### 6.1 Validité des calculs
- Le calcul du POM n'est valable que pour certaines valeurs (delta_sqrt_n doit être compris entre 0.17 et 3.38).
- Si la valeur est en dehors de cette plage, des ajustements conservateurs sont faits.
#### 6.2 Hypothèses importantes
- Il est important que les poids des produits suivent une distribution normale pour que les calculs fonctionnent correctement.
- Le processus de production doit être stable (sans grandes variations) pour garantir la validité des résultats.
### 7. Recommandations pratiques pour la production
#### 7.1 Fréquence d'échantillonnage
- **Recommandation minimum** : 1 prélèvement toutes les 4 heures pour vérifier que le lot reste conforme.
- Si le processus est plus variable, il peut être nécessaire de faire des prélèvements plus fréquents.
#### 7.2 Taille des échantillons
- **Minimum recommandé** : 5 échantillons par lot.
- Si le processus est plus variable ou que les exigences sont plus strictes, il peut être nécessaire d'augmenter la taille des échantillons.
""")
def main():
st.set_page_config(
page_title="Contrôle Métrologique - DGCCRF",
layout="wide",
initial_sidebar_state="expanded"
)
# Style CSS personnalisé
st.markdown("""
<style>
.stAlert {border-radius: 10px;}
.stButton>button {border-radius: 5px;}
.stTextInput>div>div>input {border-radius: 5px;}
.stProgress .st-bo {background-color: #4B8BBE;}
.success-box {
background-color: #D4EDDA;
color: #155724;
padding: 15px;
border-radius: 5px;
border-left: 5px solid #28A745;
margin: 10px 0;
}
.warning-box {
background-color: #FFF3CD;
color: #856404;
padding: 15px;
border-radius: 5px;
border-left: 5px solid #FFC107;
margin: 10px 0;
}
.error-box {
background-color: #F8D7DA;
color: #721C24;
padding: 15px;
border-radius: 5px;
border-left: 5px solid #DC3545;
margin: 10px 0;
}
.info-box {
background-color: #D1ECF1;
color: #0C5460;
padding: 15px;
border-radius: 5px;
border-left: 5px solid #17A2B8;
margin: 10px 0;
}
</style>
""", unsafe_allow_html=True)
# Titre principal
st.title("🎯 Contrôle Métrologique des Préemballages")
st.markdown("*Conforme au Guide DGCCRF et au Décret n°78-166 du 31 janvier 1978*")
# Affichage de l'aide dans la sidebar
show_help()
# Documentation détaillée
show_detailed_documentation()
# Interface principale
col1, col2 = st.columns(2)
with col1:
st.subheader("📝 Paramètres du contrôle")
with st.form("parametres"):
qn = st.number_input("Quantité Nominale (g):", value=500.0, min_value=5.0)
# Option pour calculer l'EMT automatiquement
auto_emt = st.checkbox("Calculer l'EMT automatiquement", value=True)
if auto_emt:
emt = get_emt(qn)
st.info(f"EMT calculée: {emt:.2f}g")
else:
emt = st.number_input("Erreur maximale tolérée (g):", value=15.0, min_value=0.0)
surpoids_max = st.number_input("Surpoids max toléré (g):", value=2.0, min_value=0.0)
n = st.number_input("Effectif d'échantillon:", value=5, min_value=1)
frequence = st.number_input("Fréquence d'échantillonnage (par heure):", value=1.0, min_value=0.1)
use_e_sign = st.checkbox("Produit avec signe 'e'")
submitted = st.form_submit_button("Valider les paramètres")
with col2:
st.subheader("📥 Import des données")
template = create_excel_template()
st.download_button(label="📋 Télécharger le template Excel", data=template, file_name="template_pesees.xlsx")
uploaded_file = st.file_uploader("Téléchargez votre fichier Excel", type=["xlsx"])
if submitted or 'control' in st.session_state:
# Création ou récupération de l'objet de calcul métrologique
if submitted:
st.session_state.control = MetrologicalCalculations(
qn=qn,
emt=emt,
surpoids_max=surpoids_max,
n=n,
frequence=frequence,
use_e_sign=use_e_sign
)
control = st.session_state.control
if uploaded_file:
try:
# Lecture du fichier Excel
df = pd.read_excel(uploaded_file)
# Vérification des colonnes requises
required_columns = ["Tare (g)", "Poids Brut (g)"]
if not all(col in df.columns for col in required_columns):
st.error("❌ Format de fichier incorrect. Utilisez le template fourni.")
else:
# Filtrer les lignes avec des données valides
df_valid = df.dropna(subset=required_columns)
if len(df_valid) < 3:
st.warning("⚠️ Pas assez de mesures valides. Au moins 3 mesures sont nécessaires pour l'analyse.")
else:
st.success(f"✅ {len(df_valid)} mesures valides trouvées dans le fichier.")
# Calcul des poids nets
df_valid['Poids Net (g)'] = df_valid['Poids Brut (g)'] - df_valid['Tare (g)']
# Analyse complète
results = control.analyze_lot_conformity(df_valid['Poids Net (g)'])
# Affichage des résultats
st.subheader("📊 Résultats de l'analyse")
# Résumé de conformité
conformity = results['criteria']
col_sum1, col_sum2, col_sum3 = st.columns(3)
with col_sum1:
if conformity['mean']:
st.markdown('<div class="success-box">✅ Critère de la moyenne: CONFORME</div>', unsafe_allow_html=True)
else:
st.markdown('<div class="error-box">❌ Critère de la moyenne: NON CONFORME</div>', unsafe_allow_html=True)
with col_sum2:
if conformity['defectives']:
st.markdown('<div class="success-box">✅ Critère des défectueux: CONFORME</div>', unsafe_allow_html=True)
else:
st.markdown('<div class="error-box">❌ Critère des défectueux: NON CONFORME</div>', unsafe_allow_html=True)
with col_sum3:
if control.use_e_sign:
if conformity['super_defectives']:
st.markdown('<div class="success-box">✅ Critère des superdéfectueux: CONFORME</div>', unsafe_allow_html=True)
else:
st.markdown('<div class="error-box">❌ Critère des superdéfectueux: NON CONFORME</div>', unsafe_allow_html=True)
else:
st.markdown('<div class="info-box">ℹ️ Critère des superdéfectueux: Non applicable (pas de signe "e")</div>', unsafe_allow_html=True)
# Statistiques globales
st.subheader("📈 Statistiques générales")
col_stat1, col_stat2, col_stat3, col_stat4 = st.columns(4)
with col_stat1:
st.metric("Moyenne", f"{results['mean']:.2f}g", f"{results['mean'] - control.qn:.2f}g")
with col_stat2:
st.metric("Écart-type", f"{results['std']:.2f}g")
with col_stat3:
st.metric("Min", f"{results['min']:.2f}g", f"{results['min'] - control.qn:.2f}g")
with col_stat4:
st.metric("Max", f"{results['max']:.2f}g", f"{results['max'] - control.qn:.2f}g")
# Analyse de la tare si disponible
if 'tare_analysis' in results:
st.subheader("🧮 Analyse de la tare")
tare_results = results['tare_analysis']
col_tare1, col_tare2 = st.columns(2)
with col_tare1:
st.metric("Moyenne de la tare", f"{tare_results['mean']:.2f}g")
st.metric("Écart-type de la tare", f"{tare_results['std']:.2f}g")
with col_tare2:
st.metric("Seuil EMT/5", f"{tare_results['threshold']:.2f}g")
if tare_results['use_individual_tare']:
st.warning("⚠️ Contrôle avec tare individuelle requis")
st.write("L'écart-type de la tare est supérieur au seuil EMT/5.")
else:
st.success("✅ Contrôle avec tare moyenne possible")
st.write("L'écart-type de la tare est inférieur au seuil EMT/5.")
# Test de normalité
st.subheader("📊 Test de normalité")
normality = results['normality_test']
if normality['is_normal']:
st.success(f"✅ Distribution normale (p-value = {normality['p_value']:.4f})")
else:
st.warning(f"⚠️ Distribution non normale (p-value = {normality['p_value']:.4f})")
st.write("La non-normalité peut affecter la fiabilité des calculs statistiques.")
# Capabilité
st.subheader("⚙️ Capabilité du processus")
capability = results['capability']
col_cap1, col_cap2 = st.columns(2)
with col_cap1:
if capability['cp'] >= 1.33:
st.success(f"✅ Cp = {capability['cp']:.2f} ≥ 1.33 : Processus CAPABLE")
elif capability['cp'] >= 1.0:
st.warning(f"⚠️ Cp = {capability['cp']:.2f} : Processus MARGINALEMENT CAPABLE")
else:
st.error(f"❌ Cp = {capability['cp']:.2f} < 1.0 : Processus NON CAPABLE")
with col_cap2:
if capability['cpk'] >= 1.33:
st.success(f"✅ Cpk = {capability['cpk']:.2f} ≥ 1.33 : Processus BIEN CENTRÉ")
elif capability['cpk'] >= 1.0:
st.warning(f"⚠️ Cpk = {capability['cpk']:.2f} : Processus MARGINALEMENT CENTRÉ")
else:
st.error(f"❌ Cpk = {capability['cpk']:.2f} < 1.0 : Processus MAL CENTRÉ")
# Affichage du graphique de capabilité
if 'graphics' in results and 'capability' in results['graphics']:
st.plotly_chart(results['graphics']['capability'], use_container_width=True)
# Plan d'échantillonnage
st.subheader("🔍 Évaluation du plan d'échantillonnage")
sampling = results['sampling_plan']
col_samp1, col_samp2 = st.columns(2)
with col_samp1:
st.metric("POM (Période Opérationnelle Moyenne)", f"{sampling['pom']:.2f}")
with col_samp2:
st.metric("POL (Période Opérationnelle Limite)", f"{sampling['pol']:.2f}")
if sampling['is_valid']:
st.success("✅ Plan d'échantillonnage VALIDÉ (POM ≤ POL)")
st.write("Le plan d'échantillonnage actuel est suffisant pour détecter les déréglages du processus.")
else:
st.error("❌ Plan d'échantillonnage INSUFFISANT (POM > POL)")
for rec in sampling['recommendations']:
st.write(rec)
# Seuil de centrage et quantité cible
st.subheader("🎯 Seuil de centrage et quantité cible")
col_target1, col_target2 = st.columns(2)
with col_target1:
st.metric("Ms (Seuil de centrage)", f"{results['threshold_centering']:.2f}g", f"{results['threshold_centering'] - control.qn:.2f}g")
with col_target2:
st.metric("QC (Quantité cible)", f"{results['target_quantity']:.2f}g", f"{results['target_quantity'] - control.qn:.2f}g")
# Analyse de régression
st.subheader("📉 Analyse de tendance")
regression = results['regression']
if regression['significant']:
if regression['slope'] > 0:
st.warning(f"⚠️ {regression['description']}")
else:
st.error(f"❌ {regression['description']}")
else:
st.success(f"✅ {regression['description']}")
st.write(f"R² = {regression['r_squared']:.4f}, p-value = {regression['p_value']:.4f}")
# Graphiques
if 'graphics' in results:
st.subheader("📊 Graphiques d'analyse")
tab1, tab2, tab3, tab4 = st.tabs(["Distribution", "Évolution", "Box Plot", "Cartes de contrôle"])
with tab1:
st.plotly_chart(results['graphics']['distribution'], use_container_width=True)
with tab2:
st.plotly_chart(results['graphics']['scatter'], use_container_width=True)
with tab3:
st.plotly_chart(results['graphics']['boxplot'], use_container_width=True)
with tab4:
st.plotly_chart(results['graphics']['control_chart_mean'], use_container_width=True)
st.plotly_chart(results['graphics']['control_chart_std'], use_container_width=True)
# Recommandations finales
st.subheader("📝 Recommandations")
recommendations = []
# Recommandations basées sur la conformité
if not conformity['mean']:
recommendations.append("- Augmenter la quantité cible de remplissage pour garantir que la moyenne soit ≥ QN.")
if not conformity['defectives']:
recommendations.append("- Réduire la variabilité du processus pour diminuer le taux de défectueux.")
recommendations.append(f"- Augmenter la quantité cible à au moins {results['threshold_centering']:.2f}g.")
# Recommandations basées sur la capabilité
if capability['cp'] < 1.33:
recommendations.append("- Améliorer la capabilité du processus en réduisant la variabilité.")
if capability['cpk'] < 1.33:
if capability['mean'] > control.qn:
if (capability['mean'] - control.qn) > control.emt:
recommendations.append("- Ajuster le centrage du processus à la baisse (surdosage excessif).")
else:
recommendations.append("- Ajuster le centrage du processus à la hausse (sous-dosage).")
# Recommandations basées sur l'analyse de tendance
if regression['significant'] and regression['slope'] < 0:
recommendations.append("- Investiguer la cause de la tendance à la baisse des poids.")
# Recommandations du plan d'échantillonnage
if not sampling['is_valid']:
recommendations.extend(sampling['recommendations'])
if len(recommendations) > 0:
for rec in recommendations:
st.write(rec)
else:
st.success("✅ Aucune recommandation particulière. Le processus est conforme et bien maîtrisé.")
except Exception as e:
st.error(f"❌ Erreur lors de l'analyse : {str(e)}")
if st.checkbox("Voir les détails de l'erreur"):
import traceback
st.code(traceback.format_exc())
if __name__ == "__main__":
main()