celpri commited on
Commit
29fec18
·
1 Parent(s): db7ff99

Update monitoring dashboard

Browse files
.github/workflows/ci.yml CHANGED
@@ -52,9 +52,9 @@ jobs:
52
  run: |
53
  git config user.name "github-actions"
54
  git config user.email "github-actions@github.com"
55
- # On ajoute le remote HF s'il n'existe pas déjà
56
  git remote add hf https://PCelia:${HF_TOKEN}@huggingface.co/spaces/PCelia/Pret-a-depenser || git remote set-url hf https://PCelia:${HF_TOKEN}@huggingface.co/spaces/PCelia/Pret-a-depenser
57
- # ATTENTION : On fetch d'abord pour ne pas écraser le modèle uploadé manuellement
58
  git fetch hf main
59
  # On pousse les changements de code
60
  git push hf main --force
 
52
  run: |
53
  git config user.name "github-actions"
54
  git config user.email "github-actions@github.com"
55
+ # Ajoute le remote HF s'il n'existe pas déjà
56
  git remote add hf https://PCelia:${HF_TOKEN}@huggingface.co/spaces/PCelia/Pret-a-depenser || git remote set-url hf https://PCelia:${HF_TOKEN}@huggingface.co/spaces/PCelia/Pret-a-depenser
57
+ # On fetch d'abord pour ne pas écraser le modèle uploadé manuellement
58
  git fetch hf main
59
  # On pousse les changements de code
60
  git push hf main --force
.gitignore CHANGED
@@ -6,4 +6,6 @@ __pycache__/
6
  model.joblib
7
  projet8
8
  app
9
- export_model.py
 
 
 
6
  model.joblib
7
  projet8
8
  app
9
+ expo
10
+ debug_model.py
11
+ api_logs.jsonl
.vscode/settings.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "python-envs.pythonProjects": []
3
+ }
README.md CHANGED
@@ -82,15 +82,23 @@ projet8/
82
  │ └── application_train_fused.csv # Données fusionnées
83
  ├── app/
84
  │ └── model.joblib # Modèle sérialisé
85
- ├── tests/ # Tests unitaires
86
- │ └── test_api.py # Tests de l'API
87
- ── docker/ # Configuration Docker
88
- ├── scripts/ # Scripts utilitaires
89
- │ └── export_model.py # Export du modèle
90
- ├── streamlit_app.py # Application de monitoring
91
- ├── debug_model.py # Script de débogage
92
- ── Dockerfile # Configuration Docker
93
- ── requirements.txt # Dépendances Python
 
 
 
 
 
 
 
 
94
  ```
95
 
96
  ---
@@ -101,11 +109,12 @@ projet8/
101
  |-----------|-------------|
102
  | **ML/Data Science** | LightGBM, scikit-learn, pandas, numpy |
103
  | **Web Backend** | FastAPI, Uvicorn |
104
- | **Monitoring** | Streamlit, MLflow |
 
105
  | **Versioning Modèle** | MLflow, Hugging Face Hub |
106
  | **Testing** | pytest, httpx |
107
  | **Containerisation** | Docker |
108
- | **Python Version** | 3.9+ (3.12 dans l'environnement) |
109
 
110
  ---
111
 
@@ -342,11 +351,52 @@ asyncio.run(get_prediction())
342
 
343
  ### Tableau de bord Streamlit
344
 
345
- L'application `streamlit_app.py` fournit :
346
 
347
- - 📊 **Latence API** : Graphique en temps réel des temps de réponse
 
 
 
 
 
 
348
  - 📉 **Distribution des scores** : Analyse des décisions de crédit
349
- - 💾 **Historique complet** : Tous les appels enregistrés
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
  ### Format des logs
352
 
@@ -367,6 +417,17 @@ tail -10 api_logs.jsonl
367
  python analyze_logs.py
368
  ```
369
 
 
 
 
 
 
 
 
 
 
 
 
370
  ---
371
 
372
  ## 🐳 Déploiement
@@ -382,7 +443,16 @@ docker build -t credit-scoring:latest .
382
  #### 2. Exécuter le conteneur
383
 
384
  ```bash
 
385
  docker run -p 8000:7860 \
 
 
 
 
 
 
 
 
386
  -e HF_TOKEN=hf_votre_token \
387
  credit-scoring:latest
388
  ```
@@ -393,6 +463,16 @@ docker run -p 8000:7860 \
393
  http://localhost:8000
394
  ```
395
 
 
 
 
 
 
 
 
 
 
 
396
  ### Docker Compose
397
 
398
  ```yaml
@@ -431,6 +511,23 @@ sdk: docker
431
 
432
  ## 🧪 Tests
433
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  ### Lancer les tests
435
 
436
  ```bash
@@ -440,37 +537,53 @@ pytest
440
  # Avec verbose
441
  pytest -v
442
 
443
- # Coverage
444
  pytest --cov=src
445
 
 
 
 
 
 
 
446
  # Test spécifique
447
- pytest tests/test_api.py::test_prediction -v
448
  ```
449
 
450
- ### Tests disponibles
451
 
452
- ```python
453
- # tests/test_api.py
 
 
 
 
454
 
455
- ✓ test_predict_valid_client() # Prédiction client valide
456
- test_predict_invalid_client() # Client inexistant
457
- ✓ test_predict_invalid_format() # Format JSON invalide
458
- ✓ test_health_check() # Vérification santé API
 
459
  ```
460
 
461
  ### Ajouter vos propres tests
462
 
463
  ```python
464
- # tests/test_api.py
 
 
 
465
 
466
- def test_mon_test():
467
- """Description du test"""
468
- client = TestClient(app)
469
- response = client.post(
470
- "/predict",
471
- json={"sk_id_curr": 100001}
472
- )
473
- assert response.status_code == 200
 
 
474
  ```
475
 
476
  ---
@@ -483,19 +596,46 @@ def test_mon_test():
483
  |----------|-------------|
484
  | [01_eda.ipynb](notebooks/01_eda.ipynb) | Analyse exploratoire des données (EDA) |
485
  | [02_fusion.ipynb](notebooks/02_fusion.ipynb) | Fusion de sources et préparation |
486
- | [03_modelisation.ipynb](notebooks/03_modelisation.ipynb) | Entraînement et validation du modèle |
487
-
488
  ### Scripts utilitaires
489
 
490
  ```bash
491
- # Exporter le modèle
492
  python scripts/export_model.py
493
 
494
- # Déboguer le modèle
495
  python debug_model.py
496
 
497
- # Analyser les logs
498
- python analyze_logs.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  ```
500
 
501
  ### Ressources externes
@@ -628,13 +768,37 @@ Pour toute question ou problème :
628
 
629
  ## ✨ Roadmap future
630
 
631
- - [ ] Ajouter explication des prédictions (SHAP)
632
- - [ ] Interface web avancée (React)
633
- - [ ] Améliorer le monitoring (Prometheus)
 
 
 
 
634
  - [ ] Déploiement Kubernetes
635
- - [ ] Tests de performance
636
- - [ ] CI/CD pipeline complet
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637
 
638
  ---
639
 
640
- **Dernière mise à jour** : Février 2026
 
82
  │ └── application_train_fused.csv # Données fusionnées
83
  ├── app/
84
  │ └── model.joblib # Modèle sérialisé
85
+ ├── tests/ # Tests automatisés
86
+ ── monitoring/ # Monitoring et analyse
87
+ │ └── (contient les scripts de monitoring)
88
+ ├── mlruns/ # Artefacts MLflow
89
+ │ └── (versioning du modèle)
90
+ ├── docker/ # Configuration Docker
91
+ ├── scripts/ # Scripts utilitaires
92
+ │ └── export_model.py # Export du modèle
93
+ ── streamlit_app.py # Dashboard de monitoring Streamlit
94
+ ├── drift_analysis.py # Analyse de data drift
95
+ ├── analyze_logs.py # Analyse des logs API
96
+ ├── debug_model.py # Script de débogage
97
+ ├── Dockerfile # Configuration Docker
98
+ ├── mlflow.db # Base de données MLflow
99
+ ├── api_logs.jsonl # Logs des prédictions API
100
+ ├── data_drift_report.html # Rapport de drift Evidently
101
+ └── requirements.txt # Dépendances Python
102
  ```
103
 
104
  ---
 
109
  |-----------|-------------|
110
  | **ML/Data Science** | LightGBM, scikit-learn, pandas, numpy |
111
  | **Web Backend** | FastAPI, Uvicorn |
112
+ | **Monitoring** | Streamlit, MLflow, Evidently.ai |
113
+ | **Data Drift Detection** | Evidently.ai (rapports HTML) |
114
  | **Versioning Modèle** | MLflow, Hugging Face Hub |
115
  | **Testing** | pytest, httpx |
116
  | **Containerisation** | Docker |
117
+ | **Python Version** | 3.12 (compatible 3.9+) |
118
 
119
  ---
120
 
 
351
 
352
  ### Tableau de bord Streamlit
353
 
354
+ Lancez le tableau de bord de monitoring :
355
 
356
+ ```bash
357
+ streamlit run streamlit_app.py
358
+ ```
359
+
360
+ L'application `streamlit_app.py` fournit en temps réel :
361
+
362
+ - 📊 **Latence API** : Métrique et graphique des temps de réponse
363
  - 📉 **Distribution des scores** : Analyse des décisions de crédit
364
+ - 💾 **Historique complet** : Tous les appels enregistrés en temps réel
365
+ - 🔍 **Data Drift** : Surveillance de la dérive des données avec Evidently
366
+ - 🎯 **Statut du système** : CPU et mémoire en temps réel
367
+
368
+ Accessible sur http://localhost:8501
369
+
370
+ ### Analyse du Data Drift
371
+
372
+ **Evidently.ai** est intégré pour détecter la dérive des données en temps réel :
373
+
374
+ #### Génération de rapports
375
+
376
+ ```bash
377
+ # Générer un rapport de drift
378
+ python monitoring/drift_analysis.py
379
+ ```
380
+
381
+ Cela génère `data_drift_report.html` avec :
382
+ - ✅ Détection automatique des dérives
383
+ - ✅ Comparaison des distributions (référence vs. données actuelles)
384
+ - ✅ Alertes sur les changements significatifs
385
+ - ✅ Graphiques détaillés par feature
386
+
387
+ #### Analyse interactive
388
+
389
+ Vous pouvez aussi utiliser le notebook interactif :
390
+
391
+ ```bash
392
+ jupyter notebook data_drift_analysis.ipynb
393
+ ```
394
+
395
+ Ce notebook permet de :
396
+ - Explorer les dérives en temps réel
397
+ - Configurer les seuils d'alerte personnalisés
398
+ - Générer des rapports HTML automatiques
399
+ - Visualiser les changements de distribution
400
 
401
  ### Format des logs
402
 
 
417
  python analyze_logs.py
418
  ```
419
 
420
+ ### MLflow Tracking
421
+
422
+ Les expériences de modélisation sont tracées avec MLflow :
423
+
424
+ ```bash
425
+ # Consulter l'historique des modèles
426
+ mlflow ui
427
+
428
+ # Accéder à http://localhost:5000
429
+ ```
430
+
431
  ---
432
 
433
  ## 🐳 Déploiement
 
443
  #### 2. Exécuter le conteneur
444
 
445
  ```bash
446
+ # Mode développement avec volumes
447
  docker run -p 8000:7860 \
448
+ -e HF_TOKEN=hf_votre_token \
449
+ -v $(pwd)/Data:/app/Data \
450
+ -v $(pwd)/api_logs.jsonl:/app/api_logs.jsonl \
451
+ credit-scoring:latest
452
+
453
+ # Mode production
454
+ docker run -d -p 8000:7860 \
455
+ --name credit-api \
456
  -e HF_TOKEN=hf_votre_token \
457
  credit-scoring:latest
458
  ```
 
463
  http://localhost:8000
464
  ```
465
 
466
+ #### 4. Monitorer le conteneur
467
+
468
+ ```bash
469
+ # Voir les logs
470
+ docker logs credit-api
471
+
472
+ # Accéder à Streamlit (dans le conteneur)
473
+ docker exec credit-api streamlit run streamlit_app.py --server.port 8501
474
+ ```
475
+
476
  ### Docker Compose
477
 
478
  ```yaml
 
511
 
512
  ## 🧪 Tests
513
 
514
+ ### Structure des tests
515
+
516
+ ```
517
+ tests/
518
+ ├── unit/ # Tests unitaires
519
+ │ ├── test_model_unit.py # Tests du modèle
520
+ │ ├── test_preprocessing.py # Tests du prétraitement
521
+ │ ├── test_input_validation.py # Validation des entrées
522
+ │ └── test_model_loading.py # Chargement du modèle
523
+ ├── fonctionnal/ # Tests fonctionnels/intégration
524
+ │ ├── test_api.py # Tests de l'API REST
525
+ │ ├── test_response_schema.py # Schéma des réponses
526
+ │ ├── test_error_handling.py # Gestion des erreurs
527
+ │ └── test_latency.py # Latence des réponses
528
+ └── conftest.py # Configurations pytest
529
+ ```
530
+
531
  ### Lancer les tests
532
 
533
  ```bash
 
537
  # Avec verbose
538
  pytest -v
539
 
540
+ # Coverage (couverture de code)
541
  pytest --cov=src
542
 
543
+ # Seulement tests unitaires
544
+ pytest tests/unit/
545
+
546
+ # Seulement tests fonctionnels
547
+ pytest tests/fonctionnal/
548
+
549
  # Test spécifique
550
+ pytest tests/unit/test_model_unit.py::test_model_prediction -v
551
  ```
552
 
553
+ ### Exemples de tests
554
 
555
+ ```bash
556
+ # Tests unitaires du modèle
557
+ pytest tests/unit/test_model_unit.py -v
558
+
559
+ # Tests API
560
+ pytest tests/fonctionnal/test_api.py -v
561
 
562
+ # Tests de latence
563
+ pytest tests/fonctionnal/test_latency.py -v
564
+
565
+ # Rapport coverage détaillé
566
+ pytest --cov=src --cov-report=html
567
  ```
568
 
569
  ### Ajouter vos propres tests
570
 
571
  ```python
572
+ # tests/unit/test_mon_test.py
573
+
574
+ import pytest
575
+ from src.model.model import load_model
576
 
577
+ def test_model_loading():
578
+ """Teste le chargement du modèle"""
579
+ model = load_model()
580
+ assert model is not None
581
+
582
+ def test_prediction_shape():
583
+ """Teste que la prédiction a la bonne forme"""
584
+ model = load_model()
585
+ predictions = model.predict([[1, 2, 3, 4, 5]])
586
+ assert predictions.shape[0] == 1
587
  ```
588
 
589
  ---
 
596
  |----------|-------------|
597
  | [01_eda.ipynb](notebooks/01_eda.ipynb) | Analyse exploratoire des données (EDA) |
598
  | [02_fusion.ipynb](notebooks/02_fusion.ipynb) | Fusion de sources et préparation |
599
+ | [03_modelisation.ipynb](notebooks/03_modelisation.ipynb) | Entraînement et validation du modèle || [data_drift_analysis.ipynb](data_drift_analysis.ipynb) | **NOUVEAU** : Analyse interactive du data drift avec Evidently |
 
600
  ### Scripts utilitaires
601
 
602
  ```bash
603
+ # Exporter le modèle depuis MLflow
604
  python scripts/export_model.py
605
 
606
+ # Déboguer et tester le modèle
607
  python debug_model.py
608
 
609
+ # Analyser les logs API en détail
610
+ python monitoring/analyze_logs.py
611
+
612
+ # Analyser la dérive des données (Data Drift)
613
+ python monitoring/drift_analysis.py
614
+ ```
615
+
616
+ ### Chaining des outils
617
+
618
+ Pipeline complet de monitoring :
619
+
620
+ ```bash
621
+ # 1. Lancer l'API
622
+ uvicorn src.api.main:app --reload &
623
+
624
+ # 2. Générer quelques prédictions
625
+ for i in {1..10}; do
626
+ curl -X POST "http://localhost:8000/predict" \
627
+ -H "Content-Type: application/json" \
628
+ -d "{\"sk_id_curr\": $((100000 + i))}"
629
+ done
630
+
631
+ # 3. Analyser les logs
632
+ python monitoring/analyze_logs.py
633
+
634
+ # 4. Générer le rapport de drift
635
+ python monitoring/drift_analysis.py
636
+
637
+ # 5. Consulter le tableau de bord
638
+ streamlit run streamlit_app.py
639
  ```
640
 
641
  ### Ressources externes
 
768
 
769
  ## ✨ Roadmap future
770
 
771
+ - [ ] **Data Drift Detection** (Evidently.ai) - COMPLÉTÉ
772
+ - [ ] **Monitoring Dashboard** (Streamlit) - COMPLÉTÉ
773
+ - [ ] **API Logging & Analytics** - COMPLÉTÉ
774
+ - [ ] Ajouter explication des prédictions (SHAP/LIME)
775
+ - [ ] Interface web avancée (React/Next.js)
776
+ - [ ] Alertes email sur data drift
777
+ - [ ] Améliorer le monitoring (Prometheus + Grafana)
778
  - [ ] Déploiement Kubernetes
779
+ - [ ] Tests de performance E2E
780
+ - [ ] CI/CD pipeline GitHub Actions
781
+
782
+ ---
783
+
784
+ ---
785
+
786
+ ## 📝 Nouveautés récentes
787
+
788
+ ### v1.1.0 (Février 2026)
789
+
790
+ ✨ **Nouvelles fonctionnalités** :
791
+ - 🔍 Détection automatique du **Data Drift** avec Evidently.ai
792
+ - 📊 Tableau de bord **Streamlit** pour le monitoring en temps réel
793
+ - 📈 Analyse des logs API avec **psutil** (CPU, mémoire)
794
+ - 📓 Notebook interactif pour l'analyse du drift
795
+ - 🚀 Support Docker amélioré avec volumes persistants
796
+
797
+ 🐛 **Corrections** :
798
+ - Amélioration du chargement du modèle (fallback multi-sources)
799
+ - Meilleure gestion des erreurs API
800
+ - Optimisation des performances
801
 
802
  ---
803
 
804
+ **Dernière mise à jour** : 15 Février 2026
api_logs.jsonl CHANGED
The diff for this file is too large to render. See raw diff
 
data_drift_analysis.ipynb ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "57a500a1",
6
+ "metadata": {},
7
+ "source": [
8
+ "Charger le dataset initial"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": 1,
14
+ "id": "265ff33b",
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "import pandas as pd\n",
19
+ "from sklearn.model_selection import train_test_split\n",
20
+ "\n",
21
+ "df = pd.read_csv(\"Data/features_clients.csv\")\n",
22
+ "df = df.drop(columns=[\"SK_ID_CURR\"])"
23
+ ]
24
+ },
25
+ {
26
+ "cell_type": "markdown",
27
+ "id": "55f5c7f9",
28
+ "metadata": {},
29
+ "source": [
30
+ "Train/Test Split"
31
+ ]
32
+ },
33
+ {
34
+ "cell_type": "code",
35
+ "execution_count": 2,
36
+ "id": "33025b1c",
37
+ "metadata": {},
38
+ "outputs": [],
39
+ "source": [
40
+ "df_train, df_test = train_test_split(\n",
41
+ " df,\n",
42
+ " test_size=0.3,\n",
43
+ " random_state=42\n",
44
+ ")"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "code",
49
+ "execution_count": 8,
50
+ "id": "ee84412a",
51
+ "metadata": {},
52
+ "outputs": [
53
+ {
54
+ "name": "stdout",
55
+ "output_type": "stream",
56
+ "text": [
57
+ "0.7.20\n",
58
+ "c:\\Users\\User\\Desktop\\Formation IA\\projet8\\projet8\\Lib\\site-packages\\evidently\\__init__.py\n"
59
+ ]
60
+ }
61
+ ],
62
+ "source": [
63
+ "import evidently\n",
64
+ "print(evidently.__version__)\n",
65
+ "print(evidently.__file__)"
66
+ ]
67
+ },
68
+ {
69
+ "cell_type": "code",
70
+ "execution_count": 14,
71
+ "id": "dc5d67c4",
72
+ "metadata": {},
73
+ "outputs": [
74
+ {
75
+ "name": "stdout",
76
+ "output_type": "stream",
77
+ "text": [
78
+ "['AbsMaxError', 'Accuracy', 'AlmostConstantColumnsCount', 'AlmostDuplicatedColumnsCount', 'CategoryCount', 'ColumnCorrelationMatrix', 'ColumnCorrelations', 'ColumnCount', 'ConstantColumnsCount', 'CorrelationMatrix', 'DatasetCorrelations', 'DatasetMissingValueCount', 'Diversity', 'DriftedColumnsCount', 'DummyAccuracy', 'DummyF1Score', 'DummyFNR', 'DummyFPR', 'DummyLogLoss', 'DummyMAE', 'DummyMAPE', 'DummyPrecision', 'DummyRMSE', 'DummyRecall', 'DummyRocAuc', 'DummyTNR', 'DummyTPR', 'DuplicatedColumnsCount', 'DuplicatedRowCount', 'EmptyColumnsCount', 'EmptyRowsCount', 'F1ByLabel', 'F1Score', 'FBetaTopK', 'FNR', 'FPR', 'GroupBy', 'HitRate', 'InListValueCount', 'InRangeValueCount', 'ItemBias', 'LogLoss', 'MAE', 'MAP', 'MAPE', 'MRR', 'MaxValue', 'MeanError', 'MeanValue', 'MedianValue', 'MinValue', 'MissingValueCount', 'NDCG', 'Novelty', 'OutListValueCount', 'OutRangeValueCount', 'Personalization', 'PopularityBiasMetric', 'Precision', 'PrecisionByLabel', 'PrecisionTopK', 'QuantileValue', 'R2Score', 'RMSE', 'RecCasesTable', 'Recall', 'RecallByLabel', 'RecallTopK', 'RocAuc', 'RocAucByLabel', 'RowCount', 'RowTestSummary', 'ScoreDistribution', 'Serendipity', 'StdValue', 'SumValue', 'TNR', 'TPR', 'UniqueValueCount', 'UserBias', 'ValueDrift', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '_legacy', 'classification', 'column_statistics', 'data_quality', 'dataset_statistics', 'group_by', 'recsys', 'regression', 'row_test_summary']\n"
79
+ ]
80
+ }
81
+ ],
82
+ "source": [
83
+ "import evidently.metrics\n",
84
+ "print(dir(evidently.metrics))"
85
+ ]
86
+ },
87
+ {
88
+ "cell_type": "markdown",
89
+ "id": "4a4bba44",
90
+ "metadata": {},
91
+ "source": [
92
+ "Lancer Evidently"
93
+ ]
94
+ },
95
+ {
96
+ "cell_type": "code",
97
+ "execution_count": null,
98
+ "id": "ba976620",
99
+ "metadata": {},
100
+ "outputs": [
101
+ {
102
+ "ename": "AttributeError",
103
+ "evalue": "'Snapshot' object has no attribute 'save'",
104
+ "output_type": "error",
105
+ "traceback": [
106
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
107
+ "\u001b[31mAttributeError\u001b[39m Traceback (most recent call last)",
108
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[16]\u001b[39m\u001b[32m, line 21\u001b[39m\n\u001b[32m 14\u001b[39m report.run(reference_data=reference, current_data=current)\n\u001b[32m 16\u001b[39m snapshot = report.run(\n\u001b[32m 17\u001b[39m reference_data=reference,\n\u001b[32m 18\u001b[39m current_data=current\n\u001b[32m 19\u001b[39m )\n\u001b[32m---> \u001b[39m\u001b[32m21\u001b[39m \u001b[43msnapshot\u001b[49m\u001b[43m.\u001b[49m\u001b[43msave\u001b[49m(\u001b[33m\"\u001b[39m\u001b[33mdata_drift_report.html\u001b[39m\u001b[33m\"\u001b[39m)\n",
109
+ "\u001b[31mAttributeError\u001b[39m: 'Snapshot' object has no attribute 'save'"
110
+ ]
111
+ }
112
+ ],
113
+ "source": [
114
+ "from evidently import Report, Dataset\n",
115
+ "from evidently.metrics import ValueDrift\n",
116
+ "\n",
117
+ "reference = Dataset.from_pandas(df_train)\n",
118
+ "current = Dataset.from_pandas(df_test)\n",
119
+ "\n",
120
+ "metrics = []\n",
121
+ "\n",
122
+ "for col in df_train.columns:\n",
123
+ " metrics.append(ValueDrift(column=col))\n",
124
+ "\n",
125
+ "report = Report(metrics)\n",
126
+ "\n",
127
+ "report.run(reference_data=reference, current_data=current)\n",
128
+ "\n",
129
+ "snapshot = report.run(\n",
130
+ " reference_data=reference,\n",
131
+ " current_data=current\n",
132
+ ")\n",
133
+ "\n",
134
+ "snapshot.save_html(\"data_drift_report.html\")"
135
+ ]
136
+ }
137
+ ],
138
+ "metadata": {
139
+ "kernelspec": {
140
+ "display_name": "projet8 (3.12.10)",
141
+ "language": "python",
142
+ "name": "python3"
143
+ },
144
+ "language_info": {
145
+ "codemirror_mode": {
146
+ "name": "ipython",
147
+ "version": 3
148
+ },
149
+ "file_extension": ".py",
150
+ "mimetype": "text/x-python",
151
+ "name": "python",
152
+ "nbconvert_exporter": "python",
153
+ "pygments_lexer": "ipython3",
154
+ "version": "3.12.10"
155
+ }
156
+ },
157
+ "nbformat": 4,
158
+ "nbformat_minor": 5
159
+ }
evidently_report.html → data_drift_report.html RENAMED
The diff for this file is too large to render. See raw diff
 
evidently_report.py DELETED
@@ -1,27 +0,0 @@
1
- import json
2
- import pandas as pd
3
- import warnings
4
- from evidently import Report
5
- from evidently.presets import DataDriftPreset
6
-
7
- warnings.filterwarnings("ignore") # enlève le spam de warnings
8
-
9
- # Charger logs
10
- records = []
11
- with open("api_logs.jsonl") as f:
12
- for line in f:
13
- records.append(json.loads(line))
14
-
15
- df = pd.DataFrame(records)
16
-
17
- # Split référence / courant
18
- reference_df = df.iloc[: len(df)//2][["score", "total_time"]]
19
- current_df = df.iloc[len(df)//2 :][["score", "total_time"]]
20
-
21
- report = Report([DataDriftPreset()])
22
-
23
- # IMPORTANT: le résultat (snapshot) porte save_html()
24
- snapshot = report.run(reference_data=reference_df, current_data=current_df)
25
- snapshot.save_html("evidently_report.html")
26
-
27
- print("OK: evidently_report.html généré")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
export_model.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mlflow
2
+ import mlflow.sklearn
3
+ import joblib
4
+ import os
5
+
6
+ mlflow.set_tracking_uri("sqlite:///mlflow.db")
7
+
8
+ model = mlflow.sklearn.load_model(
9
+ "models:/CreditScoring_LightGBM/Production"
10
+ )
11
+
12
+ os.makedirs("app", exist_ok=True)
13
+ joblib.dump(model, "app/model.joblib")
14
+
15
+ print("OK")
analyze_logs.py → monitoring/analyze_logs.py RENAMED
File without changes
monitoring/drift_analysis.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import pandas as pd
3
+ from evidently import Report
4
+ from evidently.presets import DataDriftPreset
5
+
6
+ print("=== Reconstruction dataset production ===")
7
+
8
+ # --- 1. Charger données production depuis logs ---
9
+ inputs = []
10
+
11
+ with open("api_logs.jsonl") as f:
12
+ for line in f:
13
+ log = json.loads(line)
14
+ if "inputs" in log:
15
+ inputs.append(log["inputs"])
16
+
17
+ df_current = pd.DataFrame(inputs)
18
+
19
+ print("df_current shape:", df_current.shape)
20
+
21
+ if df_current.empty:
22
+ raise ValueError("Aucune donnée production trouvée dans les logs.")
23
+
24
+ # --- 2. Charger référence ---
25
+ df_reference = pd.read_csv("Data/features_clients.csv")
26
+
27
+ if "SK_ID_CURR" in df_reference.columns:
28
+ df_reference = df_reference.drop(columns=["SK_ID_CURR"])
29
+
30
+ print("df_reference shape:", df_reference.shape)
31
+
32
+ # --- 3. Aligner colonnes ---
33
+ common_cols = df_current.columns.intersection(df_reference.columns)
34
+
35
+ df_current = df_current[common_cols]
36
+ df_reference = df_reference[common_cols]
37
+
38
+ # Supprimer colonnes entièrement vides dans current
39
+ non_empty_cols = df_current.columns[df_current.notna().any()]
40
+
41
+ df_current = df_current[non_empty_cols]
42
+ df_reference = df_reference[non_empty_cols]
43
+
44
+ print("Colonnes finales utilisées :", len(non_empty_cols))
45
+
46
+ # --- 4. Échantillonner référence pour éviter biais taille ---
47
+ df_reference = df_reference.sample(n=len(df_current), random_state=42)
48
+
49
+ # --- 5. Simulation drift volontaire ---
50
+ df_current["AMT_INCOME_TOTAL"] *= 3
51
+ df_current["AMT_CREDIT"] *= 2
52
+ df_current["AMT_ANNUITY"] *= 2
53
+
54
+ # --- 6. Lancer Data Drift ---
55
+ print("=== Lancement Evidently ===")
56
+
57
+ report = Report(metrics=[DataDriftPreset()])
58
+
59
+ snapshot = report.run(
60
+ reference_data=df_reference,
61
+ current_data=df_current
62
+ )
63
+
64
+ snapshot.save_html("data_drift_report.html")
65
+
66
+ print("Rapport généré : data_drift_report.html")
scripts/export_model.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import mlflow
3
+ import mlflow.sklearn
4
+ import joblib
5
+
6
+
7
+ BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
8
+ DB_PATH = os.path.join(BASE_DIR, "mlflow.db")
9
+
10
+ mlflow.set_tracking_uri(f"sqlite:///{DB_PATH}")
11
+
12
+ MODEL_URI = "models:/CreditScoring_LightGBM/Production"
13
+ OUT_PATH = os.path.join(BASE_DIR, "app", "model.joblib")
14
+
15
+ os.makedirs(os.path.dirname(OUT_PATH), exist_ok=True)
16
+
17
+ model = mlflow.sklearn.load_model(MODEL_URI)
18
+ joblib.dump(model, OUT_PATH)
19
+
20
+ print("Export OK ->", OUT_PATH)
src/api/main.py CHANGED
@@ -98,6 +98,8 @@ def predict_by_id(payload: ClientID):
98
  f"infer={infer_time:.3f}s | "
99
  f"total={total_time:.3f}s"
100
  )
 
 
101
  log_entry = {
102
  "timestamp": datetime.utcnow().isoformat(),
103
  "endpoint": "/predict_by_id",
@@ -105,9 +107,12 @@ def predict_by_id(payload: ClientID):
105
  "score": score,
106
  "features_time": features_time,
107
  "inference_time": infer_time,
108
- "total_time": total_time
 
109
  }
110
 
 
 
111
  with open("api_logs.jsonl", "a") as f:
112
  f.write(json.dumps(log_entry) + "\n")
113
 
 
98
  f"infer={infer_time:.3f}s | "
99
  f"total={total_time:.3f}s"
100
  )
101
+ inputs_dict = X.to_dict(orient="records")[0]
102
+
103
  log_entry = {
104
  "timestamp": datetime.utcnow().isoformat(),
105
  "endpoint": "/predict_by_id",
 
107
  "score": score,
108
  "features_time": features_time,
109
  "inference_time": infer_time,
110
+ "total_time": total_time,
111
+ "inputs": inputs_dict
112
  }
113
 
114
+ import os
115
+ print("LOG PATH:", os.path.abspath("api_logs.jsonl"))
116
  with open("api_logs.jsonl", "a") as f:
117
  f.write(json.dumps(log_entry) + "\n")
118
 
src/model/model.py CHANGED
@@ -27,7 +27,7 @@ def load_model():
27
  # Local
28
  try:
29
  import mlflow.sklearn
30
- # On définit le chemin de ta DB mlflow relative à ce fichier
31
  current_dir = os.path.dirname(os.path.abspath(__file__))
32
  db_path = os.path.join(current_dir, "..", "..", "mlflow.db")
33
  mlflow.set_tracking_uri(f"sqlite:///{db_path}")
@@ -39,36 +39,3 @@ def load_model():
39
 
40
  raise FileNotFoundError("Impossible de charger le modèle (ni HF Hub, ni MLflow)")
41
 
42
- # import joblib
43
- # from pathlib import Path
44
-
45
- # def load_model():
46
- # # HF Space
47
- # hf_path = Path("model.joblib")
48
- # if hf_path.exists():
49
- # return joblib.load(hf_path)
50
-
51
- # # Local
52
- # local_path = Path(__file__).resolve().parents[2] / "app" / "model.joblib"
53
- # if local_path.exists():
54
- # return joblib.load(local_path)
55
-
56
- # raise FileNotFoundError("model.joblib not found")
57
-
58
-
59
-
60
-
61
- # # import mlflow
62
- # # import mlflow.sklearn
63
- # # import os
64
-
65
-
66
- # # current_dir = os.path.dirname(os.path.abspath(__file__))
67
-
68
- # # db_path = os.path.join(current_dir, "..", "..", "mlflow.db")
69
-
70
- # # mlflow.set_tracking_uri(f"sqlite:///{db_path}")
71
-
72
- # # def load_model():
73
- # # model_uri = "models:/CreditScoring_LightGBM/Production"
74
- # # return mlflow.sklearn.load_model(model_uri)
 
27
  # Local
28
  try:
29
  import mlflow.sklearn
30
+ # On définit le chemin de DB mlflow relative à ce fichier
31
  current_dir = os.path.dirname(os.path.abspath(__file__))
32
  db_path = os.path.join(current_dir, "..", "..", "mlflow.db")
33
  mlflow.set_tracking_uri(f"sqlite:///{db_path}")
 
39
 
40
  raise FileNotFoundError("Impossible de charger le modèle (ni HF Hub, ni MLflow)")
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
streamlit_app.py CHANGED
@@ -1,10 +1,16 @@
1
  import json
2
  import pandas as pd
3
  import streamlit as st
 
 
 
4
 
5
  st.title("Monitoring API – Credit Scoring")
6
 
7
- # Charger les logs
 
 
 
8
  records = []
9
  with open("api_logs.jsonl") as f:
10
  for line in f:
@@ -12,11 +18,100 @@ with open("api_logs.jsonl") as f:
12
 
13
  df = pd.DataFrame(records)
14
 
15
- st.subheader("Latence API")
16
- st.write("Latence moyenne :", df["total_time"].mean())
17
- st.write("Latence max :", df["total_time"].max())
18
- st.line_chart(df["total_time"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  st.subheader("Distribution des scores")
21
- st.write("Score moyen :", df["score"].mean())
22
- st.bar_chart(df["score"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import json
2
  import pandas as pd
3
  import streamlit as st
4
+ import matplotlib.pyplot as plt
5
+ import psutil
6
+ from datetime import datetime
7
 
8
  st.title("Monitoring API – Credit Scoring")
9
 
10
+
11
+ # Chargement des logs
12
+
13
+
14
  records = []
15
  with open("api_logs.jsonl") as f:
16
  for line in f:
 
18
 
19
  df = pd.DataFrame(records)
20
 
21
+ # Conversion timestamp
22
+ df["timestamp"] = pd.to_datetime(df["timestamp"])
23
+
24
+
25
+ # Latence API (temps total)
26
+
27
+
28
+ st.subheader("Latence API (temps total)")
29
+
30
+ st.metric(
31
+ "Latence moyenne (ms)",
32
+ round(df["total_time"].mean() * 1000, 2)
33
+ )
34
+
35
+ st.metric(
36
+ "Latence max (ms)",
37
+ round(df["total_time"].max() * 1000, 2)
38
+ )
39
+
40
+ st.line_chart(df["total_time"] * 1000)
41
+
42
+
43
+ # Temps d'inférence modèle
44
+
45
+
46
+ st.subheader("Temps d'inférence modèle")
47
+
48
+ st.metric(
49
+ "Temps moyen (ms)",
50
+ round(df["inference_time"].mean() * 1000, 2)
51
+ )
52
+
53
+ st.metric(
54
+ "Temps max (ms)",
55
+ round(df["inference_time"].max() * 1000, 2)
56
+ )
57
+
58
+ fig, ax = plt.subplots()
59
+
60
+ ax.plot(df["inference_time"].values * 1000)
61
+
62
+ # Point rouge dernière requête
63
+ ax.scatter(
64
+ len(df) - 1,
65
+ df["inference_time"].iloc[-1] * 1000,
66
+ color="red",
67
+ s=80
68
+ )
69
+
70
+ ax.set_xlabel("Requête")
71
+ ax.set_ylabel("Inference time (ms)")
72
+
73
+ st.pyplot(fig)
74
+
75
+
76
+ # Distribution des scores
77
+
78
 
79
  st.subheader("Distribution des scores")
80
+
81
+ st.metric(
82
+ "Score moyen",
83
+ round(df["score"].mean(), 4)
84
+ )
85
+
86
+ st.bar_chart(df["score"])
87
+
88
+
89
+ # Requêtes par minute
90
+
91
+
92
+ requests_per_min = (
93
+ df.set_index("timestamp")
94
+ .resample("1min")
95
+ .size()
96
+ )
97
+
98
+ st.subheader("Requêtes par minute")
99
+ st.line_chart(requests_per_min)
100
+
101
+
102
+ # Utilisation CPU et RAM
103
+
104
+ st.subheader("Utilisation système")
105
+
106
+ cpu_usage = psutil.cpu_percent(interval=None)
107
+ ram_usage = psutil.virtual_memory().percent
108
+
109
+ col1, col2 = st.columns(2)
110
+
111
+ #col1.metric("CPU usage (%)", cpu_usage)
112
+ col2.metric("RAM usage (%)", ram_usage)
113
+
114
+ # Dernière requête
115
+
116
+ last_request = df.iloc[-1]
117
+ st.write("Dernière requête :", last_request["timestamp"])