- Added Express server with SQLite database connection. - Created API endpoints to fetch categories, KPIs, measurements, and statistics. - Implemented error handling for database operations. feat: Create ChartModal component for visualizing KPI data - Developed ChartModal to display line charts for KPI measurements. - Integrated Chart.js for rendering charts with responsive design. - Added styling for modal and chart components. feat: Add ExportModal component for exporting KPI data - Implemented ExportModal to allow users to select data ranges for export. - Included radio buttons for predefined time ranges (last week, month, year, all data). - Styled modal for better user experience. feat: Introduce RangeChartModal for dynamic range selection - Created RangeChartModal to visualize KPI data over user-selected time ranges. - Integrated radio buttons for selecting different time ranges. - Enhanced chart rendering with Chart.js. refactor: Create useSQLiteDatabase hook for data fetching - Developed custom hook to manage fetching categories, KPIs, and measurements. - Improved error handling and loading states for better user feedback. style: Add CSS styles for modals and charts - Created styles for ChartModal, ExportModal, and RangeChartModal. - Ensured responsive design for various screen sizes.
204 lines
11 KiB
Python
204 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
import sqlite3
|
||
import random
|
||
from datetime import datetime, timedelta
|
||
import math
|
||
|
||
# Configuration de la base de données
|
||
DB_PATH = 'database/sqdc.db'
|
||
|
||
# Catégories
|
||
CATEGORIES = [
|
||
{'id': 1, 'name': 'Sécurité', 'emoji': '🛡️', 'description': 'Indicateurs de sécurité et prévention des accidents'},
|
||
{'id': 2, 'name': 'Qualité', 'emoji': '🎯', 'description': 'Indicateurs de qualité des produits'},
|
||
{'id': 3, 'name': 'Délais & Livraison', 'emoji': '⏱️', 'description': 'Indicateurs de délais et livraison'},
|
||
{'id': 4, 'name': 'Coûts', 'emoji': '💰', 'description': 'Indicateurs de coûts de production'},
|
||
{'id': 5, 'name': 'Maintenance', 'emoji': '🔧', 'description': 'Indicateurs de maintenance'}
|
||
]
|
||
|
||
# KPIs avec fréquences
|
||
KPIS = [
|
||
# SÉCURITÉ (1 mesure par jour)
|
||
{'id': 1, 'category_id': 1, 'name': 'Taux de Fréquence (TF)', 'unit': 'par 1M heures', 'target': 1.0, 'frequency': 'daily', 'formula': '(Nombre d\'Accidents avec Arrêt / Nombre d\'Heures Travaillées) × 1000000', 'description': 'Mesurer la fréquence des accidents avec arrêt.'},
|
||
{'id': 2, 'category_id': 1, 'name': 'Nombre d\'Incidents/Near Miss', 'unit': 'incidents', 'target': 8, 'frequency': 'daily', 'formula': 'Compte des rapports d\'incidents', 'description': 'Évaluer la culture de sécurité.'},
|
||
{'id': 3, 'category_id': 1, 'name': 'Taux de Conformité aux Audits', 'unit': '%', 'target': 95, 'frequency': 'weekly', 'formula': '(Points Conformes / Total Points) × 100', 'description': 'Mesurer le respect des procédures.'},
|
||
|
||
# QUALITÉ (toutes les 10 minutes)
|
||
{'id': 4, 'category_id': 2, 'name': 'Taux de Rebut (Scrap Rate)', 'unit': '%', 'target': 1.5, 'frequency': 'per_10min', 'formula': '(Unités Rebutées / Unités Produites) × 100', 'description': 'Mesurer le % d\'unités jetées.'},
|
||
{'id': 5, 'category_id': 2, 'name': 'Taux de Retouche (Rework Rate)', 'unit': '%', 'target': 2.0, 'frequency': 'per_10min', 'formula': '(Unités Retouchées / Unités Totales) × 100', 'description': 'Mesurer le % d\'unités retouchées.'},
|
||
{'id': 6, 'category_id': 2, 'name': 'Nombre de Défauts par Unité (DPU)', 'unit': 'défauts/unité', 'target': 0.5, 'frequency': 'per_10min', 'formula': 'Défauts Totaux / Unités Inspectées', 'description': 'Mesurer le nombre moyen de défauts.'},
|
||
{'id': 7, 'category_id': 2, 'name': 'Taux de Retours Clients', 'unit': '%', 'target': 0.8, 'frequency': 'daily', 'formula': '(Unités Retournées / Unités Vendues) × 100', 'description': 'Mesurer l\'impact de la non-qualité.'},
|
||
{'id': 8, 'category_id': 2, 'name': 'Taux de rendement synthétique (TRS)', 'unit': '%', 'target': 85, 'frequency': 'per_30min', 'formula': 'Pièces bonnes × Temps cycle / Temps ouverture', 'description': 'Rendement global de la ligne.'},
|
||
{'id': 9, 'category_id': 2, 'name': 'Efficacité Globale de l\'Équipement (OEE)', 'unit': '%', 'target': 80, 'frequency': 'per_30min', 'formula': 'Disponibilité × Performance × Qualité', 'description': 'Mesurer l\'efficacité combinée.'},
|
||
|
||
# DÉLAIS (toutes les heures)
|
||
{'id': 10, 'category_id': 3, 'name': 'Taux de Respect du Plan', 'unit': '%', 'target': 95, 'frequency': 'hourly', 'formula': '(Produite / Planifiée) × 100', 'description': 'Mesurer la capacité à atteindre le volume.'},
|
||
{'id': 11, 'category_id': 3, 'name': 'Temps de Cycle (Cycle Time)', 'unit': 'min/unité', 'target': 50, 'frequency': 'per_10min', 'formula': 'Temps Total / Nombre d\'Unités', 'description': 'Mesurer le temps par unité.'},
|
||
{'id': 12, 'category_id': 3, 'name': 'Tack Time', 'unit': 'min/unité', 'target': 50, 'frequency': 'per_10min', 'formula': 'Temps production / Pièces demandées', 'description': 'Temps de production requis par unité.'},
|
||
{'id': 13, 'category_id': 3, 'name': 'Temps d\'Arrêt Imprévu (Downtime)', 'unit': 'h/jour', 'target': 1.5, 'frequency': 'daily', 'formula': 'Somme des Arrêts Non Planifiés', 'description': 'Mesurer l\'arrêt non planifié.'},
|
||
|
||
# COÛTS (quotidiennement)
|
||
{'id': 14, 'category_id': 4, 'name': 'Coût par Unité (CPU)', 'unit': '€', 'target': 240, 'frequency': 'daily', 'formula': 'Coût Total / Unités Produites', 'description': 'Mesurer l\'efficacité des coûts.'},
|
||
{'id': 15, 'category_id': 4, 'name': 'Productivité de la Main-d\'œuvre', 'unit': 'unités/h', 'target': 8.0, 'frequency': 'hourly', 'formula': 'Unités Produites / Heures Main-d\'œuvre', 'description': 'Mesurer l\'efficacité de l\'équipe.'},
|
||
{'id': 16, 'category_id': 4, 'name': 'Coût des Non-Qualité (CNQ)', 'unit': '€', 'target': 10000, 'frequency': 'daily', 'formula': 'Rebuts + Retouches + Retours', 'description': 'Mesurer le coût des défauts.'},
|
||
|
||
# MAINTENANCE (tous les 3 jours)
|
||
{'id': 17, 'category_id': 5, 'name': 'Temps Moyen Entre Pannes (MTBF)', 'unit': 'heures', 'target': 400, 'frequency': 'per_3days', 'formula': 'Temps Fonctionnement / Pannes', 'description': 'Mesurer la fiabilité.'},
|
||
{'id': 18, 'category_id': 5, 'name': 'Temps Moyen de Réparation (MTTR)', 'unit': 'heures', 'target': 2.5, 'frequency': 'per_3days', 'formula': 'Temps Réparation / Pannes', 'description': 'Mesurer la rapidité.'},
|
||
{'id': 19, 'category_id': 5, 'name': 'Ratio Maintenance Préventive/Corrective', 'unit': '%', 'target': 70, 'frequency': 'weekly', 'formula': 'Heures MP / (MP + MC)', 'description': 'Évaluer la stratégie.'},
|
||
{'id': 20, 'category_id': 5, 'name': 'Taux d\'Achèvement du Plan Préventif', 'unit': '%', 'target': 95, 'frequency': 'weekly', 'formula': '(Tâches Terminées / Tâches Planifiées) × 100', 'description': 'Mesurer le respect du plan.'},
|
||
{'id': 21, 'category_id': 5, 'name': 'Coût de Maintenance par Unité Produite', 'unit': '€', 'target': 30, 'frequency': 'daily', 'formula': 'Coûts Maintenance / Unités Produites', 'description': 'Relier dépenses à production.'}
|
||
]
|
||
|
||
def get_frequency_minutes(frequency):
|
||
"""Retourne le nombre de minutes entre les mesures"""
|
||
frequencies = {
|
||
'per_10min': 10,
|
||
'per_30min': 30,
|
||
'hourly': 60,
|
||
'daily': 1440, # 24h
|
||
'weekly': 10080, # 7 jours
|
||
'per_3days': 4320 # 3 jours
|
||
}
|
||
return frequencies.get(frequency, 1440)
|
||
|
||
def generate_value(kpi_id, target, variance_range=0.2):
|
||
"""Génère une valeur réaliste autour de la cible"""
|
||
variance = (random.random() - 0.5) * 2 * variance_range
|
||
value = target * (1 + variance)
|
||
|
||
# Ajouter du bruit réaliste pour certains KPI
|
||
if kpi_id in [4, 5, 6, 11, 12]: # Qualité et délais - plus de variabilité
|
||
noise = (random.random() - 0.5) * 0.3 * target
|
||
value += noise
|
||
|
||
return max(0, round(value, 2))
|
||
|
||
def determine_status(kpi_id, value, target):
|
||
"""Détermine le statut (good, warning, critical)"""
|
||
tolerance = abs(target * 0.1)
|
||
|
||
# KPI où plus bas est mieux
|
||
if kpi_id in [2, 4, 5, 6, 13, 16]:
|
||
if value > target + tolerance * 2:
|
||
return 'critical'
|
||
elif value > target + tolerance:
|
||
return 'warning'
|
||
else:
|
||
# KPI où plus haut est mieux
|
||
if value < target - tolerance * 2:
|
||
return 'critical'
|
||
elif value < target - tolerance:
|
||
return 'warning'
|
||
|
||
return 'good'
|
||
|
||
def populate_database():
|
||
"""Remplit la base de données sur 1 an"""
|
||
conn = sqlite3.connect(DB_PATH)
|
||
cursor = conn.cursor()
|
||
|
||
# Créer les tables d'abord
|
||
print("🔄 Création du schéma...")
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS categories (
|
||
id INTEGER PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
emoji TEXT,
|
||
description TEXT
|
||
)
|
||
''')
|
||
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS kpis (
|
||
id INTEGER PRIMARY KEY,
|
||
category_id INTEGER NOT NULL,
|
||
name TEXT NOT NULL,
|
||
unit TEXT,
|
||
target REAL,
|
||
frequency TEXT,
|
||
formula TEXT,
|
||
description TEXT,
|
||
FOREIGN KEY(category_id) REFERENCES categories(id)
|
||
)
|
||
''')
|
||
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS measurements (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
kpi_id INTEGER NOT NULL,
|
||
measurement_date DATETIME NOT NULL,
|
||
value REAL NOT NULL,
|
||
status TEXT,
|
||
FOREIGN KEY(kpi_id) REFERENCES kpis(id)
|
||
)
|
||
''')
|
||
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS alerts (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
kpi_id INTEGER NOT NULL,
|
||
alert_type TEXT,
|
||
severity TEXT,
|
||
message TEXT,
|
||
created_at DATETIME,
|
||
FOREIGN KEY(kpi_id) REFERENCES kpis(id)
|
||
)
|
||
''')
|
||
|
||
# Supprimer les anciennes données
|
||
cursor.execute('DELETE FROM measurements')
|
||
cursor.execute('DELETE FROM alerts')
|
||
cursor.execute('DELETE FROM kpis')
|
||
cursor.execute('DELETE FROM categories')
|
||
|
||
# Insérer les catégories
|
||
for cat in CATEGORIES:
|
||
cursor.execute(
|
||
'INSERT INTO categories (id, name, emoji, description) VALUES (?, ?, ?, ?)',
|
||
(cat['id'], cat['name'], cat['emoji'], cat['description'])
|
||
)
|
||
|
||
# Insérer les KPI
|
||
for kpi in KPIS:
|
||
cursor.execute(
|
||
'INSERT INTO kpis (id, category_id, name, unit, target, frequency, formula, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||
(kpi['id'], kpi['category_id'], kpi['name'], kpi['unit'], kpi['target'], kpi['frequency'], kpi['formula'], kpi['description'])
|
||
)
|
||
|
||
# Générer les mesures sur 1 an
|
||
end_date = datetime.now()
|
||
start_date = end_date - timedelta(days=365)
|
||
|
||
print("🔄 Génération des mesures sur 1 an...")
|
||
measurements_count = 0
|
||
|
||
for kpi in KPIS:
|
||
kpi_id = kpi['id']
|
||
target = kpi['target']
|
||
frequency_minutes = get_frequency_minutes(kpi['frequency'])
|
||
|
||
current_date = start_date
|
||
while current_date <= end_date:
|
||
value = generate_value(kpi_id, target)
|
||
status = determine_status(kpi_id, value, target)
|
||
|
||
cursor.execute(
|
||
'INSERT INTO measurements (kpi_id, measurement_date, value, status) VALUES (?, ?, ?, ?)',
|
||
(kpi_id, current_date.isoformat(), value, status)
|
||
)
|
||
measurements_count += 1
|
||
|
||
current_date += timedelta(minutes=frequency_minutes)
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
print(f"✅ Base de données remplie avec succès!")
|
||
print(f"📊 {measurements_count} mesures créées")
|
||
print(f"🎯 {len(KPIS)} KPI configurés")
|
||
print(f"📁 Fichier: {DB_PATH}")
|
||
|
||
if __name__ == '__main__':
|
||
populate_database()
|