Spaces:
Sleeping
Sleeping
GitHub Action commited on
Commit ·
66a0674
0
Parent(s):
Deploy to HuggingFace Spaces from main branch
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .env.example +188 -0
- .flake8 +18 -0
- .gitattributes +37 -0
- .github/workflows/cicd.yml +197 -0
- .github/workflows/doc.yml +53 -0
- .gitignore +685 -0
- .vscode/launch.json +13 -0
- .vscode/settings.json +48 -0
- .vscode/tasks.json +24 -0
- Dockerfile +47 -0
- Dockerfile_app +47 -0
- Makefile +73 -0
- README.md +20 -0
- alembic.ini +116 -0
- alembic/README +1 -0
- alembic/env.py +75 -0
- alembic/script.py.mako +26 -0
- init_db.py +746 -0
- model/Makefile +178 -0
- model/README.md +205 -0
- model/model.pkl +3 -0
- model/model.py +130 -0
- model/model_info.json +32 -0
- model/model_report.html +449 -0
- poetry.lock +0 -0
- pyproject.toml +168 -0
- src/project5/__init__.py +10 -0
- src/project5/alembic.ini +72 -0
- src/project5/config.py +63 -0
- src/project5/database.py +339 -0
- src/project5/main.py +605 -0
- src/project5/models/README.md +2 -0
- src/project5/models/__init__.py +41 -0
- src/project5/models/base.py +26 -0
- src/project5/models/building_energy_prediction.py +88 -0
- src/project5/models/building_models.py +261 -0
- src/project5/models/building_type.py +95 -0
- src/project5/models/categorie.py +84 -0
- src/project5/models/neighborhood.py +90 -0
- src/project5/models/property.py +119 -0
- src/project5/routers/__init__.py +0 -0
- src/project5/routers/building_energy_prediction_routes.py +334 -0
- src/project5/routers/building_model_routes.py +438 -0
- src/project5/routers/building_types_routes.py +205 -0
- src/project5/routers/categories_routes.py +221 -0
- src/project5/routers/neighborhoods_routes.py +328 -0
- src/project5/routers/prediction_routes.py +251 -0
- src/project5/routers/properties_routes.py +246 -0
- src/project5/schemas/__init__.py +45 -0
- src/project5/schemas/building_energy_prediction.py +253 -0
.env.example
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# FICHIER DE CONFIGURATION ENVIRONNEMENT - PROJECT5
|
| 3 |
+
# =============================================================================
|
| 4 |
+
# Copiez ce fichier vers .env et modifiez les valeurs selon vos besoins
|
| 5 |
+
# ATTENTION: Ne commitez jamais le fichier .env (il contient des secrets)
|
| 6 |
+
|
| 7 |
+
# =============================================================================
|
| 8 |
+
# CONFIGURATION DE L'APPLICATION
|
| 9 |
+
# =============================================================================
|
| 10 |
+
APP_NAME=Project5 API
|
| 11 |
+
VERSION=0.1.0
|
| 12 |
+
ENVIRONMENT=development
|
| 13 |
+
DEBUG=true
|
| 14 |
+
|
| 15 |
+
# Host et port pour le serveur
|
| 16 |
+
HOST=0.0.0.0
|
| 17 |
+
PORT=8000
|
| 18 |
+
|
| 19 |
+
# =============================================================================
|
| 20 |
+
# SÉCURITÉ - CHANGEZ CES VALEURS EN PRODUCTION !
|
| 21 |
+
# =============================================================================
|
| 22 |
+
# Clé secrète pour JWT et sessions (génération: openssl rand -hex 32)
|
| 23 |
+
SECRET_KEY=votre-cle-secrete-super-longue-et-complexe-changez-moi-en-production-123456789abcdef
|
| 24 |
+
|
| 25 |
+
# Algorithme de hachage pour JWT
|
| 26 |
+
ALGORITHM=HS256
|
| 27 |
+
|
| 28 |
+
# Durée de validité des tokens (en minutes)
|
| 29 |
+
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
| 30 |
+
|
| 31 |
+
# Clé pour le refresh token (optionnel)
|
| 32 |
+
REFRESH_TOKEN_EXPIRE_DAYS=7
|
| 33 |
+
|
| 34 |
+
# =============================================================================
|
| 35 |
+
# BASE DE DONNÉES - POSTGRESQL
|
| 36 |
+
# =============================================================================
|
| 37 |
+
# URL complète de la base de données PostgreSQL
|
| 38 |
+
DATABASE_URL=postgresql://project5_user:project5_password@localhost:5432/project5_db
|
| 39 |
+
|
| 40 |
+
# Paramètres séparés (utilisés par Docker Compose)
|
| 41 |
+
POSTGRES_HOST=localhost
|
| 42 |
+
POSTGRES_PORT=5432
|
| 43 |
+
POSTGRES_DB=project5_db
|
| 44 |
+
POSTGRES_USER=project5_user
|
| 45 |
+
POSTGRES_PASSWORD=project5_password
|
| 46 |
+
|
| 47 |
+
# Configuration SQLAlchemy
|
| 48 |
+
SQLALCHEMY_ECHO=false
|
| 49 |
+
SQLALCHEMY_POOL_SIZE=5
|
| 50 |
+
SQLALCHEMY_MAX_OVERFLOW=10
|
| 51 |
+
SQLALCHEMY_POOL_PRE_PING=true
|
| 52 |
+
SQLALCHEMY_POOL_RECYCLE=300
|
| 53 |
+
|
| 54 |
+
# =============================================================================
|
| 55 |
+
# BASE DE DONNÉES DE TEST - SQLITE
|
| 56 |
+
# =============================================================================
|
| 57 |
+
# Utilisée pour les tests unitaires (plus rapide que PostgreSQL)
|
| 58 |
+
TEST_DATABASE_URL=sqlite:///./test_project5.db
|
| 59 |
+
|
| 60 |
+
# =============================================================================
|
| 61 |
+
# LOGGING
|
| 62 |
+
# =============================================================================
|
| 63 |
+
LOG_LEVEL=INFO
|
| 64 |
+
LOG_FORMAT=%(asctime)s - %(name)s - %(levelname)s - %(message)s
|
| 65 |
+
LOG_FILE=logs/project5.log
|
| 66 |
+
LOG_MAX_SIZE=10MB
|
| 67 |
+
LOG_BACKUP_COUNT=5
|
| 68 |
+
|
| 69 |
+
# =============================================================================
|
| 70 |
+
# CORS - Configuration des domaines autorisés
|
| 71 |
+
# =============================================================================
|
| 72 |
+
# Domaines autorisés pour les requêtes CORS (séparés par des virgules)
|
| 73 |
+
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080,http://127.0.0.1:3000
|
| 74 |
+
ALLOWED_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS
|
| 75 |
+
ALLOWED_HEADERS=*
|
| 76 |
+
|
| 77 |
+
# =============================================================================
|
| 78 |
+
# REDIS - Cache et sessions (optionnel)
|
| 79 |
+
# =============================================================================
|
| 80 |
+
# REDIS_URL=redis://localhost:6379/0
|
| 81 |
+
# REDIS_HOST=localhost
|
| 82 |
+
# REDIS_PORT=6379
|
| 83 |
+
# REDIS_DB=0
|
| 84 |
+
# REDIS_PASSWORD=
|
| 85 |
+
# REDIS_EXPIRE_TIME=3600
|
| 86 |
+
|
| 87 |
+
# =============================================================================
|
| 88 |
+
# EMAIL - Configuration SMTP (optionnel)
|
| 89 |
+
# =============================================================================
|
| 90 |
+
# SMTP_HOST=smtp.gmail.com
|
| 91 |
+
# SMTP_PORT=587
|
| 92 |
+
# SMTP_USER=votre.email@gmail.com
|
| 93 |
+
# SMTP_PASSWORD=votre-mot-de-passe-app
|
| 94 |
+
# SMTP_TLS=true
|
| 95 |
+
# SMTP_FROM_EMAIL=noreply@project5.com
|
| 96 |
+
# SMTP_FROM_NAME=Project5 API
|
| 97 |
+
|
| 98 |
+
# =============================================================================
|
| 99 |
+
# STOCKAGE DE FICHIERS (optionnel)
|
| 100 |
+
# =============================================================================
|
| 101 |
+
# Répertoire local pour les uploads
|
| 102 |
+
# UPLOAD_DIRECTORY=uploads
|
| 103 |
+
# MAX_FILE_SIZE=10MB
|
| 104 |
+
# ALLOWED_FILE_EXTENSIONS=jpg,jpeg,png,gif,pdf,doc,docx
|
| 105 |
+
|
| 106 |
+
# Configuration AWS S3 (optionnel)
|
| 107 |
+
# AWS_ACCESS_KEY_ID=votre-access-key
|
| 108 |
+
# AWS_SECRET_ACCESS_KEY=votre-secret-key
|
| 109 |
+
# AWS_BUCKET_NAME=votre-bucket
|
| 110 |
+
# AWS_REGION=eu-west-1
|
| 111 |
+
|
| 112 |
+
# =============================================================================
|
| 113 |
+
# MONITORING ET OBSERVABILITÉ (optionnel)
|
| 114 |
+
# =============================================================================
|
| 115 |
+
# Sentry pour le monitoring d'erreurs
|
| 116 |
+
# SENTRY_DSN=https://votre-sentry-dsn@sentry.io/project-id
|
| 117 |
+
|
| 118 |
+
# Prometheus metrics
|
| 119 |
+
# ENABLE_METRICS=false
|
| 120 |
+
# METRICS_PORT=9090
|
| 121 |
+
|
| 122 |
+
# =============================================================================
|
| 123 |
+
# RATE LIMITING (optionnel)
|
| 124 |
+
# =============================================================================
|
| 125 |
+
# RATE_LIMIT_REQUESTS=100
|
| 126 |
+
# RATE_LIMIT_WINDOW=3600
|
| 127 |
+
# RATE_LIMIT_STORAGE=memory
|
| 128 |
+
|
| 129 |
+
# =============================================================================
|
| 130 |
+
# CONFIGURATION DOCKER COMPOSE
|
| 131 |
+
# =============================================================================
|
| 132 |
+
# Variables utilisées par docker-compose.yml
|
| 133 |
+
COMPOSE_PROJECT_NAME=project5
|
| 134 |
+
COMPOSE_FILE=docker-compose.yml
|
| 135 |
+
|
| 136 |
+
# Ports exposés
|
| 137 |
+
API_PORT=8000
|
| 138 |
+
DB_PORT=5432
|
| 139 |
+
|
| 140 |
+
# =============================================================================
|
| 141 |
+
# DÉVELOPPEMENT LOCAL
|
| 142 |
+
# =============================================================================
|
| 143 |
+
# Rechargement automatique en développement
|
| 144 |
+
AUTO_RELOAD=true
|
| 145 |
+
|
| 146 |
+
# Affichage détaillé des requêtes SQL
|
| 147 |
+
SQL_DEBUG=false
|
| 148 |
+
|
| 149 |
+
# Mode de développement pour FastAPI
|
| 150 |
+
FASTAPI_DEBUG=true
|
| 151 |
+
FASTAPI_RELOAD=true
|
| 152 |
+
|
| 153 |
+
# =============================================================================
|
| 154 |
+
# TESTS
|
| 155 |
+
# =============================================================================
|
| 156 |
+
# Configuration spécifique aux tests
|
| 157 |
+
TESTING=false
|
| 158 |
+
TEST_LOG_LEVEL=WARNING
|
| 159 |
+
|
| 160 |
+
# Base de données de test séparée
|
| 161 |
+
TEST_POSTGRES_DB=project5_test_db
|
| 162 |
+
TEST_POSTGRES_USER=project5_test_user
|
| 163 |
+
TEST_POSTGRES_PASSWORD=project5_test_password
|
| 164 |
+
|
| 165 |
+
# =============================================================================
|
| 166 |
+
# PRODUCTION - Variables critiques à définir
|
| 167 |
+
# =============================================================================
|
| 168 |
+
# En production, assurez-vous de définir:
|
| 169 |
+
# - SECRET_KEY (généré de manière sécurisée)
|
| 170 |
+
# - DATABASE_URL (avec les vrais credentials)
|
| 171 |
+
# - ALLOWED_ORIGINS (domaines de production)
|
| 172 |
+
# - ENVIRONMENT=production
|
| 173 |
+
# - DEBUG=false
|
| 174 |
+
# - LOG_LEVEL=WARNING ou ERROR
|
| 175 |
+
|
| 176 |
+
# =============================================================================
|
| 177 |
+
# NOTES D'UTILISATION
|
| 178 |
+
# =============================================================================
|
| 179 |
+
# 1. Copiez ce fichier: cp .env.example .env
|
| 180 |
+
# 2. Modifiez les valeurs dans .env selon vos besoins
|
| 181 |
+
# 3. Le fichier .env ne doit JAMAIS être commité dans Git
|
| 182 |
+
# 4. Utilisez des valeurs différentes pour dev/test/production
|
| 183 |
+
# 5. Générez des clés secrètes fortes pour la production
|
| 184 |
+
#
|
| 185 |
+
# Génération de SECRET_KEY sécurisée:
|
| 186 |
+
# python -c "import secrets; print(secrets.token_urlsafe(32))"
|
| 187 |
+
# ou
|
| 188 |
+
# openssl rand -base64 32
|
.flake8
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[flake8]
|
| 2 |
+
max-line-length = 88
|
| 3 |
+
extend-ignore =
|
| 4 |
+
E203,
|
| 5 |
+
W503,
|
| 6 |
+
E402,
|
| 7 |
+
E501
|
| 8 |
+
|
| 9 |
+
exclude =
|
| 10 |
+
.git,
|
| 11 |
+
__pycache__,
|
| 12 |
+
.venv,
|
| 13 |
+
venv,
|
| 14 |
+
.jupyter,
|
| 15 |
+
.ipynb_checkpoints
|
| 16 |
+
|
| 17 |
+
per-file-ignores =
|
| 18 |
+
*.ipynb:E402,E501
|
.gitattributes
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Git LFS configuration for large files (e.g., models, datasets)
|
| 2 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
model/model.pkl filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/cicd.yml
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Project5 CI/CD
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, develop ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main, develop ]
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
inputs:
|
| 10 |
+
environment:
|
| 11 |
+
description: 'Environnement'
|
| 12 |
+
type: choice
|
| 13 |
+
options: ['dev', 'production']
|
| 14 |
+
jobs:
|
| 15 |
+
test:
|
| 16 |
+
runs-on: ubuntu-latest
|
| 17 |
+
strategy:
|
| 18 |
+
matrix:
|
| 19 |
+
python-version: ["3.11"]
|
| 20 |
+
|
| 21 |
+
steps:
|
| 22 |
+
- uses: actions/checkout@v4
|
| 23 |
+
|
| 24 |
+
- name: Set up Python ${{ matrix.python-version }}
|
| 25 |
+
uses: actions/setup-python@v4
|
| 26 |
+
with:
|
| 27 |
+
python-version: ${{ matrix.python-version }}
|
| 28 |
+
|
| 29 |
+
- name: Install Poetry
|
| 30 |
+
uses: snok/install-poetry@v1
|
| 31 |
+
with:
|
| 32 |
+
version: latest
|
| 33 |
+
virtualenvs-create: true
|
| 34 |
+
virtualenvs-in-project: true
|
| 35 |
+
installer-parallel: true
|
| 36 |
+
|
| 37 |
+
- name: Load cached venv
|
| 38 |
+
id: cached-poetry-dependencies
|
| 39 |
+
uses: actions/cache@v3
|
| 40 |
+
with:
|
| 41 |
+
path: .venv
|
| 42 |
+
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
|
| 43 |
+
|
| 44 |
+
- name: Install dependencies
|
| 45 |
+
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
| 46 |
+
run: poetry install --no-interaction --no-root
|
| 47 |
+
|
| 48 |
+
- name: Install project
|
| 49 |
+
run: poetry install --no-interaction
|
| 50 |
+
|
| 51 |
+
- name: Run tests with pytest
|
| 52 |
+
run: |
|
| 53 |
+
DATABASE_URL="sqlite:///:memory:" poetry run pytest tests/ --cov=src/project5 --cov-report=xml --cov-report=html --cov-report=term-missing --cov-fail-under=80 -v
|
| 54 |
+
|
| 55 |
+
- name: Upload coverage to Codecov
|
| 56 |
+
uses: codecov/codecov-action@v3
|
| 57 |
+
with:
|
| 58 |
+
token: ${{ secrets.CODECOV_TOKEN }}
|
| 59 |
+
file: ./coverage.xml
|
| 60 |
+
flags: unittests
|
| 61 |
+
name: codecov-umbrella
|
| 62 |
+
|
| 63 |
+
lint:
|
| 64 |
+
runs-on: ubuntu-latest
|
| 65 |
+
steps:
|
| 66 |
+
- uses: actions/checkout@v4
|
| 67 |
+
|
| 68 |
+
- name: Set up Python
|
| 69 |
+
uses: actions/setup-python@v4
|
| 70 |
+
with:
|
| 71 |
+
python-version: "3.11"
|
| 72 |
+
|
| 73 |
+
- name: Install Poetry
|
| 74 |
+
uses: snok/install-poetry@v1
|
| 75 |
+
with:
|
| 76 |
+
version: latest
|
| 77 |
+
virtualenvs-create: true
|
| 78 |
+
virtualenvs-in-project: true
|
| 79 |
+
|
| 80 |
+
- name: Install dependencies
|
| 81 |
+
run: poetry install --no-interaction
|
| 82 |
+
|
| 83 |
+
- name: Run Black
|
| 84 |
+
run: poetry run black --check ./src
|
| 85 |
+
|
| 86 |
+
- name: Run isort
|
| 87 |
+
run: poetry run isort --check-only .
|
| 88 |
+
|
| 89 |
+
- name: Run flake8
|
| 90 |
+
run: poetry run flake8 ./src
|
| 91 |
+
|
| 92 |
+
# - name: Run mypy
|
| 93 |
+
# run: poetry run mypy ./src
|
| 94 |
+
|
| 95 |
+
security:
|
| 96 |
+
runs-on: ubuntu-latest
|
| 97 |
+
steps:
|
| 98 |
+
- uses: actions/checkout@v4
|
| 99 |
+
|
| 100 |
+
- name: Set up Python
|
| 101 |
+
uses: actions/setup-python@v4
|
| 102 |
+
with:
|
| 103 |
+
python-version: "3.11"
|
| 104 |
+
|
| 105 |
+
- name: Install Poetry
|
| 106 |
+
uses: snok/install-poetry@v1
|
| 107 |
+
|
| 108 |
+
- name: Install dependencies
|
| 109 |
+
run: poetry install --no-interaction
|
| 110 |
+
|
| 111 |
+
- name: Run Bandit security linter
|
| 112 |
+
run: poetry run bandit -r ./src -f json
|
| 113 |
+
|
| 114 |
+
- name: Run Safety check
|
| 115 |
+
run: poetry run safety check --output json
|
| 116 |
+
|
| 117 |
+
deploy:
|
| 118 |
+
runs-on: ubuntu-latest
|
| 119 |
+
needs: [test, lint, security]
|
| 120 |
+
if: github.ref == 'refs/heads/main' && github.event.inputs.environment == 'production'
|
| 121 |
+
environment: production # Nécessite approbation dans Settings > Environments
|
| 122 |
+
|
| 123 |
+
steps:
|
| 124 |
+
- name: Setup Git LFS
|
| 125 |
+
run: |
|
| 126 |
+
git lfs install
|
| 127 |
+
|
| 128 |
+
- uses: actions/checkout@v4
|
| 129 |
+
with:
|
| 130 |
+
fetch-depth: 0
|
| 131 |
+
lfs: true
|
| 132 |
+
|
| 133 |
+
- name: Pull LFS files
|
| 134 |
+
run: git lfs pull
|
| 135 |
+
|
| 136 |
+
- name: Push to Hugging Face Space
|
| 137 |
+
env:
|
| 138 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 139 |
+
HF_SPACE_NAME: ${{ secrets.HF_SPACE_NAME }}
|
| 140 |
+
run: |
|
| 141 |
+
echo "Configuration Git pour HF"
|
| 142 |
+
git config --global user.email "action@github.com"
|
| 143 |
+
git config --global user.name "GitHub Action"
|
| 144 |
+
|
| 145 |
+
echo "Ajouter remote HF si pas déjà présent"
|
| 146 |
+
if ! git remote | grep -q huggingface; then
|
| 147 |
+
git remote add huggingface https://huggingface.co/spaces/$HF_SPACE_NAME
|
| 148 |
+
fi
|
| 149 |
+
|
| 150 |
+
echo "Créer une branche orpheline pour éviter l'historique"
|
| 151 |
+
git checkout --orphan deploy-hf
|
| 152 |
+
|
| 153 |
+
echo "Configuration Git LFS pour HuggingFace"
|
| 154 |
+
git lfs track "*.pkl"
|
| 155 |
+
|
| 156 |
+
echo "Copier le Dockerfile pour HF (renommer)"
|
| 157 |
+
cp Dockerfile_app Dockerfile
|
| 158 |
+
git rm -f README.md
|
| 159 |
+
echo "Créer README.md pour HF Spaces si absent"
|
| 160 |
+
if [ ! -f README.md ]; then
|
| 161 |
+
cat > README.md << EOF
|
| 162 |
+
---
|
| 163 |
+
title: Building Energy Prediction API
|
| 164 |
+
emoji: 🏢
|
| 165 |
+
colorFrom: blue
|
| 166 |
+
colorTo: green
|
| 167 |
+
sdk: docker
|
| 168 |
+
app_port: 7860
|
| 169 |
+
pinned: false
|
| 170 |
+
license: mit
|
| 171 |
+
---
|
| 172 |
+
|
| 173 |
+
# Building Energy Prediction API
|
| 174 |
+
|
| 175 |
+
API FastAPI pour la prédiction de consommation énergétique des bâtiments.
|
| 176 |
+
|
| 177 |
+
## Fonctionnalités
|
| 178 |
+
- 🏢 Gestion des quartiers, bâtiments et propriétés
|
| 179 |
+
- 🤖 Prédictions ML avec RandomForest
|
| 180 |
+
- 📊 API REST complète avec documentation Swagger
|
| 181 |
+
|
| 182 |
+
EOF
|
| 183 |
+
fi
|
| 184 |
+
|
| 185 |
+
echo "Echo suppression des données non nécessaires en production"
|
| 186 |
+
git rm -f *.pdf
|
| 187 |
+
git rm -rf docs
|
| 188 |
+
git rm -rf sql
|
| 189 |
+
git rm -rf tests
|
| 190 |
+
echo "Ajouter tous les fichiers nécessaires (y compris LFS)"
|
| 191 |
+
git add -A
|
| 192 |
+
git commit -m "Deploy to HuggingFace Spaces from main branch"
|
| 193 |
+
|
| 194 |
+
echo "Push vers HF avec authentification (LFS supporté)"
|
| 195 |
+
git push https://oauth2:$HF_TOKEN@huggingface.co/spaces/$HF_SPACE_NAME deploy-hf:main --force
|
| 196 |
+
|
| 197 |
+
|
.github/workflows/doc.yml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Build and Deploy Documentation
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main ]
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
build-and-deploy:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
|
| 13 |
+
steps:
|
| 14 |
+
- uses: actions/checkout@v3
|
| 15 |
+
|
| 16 |
+
- name: Set up Python
|
| 17 |
+
uses: actions/setup-python@v4
|
| 18 |
+
with:
|
| 19 |
+
python-version: '3.11'
|
| 20 |
+
|
| 21 |
+
- name: Install Poetry
|
| 22 |
+
uses: snok/install-poetry@v1
|
| 23 |
+
with:
|
| 24 |
+
version: latest
|
| 25 |
+
virtualenvs-create: true
|
| 26 |
+
virtualenvs-in-project: true
|
| 27 |
+
|
| 28 |
+
- name: Load cached venv
|
| 29 |
+
id: cached-poetry-dependencies
|
| 30 |
+
uses: actions/cache@v3
|
| 31 |
+
with:
|
| 32 |
+
path: .venv
|
| 33 |
+
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
|
| 34 |
+
|
| 35 |
+
- name: Install dependencies
|
| 36 |
+
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
| 37 |
+
run: poetry install --no-interaction --no-root
|
| 38 |
+
|
| 39 |
+
- name: Build Sphinx documentation
|
| 40 |
+
run: |
|
| 41 |
+
cd docs && make sphinx-html
|
| 42 |
+
|
| 43 |
+
- name: Deploy to GitHub Pages
|
| 44 |
+
uses: peaceiris/actions-gh-pages@v3
|
| 45 |
+
if: github.ref == 'refs/heads/main'
|
| 46 |
+
with:
|
| 47 |
+
personal_token: ${{ secrets.DOCS_DEPLOY_TOKEN }}
|
| 48 |
+
external_repository: ${{ secrets.DOCS_REPOSITORY }}
|
| 49 |
+
publish_dir: ./docs/build/html
|
| 50 |
+
publish_branch: gh-pages
|
| 51 |
+
user_name: 'github-actions[bot]'
|
| 52 |
+
user_email: 'github-actions[bot]@users.noreply.github.com'
|
| 53 |
+
commit_message: 'Deploy documentation from ${{ github.repository }}@${{ github.sha }}'
|
.gitignore
ADDED
|
@@ -0,0 +1,685 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Created by https://www.toptal.com/developers/gitignore/api/pycharm,python,git
|
| 2 |
+
# Edit at https://www.toptal.com/developers/gitignore?templates=pycharm,python,git
|
| 3 |
+
|
| 4 |
+
### Git ###
|
| 5 |
+
# Created by git for backups. To disable backups in Git:
|
| 6 |
+
# $ git config --global mergetool.keepBackup false
|
| 7 |
+
*.orig
|
| 8 |
+
|
| 9 |
+
# Created by git when using merge tools for conflicts
|
| 10 |
+
*.BACKUP.*
|
| 11 |
+
*.BASE.*
|
| 12 |
+
*.LOCAL.*
|
| 13 |
+
*.REMOTE.*
|
| 14 |
+
*_BACKUP_*.txt
|
| 15 |
+
*_BASE_*.txt
|
| 16 |
+
*_LOCAL_*.txt
|
| 17 |
+
*_REMOTE_*.txt
|
| 18 |
+
|
| 19 |
+
### PyCharm ###
|
| 20 |
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
| 21 |
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
| 22 |
+
|
| 23 |
+
# User-specific stuff
|
| 24 |
+
.idea/**/workspace.xml
|
| 25 |
+
.idea/**/tasks.xml
|
| 26 |
+
.idea/**/usage.statistics.xml
|
| 27 |
+
.idea/**/dictionaries
|
| 28 |
+
.idea/**/shelf
|
| 29 |
+
|
| 30 |
+
# AWS User-specific
|
| 31 |
+
.idea/**/aws.xml
|
| 32 |
+
|
| 33 |
+
# Generated files
|
| 34 |
+
.idea/**/contentModel.xml
|
| 35 |
+
|
| 36 |
+
# Sensitive or high-churn files
|
| 37 |
+
.idea/**/dataSources/
|
| 38 |
+
.idea/**/dataSources.ids
|
| 39 |
+
.idea/**/dataSources.local.xml
|
| 40 |
+
.idea/**/sqlDataSources.xml
|
| 41 |
+
.idea/**/dynamic.xml
|
| 42 |
+
.idea/**/uiDesigner.xml
|
| 43 |
+
.idea/**/dbnavigator.xml
|
| 44 |
+
|
| 45 |
+
# Gradle
|
| 46 |
+
.idea/**/gradle.xml
|
| 47 |
+
.idea/**/libraries
|
| 48 |
+
|
| 49 |
+
# Gradle and Maven with auto-import
|
| 50 |
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
| 51 |
+
# since they will be recreated, and may cause churn. Uncomment if using
|
| 52 |
+
# auto-import.
|
| 53 |
+
# .idea/artifacts
|
| 54 |
+
# .idea/compiler.xml
|
| 55 |
+
# .idea/jarRepositories.xml
|
| 56 |
+
# .idea/modules.xml
|
| 57 |
+
# .idea/*.iml
|
| 58 |
+
# .idea/modules
|
| 59 |
+
# *.iml
|
| 60 |
+
# *.ipr
|
| 61 |
+
|
| 62 |
+
# CMake
|
| 63 |
+
cmake-build-*/
|
| 64 |
+
|
| 65 |
+
# Mongo Explorer plugin
|
| 66 |
+
.idea/**/mongoSettings.xml
|
| 67 |
+
|
| 68 |
+
# File-based project format
|
| 69 |
+
*.iws
|
| 70 |
+
|
| 71 |
+
# IntelliJ
|
| 72 |
+
out/
|
| 73 |
+
|
| 74 |
+
# mpeltonen/sbt-idea plugin
|
| 75 |
+
.idea_modules/
|
| 76 |
+
|
| 77 |
+
# JIRA plugin
|
| 78 |
+
atlassian-ide-plugin.xml
|
| 79 |
+
|
| 80 |
+
# Cursive Clojure plugin
|
| 81 |
+
.idea/replstate.xml
|
| 82 |
+
|
| 83 |
+
# SonarLint plugin
|
| 84 |
+
.idea/sonarlint/
|
| 85 |
+
|
| 86 |
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
| 87 |
+
com_crashlytics_export_strings.xml
|
| 88 |
+
crashlytics.properties
|
| 89 |
+
crashlytics-build.properties
|
| 90 |
+
fabric.properties
|
| 91 |
+
|
| 92 |
+
# Editor-based Rest Client
|
| 93 |
+
.idea/httpRequests
|
| 94 |
+
|
| 95 |
+
# Android studio 3.1+ serialized cache file
|
| 96 |
+
.idea/caches/build_file_checksums.ser
|
| 97 |
+
|
| 98 |
+
### PyCharm Patch ###
|
| 99 |
+
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
| 100 |
+
|
| 101 |
+
# *.iml
|
| 102 |
+
# modules.xml
|
| 103 |
+
# .idea/misc.xml
|
| 104 |
+
# *.ipr
|
| 105 |
+
|
| 106 |
+
# Sonarlint plugin
|
| 107 |
+
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
| 108 |
+
.idea/**/sonarlint/
|
| 109 |
+
|
| 110 |
+
# SonarQube Plugin
|
| 111 |
+
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
| 112 |
+
.idea/**/sonarIssues.xml
|
| 113 |
+
|
| 114 |
+
# Markdown Navigator plugin
|
| 115 |
+
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
| 116 |
+
.idea/**/markdown-navigator.xml
|
| 117 |
+
.idea/**/markdown-navigator-enh.xml
|
| 118 |
+
.idea/**/markdown-navigator/
|
| 119 |
+
|
| 120 |
+
# Cache file creation bug
|
| 121 |
+
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
| 122 |
+
.idea/$CACHE_FILE$
|
| 123 |
+
|
| 124 |
+
# CodeStream plugin
|
| 125 |
+
# https://plugins.jetbrains.com/plugin/12206-codestream
|
| 126 |
+
.idea/codestream.xml
|
| 127 |
+
|
| 128 |
+
# Azure Toolkit for IntelliJ plugin
|
| 129 |
+
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
| 130 |
+
.idea/**/azureSettings.xml
|
| 131 |
+
|
| 132 |
+
### Python ###
|
| 133 |
+
# Byte-compiled / optimized / DLL files
|
| 134 |
+
__pycache__/
|
| 135 |
+
*.py[cod]
|
| 136 |
+
*$py.class
|
| 137 |
+
|
| 138 |
+
# C extensions
|
| 139 |
+
*.so
|
| 140 |
+
|
| 141 |
+
# Distribution / packaging
|
| 142 |
+
.Python
|
| 143 |
+
build/
|
| 144 |
+
develop-eggs/
|
| 145 |
+
dist/
|
| 146 |
+
downloads/
|
| 147 |
+
eggs/
|
| 148 |
+
.eggs/
|
| 149 |
+
lib/
|
| 150 |
+
lib64/
|
| 151 |
+
parts/
|
| 152 |
+
sdist/
|
| 153 |
+
var/
|
| 154 |
+
wheels/
|
| 155 |
+
share/python-wheels/
|
| 156 |
+
*.egg-info/
|
| 157 |
+
.installed.cfg
|
| 158 |
+
*.egg
|
| 159 |
+
MANIFEST
|
| 160 |
+
|
| 161 |
+
# PyInstaller
|
| 162 |
+
# Usually these files are written by a python script from a template
|
| 163 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 164 |
+
*.manifest
|
| 165 |
+
*.spec
|
| 166 |
+
|
| 167 |
+
# Installer logs
|
| 168 |
+
pip-log.txt
|
| 169 |
+
pip-delete-this-directory.txt
|
| 170 |
+
|
| 171 |
+
# Unit test / coverage reports
|
| 172 |
+
htmlcov/
|
| 173 |
+
.tox/
|
| 174 |
+
.nox/
|
| 175 |
+
.coverage
|
| 176 |
+
.coverage.*
|
| 177 |
+
.cache
|
| 178 |
+
nosetests.xml
|
| 179 |
+
coverage.xml
|
| 180 |
+
*.cover
|
| 181 |
+
*.py,cover
|
| 182 |
+
.hypothesis/
|
| 183 |
+
.pytest_cache/
|
| 184 |
+
cover/
|
| 185 |
+
|
| 186 |
+
# Translations
|
| 187 |
+
*.mo
|
| 188 |
+
*.pot
|
| 189 |
+
|
| 190 |
+
# Django stuff:
|
| 191 |
+
*.log
|
| 192 |
+
local_settings.py
|
| 193 |
+
db.sqlite3
|
| 194 |
+
db.sqlite3-journal
|
| 195 |
+
|
| 196 |
+
# Flask stuff:
|
| 197 |
+
instance/
|
| 198 |
+
.webassets-cache
|
| 199 |
+
|
| 200 |
+
# Scrapy stuff:
|
| 201 |
+
.scrapy
|
| 202 |
+
|
| 203 |
+
# Sphinx documentation
|
| 204 |
+
docs/_build/
|
| 205 |
+
|
| 206 |
+
# PyBuilder
|
| 207 |
+
.pybuilder/
|
| 208 |
+
target/
|
| 209 |
+
|
| 210 |
+
# Jupyter Notebook
|
| 211 |
+
.ipynb_checkpoints
|
| 212 |
+
|
| 213 |
+
# IPython
|
| 214 |
+
profile_default/
|
| 215 |
+
ipython_config.py
|
| 216 |
+
|
| 217 |
+
# pyenv
|
| 218 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 219 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 220 |
+
# .python-version
|
| 221 |
+
|
| 222 |
+
# pipenv
|
| 223 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 224 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 225 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 226 |
+
# install all needed dependencies.
|
| 227 |
+
#Pipfile.lock
|
| 228 |
+
|
| 229 |
+
# poetry
|
| 230 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 231 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 232 |
+
# commonly ignored for libraries.
|
| 233 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 234 |
+
#poetry.lock
|
| 235 |
+
|
| 236 |
+
# pdm
|
| 237 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 238 |
+
#pdm.lock
|
| 239 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
| 240 |
+
# in version control.
|
| 241 |
+
# https://pdm.fming.dev/#use-with-ide
|
| 242 |
+
.pdm.toml
|
| 243 |
+
|
| 244 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 245 |
+
__pypackages__/
|
| 246 |
+
|
| 247 |
+
# Celery stuff
|
| 248 |
+
celerybeat-schedule
|
| 249 |
+
celerybeat.pid
|
| 250 |
+
|
| 251 |
+
# SageMath parsed files
|
| 252 |
+
*.sage.py
|
| 253 |
+
|
| 254 |
+
# Environments
|
| 255 |
+
.env
|
| 256 |
+
.venv
|
| 257 |
+
env/
|
| 258 |
+
venv/
|
| 259 |
+
ENV/
|
| 260 |
+
env.bak/
|
| 261 |
+
venv.bak/
|
| 262 |
+
|
| 263 |
+
# Spyder project settings
|
| 264 |
+
.spyderproject
|
| 265 |
+
.spyproject
|
| 266 |
+
|
| 267 |
+
# Rope project settings
|
| 268 |
+
.ropeproject
|
| 269 |
+
|
| 270 |
+
# mkdocs documentation
|
| 271 |
+
/site
|
| 272 |
+
|
| 273 |
+
# mypy
|
| 274 |
+
.mypy_cache/
|
| 275 |
+
.dmypy.json
|
| 276 |
+
dmypy.json
|
| 277 |
+
|
| 278 |
+
# Pyre type checker
|
| 279 |
+
.pyre/
|
| 280 |
+
|
| 281 |
+
# pytype static type analyzer
|
| 282 |
+
.pytype/
|
| 283 |
+
|
| 284 |
+
# Cython debug symbols
|
| 285 |
+
cython_debug/
|
| 286 |
+
|
| 287 |
+
# PyCharm
|
| 288 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 289 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 290 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 291 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 292 |
+
#.idea/
|
| 293 |
+
|
| 294 |
+
### Python Patch ###
|
| 295 |
+
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
| 296 |
+
poetry.toml
|
| 297 |
+
|
| 298 |
+
# ruff
|
| 299 |
+
.ruff_cache/
|
| 300 |
+
|
| 301 |
+
# LSP config files
|
| 302 |
+
pyrightconfig.json
|
| 303 |
+
|
| 304 |
+
# End of https://www.toptal.com/developers/gitignore/api/pycharm,python,git
|
| 305 |
+
.env
|
| 306 |
+
|
| 307 |
+
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
|
| 308 |
+
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
|
| 309 |
+
|
| 310 |
+
### VisualStudioCode ###
|
| 311 |
+
.vscode/*
|
| 312 |
+
!.vscode/settings.json
|
| 313 |
+
!.vscode/tasks.json
|
| 314 |
+
!.vscode/launch.json
|
| 315 |
+
!.vscode/extensions.json
|
| 316 |
+
!.vscode/*.code-snippets
|
| 317 |
+
|
| 318 |
+
# Local History for Visual Studio Code
|
| 319 |
+
.history/
|
| 320 |
+
|
| 321 |
+
# Built Visual Studio Code Extensions
|
| 322 |
+
*.vsix
|
| 323 |
+
|
| 324 |
+
### VisualStudioCode Patch ###
|
| 325 |
+
# Ignore all local history of files
|
| 326 |
+
.history
|
| 327 |
+
.ionide
|
| 328 |
+
|
| 329 |
+
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode
|
| 330 |
+
# Created by https://www.toptal.com/developers/gitignore/api/macos
|
| 331 |
+
# Edit at https://www.toptal.com/developers/gitignore?templates=macos
|
| 332 |
+
|
| 333 |
+
### macOS ###
|
| 334 |
+
# General
|
| 335 |
+
.DS_Store
|
| 336 |
+
.AppleDouble
|
| 337 |
+
.LSOverride
|
| 338 |
+
|
| 339 |
+
# Icon must end with two \r
|
| 340 |
+
Icon
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
# Thumbnails
|
| 344 |
+
._*
|
| 345 |
+
|
| 346 |
+
# Files that might appear in the root of a volume
|
| 347 |
+
.DocumentRevisions-V100
|
| 348 |
+
.fseventsd
|
| 349 |
+
.Spotlight-V100
|
| 350 |
+
.TemporaryItems
|
| 351 |
+
.Trashes
|
| 352 |
+
.VolumeIcon.icns
|
| 353 |
+
.com.apple.timemachine.donotpresent
|
| 354 |
+
|
| 355 |
+
# Directories potentially created on remote AFP share
|
| 356 |
+
.AppleDB
|
| 357 |
+
.AppleDesktop
|
| 358 |
+
Network Trash Folder
|
| 359 |
+
Temporary Items
|
| 360 |
+
.apdisk
|
| 361 |
+
|
| 362 |
+
### macOS Patch ###
|
| 363 |
+
# iCloud generated files
|
| 364 |
+
*.icloud
|
| 365 |
+
|
| 366 |
+
# End of https://www.toptal.com/developers/gitignore/api/macos
|
| 367 |
+
# Created by https://www.toptal.com/developers/gitignore/api/latex
|
| 368 |
+
# Edit at https://www.toptal.com/developers/gitignore?templates=latex
|
| 369 |
+
|
| 370 |
+
### LaTeX ###
|
| 371 |
+
## Core latex/pdflatex auxiliary files:
|
| 372 |
+
*.aux
|
| 373 |
+
*.lof
|
| 374 |
+
*.log
|
| 375 |
+
*.lot
|
| 376 |
+
*.fls
|
| 377 |
+
*.out
|
| 378 |
+
*.toc
|
| 379 |
+
*.fmt
|
| 380 |
+
*.fot
|
| 381 |
+
*.cb
|
| 382 |
+
*.cb2
|
| 383 |
+
.*.lb
|
| 384 |
+
|
| 385 |
+
## Intermediate documents:
|
| 386 |
+
*.dvi
|
| 387 |
+
*.xdv
|
| 388 |
+
*-converted-to.*
|
| 389 |
+
# these rules might exclude image files for figures etc.
|
| 390 |
+
# *.ps
|
| 391 |
+
# *.eps
|
| 392 |
+
# *.pdf
|
| 393 |
+
|
| 394 |
+
## Generated if empty string is given at "Please type another file name for output:"
|
| 395 |
+
.pdf
|
| 396 |
+
|
| 397 |
+
## Bibliography auxiliary files (bibtex/biblatex/biber):
|
| 398 |
+
*.bbl
|
| 399 |
+
*.bcf
|
| 400 |
+
*.blg
|
| 401 |
+
*-blx.aux
|
| 402 |
+
*-blx.bib
|
| 403 |
+
*.run.xml
|
| 404 |
+
|
| 405 |
+
## Build tool auxiliary files:
|
| 406 |
+
*.fdb_latexmk
|
| 407 |
+
*.synctex
|
| 408 |
+
*.synctex(busy)
|
| 409 |
+
*.synctex.gz
|
| 410 |
+
*.synctex.gz(busy)
|
| 411 |
+
*.pdfsync
|
| 412 |
+
|
| 413 |
+
## Build tool directories for auxiliary files
|
| 414 |
+
# latexrun
|
| 415 |
+
latex.out/
|
| 416 |
+
|
| 417 |
+
## Auxiliary and intermediate files from other packages:
|
| 418 |
+
# algorithms
|
| 419 |
+
*.alg
|
| 420 |
+
*.loa
|
| 421 |
+
|
| 422 |
+
# achemso
|
| 423 |
+
acs-*.bib
|
| 424 |
+
|
| 425 |
+
# amsthm
|
| 426 |
+
*.thm
|
| 427 |
+
|
| 428 |
+
# beamer
|
| 429 |
+
*.nav
|
| 430 |
+
*.pre
|
| 431 |
+
*.snm
|
| 432 |
+
*.vrb
|
| 433 |
+
|
| 434 |
+
# changes
|
| 435 |
+
*.soc
|
| 436 |
+
|
| 437 |
+
# comment
|
| 438 |
+
*.cut
|
| 439 |
+
|
| 440 |
+
# cprotect
|
| 441 |
+
*.cpt
|
| 442 |
+
|
| 443 |
+
# elsarticle (documentclass of Elsevier journals)
|
| 444 |
+
*.spl
|
| 445 |
+
|
| 446 |
+
# endnotes
|
| 447 |
+
*.ent
|
| 448 |
+
|
| 449 |
+
# fixme
|
| 450 |
+
*.lox
|
| 451 |
+
|
| 452 |
+
# feynmf/feynmp
|
| 453 |
+
*.mf
|
| 454 |
+
*.mp
|
| 455 |
+
*.t[1-9]
|
| 456 |
+
*.t[1-9][0-9]
|
| 457 |
+
*.tfm
|
| 458 |
+
|
| 459 |
+
#(r)(e)ledmac/(r)(e)ledpar
|
| 460 |
+
*.end
|
| 461 |
+
*.?end
|
| 462 |
+
*.[1-9]
|
| 463 |
+
*.[1-9][0-9]
|
| 464 |
+
*.[1-9][0-9][0-9]
|
| 465 |
+
*.[1-9]R
|
| 466 |
+
*.[1-9][0-9]R
|
| 467 |
+
*.[1-9][0-9][0-9]R
|
| 468 |
+
*.eledsec[1-9]
|
| 469 |
+
*.eledsec[1-9]R
|
| 470 |
+
*.eledsec[1-9][0-9]
|
| 471 |
+
*.eledsec[1-9][0-9]R
|
| 472 |
+
*.eledsec[1-9][0-9][0-9]
|
| 473 |
+
*.eledsec[1-9][0-9][0-9]R
|
| 474 |
+
|
| 475 |
+
# glossaries
|
| 476 |
+
*.acn
|
| 477 |
+
*.acr
|
| 478 |
+
*.glg
|
| 479 |
+
*.glo
|
| 480 |
+
*.gls
|
| 481 |
+
*.glsdefs
|
| 482 |
+
*.lzo
|
| 483 |
+
*.lzs
|
| 484 |
+
*.slg
|
| 485 |
+
*.slo
|
| 486 |
+
*.sls
|
| 487 |
+
|
| 488 |
+
# uncomment this for glossaries-extra (will ignore makeindex's style files!)
|
| 489 |
+
# *.ist
|
| 490 |
+
|
| 491 |
+
# gnuplot
|
| 492 |
+
*.gnuplot
|
| 493 |
+
*.table
|
| 494 |
+
|
| 495 |
+
# gnuplottex
|
| 496 |
+
*-gnuplottex-*
|
| 497 |
+
|
| 498 |
+
# gregoriotex
|
| 499 |
+
*.gaux
|
| 500 |
+
*.glog
|
| 501 |
+
*.gtex
|
| 502 |
+
|
| 503 |
+
# htlatex
|
| 504 |
+
*.4ct
|
| 505 |
+
*.4tc
|
| 506 |
+
*.idv
|
| 507 |
+
*.lg
|
| 508 |
+
*.trc
|
| 509 |
+
*.xref
|
| 510 |
+
|
| 511 |
+
# hyperref
|
| 512 |
+
*.brf
|
| 513 |
+
|
| 514 |
+
# knitr
|
| 515 |
+
*-concordance.tex
|
| 516 |
+
# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files
|
| 517 |
+
# *.tikz
|
| 518 |
+
*-tikzDictionary
|
| 519 |
+
|
| 520 |
+
# listings
|
| 521 |
+
*.lol
|
| 522 |
+
|
| 523 |
+
# luatexja-ruby
|
| 524 |
+
*.ltjruby
|
| 525 |
+
|
| 526 |
+
# makeidx
|
| 527 |
+
*.idx
|
| 528 |
+
*.ilg
|
| 529 |
+
*.ind
|
| 530 |
+
|
| 531 |
+
# minitoc
|
| 532 |
+
*.maf
|
| 533 |
+
*.mlf
|
| 534 |
+
*.mlt
|
| 535 |
+
*.mtc[0-9]*
|
| 536 |
+
*.slf[0-9]*
|
| 537 |
+
*.slt[0-9]*
|
| 538 |
+
*.stc[0-9]*
|
| 539 |
+
|
| 540 |
+
# minted
|
| 541 |
+
_minted*
|
| 542 |
+
*.pyg
|
| 543 |
+
|
| 544 |
+
# morewrites
|
| 545 |
+
*.mw
|
| 546 |
+
|
| 547 |
+
# newpax
|
| 548 |
+
*.newpax
|
| 549 |
+
|
| 550 |
+
# nomencl
|
| 551 |
+
*.nlg
|
| 552 |
+
*.nlo
|
| 553 |
+
*.nls
|
| 554 |
+
|
| 555 |
+
# pax
|
| 556 |
+
*.pax
|
| 557 |
+
|
| 558 |
+
# pdfpcnotes
|
| 559 |
+
*.pdfpc
|
| 560 |
+
|
| 561 |
+
# sagetex
|
| 562 |
+
*.sagetex.sage
|
| 563 |
+
*.sagetex.py
|
| 564 |
+
*.sagetex.scmd
|
| 565 |
+
|
| 566 |
+
# scrwfile
|
| 567 |
+
*.wrt
|
| 568 |
+
|
| 569 |
+
# svg
|
| 570 |
+
svg-inkscape/
|
| 571 |
+
|
| 572 |
+
# sympy
|
| 573 |
+
*.sout
|
| 574 |
+
*.sympy
|
| 575 |
+
sympy-plots-for-*.tex/
|
| 576 |
+
|
| 577 |
+
# pdfcomment
|
| 578 |
+
*.upa
|
| 579 |
+
*.upb
|
| 580 |
+
|
| 581 |
+
# pythontex
|
| 582 |
+
*.pytxcode
|
| 583 |
+
pythontex-files-*/
|
| 584 |
+
|
| 585 |
+
# tcolorbox
|
| 586 |
+
*.listing
|
| 587 |
+
|
| 588 |
+
# thmtools
|
| 589 |
+
*.loe
|
| 590 |
+
|
| 591 |
+
# TikZ & PGF
|
| 592 |
+
*.dpth
|
| 593 |
+
*.md5
|
| 594 |
+
*.auxlock
|
| 595 |
+
|
| 596 |
+
# titletoc
|
| 597 |
+
*.ptc
|
| 598 |
+
|
| 599 |
+
# todonotes
|
| 600 |
+
*.tdo
|
| 601 |
+
|
| 602 |
+
# vhistory
|
| 603 |
+
*.hst
|
| 604 |
+
*.ver
|
| 605 |
+
|
| 606 |
+
# easy-todo
|
| 607 |
+
*.lod
|
| 608 |
+
|
| 609 |
+
# xcolor
|
| 610 |
+
*.xcp
|
| 611 |
+
|
| 612 |
+
# xmpincl
|
| 613 |
+
*.xmpi
|
| 614 |
+
|
| 615 |
+
# xindy
|
| 616 |
+
*.xdy
|
| 617 |
+
|
| 618 |
+
# xypic precompiled matrices and outlines
|
| 619 |
+
*.xyc
|
| 620 |
+
*.xyd
|
| 621 |
+
|
| 622 |
+
# endfloat
|
| 623 |
+
*.ttt
|
| 624 |
+
*.fff
|
| 625 |
+
|
| 626 |
+
# Latexian
|
| 627 |
+
TSWLatexianTemp*
|
| 628 |
+
|
| 629 |
+
## Editors:
|
| 630 |
+
# WinEdt
|
| 631 |
+
*.bak
|
| 632 |
+
*.sav
|
| 633 |
+
|
| 634 |
+
# Texpad
|
| 635 |
+
.texpadtmp
|
| 636 |
+
|
| 637 |
+
# LyX
|
| 638 |
+
*.lyx~
|
| 639 |
+
|
| 640 |
+
# Kile
|
| 641 |
+
*.backup
|
| 642 |
+
|
| 643 |
+
# gummi
|
| 644 |
+
.*.swp
|
| 645 |
+
|
| 646 |
+
# KBibTeX
|
| 647 |
+
*~[0-9]*
|
| 648 |
+
|
| 649 |
+
# TeXnicCenter
|
| 650 |
+
*.tps
|
| 651 |
+
|
| 652 |
+
# auto folder when using emacs and auctex
|
| 653 |
+
./auto/*
|
| 654 |
+
*.el
|
| 655 |
+
|
| 656 |
+
# expex forward references with \gathertags
|
| 657 |
+
*-tags.tex
|
| 658 |
+
|
| 659 |
+
# standalone packages
|
| 660 |
+
*.sta
|
| 661 |
+
|
| 662 |
+
# Makeindex log files
|
| 663 |
+
*.lpz
|
| 664 |
+
|
| 665 |
+
# xwatermark package
|
| 666 |
+
*.xwm
|
| 667 |
+
|
| 668 |
+
# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib
|
| 669 |
+
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
|
| 670 |
+
# Uncomment the next line to have this generated file ignored.
|
| 671 |
+
#*Notes.bib
|
| 672 |
+
|
| 673 |
+
### LaTeX Patch ###
|
| 674 |
+
# LIPIcs / OASIcs
|
| 675 |
+
*.vtc
|
| 676 |
+
|
| 677 |
+
# glossaries
|
| 678 |
+
*.glstex
|
| 679 |
+
|
| 680 |
+
# End of https://www.toptal.com/developers/gitignore/api/latex
|
| 681 |
+
.gradio
|
| 682 |
+
.env
|
| 683 |
+
sql/data/psql_data
|
| 684 |
+
app.db
|
| 685 |
+
model/backup
|
.vscode/launch.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "0.2.0",
|
| 3 |
+
"configurations": [
|
| 4 |
+
{
|
| 5 |
+
"name": "Python: Current File",
|
| 6 |
+
"type": "debugpy",
|
| 7 |
+
"request": "launch",
|
| 8 |
+
"program": "${file}",
|
| 9 |
+
"console": "integratedTerminal",
|
| 10 |
+
"python": "${workspaceFolder}/.venv/bin/python"
|
| 11 |
+
}
|
| 12 |
+
]
|
| 13 |
+
}
|
.vscode/settings.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"python.defaultInterpreterPath": "./venv/bin/python",
|
| 3 |
+
"python.terminal.activateEnvironment": true,
|
| 4 |
+
// Linting avec Poetry
|
| 5 |
+
"python.linting.enabled": true,
|
| 6 |
+
"python.linting.flake8Enabled": true,
|
| 7 |
+
"python.linting.lintOnSave": true,
|
| 8 |
+
"python.linting.flake8Path": "./venv/bin/flake8",
|
| 9 |
+
// Configuration spécifique pour Jupyter
|
| 10 |
+
"jupyter.linting.enabled": true,
|
| 11 |
+
// Formatting (si vous utilisez Black)
|
| 12 |
+
"python.formatting.provider": "black",
|
| 13 |
+
"python.formatting.blackPath": "./venv/bin/black",
|
| 14 |
+
"editor.formatOnSave": true,
|
| 15 |
+
// Désactiver autres linters
|
| 16 |
+
"python.linting.pylintEnabled": false,
|
| 17 |
+
"python.linting.pycodestyleEnabled": false,
|
| 18 |
+
"makefile.configureOnOpen": false,
|
| 19 |
+
"python.linting.flake8Args": [
|
| 20 |
+
"--max-line-length=88",
|
| 21 |
+
"--extend-ignore=E203,W503,E402"
|
| 22 |
+
],
|
| 23 |
+
"editor.rulers": [
|
| 24 |
+
88
|
| 25 |
+
],
|
| 26 |
+
// === ACTIONS SUR SAUVEGARDE NOTEBOOKS ===
|
| 27 |
+
"notebook.codeActionsOnSave": {
|
| 28 |
+
"source.organizeImports": true, // Organise les imports
|
| 29 |
+
"source.fixAll": false, // Désactiver les fix auto (optionnel)
|
| 30 |
+
},
|
| 31 |
+
// === ACTIONS SUR SAUVEGARDE FICHIERS PYTHON ===
|
| 32 |
+
"[python]": {
|
| 33 |
+
"editor.codeActionsOnSave": {
|
| 34 |
+
"source.organizeImports": "explicit",
|
| 35 |
+
"source.fixAll.flake8": "explicit"
|
| 36 |
+
},
|
| 37 |
+
"editor.defaultFormatter": "ms-python.black-formatter",
|
| 38 |
+
"editor.formatOnSave": true
|
| 39 |
+
},
|
| 40 |
+
// === CONFIGURATION ISORT (pour organiser imports) ===
|
| 41 |
+
"python.sortImports.args": [
|
| 42 |
+
"--profile=black", // Compatible avec Black
|
| 43 |
+
"--line-length=88"
|
| 44 |
+
],
|
| 45 |
+
"black-formatter.args": [
|
| 46 |
+
"--line-length=88"
|
| 47 |
+
]
|
| 48 |
+
}
|
.vscode/tasks.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "2.0.0",
|
| 3 |
+
"tasks": [
|
| 4 |
+
{
|
| 5 |
+
"label": "poetry install",
|
| 6 |
+
"type": "shell",
|
| 7 |
+
"command": "poetry",
|
| 8 |
+
"args": [
|
| 9 |
+
"install"
|
| 10 |
+
],
|
| 11 |
+
"group": "build"
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"label": "poetry run tests",
|
| 15 |
+
"type": "shell",
|
| 16 |
+
"command": "poetry",
|
| 17 |
+
"args": [
|
| 18 |
+
"run",
|
| 19 |
+
"pytest"
|
| 20 |
+
],
|
| 21 |
+
"group": "test"
|
| 22 |
+
}
|
| 23 |
+
]
|
| 24 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
# Installer Poetry et les dépendances système pour SQLite
|
| 4 |
+
RUN apt-get update && apt-get install -y \
|
| 5 |
+
sqlite3 \
|
| 6 |
+
libsqlite3-dev \
|
| 7 |
+
&& rm -rf /var/lib/apt/lists/* \
|
| 8 |
+
&& pip install poetry
|
| 9 |
+
|
| 10 |
+
# Configuration Poetry
|
| 11 |
+
ENV POETRY_NO_INTERACTION=1 \
|
| 12 |
+
POETRY_VENV_IN_PROJECT=1 \
|
| 13 |
+
POETRY_CACHE_DIR=/tmp/poetry_cache \
|
| 14 |
+
VIRTUAL_ENV=/code/.venv \
|
| 15 |
+
PATH="/code/.venv/bin:$PATH"
|
| 16 |
+
|
| 17 |
+
WORKDIR /code
|
| 18 |
+
|
| 19 |
+
# Copier les fichiers de dépendances
|
| 20 |
+
COPY pyproject.toml poetry.lock ./
|
| 21 |
+
|
| 22 |
+
# Copier le code source d'abord
|
| 23 |
+
COPY . .
|
| 24 |
+
|
| 25 |
+
# Configurer Poetry pour créer le venv dans le projet
|
| 26 |
+
|
| 27 |
+
RUN poetry config virtualenvs.in-project true && \
|
| 28 |
+
poetry install --only main --no-interaction --no-ansi
|
| 29 |
+
|
| 30 |
+
# Installer toutes les dépendances et le projet
|
| 31 |
+
#RUN poetry install --only=main && rm -rf $POETRY_CACHE_DIR
|
| 32 |
+
|
| 33 |
+
# Configuration pour SQLite et HuggingFace Spaces
|
| 34 |
+
ENV DATABASE_URL="sqlite:///:memory:" \
|
| 35 |
+
ENVIRONMENT="production" \
|
| 36 |
+
PORT=7860
|
| 37 |
+
|
| 38 |
+
ENV ML_MODEL_PATH=../model/model.pkl
|
| 39 |
+
|
| 40 |
+
# Exposer le port requis par HF Spaces
|
| 41 |
+
EXPOSE 7860
|
| 42 |
+
|
| 43 |
+
# Rendre le script d'initialisation exécutable
|
| 44 |
+
RUN chmod +x init_db.py
|
| 45 |
+
|
| 46 |
+
# Commande de démarrage avec initialisation de la base
|
| 47 |
+
CMD ["sh", "-c", " poetry env activate && cd src && poetry run uvicorn project5.main:app --host 0.0.0.0 --port 7860"]
|
Dockerfile_app
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
# Installer Poetry et les dépendances système pour SQLite
|
| 4 |
+
RUN apt-get update && apt-get install -y \
|
| 5 |
+
sqlite3 \
|
| 6 |
+
libsqlite3-dev \
|
| 7 |
+
&& rm -rf /var/lib/apt/lists/* \
|
| 8 |
+
&& pip install poetry
|
| 9 |
+
|
| 10 |
+
# Configuration Poetry
|
| 11 |
+
ENV POETRY_NO_INTERACTION=1 \
|
| 12 |
+
POETRY_VENV_IN_PROJECT=1 \
|
| 13 |
+
POETRY_CACHE_DIR=/tmp/poetry_cache \
|
| 14 |
+
VIRTUAL_ENV=/code/.venv \
|
| 15 |
+
PATH="/code/.venv/bin:$PATH"
|
| 16 |
+
|
| 17 |
+
WORKDIR /code
|
| 18 |
+
|
| 19 |
+
# Copier les fichiers de dépendances
|
| 20 |
+
COPY pyproject.toml poetry.lock ./
|
| 21 |
+
|
| 22 |
+
# Copier le code source d'abord
|
| 23 |
+
COPY . .
|
| 24 |
+
|
| 25 |
+
# Configurer Poetry pour créer le venv dans le projet
|
| 26 |
+
|
| 27 |
+
RUN poetry config virtualenvs.in-project true && \
|
| 28 |
+
poetry install --only main --no-interaction --no-ansi
|
| 29 |
+
|
| 30 |
+
# Installer toutes les dépendances et le projet
|
| 31 |
+
#RUN poetry install --only=main && rm -rf $POETRY_CACHE_DIR
|
| 32 |
+
|
| 33 |
+
# Configuration pour SQLite et HuggingFace Spaces
|
| 34 |
+
ENV DATABASE_URL="sqlite:///:memory:" \
|
| 35 |
+
ENVIRONMENT="production" \
|
| 36 |
+
PORT=7860
|
| 37 |
+
|
| 38 |
+
ENV ML_MODEL_PATH=../model/model.pkl
|
| 39 |
+
|
| 40 |
+
# Exposer le port requis par HF Spaces
|
| 41 |
+
EXPOSE 7860
|
| 42 |
+
|
| 43 |
+
# Rendre le script d'initialisation exécutable
|
| 44 |
+
RUN chmod +x init_db.py
|
| 45 |
+
|
| 46 |
+
# Commande de démarrage avec initialisation de la base
|
| 47 |
+
CMD ["sh", "-c", " poetry env activate && cd src && poetry run uvicorn project5.main:app --host 0.0.0.0 --port 7860"]
|
Makefile
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Makefile pour FastAPI avec Poetry et Docker
|
| 2 |
+
.PHONY: help build up down restart logs shell db-shell test lint format clean install migrate seed backup restore
|
| 3 |
+
|
| 4 |
+
# Variables
|
| 5 |
+
DOCKER_COMPOSE = docker-compose
|
| 6 |
+
SERVICE_API = api
|
| 7 |
+
SERVICE_DB = db
|
| 8 |
+
PYTHON_FILES = src/project5/ tests/ docs/
|
| 9 |
+
PROJECT_NAME = project5
|
| 10 |
+
|
| 11 |
+
# Format des images par default
|
| 12 |
+
FORMAT=png
|
| 13 |
+
|
| 14 |
+
# Couleurs pour l'affichage
|
| 15 |
+
GREEN = \033[0;32m
|
| 16 |
+
YELLOW = \033[0;33m
|
| 17 |
+
RED = \033[0;31m
|
| 18 |
+
NC = \033[0m
|
| 19 |
+
|
| 20 |
+
# Help - Affiche l'aide
|
| 21 |
+
help: ## Affiche cette aide
|
| 22 |
+
@echo "$(GREEN)Makefile pour $(PROJECT_NAME) - FastAPI avec Poetry$(NC)"
|
| 23 |
+
@echo ""
|
| 24 |
+
@echo "$(YELLOW)Developpement LOCAL (recommande):$(NC)"
|
| 25 |
+
@grep -E '^[a-zA-Z_-]*-local:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(YELLOW)%-25s$(NC) %s\n", $$1, $$2}'
|
| 26 |
+
|
| 27 |
+
# Developpement local (sans Docker)
|
| 28 |
+
dev-local: ## Lance l'API en local avec Poetry
|
| 29 |
+
@echo "$(GREEN)Demarrage local avec Poetry...$(NC)"
|
| 30 |
+
poetry run uvicorn src.$(PROJECT_NAME).main:app --host 0.0.0.0 --port 8000 --reload
|
| 31 |
+
|
| 32 |
+
start-local: ## Alias pour dev-local
|
| 33 |
+
$(MAKE) dev-local
|
| 34 |
+
|
| 35 |
+
install-local: ## Installe les dependances avec Poetry
|
| 36 |
+
@echo "$(GREEN)Installation des dependances avec Poetry...$(NC)"
|
| 37 |
+
poetry install
|
| 38 |
+
|
| 39 |
+
update-local: ## Met a jour les dependances
|
| 40 |
+
@echo "$(GREEN)Mise a jour des dependances...$(NC)"
|
| 41 |
+
poetry update
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
# Qualite de code (local)
|
| 45 |
+
lint-local: ## Verifie le code avec flake8 et mypy en local
|
| 46 |
+
@echo "$(GREEN)Verification du code en local...$(NC)"
|
| 47 |
+
poetry run flake8 $(PYTHON_FILES)
|
| 48 |
+
poetry run mypy $(PYTHON_FILES) --ignore-missing-imports
|
| 49 |
+
|
| 50 |
+
format-local: ## Formate le code avec black et isort en local
|
| 51 |
+
@echo "$(GREEN)Formatage du code en local...$(NC)"
|
| 52 |
+
poetry run black $(PYTHON_FILES)
|
| 53 |
+
poetry run isort $(PYTHON_FILES)
|
| 54 |
+
|
| 55 |
+
format-check-local: ## Verifie le formatage sans modifier en local
|
| 56 |
+
@echo "$(GREEN)Verification du formatage en local...$(NC)"
|
| 57 |
+
poetry run black --check $(PYTHON_FILES)
|
| 58 |
+
poetry run isort --check-only $(PYTHON_FILES)
|
| 59 |
+
|
| 60 |
+
check-local: format-check-local lint-local ## Verifie le formatage et la qualite du code en local
|
| 61 |
+
|
| 62 |
+
# Tests local
|
| 63 |
+
test-local: ## Lance les tests en local avec Poetry
|
| 64 |
+
@echo "$(GREEN)Execution des tests en local...$(NC)"
|
| 65 |
+
poetry run pytest -v
|
| 66 |
+
|
| 67 |
+
test-cov-local: ## Lance les tests avec couverture en local
|
| 68 |
+
@echo "$(GREEN)Tests avec couverture en local...$(NC)"
|
| 69 |
+
poetry run pytest --cov=src/$(PROJECT_NAME) --cov-report=html --cov-report=term-missing
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
# Commande par defaut
|
| 73 |
+
.DEFAULT_GOAL := help
|
README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Building Energy Prediction API
|
| 3 |
+
emoji: 🏢
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# Building Energy Prediction API
|
| 13 |
+
|
| 14 |
+
API FastAPI pour la prédiction de consommation énergétique des bâtiments.
|
| 15 |
+
|
| 16 |
+
## Fonctionnalités
|
| 17 |
+
- 🏢 Gestion des quartiers, bâtiments et propriétés
|
| 18 |
+
- 🤖 Prédictions ML avec RandomForest
|
| 19 |
+
- 📊 API REST complète avec documentation Swagger
|
| 20 |
+
|
alembic.ini
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# A generic, single database configuration.
|
| 2 |
+
|
| 3 |
+
[alembic]
|
| 4 |
+
# path to migration scripts
|
| 5 |
+
script_location = alembic
|
| 6 |
+
|
| 7 |
+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
| 8 |
+
# Uncomment the line below if you want the files to be prepended with date and time
|
| 9 |
+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
| 10 |
+
# for all available tokens
|
| 11 |
+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
| 12 |
+
|
| 13 |
+
# sys.path path, will be prepended to sys.path if present.
|
| 14 |
+
# defaults to the current working directory.
|
| 15 |
+
prepend_sys_path = .
|
| 16 |
+
|
| 17 |
+
# timezone to use when rendering the date within the migration file
|
| 18 |
+
# as well as the filename.
|
| 19 |
+
# If specified, requires the python-dateutil library that can be
|
| 20 |
+
# installed by adding `alembic[tz]` to the pip requirements
|
| 21 |
+
# string value is passed to dateutil.tz.gettz()
|
| 22 |
+
# leave blank for localtime
|
| 23 |
+
# timezone =
|
| 24 |
+
|
| 25 |
+
# max length of characters to apply to the
|
| 26 |
+
# "slug" field
|
| 27 |
+
# truncate_slug_length = 40
|
| 28 |
+
|
| 29 |
+
# set to 'true' to run the environment during
|
| 30 |
+
# the 'revision' command, regardless of autogenerate
|
| 31 |
+
# revision_environment = false
|
| 32 |
+
|
| 33 |
+
# set to 'true' to allow .pyc and .pyo files without
|
| 34 |
+
# a source .py file to be detected as revisions in the
|
| 35 |
+
# versions/ directory
|
| 36 |
+
# sourceless = false
|
| 37 |
+
|
| 38 |
+
# version location specification; This defaults
|
| 39 |
+
# to alembic/versions. When using multiple version
|
| 40 |
+
# directories, initial revisions must be specified with --version-path.
|
| 41 |
+
# The path separator used here should be the separator specified by "version_path_separator" below.
|
| 42 |
+
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
| 43 |
+
|
| 44 |
+
# version path separator; As mentioned above, this is the character used to split
|
| 45 |
+
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
| 46 |
+
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
| 47 |
+
# Valid values for version_path_separator are:
|
| 48 |
+
#
|
| 49 |
+
# version_path_separator = :
|
| 50 |
+
# version_path_separator = ;
|
| 51 |
+
# version_path_separator = space
|
| 52 |
+
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
| 53 |
+
|
| 54 |
+
# set to 'true' to search source files recursively
|
| 55 |
+
# in each "version_locations" directory
|
| 56 |
+
# new in Alembic version 1.10
|
| 57 |
+
# recursive_version_locations = false
|
| 58 |
+
|
| 59 |
+
# the output encoding used when revision files
|
| 60 |
+
# are written from script.py.mako
|
| 61 |
+
# output_encoding = utf-8
|
| 62 |
+
|
| 63 |
+
sqlalchemy.url = driver://user:pass@localhost/dbname
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
[post_write_hooks]
|
| 67 |
+
# post_write_hooks defines scripts or Python functions that are run
|
| 68 |
+
# on newly generated revision scripts. See the documentation for further
|
| 69 |
+
# detail and examples
|
| 70 |
+
|
| 71 |
+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
| 72 |
+
# hooks = black
|
| 73 |
+
# black.type = console_scripts
|
| 74 |
+
# black.entrypoint = black
|
| 75 |
+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
| 76 |
+
|
| 77 |
+
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
| 78 |
+
# hooks = ruff
|
| 79 |
+
# ruff.type = exec
|
| 80 |
+
# ruff.executable = %(here)s/.venv/bin/ruff
|
| 81 |
+
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
| 82 |
+
|
| 83 |
+
# Logging configuration
|
| 84 |
+
[loggers]
|
| 85 |
+
keys = root,sqlalchemy,alembic
|
| 86 |
+
|
| 87 |
+
[handlers]
|
| 88 |
+
keys = console
|
| 89 |
+
|
| 90 |
+
[formatters]
|
| 91 |
+
keys = generic
|
| 92 |
+
|
| 93 |
+
[logger_root]
|
| 94 |
+
level = WARN
|
| 95 |
+
handlers = console
|
| 96 |
+
qualname =
|
| 97 |
+
|
| 98 |
+
[logger_sqlalchemy]
|
| 99 |
+
level = WARN
|
| 100 |
+
handlers =
|
| 101 |
+
qualname = sqlalchemy.engine
|
| 102 |
+
|
| 103 |
+
[logger_alembic]
|
| 104 |
+
level = INFO
|
| 105 |
+
handlers =
|
| 106 |
+
qualname = alembic
|
| 107 |
+
|
| 108 |
+
[handler_console]
|
| 109 |
+
class = StreamHandler
|
| 110 |
+
args = (sys.stderr,)
|
| 111 |
+
level = NOTSET
|
| 112 |
+
formatter = generic
|
| 113 |
+
|
| 114 |
+
[formatter_generic]
|
| 115 |
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
| 116 |
+
datefmt = %H:%M:%S
|
alembic/README
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Generic single-database configuration.
|
alembic/env.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from logging.config import fileConfig
|
| 2 |
+
|
| 3 |
+
from sqlalchemy import engine_from_config, pool
|
| 4 |
+
|
| 5 |
+
from alembic import context
|
| 6 |
+
|
| 7 |
+
# this is the Alembic Config object, which provides
|
| 8 |
+
# access to the values within the .ini file in use.
|
| 9 |
+
config = context.config
|
| 10 |
+
|
| 11 |
+
# Interpret the config file for Python logging.
|
| 12 |
+
# This line sets up loggers basically.
|
| 13 |
+
if config.config_file_name is not None:
|
| 14 |
+
fileConfig(config.config_file_name)
|
| 15 |
+
|
| 16 |
+
# add your model's MetaData object here
|
| 17 |
+
# for 'autogenerate' support
|
| 18 |
+
# from myapp import mymodel
|
| 19 |
+
# target_metadata = mymodel.Base.metadata
|
| 20 |
+
target_metadata = None
|
| 21 |
+
|
| 22 |
+
# other values from the config, defined by the needs of env.py,
|
| 23 |
+
# can be acquired:
|
| 24 |
+
# my_important_option = config.get_main_option("my_important_option")
|
| 25 |
+
# ... etc.
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def run_migrations_offline() -> None:
|
| 29 |
+
"""Run migrations in 'offline' mode.
|
| 30 |
+
|
| 31 |
+
This configures the context with just a URL
|
| 32 |
+
and not an Engine, though an Engine is acceptable
|
| 33 |
+
here as well. By skipping the Engine creation
|
| 34 |
+
we don't even need a DBAPI to be available.
|
| 35 |
+
|
| 36 |
+
Calls to context.execute() here emit the given string to the
|
| 37 |
+
script output.
|
| 38 |
+
|
| 39 |
+
"""
|
| 40 |
+
url = config.get_main_option("sqlalchemy.url")
|
| 41 |
+
context.configure(
|
| 42 |
+
url=url,
|
| 43 |
+
target_metadata=target_metadata,
|
| 44 |
+
literal_binds=True,
|
| 45 |
+
dialect_opts={"paramstyle": "named"},
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
with context.begin_transaction():
|
| 49 |
+
context.run_migrations()
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def run_migrations_online() -> None:
|
| 53 |
+
"""Run migrations in 'online' mode.
|
| 54 |
+
|
| 55 |
+
In this scenario we need to create an Engine
|
| 56 |
+
and associate a connection with the context.
|
| 57 |
+
|
| 58 |
+
"""
|
| 59 |
+
connectable = engine_from_config(
|
| 60 |
+
config.get_section(config.config_ini_section, {}),
|
| 61 |
+
prefix="sqlalchemy.",
|
| 62 |
+
poolclass=pool.NullPool,
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
with connectable.connect() as connection:
|
| 66 |
+
context.configure(connection=connection, target_metadata=target_metadata)
|
| 67 |
+
|
| 68 |
+
with context.begin_transaction():
|
| 69 |
+
context.run_migrations()
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
if context.is_offline_mode():
|
| 73 |
+
run_migrations_offline()
|
| 74 |
+
else:
|
| 75 |
+
run_migrations_online()
|
alembic/script.py.mako
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""${message}
|
| 2 |
+
|
| 3 |
+
Revision ID: ${up_revision}
|
| 4 |
+
Revises: ${down_revision | comma,n}
|
| 5 |
+
Create Date: ${create_date}
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
from typing import Sequence, Union
|
| 9 |
+
|
| 10 |
+
from alembic import op
|
| 11 |
+
import sqlalchemy as sa
|
| 12 |
+
${imports if imports else ""}
|
| 13 |
+
|
| 14 |
+
# revision identifiers, used by Alembic.
|
| 15 |
+
revision: str = ${repr(up_revision)}
|
| 16 |
+
down_revision: Union[str, None] = ${repr(down_revision)}
|
| 17 |
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
| 18 |
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def upgrade() -> None:
|
| 22 |
+
${upgrades if upgrades else "pass"}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def downgrade() -> None:
|
| 26 |
+
${downgrades if downgrades else "pass"}
|
init_db.py
ADDED
|
@@ -0,0 +1,746 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Script d'initialisation de la base de données SQLite pour HuggingFace Spaces."""
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import sqlite3
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
# Ajouter le répertoire src au path pour les imports
|
| 9 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
|
| 10 |
+
|
| 11 |
+
# Connexion globale en mémoire partagée
|
| 12 |
+
_db_connection = None
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def get_db_connection():
|
| 16 |
+
"""Retourne la connexion SQLite partagée en mémoire."""
|
| 17 |
+
global _db_connection
|
| 18 |
+
if _db_connection is None:
|
| 19 |
+
_db_connection = sqlite3.connect(":memory:", check_same_thread=False)
|
| 20 |
+
return _db_connection
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def init_sqlite_data():
|
| 24 |
+
"""Initialise la base SQLite avec les données essentielles en utilisant SQLite direct."""
|
| 25 |
+
try:
|
| 26 |
+
conn = get_db_connection()
|
| 27 |
+
cursor = conn.cursor()
|
| 28 |
+
|
| 29 |
+
# Vérifier les tables existantes
|
| 30 |
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
| 31 |
+
tables = cursor.fetchall()
|
| 32 |
+
print(f"📋 Tables existantes: {[table[0] for table in tables]}")
|
| 33 |
+
|
| 34 |
+
print("📊 Insertion des données de référence...")
|
| 35 |
+
|
| 36 |
+
# Données Neighborhoods
|
| 37 |
+
neighborhoods = [
|
| 38 |
+
(1, "UNKNOWN", -1, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 39 |
+
(2, "BALLARD", 0, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 40 |
+
(3, "CENTRAL", 1, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 41 |
+
(4, "DELRIDGE", 2, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 42 |
+
(5, "DOWNTOWN", 3, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 43 |
+
(6, "EAST", 4, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 44 |
+
(7, "GREATER DUWAMISH", 5, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 45 |
+
(8, "LAKE UNION", 6, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 46 |
+
(
|
| 47 |
+
9,
|
| 48 |
+
"MAGNOLIA / QUEEN ANNE",
|
| 49 |
+
7,
|
| 50 |
+
"2025-09-09 09:56:21",
|
| 51 |
+
"2025-09-09 09:56:21",
|
| 52 |
+
),
|
| 53 |
+
(10, "NORTH", 8, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 54 |
+
(11, "NORTHEAST", 9, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 55 |
+
(12, "NORTHWEST", 10, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 56 |
+
(13, "SOUTHEAST", 11, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 57 |
+
(14, "SOUTHWEST", 12, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 58 |
+
(15, "WEST", 13, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 59 |
+
]
|
| 60 |
+
|
| 61 |
+
cursor.executemany(
|
| 62 |
+
"INSERT OR REPLACE INTO neighborhood (id, neighborhood_name, model_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
| 63 |
+
neighborhoods,
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
# Données Building Types
|
| 67 |
+
building_types = [
|
| 68 |
+
(
|
| 69 |
+
1,
|
| 70 |
+
-1,
|
| 71 |
+
"UNKNOWN",
|
| 72 |
+
"Type de bâtiment inconnu ou non spécifié",
|
| 73 |
+
"2025-09-09 09:56:21",
|
| 74 |
+
"2025-09-09 09:56:21",
|
| 75 |
+
),
|
| 76 |
+
(
|
| 77 |
+
2,
|
| 78 |
+
0,
|
| 79 |
+
"CAMPUS",
|
| 80 |
+
"Campus building complex",
|
| 81 |
+
"2025-09-09 09:56:21",
|
| 82 |
+
"2025-09-09 09:56:21",
|
| 83 |
+
),
|
| 84 |
+
(
|
| 85 |
+
3,
|
| 86 |
+
1,
|
| 87 |
+
"NONRESIDENTIAL",
|
| 88 |
+
"Non-residential building",
|
| 89 |
+
"2025-09-09 09:56:21",
|
| 90 |
+
"2025-09-09 09:56:21",
|
| 91 |
+
),
|
| 92 |
+
(
|
| 93 |
+
4,
|
| 94 |
+
2,
|
| 95 |
+
"NONRESIDENTIAL COS",
|
| 96 |
+
"Non-residential COS type",
|
| 97 |
+
"2025-09-09 09:56:21",
|
| 98 |
+
"2025-09-09 09:56:21",
|
| 99 |
+
),
|
| 100 |
+
(
|
| 101 |
+
5,
|
| 102 |
+
3,
|
| 103 |
+
"NONRESIDENTIAL WA",
|
| 104 |
+
"Non-residential WA type",
|
| 105 |
+
"2025-09-09 09:56:21",
|
| 106 |
+
"2025-09-09 09:56:21",
|
| 107 |
+
),
|
| 108 |
+
(
|
| 109 |
+
6,
|
| 110 |
+
4,
|
| 111 |
+
"SPS-DISTRICT K-12",
|
| 112 |
+
"Seattle Public Schools District K-12",
|
| 113 |
+
"2025-09-09 09:56:21",
|
| 114 |
+
"2025-09-09 09:56:21",
|
| 115 |
+
),
|
| 116 |
+
(
|
| 117 |
+
7,
|
| 118 |
+
5,
|
| 119 |
+
"Multifamily MR (5-9)",
|
| 120 |
+
"Multifamily Mid-Rise 5-9 units",
|
| 121 |
+
"2025-09-09 09:56:21",
|
| 122 |
+
"2025-09-09 09:56:21",
|
| 123 |
+
),
|
| 124 |
+
(
|
| 125 |
+
8,
|
| 126 |
+
6,
|
| 127 |
+
"Multifamily HR (10+)",
|
| 128 |
+
"Multifamily High-Rise 10+ units",
|
| 129 |
+
"2025-09-09 09:56:21",
|
| 130 |
+
"2025-09-09 09:56:21",
|
| 131 |
+
),
|
| 132 |
+
(
|
| 133 |
+
9,
|
| 134 |
+
7,
|
| 135 |
+
"Multifamily LR (2-4)",
|
| 136 |
+
"Multifamily Low-Rise 2-4 units",
|
| 137 |
+
"2025-09-09 09:56:21",
|
| 138 |
+
"2025-09-09 09:56:21",
|
| 139 |
+
),
|
| 140 |
+
]
|
| 141 |
+
|
| 142 |
+
cursor.executemany(
|
| 143 |
+
"INSERT OR REPLACE INTO building_type (id, model_id, building_type_name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
| 144 |
+
building_types,
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
# Données Categories
|
| 148 |
+
categories = [
|
| 149 |
+
(
|
| 150 |
+
1,
|
| 151 |
+
"UNKNOWN",
|
| 152 |
+
"UNKNOWN",
|
| 153 |
+
"Catégorie inconnue ou non spécifiée",
|
| 154 |
+
"2025-09-09 09:56:21",
|
| 155 |
+
"2025-09-09 09:56:21",
|
| 156 |
+
),
|
| 157 |
+
(
|
| 158 |
+
2,
|
| 159 |
+
"CAMPUS",
|
| 160 |
+
"Campus",
|
| 161 |
+
"Complexes de campus et installations multiples",
|
| 162 |
+
"2025-09-09 09:56:21",
|
| 163 |
+
"2025-09-09 09:56:21",
|
| 164 |
+
),
|
| 165 |
+
(
|
| 166 |
+
3,
|
| 167 |
+
"EDUCATION",
|
| 168 |
+
"Éducation",
|
| 169 |
+
"Établissements denseignement et de formation",
|
| 170 |
+
"2025-09-09 09:56:21",
|
| 171 |
+
"2025-09-09 09:56:21",
|
| 172 |
+
),
|
| 173 |
+
(
|
| 174 |
+
4,
|
| 175 |
+
"ENTERTAINMENT",
|
| 176 |
+
"Divertissement",
|
| 177 |
+
"Théâtres, cinémas et espaces de divertissement",
|
| 178 |
+
"2025-09-09 09:56:21",
|
| 179 |
+
"2025-09-09 09:56:21",
|
| 180 |
+
),
|
| 181 |
+
(
|
| 182 |
+
5,
|
| 183 |
+
"FINANCIAL",
|
| 184 |
+
"Services financiers",
|
| 185 |
+
"Banques, bureaux financiers et services monétaires",
|
| 186 |
+
"2025-09-09 09:56:21",
|
| 187 |
+
"2025-09-09 09:56:21",
|
| 188 |
+
),
|
| 189 |
+
(
|
| 190 |
+
6,
|
| 191 |
+
"HEALTHCARE",
|
| 192 |
+
"Santé",
|
| 193 |
+
"Hôpitaux, cliniques et établissements de soins médicaux",
|
| 194 |
+
"2025-09-09 09:56:21",
|
| 195 |
+
"2025-09-09 09:56:21",
|
| 196 |
+
),
|
| 197 |
+
(
|
| 198 |
+
7,
|
| 199 |
+
"INDUSTRIAL",
|
| 200 |
+
"Industrie",
|
| 201 |
+
"Usines et installations industrielles",
|
| 202 |
+
"2025-09-09 09:56:21",
|
| 203 |
+
"2025-09-09 09:56:21",
|
| 204 |
+
),
|
| 205 |
+
(
|
| 206 |
+
8,
|
| 207 |
+
"LODGING",
|
| 208 |
+
"Hébergement",
|
| 209 |
+
"Hôtels et logements temporaires",
|
| 210 |
+
"2025-09-09 09:56:21",
|
| 211 |
+
"2025-09-09 09:56:21",
|
| 212 |
+
),
|
| 213 |
+
(
|
| 214 |
+
9,
|
| 215 |
+
"MIXED",
|
| 216 |
+
"Usage mixte",
|
| 217 |
+
"Propriétés à usage multiple",
|
| 218 |
+
"2025-09-09 09:56:21",
|
| 219 |
+
"2025-09-09 09:56:21",
|
| 220 |
+
),
|
| 221 |
+
(
|
| 222 |
+
10,
|
| 223 |
+
"NONE",
|
| 224 |
+
"Aucun",
|
| 225 |
+
"Aucune utilisation spécifique",
|
| 226 |
+
"2025-09-09 09:56:21",
|
| 227 |
+
"2025-09-09 09:56:21",
|
| 228 |
+
),
|
| 229 |
+
(
|
| 230 |
+
11,
|
| 231 |
+
"OFFICE",
|
| 232 |
+
"Bureaux",
|
| 233 |
+
"Espaces de bureaux et administratifs",
|
| 234 |
+
"2025-09-09 09:56:21",
|
| 235 |
+
"2025-09-09 09:56:21",
|
| 236 |
+
),
|
| 237 |
+
(
|
| 238 |
+
12,
|
| 239 |
+
"PARKING",
|
| 240 |
+
"Stationnement",
|
| 241 |
+
"Structures et espaces de stationnement",
|
| 242 |
+
"2025-09-09 09:56:21",
|
| 243 |
+
"2025-09-09 09:56:21",
|
| 244 |
+
),
|
| 245 |
+
(
|
| 246 |
+
13,
|
| 247 |
+
"PUBLIC",
|
| 248 |
+
"Services publics",
|
| 249 |
+
"Services gouvernementaux et publics",
|
| 250 |
+
"2025-09-09 09:56:21",
|
| 251 |
+
"2025-09-09 09:56:21",
|
| 252 |
+
),
|
| 253 |
+
(
|
| 254 |
+
14,
|
| 255 |
+
"RECREATION",
|
| 256 |
+
"Loisirs",
|
| 257 |
+
"Installations sportives et récréatives",
|
| 258 |
+
"2025-09-09 09:56:21",
|
| 259 |
+
"2025-09-09 09:56:21",
|
| 260 |
+
),
|
| 261 |
+
(
|
| 262 |
+
15,
|
| 263 |
+
"RELIGIOUS",
|
| 264 |
+
"Religieux",
|
| 265 |
+
"Églises et lieux de culte",
|
| 266 |
+
"2025-09-09 09:56:21",
|
| 267 |
+
"2025-09-09 09:56:21",
|
| 268 |
+
),
|
| 269 |
+
(
|
| 270 |
+
16,
|
| 271 |
+
"RESIDENTIAL",
|
| 272 |
+
"Résidentiel",
|
| 273 |
+
"Logements et habitations",
|
| 274 |
+
"2025-09-09 09:56:21",
|
| 275 |
+
"2025-09-09 09:56:21",
|
| 276 |
+
),
|
| 277 |
+
(
|
| 278 |
+
17,
|
| 279 |
+
"RESTAURANT",
|
| 280 |
+
"Restauration",
|
| 281 |
+
"Restaurants et services alimentaires",
|
| 282 |
+
"2025-09-09 09:56:21",
|
| 283 |
+
"2025-09-09 09:56:21",
|
| 284 |
+
),
|
| 285 |
+
(
|
| 286 |
+
18,
|
| 287 |
+
"RETAIL",
|
| 288 |
+
"Commerce de détail",
|
| 289 |
+
"Magasins et commerces",
|
| 290 |
+
"2025-09-09 09:56:21",
|
| 291 |
+
"2025-09-09 09:56:21",
|
| 292 |
+
),
|
| 293 |
+
(
|
| 294 |
+
19,
|
| 295 |
+
"SOCIAL",
|
| 296 |
+
"Social",
|
| 297 |
+
"Clubs et espaces sociaux",
|
| 298 |
+
"2025-09-09 09:56:21",
|
| 299 |
+
"2025-09-09 09:56:21",
|
| 300 |
+
),
|
| 301 |
+
(
|
| 302 |
+
20,
|
| 303 |
+
"STORE",
|
| 304 |
+
"Magasins",
|
| 305 |
+
"Commerces et magasins divers",
|
| 306 |
+
"2025-09-09 09:56:21",
|
| 307 |
+
"2025-09-09 09:56:21",
|
| 308 |
+
),
|
| 309 |
+
]
|
| 310 |
+
|
| 311 |
+
cursor.executemany(
|
| 312 |
+
"INSERT OR REPLACE INTO categories (id, category_code, category_name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
| 313 |
+
categories,
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
# Données Properties (sélection des principales)
|
| 317 |
+
properties = [
|
| 318 |
+
(1, -1, "UNKNOWN", 1, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 319 |
+
(2, 0, "ADULT EDUCATION", 3, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 320 |
+
(
|
| 321 |
+
3,
|
| 322 |
+
1,
|
| 323 |
+
"AUTOMOBILE DEALERSHIP",
|
| 324 |
+
20,
|
| 325 |
+
"2025-09-09 09:56:21",
|
| 326 |
+
"2025-09-09 09:56:21",
|
| 327 |
+
),
|
| 328 |
+
(4, 2, "BANK BRANCH", 5, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 329 |
+
(5, 3, "BAR/NIGHTCLUB", 19, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 330 |
+
(
|
| 331 |
+
6,
|
| 332 |
+
4,
|
| 333 |
+
"COLLEGE/UNIVERSITY",
|
| 334 |
+
3,
|
| 335 |
+
"2025-09-09 09:56:21",
|
| 336 |
+
"2025-09-09 09:56:21",
|
| 337 |
+
),
|
| 338 |
+
(
|
| 339 |
+
7,
|
| 340 |
+
5,
|
| 341 |
+
"CONVENIENCE STORE WITHOUT GAS STATION",
|
| 342 |
+
20,
|
| 343 |
+
"2025-09-09 09:56:21",
|
| 344 |
+
"2025-09-09 09:56:21",
|
| 345 |
+
),
|
| 346 |
+
(8, 6, "COURTHOUSE", 15, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 347 |
+
(9, 7, "DATA CENTER", 11, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 348 |
+
(
|
| 349 |
+
10,
|
| 350 |
+
8,
|
| 351 |
+
"DISTRIBUTION CENTER",
|
| 352 |
+
7,
|
| 353 |
+
"2025-09-09 09:56:21",
|
| 354 |
+
"2025-09-09 09:56:21",
|
| 355 |
+
),
|
| 356 |
+
(
|
| 357 |
+
11,
|
| 358 |
+
9,
|
| 359 |
+
"FINANCIAL OFFICE",
|
| 360 |
+
5,
|
| 361 |
+
"2025-09-09 09:56:21",
|
| 362 |
+
"2025-09-09 09:56:21",
|
| 363 |
+
),
|
| 364 |
+
(12, 10, "FOOD SALES", 17, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 365 |
+
(
|
| 366 |
+
13,
|
| 367 |
+
11,
|
| 368 |
+
"HOSPITAL (GENERAL MEDICAL & SURGICAL)",
|
| 369 |
+
6,
|
| 370 |
+
"2025-09-09 09:56:21",
|
| 371 |
+
"2025-09-09 09:56:21",
|
| 372 |
+
),
|
| 373 |
+
(14, 12, "HOTEL", 8, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 374 |
+
(15, 13, "K-12 SCHOOL", 3, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 375 |
+
(16, 14, "LIBRARY", 3, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 376 |
+
(17, 15, "MEDICAL OFFICE", 6, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 377 |
+
(
|
| 378 |
+
18,
|
| 379 |
+
16,
|
| 380 |
+
"MULTIFAMILY HOUSING",
|
| 381 |
+
16,
|
| 382 |
+
"2025-09-09 09:56:21",
|
| 383 |
+
"2025-09-09 09:56:21",
|
| 384 |
+
),
|
| 385 |
+
(
|
| 386 |
+
19,
|
| 387 |
+
17,
|
| 388 |
+
"MUNICIPAL WASTEWATER TREATMENT PLANT",
|
| 389 |
+
7,
|
| 390 |
+
"2025-09-09 09:56:21",
|
| 391 |
+
"2025-09-09 09:56:21",
|
| 392 |
+
),
|
| 393 |
+
(20, 18, "OFFICE", 11, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 394 |
+
(21, 19, "OTHER", 1, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 395 |
+
(22, 20, "PARKING", 12, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 396 |
+
(23, 21, "RESTAURANT", 17, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 397 |
+
(24, 22, "RETAIL STORE", 18, "2025-09-09 09:56:21", "2025-09-09 09:56:21"),
|
| 398 |
+
(
|
| 399 |
+
25,
|
| 400 |
+
23,
|
| 401 |
+
"SELF-STORAGE FACILITY",
|
| 402 |
+
7,
|
| 403 |
+
"2025-09-09 09:56:21",
|
| 404 |
+
"2025-09-09 09:56:21",
|
| 405 |
+
),
|
| 406 |
+
(
|
| 407 |
+
26,
|
| 408 |
+
24,
|
| 409 |
+
"SENIOR LIVING COMMUNITY",
|
| 410 |
+
8,
|
| 411 |
+
"2025-09-09 09:56:21",
|
| 412 |
+
"2025-09-09 09:56:21",
|
| 413 |
+
),
|
| 414 |
+
(
|
| 415 |
+
27,
|
| 416 |
+
25,
|
| 417 |
+
"SUPERMARKET/GROCERY STORE",
|
| 418 |
+
17,
|
| 419 |
+
"2025-09-09 09:56:21",
|
| 420 |
+
"2025-09-09 09:56:21",
|
| 421 |
+
),
|
| 422 |
+
(
|
| 423 |
+
28,
|
| 424 |
+
26,
|
| 425 |
+
"WAREHOUSE (UNREFRIGERATED)",
|
| 426 |
+
7,
|
| 427 |
+
"2025-09-09 09:56:21",
|
| 428 |
+
"2025-09-09 09:56:21",
|
| 429 |
+
),
|
| 430 |
+
(
|
| 431 |
+
29,
|
| 432 |
+
27,
|
| 433 |
+
"WORSHIP FACILITY",
|
| 434 |
+
15,
|
| 435 |
+
"2025-09-09 09:56:21",
|
| 436 |
+
"2025-09-09 09:56:21",
|
| 437 |
+
),
|
| 438 |
+
]
|
| 439 |
+
|
| 440 |
+
cursor.executemany(
|
| 441 |
+
"INSERT OR REPLACE INTO property (id, model_id, property_name, category_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
| 442 |
+
properties,
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
# Données Building Models (exemples pour tester l'API)
|
| 446 |
+
building_models = [
|
| 447 |
+
(
|
| 448 |
+
1,
|
| 449 |
+
"SEATTLE001",
|
| 450 |
+
"123 Main Street",
|
| 451 |
+
"Seattle",
|
| 452 |
+
"WA",
|
| 453 |
+
"98101",
|
| 454 |
+
"TAX001",
|
| 455 |
+
"DISTRICT1",
|
| 456 |
+
47.6062,
|
| 457 |
+
-122.3321,
|
| 458 |
+
1995,
|
| 459 |
+
1,
|
| 460 |
+
15,
|
| 461 |
+
50000.0,
|
| 462 |
+
5000.0,
|
| 463 |
+
2000.0,
|
| 464 |
+
500.0,
|
| 465 |
+
0, # multiusage
|
| 466 |
+
0, # steam
|
| 467 |
+
1, # electricity
|
| 468 |
+
1, # natural_gas
|
| 469 |
+
2, # neighborhood_id (BALLARD)
|
| 470 |
+
3, # building_type_id (NONRESIDENTIAL)
|
| 471 |
+
20, # largest_property_use_type_id (OFFICE)
|
| 472 |
+
20, # primary_property_type_id (OFFICE)
|
| 473 |
+
1, # second_largest_property_use_type_id (UNKNOWN)
|
| 474 |
+
1, # third_largest_property_use_type_id (UNKNOWN)
|
| 475 |
+
"2025-09-09 09:56:21",
|
| 476 |
+
"2025-09-09 09:56:21",
|
| 477 |
+
),
|
| 478 |
+
(
|
| 479 |
+
2,
|
| 480 |
+
"SEATTLE002",
|
| 481 |
+
"456 Pine Avenue",
|
| 482 |
+
"Seattle",
|
| 483 |
+
"WA",
|
| 484 |
+
"98102",
|
| 485 |
+
"TAX002",
|
| 486 |
+
"DISTRICT2",
|
| 487 |
+
47.6205,
|
| 488 |
+
-122.3493,
|
| 489 |
+
2010,
|
| 490 |
+
1,
|
| 491 |
+
8,
|
| 492 |
+
25000.0,
|
| 493 |
+
2000.0,
|
| 494 |
+
0.0,
|
| 495 |
+
0.0,
|
| 496 |
+
0, # multiusage
|
| 497 |
+
0, # steam
|
| 498 |
+
1, # electricity
|
| 499 |
+
1, # natural_gas
|
| 500 |
+
5, # neighborhood_id (DOWNTOWN)
|
| 501 |
+
7, # building_type_id (Multifamily MR 5-9)
|
| 502 |
+
18, # largest_property_use_type_id (MULTIFAMILY HOUSING)
|
| 503 |
+
18, # primary_property_type_id (MULTIFAMILY HOUSING)
|
| 504 |
+
1, # second_largest_property_use_type_id (UNKNOWN)
|
| 505 |
+
1, # third_largest_property_use_type_id (UNKNOWN)
|
| 506 |
+
"2025-09-09 09:56:21",
|
| 507 |
+
"2025-09-09 09:56:21",
|
| 508 |
+
),
|
| 509 |
+
(
|
| 510 |
+
3,
|
| 511 |
+
"SEATTLE003",
|
| 512 |
+
"789 University Way",
|
| 513 |
+
"Seattle",
|
| 514 |
+
"WA",
|
| 515 |
+
"98105",
|
| 516 |
+
"TAX003",
|
| 517 |
+
"DISTRICT3",
|
| 518 |
+
47.6587,
|
| 519 |
+
-122.3128,
|
| 520 |
+
1980,
|
| 521 |
+
1,
|
| 522 |
+
3,
|
| 523 |
+
15000.0,
|
| 524 |
+
1000.0,
|
| 525 |
+
500.0,
|
| 526 |
+
200.0,
|
| 527 |
+
1, # multiusage
|
| 528 |
+
0, # steam
|
| 529 |
+
1, # electricity
|
| 530 |
+
1, # natural_gas
|
| 531 |
+
11, # neighborhood_id (NORTHEAST)
|
| 532 |
+
6, # building_type_id (SPS-DISTRICT K-12)
|
| 533 |
+
15, # largest_property_use_type_id (K-12 SCHOOL)
|
| 534 |
+
15, # primary_property_type_id (K-12 SCHOOL)
|
| 535 |
+
16, # second_largest_property_use_type_id (LIBRARY)
|
| 536 |
+
1, # third_largest_property_use_type_id (UNKNOWN)
|
| 537 |
+
"2025-09-09 09:56:21",
|
| 538 |
+
"2025-09-09 09:56:21",
|
| 539 |
+
),
|
| 540 |
+
]
|
| 541 |
+
|
| 542 |
+
cursor.executemany(
|
| 543 |
+
"""INSERT OR REPLACE INTO building_models (
|
| 544 |
+
id, ose_building_id, address, city, state, zip_code,
|
| 545 |
+
tax_parcel_identification_number, council_district_code,
|
| 546 |
+
latitude, longitude, year_built, number_of_buildings, number_of_floors,
|
| 547 |
+
property_gfa_total, property_gfa_parking,
|
| 548 |
+
second_largest_property_use_type_gfa, third_largest_property_use_type_gfa,
|
| 549 |
+
multiusage, steam, electricity, natural_gas,
|
| 550 |
+
neighborhood_id, building_type_id,
|
| 551 |
+
largest_property_use_type_id, primary_property_type_id,
|
| 552 |
+
second_largest_property_use_type_id, third_largest_property_use_type_id,
|
| 553 |
+
created_at, updated_at
|
| 554 |
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
| 555 |
+
building_models,
|
| 556 |
+
)
|
| 557 |
+
|
| 558 |
+
# Données Building Energy Predictions (exemples de prédictions)
|
| 559 |
+
energy_predictions = [
|
| 560 |
+
(
|
| 561 |
+
1,
|
| 562 |
+
1, # building_id (correspond à SEATTLE001)
|
| 563 |
+
45678.5, # site_energy_use_wn_kbtu
|
| 564 |
+
1, # predicted (True)
|
| 565 |
+
"2025-09-09 10:30:00",
|
| 566 |
+
),
|
| 567 |
+
(
|
| 568 |
+
2,
|
| 569 |
+
2, # building_id (correspond à SEATTLE002)
|
| 570 |
+
23456.7, # site_energy_use_wn_kbtu
|
| 571 |
+
1, # predicted (True)
|
| 572 |
+
"2025-09-09 11:15:00",
|
| 573 |
+
),
|
| 574 |
+
(
|
| 575 |
+
3,
|
| 576 |
+
3, # building_id (correspond à SEATTLE003)
|
| 577 |
+
12345.3, # site_energy_use_wn_kbtu
|
| 578 |
+
1, # predicted (True)
|
| 579 |
+
"2025-09-09 12:00:00",
|
| 580 |
+
),
|
| 581 |
+
]
|
| 582 |
+
|
| 583 |
+
cursor.executemany(
|
| 584 |
+
"""INSERT OR REPLACE INTO building_energy_predictions (
|
| 585 |
+
id, building_id, site_energy_use_wn_kbtu, predicted, updated_at
|
| 586 |
+
) VALUES (?, ?, ?, ?, ?)""",
|
| 587 |
+
energy_predictions,
|
| 588 |
+
)
|
| 589 |
+
|
| 590 |
+
conn.commit()
|
| 591 |
+
# Ne pas fermer la connexion - elle reste en mémoire
|
| 592 |
+
|
| 593 |
+
print(f"✅ {len(neighborhoods)} quartiers insérés")
|
| 594 |
+
print(f"✅ {len(building_types)} types de bâtiments insérés")
|
| 595 |
+
print(f"✅ {len(categories)} catégories insérées")
|
| 596 |
+
print(f"✅ {len(properties)} propriétés insérées")
|
| 597 |
+
print(f"✅ {len(building_models)} bâtiments d'exemple insérés")
|
| 598 |
+
print(f"✅ {len(energy_predictions)} prédictions d'exemple insérées")
|
| 599 |
+
|
| 600 |
+
except Exception as e:
|
| 601 |
+
print(f"❌ Erreur lors de l'insertion des données: {e}")
|
| 602 |
+
raise
|
| 603 |
+
|
| 604 |
+
|
| 605 |
+
def create_sqlite_tables():
|
| 606 |
+
"""Crée les tables SQLite nécessaires avec raw SQL."""
|
| 607 |
+
|
| 608 |
+
try:
|
| 609 |
+
conn = get_db_connection()
|
| 610 |
+
cursor = conn.cursor()
|
| 611 |
+
|
| 612 |
+
# Créer les tables de référence principales
|
| 613 |
+
cursor.execute(
|
| 614 |
+
"""
|
| 615 |
+
CREATE TABLE IF NOT EXISTS neighborhood (
|
| 616 |
+
id INTEGER PRIMARY KEY,
|
| 617 |
+
neighborhood_name VARCHAR(50) NOT NULL UNIQUE,
|
| 618 |
+
model_id INTEGER NOT NULL UNIQUE,
|
| 619 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 620 |
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 621 |
+
);
|
| 622 |
+
"""
|
| 623 |
+
)
|
| 624 |
+
|
| 625 |
+
cursor.execute(
|
| 626 |
+
"""
|
| 627 |
+
CREATE TABLE IF NOT EXISTS building_type (
|
| 628 |
+
id INTEGER PRIMARY KEY,
|
| 629 |
+
model_id INTEGER NOT NULL UNIQUE,
|
| 630 |
+
building_type_name VARCHAR(100) NOT NULL UNIQUE,
|
| 631 |
+
description TEXT,
|
| 632 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 633 |
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 634 |
+
);
|
| 635 |
+
"""
|
| 636 |
+
)
|
| 637 |
+
|
| 638 |
+
cursor.execute(
|
| 639 |
+
"""
|
| 640 |
+
CREATE TABLE IF NOT EXISTS categories (
|
| 641 |
+
id INTEGER PRIMARY KEY,
|
| 642 |
+
category_code VARCHAR(50) NOT NULL UNIQUE,
|
| 643 |
+
category_name VARCHAR(100) NOT NULL,
|
| 644 |
+
description TEXT,
|
| 645 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 646 |
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 647 |
+
);
|
| 648 |
+
"""
|
| 649 |
+
)
|
| 650 |
+
|
| 651 |
+
cursor.execute(
|
| 652 |
+
"""
|
| 653 |
+
CREATE TABLE IF NOT EXISTS property (
|
| 654 |
+
id INTEGER PRIMARY KEY,
|
| 655 |
+
model_id INTEGER NOT NULL UNIQUE,
|
| 656 |
+
property_name VARCHAR(150) NOT NULL UNIQUE,
|
| 657 |
+
category_id INTEGER NOT NULL,
|
| 658 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 659 |
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 660 |
+
FOREIGN KEY (category_id) REFERENCES categories (id)
|
| 661 |
+
);
|
| 662 |
+
"""
|
| 663 |
+
)
|
| 664 |
+
|
| 665 |
+
# Créer les tables principales
|
| 666 |
+
cursor.execute(
|
| 667 |
+
"""
|
| 668 |
+
CREATE TABLE IF NOT EXISTS building_energy_predictions (
|
| 669 |
+
id INTEGER PRIMARY KEY,
|
| 670 |
+
building_id INTEGER NOT NULL,
|
| 671 |
+
site_energy_use_wn_kbtu FLOAT NOT NULL,
|
| 672 |
+
predicted BOOLEAN DEFAULT 0,
|
| 673 |
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 674 |
+
);
|
| 675 |
+
"""
|
| 676 |
+
)
|
| 677 |
+
|
| 678 |
+
cursor.execute(
|
| 679 |
+
"""
|
| 680 |
+
CREATE TABLE IF NOT EXISTS building_models (
|
| 681 |
+
id INTEGER PRIMARY KEY,
|
| 682 |
+
ose_building_id INTEGER NOT NULL UNIQUE,
|
| 683 |
+
address VARCHAR(255) NOT NULL,
|
| 684 |
+
city VARCHAR(100) NULL,
|
| 685 |
+
state VARCHAR(50) NULL,
|
| 686 |
+
zip_code VARCHAR(20) NULL,
|
| 687 |
+
tax_parcel_identification_number VARCHAR(100) NULL,
|
| 688 |
+
council_district_code varchar(20) NULL,
|
| 689 |
+
latitude FLOAT NULL,
|
| 690 |
+
longitude FLOAT NULL,
|
| 691 |
+
year_built INTEGER NULL,
|
| 692 |
+
number_of_buildings INTEGER NULL,
|
| 693 |
+
number_of_floors INTEGER NULL,
|
| 694 |
+
property_gfa_total FLOAT NULL,
|
| 695 |
+
property_gfa_parking FLOAT NULL,
|
| 696 |
+
second_largest_property_use_type_gfa FLOAT NULL,
|
| 697 |
+
third_largest_property_use_type_gfa FLOAT NULL,
|
| 698 |
+
multiusage BOOLEAN DEFAULT 0,
|
| 699 |
+
steam BOOLEAN DEFAULT 0,
|
| 700 |
+
electricity BOOLEAN DEFAULT 0,
|
| 701 |
+
natural_gas BOOLEAN DEFAULT 0,
|
| 702 |
+
neighborhood_id INTEGER NULL,
|
| 703 |
+
building_type_id INTEGER NULL,
|
| 704 |
+
largest_property_use_type_id INTEGER NULL,
|
| 705 |
+
primary_property_type_id INTEGER NULL,
|
| 706 |
+
second_largest_property_use_type_id INTEGER NULL,
|
| 707 |
+
third_largest_property_use_type_id INTEGER NULL,
|
| 708 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 709 |
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 710 |
+
FOREIGN KEY (id) REFERENCES building_energy_predictions (building_id),
|
| 711 |
+
FOREIGN KEY (largest_property_use_type_id) REFERENCES property (id),
|
| 712 |
+
FOREIGN KEY (primary_property_type_id) REFERENCES property (id),
|
| 713 |
+
FOREIGN KEY (second_largest_property_use_type_id) REFERENCES property (id),
|
| 714 |
+
FOREIGN KEY (third_largest_property_use_type_id) REFERENCES property (id),
|
| 715 |
+
FOREIGN KEY (neighborhood_id) REFERENCES neighborhood (id),
|
| 716 |
+
FOREIGN KEY (building_type_id) REFERENCES building_type (id)
|
| 717 |
+
);
|
| 718 |
+
"""
|
| 719 |
+
)
|
| 720 |
+
|
| 721 |
+
conn.commit()
|
| 722 |
+
# Ne pas fermer la connexion - elle reste en mémoire
|
| 723 |
+
print("🗄️ Structure des tables créée avec succès!")
|
| 724 |
+
|
| 725 |
+
except Exception as e:
|
| 726 |
+
print(f"❌ Erreur lors de la création des tables: {e}")
|
| 727 |
+
raise
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
if __name__ == "__main__":
|
| 731 |
+
try:
|
| 732 |
+
print("🚀 Initialisation de la base de données SQLite...")
|
| 733 |
+
|
| 734 |
+
# Créer les tables avec raw SQL
|
| 735 |
+
create_sqlite_tables()
|
| 736 |
+
|
| 737 |
+
print("📊 Insertion des données de référence...")
|
| 738 |
+
init_sqlite_data()
|
| 739 |
+
print("✅ Base de données SQLite initialisée avec succès!")
|
| 740 |
+
|
| 741 |
+
except Exception as e:
|
| 742 |
+
print(f"❌ Erreur lors de l'initialisation: {e}")
|
| 743 |
+
import traceback
|
| 744 |
+
|
| 745 |
+
traceback.print_exc()
|
| 746 |
+
sys.exit(1)
|
model/Makefile
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Makefile pour la génération et gestion du modèle ML
|
| 2 |
+
.PHONY: help model train validate clean info backup restore test
|
| 3 |
+
|
| 4 |
+
# Variables
|
| 5 |
+
PYTHON = python
|
| 6 |
+
MODEL_FILE = model.pkl
|
| 7 |
+
MODEL_INFO = model_info.json
|
| 8 |
+
BACKUP_DIR = backup
|
| 9 |
+
TIMESTAMP = $(shell date +%Y%m%d_%H%M%S)
|
| 10 |
+
|
| 11 |
+
# Couleurs pour l'affichage
|
| 12 |
+
GREEN = \033[0;32m
|
| 13 |
+
YELLOW = \033[0;33m
|
| 14 |
+
RED = \033[0;31m
|
| 15 |
+
NC = \033[0m
|
| 16 |
+
|
| 17 |
+
# Help - Affiche l'aide
|
| 18 |
+
help: ## Affiche cette aide
|
| 19 |
+
@echo "$(GREEN)Makefile pour la gestion du modele ML$(NC)"
|
| 20 |
+
@echo ""
|
| 21 |
+
@echo "$(YELLOW)Generation du modele:$(NC)"
|
| 22 |
+
@echo " $(YELLOW)model $(NC) Alias pour train"
|
| 23 |
+
@echo " $(YELLOW)train $(NC) Genere le modele ML via model.py"
|
| 24 |
+
@echo " $(YELLOW)retrain $(NC) Force la regeneration du modele"
|
| 25 |
+
@echo " $(YELLOW)setup $(NC) Configuration et generation complete"
|
| 26 |
+
@echo ""
|
| 27 |
+
@echo "$(YELLOW)Validation et tests:$(NC)"
|
| 28 |
+
@echo " $(YELLOW)validate $(NC) Valide l'existence et l'integrite du modele"
|
| 29 |
+
@echo " $(YELLOW)test $(NC) Lance les tests de validation du modele"
|
| 30 |
+
@echo " $(YELLOW)test-prediction $(NC) Test une prediction simple"
|
| 31 |
+
@echo ""
|
| 32 |
+
@echo "$(YELLOW)Gestion et maintenance:$(NC)"
|
| 33 |
+
@echo " $(YELLOW)info $(NC) Affiche les informations detaillees du modele"
|
| 34 |
+
@echo " $(YELLOW)model-size $(NC) Affiche la taille des fichiers du modele"
|
| 35 |
+
@echo " $(YELLOW)backup $(NC) Sauvegarde le modele actuel"
|
| 36 |
+
@echo " $(YELLOW)restore $(NC) Restaure le dernier modele sauvegarde"
|
| 37 |
+
@echo " $(YELLOW)list-backups $(NC) Liste toutes les sauvegardes disponibles"
|
| 38 |
+
@echo " $(YELLOW)clean $(NC) Supprime les fichiers du modele actuel"
|
| 39 |
+
@echo " $(YELLOW)clean-all $(NC) Supprime tout (modele + sauvegardes)"
|
| 40 |
+
@echo ""
|
| 41 |
+
@echo "$(YELLOW)Environnement et verifications:$(NC)"
|
| 42 |
+
@echo " $(YELLOW)check-deps $(NC) Verifie les dependances Python"
|
| 43 |
+
@echo " $(YELLOW)check-db $(NC) Verifie la connexion a la base de donnees"
|
| 44 |
+
|
| 45 |
+
# Génération du modèle
|
| 46 |
+
model: train ## Alias pour train
|
| 47 |
+
|
| 48 |
+
train: ## Génère le modèle ML via model.py
|
| 49 |
+
@echo "$(GREEN)Generation du modele de Machine Learning...$(NC)"
|
| 50 |
+
@if [ ! -f "$(MODEL_FILE)" ]; then \
|
| 51 |
+
echo "$(YELLOW)Aucun modele existant trouve. Generation d'un nouveau modele...$(NC)"; \
|
| 52 |
+
else \
|
| 53 |
+
echo "$(YELLOW)Un modele existant a ete trouve. Sauvegarde avant regeneration...$(NC)"; \
|
| 54 |
+
$(MAKE) backup; \
|
| 55 |
+
fi
|
| 56 |
+
$(PYTHON) model.py
|
| 57 |
+
@echo "$(GREEN)✅ Modele genere avec succes!$(NC)"
|
| 58 |
+
@if [ -f "$(MODEL_INFO)" ]; then \
|
| 59 |
+
echo "$(YELLOW)Informations du modele:$(NC)"; \
|
| 60 |
+
cat $(MODEL_INFO) | head -10; \
|
| 61 |
+
fi
|
| 62 |
+
|
| 63 |
+
retrain: clean train ## Force la régénération du modèle
|
| 64 |
+
|
| 65 |
+
# Validation et tests
|
| 66 |
+
validate: ## Valide l'existence et l'intégrité du modèle
|
| 67 |
+
@echo "$(GREEN)Validation du modele...$(NC)"
|
| 68 |
+
@if [ ! -f "$(MODEL_FILE)" ]; then \
|
| 69 |
+
echo "$(RED)❌ Fichier modele manquant: $(MODEL_FILE)$(NC)"; \
|
| 70 |
+
exit 1; \
|
| 71 |
+
else \
|
| 72 |
+
echo "$(GREEN)✅ Fichier modele trouve: $(MODEL_FILE)$(NC)"; \
|
| 73 |
+
fi
|
| 74 |
+
@if [ ! -f "$(MODEL_INFO)" ]; then \
|
| 75 |
+
echo "$(RED)❌ Fichier d'information manquant: $(MODEL_INFO)$(NC)"; \
|
| 76 |
+
exit 1; \
|
| 77 |
+
else \
|
| 78 |
+
echo "$(GREEN)✅ Fichier d'information trouve: $(MODEL_INFO)$(NC)"; \
|
| 79 |
+
fi
|
| 80 |
+
@echo "$(GREEN)✅ Modele valide avec succes!$(NC)"
|
| 81 |
+
|
| 82 |
+
test: validate ## Lance les tests de validation du modèle
|
| 83 |
+
@echo "$(GREEN)Test de chargement du modele...$(NC)"
|
| 84 |
+
@$(PYTHON) -c "import joblib; model = joblib.load('$(MODEL_FILE)'); print('✅ Modele charge avec succes'); print(f'Type: {type(model).__name__}'); import json; info = json.load(open('$(MODEL_INFO)')); print(f'Accuracy: {info.get(\"accuracy\", \"N/A\")}'); print(f'Features: {len(info.get(\"features\", []))} variables')"
|
| 85 |
+
|
| 86 |
+
test-prediction: validate ## Test une prédiction simple
|
| 87 |
+
@echo "$(GREEN)Test de prediction...$(NC)"
|
| 88 |
+
@$(PYTHON) -c "import joblib; import json; import numpy as np; model = joblib.load('$(MODEL_FILE)'); info = json.load(open('$(MODEL_INFO)')); features = info['features']; test_data = np.random.random((1, len(features))); pred = model.predict(test_data); print(f'✅ Prediction test reussie: {pred[0]:.4f}')"
|
| 89 |
+
|
| 90 |
+
# Gestion et maintenance
|
| 91 |
+
info: ## Affiche les informations détaillées du modèle
|
| 92 |
+
@echo "$(GREEN)Informations du modele:$(NC)"
|
| 93 |
+
@if [ -f "$(MODEL_INFO)" ]; then \
|
| 94 |
+
cat $(MODEL_INFO); \
|
| 95 |
+
else \
|
| 96 |
+
echo "$(RED)❌ Fichier d'information non trouve$(NC)"; \
|
| 97 |
+
exit 1; \
|
| 98 |
+
fi
|
| 99 |
+
|
| 100 |
+
model-size: ## Affiche la taille des fichiers du modèle
|
| 101 |
+
@echo "$(GREEN)Taille des fichiers:$(NC)"
|
| 102 |
+
@if [ -f "$(MODEL_FILE)" ]; then \
|
| 103 |
+
ls -lh $(MODEL_FILE) | awk '{print "Modele: " $$5 " (" $$9 ")"}'; \
|
| 104 |
+
fi
|
| 105 |
+
@if [ -f "$(MODEL_INFO)" ]; then \
|
| 106 |
+
ls -lh $(MODEL_INFO) | awk '{print "Info: " $$5 " (" $$9 ")"}'; \
|
| 107 |
+
fi
|
| 108 |
+
|
| 109 |
+
backup: ## Sauvegarde le modèle actuel
|
| 110 |
+
@echo "$(GREEN)Sauvegarde du modele...$(NC)"
|
| 111 |
+
@mkdir -p $(BACKUP_DIR)
|
| 112 |
+
@if [ -f "$(MODEL_FILE)" ]; then \
|
| 113 |
+
cp $(MODEL_FILE) $(BACKUP_DIR)/model_$(TIMESTAMP).pkl; \
|
| 114 |
+
echo "$(GREEN)✅ Modele sauvegarde: $(BACKUP_DIR)/model_$(TIMESTAMP).pkl$(NC)"; \
|
| 115 |
+
fi
|
| 116 |
+
@if [ -f "$(MODEL_INFO)" ]; then \
|
| 117 |
+
cp $(MODEL_INFO) $(BACKUP_DIR)/model_info_$(TIMESTAMP).json; \
|
| 118 |
+
echo "$(GREEN)✅ Info sauvegardee: $(BACKUP_DIR)/model_info_$(TIMESTAMP).json$(NC)"; \
|
| 119 |
+
fi
|
| 120 |
+
|
| 121 |
+
restore: ## Restaure le dernier modèle sauvegardé
|
| 122 |
+
@echo "$(GREEN)Restauration du dernier modele...$(NC)"
|
| 123 |
+
@if [ ! -d "$(BACKUP_DIR)" ]; then \
|
| 124 |
+
echo "$(RED)❌ Aucune sauvegarde trouvee$(NC)"; \
|
| 125 |
+
exit 1; \
|
| 126 |
+
fi
|
| 127 |
+
@LATEST_MODEL=$$(ls -t $(BACKUP_DIR)/model_*.pkl 2>/dev/null | head -1); \
|
| 128 |
+
LATEST_INFO=$$(ls -t $(BACKUP_DIR)/model_info_*.json 2>/dev/null | head -1); \
|
| 129 |
+
if [ -n "$$LATEST_MODEL" ]; then \
|
| 130 |
+
cp "$$LATEST_MODEL" $(MODEL_FILE); \
|
| 131 |
+
echo "$(GREEN)✅ Modele restaure: $$LATEST_MODEL$(NC)"; \
|
| 132 |
+
fi; \
|
| 133 |
+
if [ -n "$$LATEST_INFO" ]; then \
|
| 134 |
+
cp "$$LATEST_INFO" $(MODEL_INFO); \
|
| 135 |
+
echo "$(GREEN)✅ Info restauree: $$LATEST_INFO$(NC)"; \
|
| 136 |
+
fi
|
| 137 |
+
|
| 138 |
+
list-backups: ## Liste toutes les sauvegardes disponibles
|
| 139 |
+
@echo "$(GREEN)Sauvegardes disponibles:$(NC)"
|
| 140 |
+
@if [ -d "$(BACKUP_DIR)" ]; then \
|
| 141 |
+
ls -la $(BACKUP_DIR)/ | grep -E '\.(pkl|json)$$' | awk '{print $$9 " (" $$5 " bytes, " $$6 " " $$7 " " $$8 ")"}' || echo "$(YELLOW)Aucune sauvegarde trouvee$(NC)"; \
|
| 142 |
+
else \
|
| 143 |
+
echo "$(YELLOW)Aucun dossier de sauvegarde$(NC)"; \
|
| 144 |
+
fi
|
| 145 |
+
|
| 146 |
+
clean: ## Supprime les fichiers du modèle actuel
|
| 147 |
+
@echo "$(GREEN)Nettoyage des fichiers du modele...$(NC)"
|
| 148 |
+
@if [ -f "$(MODEL_FILE)" ]; then \
|
| 149 |
+
rm $(MODEL_FILE); \
|
| 150 |
+
echo "$(GREEN)✅ $(MODEL_FILE) supprime$(NC)"; \
|
| 151 |
+
fi
|
| 152 |
+
@if [ -f "$(MODEL_INFO)" ]; then \
|
| 153 |
+
rm $(MODEL_INFO); \
|
| 154 |
+
echo "$(GREEN)✅ $(MODEL_INFO) supprime$(NC)"; \
|
| 155 |
+
fi
|
| 156 |
+
|
| 157 |
+
clean-all: clean ## Supprime tout (modèle + sauvegardes)
|
| 158 |
+
@echo "$(GREEN)Nettoyage complet...$(NC)"
|
| 159 |
+
@if [ -d "$(BACKUP_DIR)" ]; then \
|
| 160 |
+
rm -rf $(BACKUP_DIR); \
|
| 161 |
+
echo "$(GREEN)✅ Dossier de sauvegarde supprime$(NC)"; \
|
| 162 |
+
fi
|
| 163 |
+
|
| 164 |
+
# Environnement et dépendances
|
| 165 |
+
check-deps: ## Vérifie les dépendances Python
|
| 166 |
+
@echo "$(GREEN)Verification des dependances...$(NC)"
|
| 167 |
+
@$(PYTHON) -c "import sklearn, pandas, numpy, joblib; print('✅ Toutes les dependances sont disponibles')" || (echo "$(RED)❌ Dependances manquantes$(NC)" && exit 1)
|
| 168 |
+
|
| 169 |
+
check-db: ## Vérifie la connexion à la base de données
|
| 170 |
+
@echo "$(GREEN)Verification de la connexion DB...$(NC)"
|
| 171 |
+
@$(PYTHON) -c "from sqlalchemy import create_engine; import os; engine = create_engine(os.getenv('DATABASE_URL', 'postgresql://user:password@localhost/dbname')); engine.connect(); print('✅ Connexion DB reussie')" || (echo "$(RED)❌ Erreur de connexion DB$(NC)" && exit 1)
|
| 172 |
+
|
| 173 |
+
# Workflow complet
|
| 174 |
+
setup: check-deps check-db train ## Configuration et génération complète
|
| 175 |
+
@echo "$(GREEN)✅ Setup complet termine!$(NC)"
|
| 176 |
+
|
| 177 |
+
# Commande par défaut
|
| 178 |
+
.DEFAULT_GOAL := help
|
model/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🤖 Modèle de Machine Learning - Project5
|
| 2 |
+
|
| 3 |
+
Ce dossier contient le modèle de Machine Learning pour la prédiction de consommation énergétique des bâtiments.
|
| 4 |
+
|
| 5 |
+
## 📁 Structure des fichiers
|
| 6 |
+
|
| 7 |
+
```
|
| 8 |
+
model/
|
| 9 |
+
├── model.py # Script de génération du modèle
|
| 10 |
+
├── model.pkl # Modèle RandomForest sérialisé (26MB)
|
| 11 |
+
├── model_info.json # Métadonnées et métriques du modèle
|
| 12 |
+
├── generate_model_report.py # Générateur de rapport HTML
|
| 13 |
+
├── Makefile # Automatisation des tâches
|
| 14 |
+
├── backup/ # Sauvegardes des modèles
|
| 15 |
+
└── README.md # Cette documentation
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
## 🎯 Modèle actuel
|
| 19 |
+
|
| 20 |
+
### Informations générales
|
| 21 |
+
- **Type** : RandomForestRegressor
|
| 22 |
+
- **Version** : 1.0
|
| 23 |
+
- **Créé le** : 2025-09-21T11:07:42
|
| 24 |
+
- **Taille** : 26.0 MB
|
| 25 |
+
- **Variables** : 17 features
|
| 26 |
+
|
| 27 |
+
### Métriques de performance
|
| 28 |
+
```json
|
| 29 |
+
{
|
| 30 |
+
"accuracy": 0.916,
|
| 31 |
+
"RMSE": 0.09,
|
| 32 |
+
"MAE": 0.22,
|
| 33 |
+
"R²": 0.92
|
| 34 |
+
}
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
### Variables d'entrée (17 features)
|
| 38 |
+
```
|
| 39 |
+
year_built
|
| 40 |
+
number_of_buildings
|
| 41 |
+
number_of_floors
|
| 42 |
+
property_gfa_total
|
| 43 |
+
property_gfa_parking
|
| 44 |
+
second_largest_property_use_type_gfa
|
| 45 |
+
third_largest_property_use_type_gfa
|
| 46 |
+
multiusage
|
| 47 |
+
steam
|
| 48 |
+
electricity
|
| 49 |
+
natural_gas
|
| 50 |
+
neighborhood_id
|
| 51 |
+
building_type_id
|
| 52 |
+
largest_property_use_type_id
|
| 53 |
+
primary_property_type_id
|
| 54 |
+
second_largest_property_use_type_id
|
| 55 |
+
third_largest_property_use_type_id
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
## 🚀 Utilisation rapide
|
| 59 |
+
|
| 60 |
+
### Génération du modèle
|
| 61 |
+
```bash
|
| 62 |
+
# Installation et génération complète
|
| 63 |
+
make setup
|
| 64 |
+
|
| 65 |
+
# Génération simple
|
| 66 |
+
make train
|
| 67 |
+
|
| 68 |
+
# Force la régénération
|
| 69 |
+
make retrain
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
### Validation et tests
|
| 73 |
+
```bash
|
| 74 |
+
# Valider l'intégrité du modèle
|
| 75 |
+
make validate
|
| 76 |
+
|
| 77 |
+
# Tester le chargement
|
| 78 |
+
make test
|
| 79 |
+
|
| 80 |
+
# Tester une prédiction
|
| 81 |
+
make test-prediction
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
### Rapport et informations
|
| 85 |
+
```bash
|
| 86 |
+
# Générer un rapport HTML complet
|
| 87 |
+
make report
|
| 88 |
+
|
| 89 |
+
# Informations rapides
|
| 90 |
+
make info
|
| 91 |
+
|
| 92 |
+
# Taille des fichiers
|
| 93 |
+
make model-size
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
## 📊 Commandes disponibles
|
| 97 |
+
|
| 98 |
+
### **Génération du modèle**
|
| 99 |
+
- `make model` / `make train` - Génère le modèle via model.py
|
| 100 |
+
- `make retrain` - Force la régénération (supprime + recrée)
|
| 101 |
+
- `make setup` - Workflow complet (dépendances + DB + génération)
|
| 102 |
+
|
| 103 |
+
### **Validation et tests**
|
| 104 |
+
- `make validate` - Vérifie l'existence et l'intégrité des fichiers
|
| 105 |
+
- `make test` - Test de chargement et validation du modèle
|
| 106 |
+
- `make test-prediction` - Test d'une prédiction avec données aléatoires
|
| 107 |
+
|
| 108 |
+
### **Gestion et maintenance**
|
| 109 |
+
- `make info` - Affiche les informations détaillées (JSON)
|
| 110 |
+
- `make model-size` - Affiche la taille des fichiers
|
| 111 |
+
- `make backup` - Sauvegarde avec timestamp
|
| 112 |
+
- `make restore` - Restaure la dernière sauvegarde
|
| 113 |
+
- `make list-backups` - Liste toutes les sauvegardes
|
| 114 |
+
- `make clean` / `make clean-all` - Nettoyage
|
| 115 |
+
|
| 116 |
+
### **Environnement**
|
| 117 |
+
- `make check-deps` - Vérifie les dépendances Python
|
| 118 |
+
- `make check-db` - Teste la connexion à la base de données
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
## 🔧 Configuration technique
|
| 122 |
+
|
| 123 |
+
### Prérequis
|
| 124 |
+
```python
|
| 125 |
+
# Dépendances principales
|
| 126 |
+
import sklearn
|
| 127 |
+
import pandas
|
| 128 |
+
import numpy
|
| 129 |
+
import joblib
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
### Variables d'environnement
|
| 133 |
+
```bash
|
| 134 |
+
DATABASE_URL=postgresql://user:password@localhost/dbname
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
### Source des données
|
| 138 |
+
Le modèle est entraîné sur la vue `model_view` de la base de données PostgreSQL qui agrège :
|
| 139 |
+
- Données Seattle (2016_Building_Energy_Benchmarking.csv)
|
| 140 |
+
- Données OSE (building_consumption_OSEBuildingID.csv)
|
| 141 |
+
|
| 142 |
+
## 🛠️ Développement
|
| 143 |
+
|
| 144 |
+
### Regeneration du modèle
|
| 145 |
+
1. Modifier les hyperparamètres dans `model.py`
|
| 146 |
+
2. Exécuter `make train`
|
| 147 |
+
3. Valider avec `make test`
|
| 148 |
+
4. Générer le rapport avec `make report`
|
| 149 |
+
|
| 150 |
+
### Sauvegarde et versioning
|
| 151 |
+
```bash
|
| 152 |
+
# Sauvegarde manuelle
|
| 153 |
+
make backup
|
| 154 |
+
|
| 155 |
+
# Lister les versions
|
| 156 |
+
make list-backups
|
| 157 |
+
|
| 158 |
+
# Restaurer une version précédente
|
| 159 |
+
make restore
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
### Structure du modèle
|
| 163 |
+
```python
|
| 164 |
+
# Configuration RandomForest
|
| 165 |
+
RandomForestRegressor(
|
| 166 |
+
n_estimators=500,
|
| 167 |
+
max_features=0.5,
|
| 168 |
+
random_state=42,
|
| 169 |
+
min_samples_split=5,
|
| 170 |
+
max_depth=20
|
| 171 |
+
)
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
## 📊 Pipeline de données
|
| 175 |
+
|
| 176 |
+
1. **Source** : Vue `model_view` (PostgreSQL)
|
| 177 |
+
2. **Transformation** : Log de la variable cible `site_energy_use_wn_kbtu`
|
| 178 |
+
3. **Split** : 80/20 train/test (random_state=42)
|
| 179 |
+
4. **Validation** : Cross-validation 5-fold
|
| 180 |
+
5. **Métriques** : RMSE, MAE, R², Accuracy
|
| 181 |
+
|
| 182 |
+
## 🔗 Intégration API
|
| 183 |
+
|
| 184 |
+
Le modèle est automatiquement chargé par l'API FastAPI au démarrage :
|
| 185 |
+
|
| 186 |
+
```python
|
| 187 |
+
# Dans main.py
|
| 188 |
+
from project5.ml.model import MLModel
|
| 189 |
+
|
| 190 |
+
model = MLModel()
|
| 191 |
+
model.load_model() # Charge model.pkl
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
## 📞 Support
|
| 195 |
+
|
| 196 |
+
Pour toute question ou problème :
|
| 197 |
+
|
| 198 |
+
1. **Vérifier l'intégrité** : `make validate`
|
| 199 |
+
2. **Tester le modèle** : `make test`
|
| 200 |
+
3. **Générer un rapport** : `make report`
|
| 201 |
+
4. **Consulter les logs** : Vérifier les sorties des commandes make
|
| 202 |
+
|
| 203 |
+
---
|
| 204 |
+
|
| 205 |
+
**🏢 Project5 - API de gestion énergétique des bâtiments**
|
model/model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:40c2d682207023def3bfbebf149e5c33e219aa80b5a95f7daec8452ccee1f697
|
| 3 |
+
size 27298225
|
model/model.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This Python file uses the following encoding: utf-8
|
| 2 |
+
# Permet de générer le modèle à partir de la base de données
|
| 3 |
+
# Reprise et adaptation du notebook du projet3
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
|
| 8 |
+
import joblib
|
| 9 |
+
import numpy as np
|
| 10 |
+
import pandas as pd
|
| 11 |
+
from sklearn.ensemble import RandomForestRegressor
|
| 12 |
+
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
|
| 13 |
+
from sklearn.model_selection import cross_validate, train_test_split
|
| 14 |
+
from sqlalchemy import create_engine
|
| 15 |
+
|
| 16 |
+
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:password@localhost/dbname")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def get_for_model(features):
|
| 20 |
+
# Separation du jeu de données en train, test
|
| 21 |
+
X = building_consumption[features]
|
| 22 |
+
y = building_consumption["log_" + var_a_predire]
|
| 23 |
+
X_train, X_test, y_train, y_test = train_test_split(
|
| 24 |
+
X, y, test_size=0.2, random_state=42, shuffle=True
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
# X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.25, random_state=42)
|
| 28 |
+
print(f"Shape de X_train: {X_train.shape}")
|
| 29 |
+
print(f"Shape de y_train: {y_train.shape}")
|
| 30 |
+
print(f"Shape de X_test: {X_test.shape}")
|
| 31 |
+
print(f"Shape de y_train: {y_test.shape}")
|
| 32 |
+
return X_train, X_test, y_train, y_test
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def get_score(y, prediction):
|
| 36 |
+
mse = mean_squared_error(y, prediction)
|
| 37 |
+
mae = mean_absolute_error(y, prediction)
|
| 38 |
+
r2 = r2_score(y, prediction)
|
| 39 |
+
return mse, mae, r2
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
scores = {}
|
| 43 |
+
|
| 44 |
+
engine = create_engine(DATABASE_URL)
|
| 45 |
+
|
| 46 |
+
query = "SELECT * FROM model_view"
|
| 47 |
+
building_consumption = pd.read_sql(query, engine)
|
| 48 |
+
|
| 49 |
+
print(building_consumption.info())
|
| 50 |
+
engine.dispose()
|
| 51 |
+
|
| 52 |
+
var_a_predire = "site_energy_use_wn_kbtu"
|
| 53 |
+
building_consumption["log_" + var_a_predire] = np.log(
|
| 54 |
+
building_consumption[var_a_predire]
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# Features
|
| 59 |
+
features = [
|
| 60 |
+
"year_built",
|
| 61 |
+
"number_of_buildings",
|
| 62 |
+
"number_of_floors",
|
| 63 |
+
"property_gfa_total",
|
| 64 |
+
"property_gfa_parking",
|
| 65 |
+
"second_largest_property_use_type_gfa",
|
| 66 |
+
"third_largest_property_use_type_gfa",
|
| 67 |
+
"multiusage",
|
| 68 |
+
"steam",
|
| 69 |
+
"electricity",
|
| 70 |
+
"natural_gas",
|
| 71 |
+
"neighborhood_id",
|
| 72 |
+
"building_type_id",
|
| 73 |
+
"largest_property_use_type_id",
|
| 74 |
+
"primary_property_type_id",
|
| 75 |
+
"second_largest_property_use_type_id",
|
| 76 |
+
"third_largest_property_use_type_id",
|
| 77 |
+
]
|
| 78 |
+
X_train, X_test, y_train, y_test = get_for_model(features)
|
| 79 |
+
|
| 80 |
+
# Initialisation du modèle
|
| 81 |
+
rf = RandomForestRegressor(
|
| 82 |
+
n_estimators=500,
|
| 83 |
+
max_features=0.5,
|
| 84 |
+
random_state=42,
|
| 85 |
+
min_samples_split=5,
|
| 86 |
+
max_depth=20,
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
# Entraînement sur l'ensemble des données
|
| 90 |
+
X = building_consumption[features]
|
| 91 |
+
y = building_consumption["log_" + var_a_predire]
|
| 92 |
+
rf.fit(X, y)
|
| 93 |
+
|
| 94 |
+
# Prédiction
|
| 95 |
+
y_pred = rf.predict(X_test)
|
| 96 |
+
scores_cross = cross_validate(
|
| 97 |
+
rf, X_train, y_train, cv=5
|
| 98 |
+
) # cv=5 pour une validation croisée à 5 plis
|
| 99 |
+
fit_time = scores_cross["fit_time"].mean()
|
| 100 |
+
score_time = scores_cross["score_time"].mean()
|
| 101 |
+
scores.update(
|
| 102 |
+
{"RandomForestRegressor HP": get_score(y_test, y_pred) + (fit_time, score_time)}
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
resultats = pd.DataFrame(scores).T
|
| 106 |
+
resultats.columns = ["RMSE", "MAE", "R^2", "Fit Time", "Score Time"]
|
| 107 |
+
resultats = resultats.round(2)
|
| 108 |
+
print(resultats[-1:1]["RMSE"].values)
|
| 109 |
+
|
| 110 |
+
# Sauvegarde du modèle
|
| 111 |
+
# Informations du modèle
|
| 112 |
+
model_info = {
|
| 113 |
+
"model_type": type(rf).__name__,
|
| 114 |
+
"model_module": type(rf).__module__,
|
| 115 |
+
"has_feature_importances": hasattr(rf, "feature_importances_"),
|
| 116 |
+
"has_coefficients": hasattr(rf, "coef_"),
|
| 117 |
+
"has_predict_proba": hasattr(rf, "predict_proba"),
|
| 118 |
+
"version": "1.0",
|
| 119 |
+
"created_at": datetime.now().isoformat(),
|
| 120 |
+
"features": list(X_train.columns),
|
| 121 |
+
"accuracy": rf.score(X_test, y_test),
|
| 122 |
+
"RMSE": resultats[-1:1]["RMSE"].values[0],
|
| 123 |
+
"MAE": resultats[-1:1]["MAE"].values[0],
|
| 124 |
+
"R^2": resultats[-1:1]["R^2"].values[0],
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
# Sauvegarder modèle et métadonnées
|
| 128 |
+
joblib.dump(rf, "model.pkl")
|
| 129 |
+
with open("model_info.json", "w") as f:
|
| 130 |
+
json.dump(model_info, f, indent=2)
|
model/model_info.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "RandomForestRegressor",
|
| 3 |
+
"model_module": "sklearn.ensemble._forest",
|
| 4 |
+
"has_feature_importances": true,
|
| 5 |
+
"has_coefficients": false,
|
| 6 |
+
"has_predict_proba": false,
|
| 7 |
+
"version": "1.0",
|
| 8 |
+
"created_at": "2025-09-21T11:07:42.926661",
|
| 9 |
+
"features": [
|
| 10 |
+
"year_built",
|
| 11 |
+
"number_of_buildings",
|
| 12 |
+
"number_of_floors",
|
| 13 |
+
"property_gfa_total",
|
| 14 |
+
"property_gfa_parking",
|
| 15 |
+
"second_largest_property_use_type_gfa",
|
| 16 |
+
"third_largest_property_use_type_gfa",
|
| 17 |
+
"multiusage",
|
| 18 |
+
"steam",
|
| 19 |
+
"electricity",
|
| 20 |
+
"natural_gas",
|
| 21 |
+
"neighborhood_id",
|
| 22 |
+
"building_type_id",
|
| 23 |
+
"largest_property_use_type_id",
|
| 24 |
+
"primary_property_type_id",
|
| 25 |
+
"second_largest_property_use_type_id",
|
| 26 |
+
"third_largest_property_use_type_id"
|
| 27 |
+
],
|
| 28 |
+
"accuracy": 0.9160018446396789,
|
| 29 |
+
"RMSE": 0.09,
|
| 30 |
+
"MAE": 0.22,
|
| 31 |
+
"R^2": 0.92
|
| 32 |
+
}
|
model/model_report.html
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
<!DOCTYPE html>
|
| 3 |
+
<html lang="fr">
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Rapport Modèle ML - Project5</title>
|
| 8 |
+
<style>
|
| 9 |
+
body {
|
| 10 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 11 |
+
line-height: 1.6;
|
| 12 |
+
margin: 0;
|
| 13 |
+
padding: 20px;
|
| 14 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 15 |
+
color: #333;
|
| 16 |
+
min-height: 100vh;
|
| 17 |
+
}
|
| 18 |
+
.container {
|
| 19 |
+
max-width: 1200px;
|
| 20 |
+
margin: 0 auto;
|
| 21 |
+
background: white;
|
| 22 |
+
border-radius: 15px;
|
| 23 |
+
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
| 24 |
+
overflow: hidden;
|
| 25 |
+
}
|
| 26 |
+
.header {
|
| 27 |
+
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
|
| 28 |
+
color: white;
|
| 29 |
+
padding: 30px;
|
| 30 |
+
text-align: center;
|
| 31 |
+
}
|
| 32 |
+
.header h1 {
|
| 33 |
+
margin: 0;
|
| 34 |
+
font-size: 2.5em;
|
| 35 |
+
font-weight: 300;
|
| 36 |
+
}
|
| 37 |
+
.header p {
|
| 38 |
+
margin: 10px 0 0 0;
|
| 39 |
+
opacity: 0.9;
|
| 40 |
+
font-size: 1.1em;
|
| 41 |
+
}
|
| 42 |
+
.content {
|
| 43 |
+
padding: 30px;
|
| 44 |
+
}
|
| 45 |
+
.section {
|
| 46 |
+
margin-bottom: 30px;
|
| 47 |
+
background: #f8f9fa;
|
| 48 |
+
border-radius: 10px;
|
| 49 |
+
padding: 25px;
|
| 50 |
+
border-left: 5px solid #3498db;
|
| 51 |
+
}
|
| 52 |
+
.section h2 {
|
| 53 |
+
color: #2c3e50;
|
| 54 |
+
margin-top: 0;
|
| 55 |
+
margin-bottom: 20px;
|
| 56 |
+
font-size: 1.5em;
|
| 57 |
+
display: flex;
|
| 58 |
+
align-items: center;
|
| 59 |
+
}
|
| 60 |
+
.section h2::before {
|
| 61 |
+
content: "📊";
|
| 62 |
+
margin-right: 10px;
|
| 63 |
+
font-size: 1.2em;
|
| 64 |
+
}
|
| 65 |
+
.metrics-grid {
|
| 66 |
+
display: grid;
|
| 67 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 68 |
+
gap: 20px;
|
| 69 |
+
margin: 20px 0;
|
| 70 |
+
}
|
| 71 |
+
.metric-card {
|
| 72 |
+
background: white;
|
| 73 |
+
padding: 20px;
|
| 74 |
+
border-radius: 8px;
|
| 75 |
+
text-align: center;
|
| 76 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
| 77 |
+
border: 1px solid #e1e8ed;
|
| 78 |
+
}
|
| 79 |
+
.metric-value {
|
| 80 |
+
font-size: 2em;
|
| 81 |
+
font-weight: bold;
|
| 82 |
+
color: #3498db;
|
| 83 |
+
margin-bottom: 5px;
|
| 84 |
+
}
|
| 85 |
+
.metric-label {
|
| 86 |
+
color: #7f8c8d;
|
| 87 |
+
font-size: 0.9em;
|
| 88 |
+
text-transform: uppercase;
|
| 89 |
+
letter-spacing: 1px;
|
| 90 |
+
}
|
| 91 |
+
.info-grid {
|
| 92 |
+
display: grid;
|
| 93 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 94 |
+
gap: 15px;
|
| 95 |
+
}
|
| 96 |
+
.info-item {
|
| 97 |
+
background: white;
|
| 98 |
+
padding: 15px;
|
| 99 |
+
border-radius: 8px;
|
| 100 |
+
border-left: 4px solid #3498db;
|
| 101 |
+
}
|
| 102 |
+
.info-label {
|
| 103 |
+
font-weight: bold;
|
| 104 |
+
color: #2c3e50;
|
| 105 |
+
margin-bottom: 5px;
|
| 106 |
+
}
|
| 107 |
+
.info-value {
|
| 108 |
+
color: #7f8c8d;
|
| 109 |
+
}
|
| 110 |
+
.features-list {
|
| 111 |
+
display: grid;
|
| 112 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 113 |
+
gap: 10px;
|
| 114 |
+
margin-top: 15px;
|
| 115 |
+
}
|
| 116 |
+
.feature-item {
|
| 117 |
+
background: white;
|
| 118 |
+
padding: 10px 15px;
|
| 119 |
+
border-radius: 6px;
|
| 120 |
+
border-left: 3px solid #2ecc71;
|
| 121 |
+
font-size: 0.9em;
|
| 122 |
+
}
|
| 123 |
+
.importance-table {
|
| 124 |
+
background: white;
|
| 125 |
+
border-radius: 8px;
|
| 126 |
+
overflow: hidden;
|
| 127 |
+
margin-top: 20px;
|
| 128 |
+
}
|
| 129 |
+
.importance-table table {
|
| 130 |
+
width: 100%;
|
| 131 |
+
border-collapse: collapse;
|
| 132 |
+
}
|
| 133 |
+
.importance-table th {
|
| 134 |
+
background: #3498db;
|
| 135 |
+
color: white;
|
| 136 |
+
padding: 15px;
|
| 137 |
+
text-align: left;
|
| 138 |
+
}
|
| 139 |
+
.importance-table td {
|
| 140 |
+
padding: 12px 15px;
|
| 141 |
+
border-bottom: 1px solid #ecf0f1;
|
| 142 |
+
}
|
| 143 |
+
.importance-table tr:nth-child(even) {
|
| 144 |
+
background: #f8f9fa;
|
| 145 |
+
}
|
| 146 |
+
.importance-bar {
|
| 147 |
+
background: #ecf0f1;
|
| 148 |
+
height: 20px;
|
| 149 |
+
border-radius: 10px;
|
| 150 |
+
overflow: hidden;
|
| 151 |
+
position: relative;
|
| 152 |
+
}
|
| 153 |
+
.importance-fill {
|
| 154 |
+
background: linear-gradient(90deg, #3498db, #2ecc71);
|
| 155 |
+
height: 100%;
|
| 156 |
+
border-radius: 10px;
|
| 157 |
+
transition: width 0.3s ease;
|
| 158 |
+
}
|
| 159 |
+
.footer {
|
| 160 |
+
background: #2c3e50;
|
| 161 |
+
color: white;
|
| 162 |
+
text-align: center;
|
| 163 |
+
padding: 20px;
|
| 164 |
+
margin-top: 30px;
|
| 165 |
+
}
|
| 166 |
+
.status-badge {
|
| 167 |
+
display: inline-block;
|
| 168 |
+
padding: 5px 15px;
|
| 169 |
+
border-radius: 20px;
|
| 170 |
+
font-size: 0.8em;
|
| 171 |
+
font-weight: bold;
|
| 172 |
+
text-transform: uppercase;
|
| 173 |
+
}
|
| 174 |
+
.status-success {
|
| 175 |
+
background: #2ecc71;
|
| 176 |
+
color: white;
|
| 177 |
+
}
|
| 178 |
+
.status-info {
|
| 179 |
+
background: #3498db;
|
| 180 |
+
color: white;
|
| 181 |
+
}
|
| 182 |
+
@media (max-width: 768px) {
|
| 183 |
+
.container {
|
| 184 |
+
margin: 10px;
|
| 185 |
+
border-radius: 0;
|
| 186 |
+
}
|
| 187 |
+
.metrics-grid {
|
| 188 |
+
grid-template-columns: 1fr;
|
| 189 |
+
}
|
| 190 |
+
.info-grid {
|
| 191 |
+
grid-template-columns: 1fr;
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
</style>
|
| 195 |
+
</head>
|
| 196 |
+
<body>
|
| 197 |
+
<div class="container">
|
| 198 |
+
<div class="header">
|
| 199 |
+
<h1>🤖 Rapport Modèle ML</h1>
|
| 200 |
+
<p>Project5 - Prédiction énergétique des bâtiments</p>
|
| 201 |
+
<p>Généré le 21/09/2025 à 11:11:02</p>
|
| 202 |
+
</div>
|
| 203 |
+
|
| 204 |
+
<div class="content">
|
| 205 |
+
|
| 206 |
+
<div class="section">
|
| 207 |
+
<h2>Informations Générales</h2>
|
| 208 |
+
<div class="info-grid">
|
| 209 |
+
<div class="info-item">
|
| 210 |
+
<div class="info-label">Type de Modèle</div>
|
| 211 |
+
<div class="info-value">RandomForestRegressor</div>
|
| 212 |
+
</div>
|
| 213 |
+
<div class="info-item">
|
| 214 |
+
<div class="info-label">Version</div>
|
| 215 |
+
<div class="info-value">1.0</div>
|
| 216 |
+
</div>
|
| 217 |
+
<div class="info-item">
|
| 218 |
+
<div class="info-label">Date de Création</div>
|
| 219 |
+
<div class="info-value">2025-09-21T11:07:42.926661</div>
|
| 220 |
+
</div>
|
| 221 |
+
<div class="info-item">
|
| 222 |
+
<div class="info-label">Module Python</div>
|
| 223 |
+
<div class="info-value">sklearn.ensemble._forest</div>
|
| 224 |
+
</div>
|
| 225 |
+
<div class="info-item">
|
| 226 |
+
<div class="info-label">Taille du Modèle</div>
|
| 227 |
+
<div class="info-value">26.0 MB</div>
|
| 228 |
+
</div>
|
| 229 |
+
<div class="info-item">
|
| 230 |
+
<div class="info-label">Nombre de Features</div>
|
| 231 |
+
<div class="info-value">17</div>
|
| 232 |
+
</div>
|
| 233 |
+
</div>
|
| 234 |
+
</div>
|
| 235 |
+
|
| 236 |
+
<div class="section">
|
| 237 |
+
<h2>Métriques de Performance</h2>
|
| 238 |
+
<div class="metrics-grid">
|
| 239 |
+
<div class="metric-card">
|
| 240 |
+
<div class="metric-value">0.916</div>
|
| 241 |
+
<div class="metric-label">Accuracy (R²)</div>
|
| 242 |
+
</div>
|
| 243 |
+
<div class="metric-card">
|
| 244 |
+
<div class="metric-value">0.090</div>
|
| 245 |
+
<div class="metric-label">RMSE</div>
|
| 246 |
+
</div>
|
| 247 |
+
<div class="metric-card">
|
| 248 |
+
<div class="metric-value">0.220</div>
|
| 249 |
+
<div class="metric-label">MAE</div>
|
| 250 |
+
</div>
|
| 251 |
+
<div class="metric-card">
|
| 252 |
+
<div class="metric-value">0.920</div>
|
| 253 |
+
<div class="metric-label">R² Score</div>
|
| 254 |
+
</div>
|
| 255 |
+
</div>
|
| 256 |
+
</div>
|
| 257 |
+
|
| 258 |
+
<div class="section">
|
| 259 |
+
<h2>Capacités du Modèle</h2>
|
| 260 |
+
<div class="info-grid">
|
| 261 |
+
<div class="info-item">
|
| 262 |
+
<div class="info-label">Feature Importances</div>
|
| 263 |
+
<div class="info-value">
|
| 264 |
+
<span class="status-badge status-success">
|
| 265 |
+
✓ Disponible
|
| 266 |
+
</span>
|
| 267 |
+
</div>
|
| 268 |
+
</div>
|
| 269 |
+
<div class="info-item">
|
| 270 |
+
<div class="info-label">Coefficients</div>
|
| 271 |
+
<div class="info-value">
|
| 272 |
+
<span class="status-badge status-info">
|
| 273 |
+
✗ Non disponible
|
| 274 |
+
</span>
|
| 275 |
+
</div>
|
| 276 |
+
</div>
|
| 277 |
+
<div class="info-item">
|
| 278 |
+
<div class="info-label">Prédiction Probabiliste</div>
|
| 279 |
+
<div class="info-value">
|
| 280 |
+
<span class="status-badge status-info">
|
| 281 |
+
✗ Non disponible
|
| 282 |
+
</span>
|
| 283 |
+
</div>
|
| 284 |
+
</div>
|
| 285 |
+
</div>
|
| 286 |
+
</div>
|
| 287 |
+
|
| 288 |
+
<div class="section">
|
| 289 |
+
<h2>Variables d'Entrée (17 features)</h2>
|
| 290 |
+
<div class="features-list">
|
| 291 |
+
<div class="feature-item">year_built</div>
|
| 292 |
+
<div class="feature-item">number_of_buildings</div>
|
| 293 |
+
<div class="feature-item">number_of_floors</div>
|
| 294 |
+
<div class="feature-item">property_gfa_total</div>
|
| 295 |
+
<div class="feature-item">property_gfa_parking</div>
|
| 296 |
+
<div class="feature-item">second_largest_property_use_type_gfa</div>
|
| 297 |
+
<div class="feature-item">third_largest_property_use_type_gfa</div>
|
| 298 |
+
<div class="feature-item">multiusage</div>
|
| 299 |
+
<div class="feature-item">steam</div>
|
| 300 |
+
<div class="feature-item">electricity</div>
|
| 301 |
+
<div class="feature-item">natural_gas</div>
|
| 302 |
+
<div class="feature-item">neighborhood_id</div>
|
| 303 |
+
<div class="feature-item">building_type_id</div>
|
| 304 |
+
<div class="feature-item">largest_property_use_type_id</div>
|
| 305 |
+
<div class="feature-item">primary_property_type_id</div>
|
| 306 |
+
<div class="feature-item">second_largest_property_use_type_id</div>
|
| 307 |
+
<div class="feature-item">third_largest_property_use_type_id</div>
|
| 308 |
+
|
| 309 |
+
</div>
|
| 310 |
+
</div>
|
| 311 |
+
|
| 312 |
+
<div class="section">
|
| 313 |
+
<h2>Importance des Variables</h2>
|
| 314 |
+
<div class="importance-table">
|
| 315 |
+
<table>
|
| 316 |
+
<thead>
|
| 317 |
+
<tr>
|
| 318 |
+
<th>Variable</th>
|
| 319 |
+
<th>Importance</th>
|
| 320 |
+
<th>Pourcentage</th>
|
| 321 |
+
<th>Visualisation</th>
|
| 322 |
+
</tr>
|
| 323 |
+
</thead>
|
| 324 |
+
<tbody>
|
| 325 |
+
|
| 326 |
+
<tr>
|
| 327 |
+
<td><strong>property_gfa_total</strong></td>
|
| 328 |
+
<td>0.4159</td>
|
| 329 |
+
<td>41.59%</td>
|
| 330 |
+
<td>
|
| 331 |
+
<div class="importance-bar">
|
| 332 |
+
<div class="importance-fill" style="width: 100.0%"></div>
|
| 333 |
+
</div>
|
| 334 |
+
</td>
|
| 335 |
+
</tr>
|
| 336 |
+
|
| 337 |
+
<tr>
|
| 338 |
+
<td><strong>primary_property_type_id</strong></td>
|
| 339 |
+
<td>0.1205</td>
|
| 340 |
+
<td>12.05%</td>
|
| 341 |
+
<td>
|
| 342 |
+
<div class="importance-bar">
|
| 343 |
+
<div class="importance-fill" style="width: 28.96743295740477%"></div>
|
| 344 |
+
</div>
|
| 345 |
+
</td>
|
| 346 |
+
</tr>
|
| 347 |
+
|
| 348 |
+
<tr>
|
| 349 |
+
<td><strong>largest_property_use_type_id</strong></td>
|
| 350 |
+
<td>0.0907</td>
|
| 351 |
+
<td>9.07%</td>
|
| 352 |
+
<td>
|
| 353 |
+
<div class="importance-bar">
|
| 354 |
+
<div class="importance-fill" style="width: 21.803967049724744%"></div>
|
| 355 |
+
</div>
|
| 356 |
+
</td>
|
| 357 |
+
</tr>
|
| 358 |
+
|
| 359 |
+
<tr>
|
| 360 |
+
<td><strong>year_built</strong></td>
|
| 361 |
+
<td>0.0760</td>
|
| 362 |
+
<td>7.60%</td>
|
| 363 |
+
<td>
|
| 364 |
+
<div class="importance-bar">
|
| 365 |
+
<div class="importance-fill" style="width: 18.264056040746727%"></div>
|
| 366 |
+
</div>
|
| 367 |
+
</td>
|
| 368 |
+
</tr>
|
| 369 |
+
|
| 370 |
+
<tr>
|
| 371 |
+
<td><strong>second_largest_property_use_type_gfa</strong></td>
|
| 372 |
+
<td>0.0665</td>
|
| 373 |
+
<td>6.65%</td>
|
| 374 |
+
<td>
|
| 375 |
+
<div class="importance-bar">
|
| 376 |
+
<div class="importance-fill" style="width: 15.990714913050171%"></div>
|
| 377 |
+
</div>
|
| 378 |
+
</td>
|
| 379 |
+
</tr>
|
| 380 |
+
|
| 381 |
+
<tr>
|
| 382 |
+
<td><strong>number_of_floors</strong></td>
|
| 383 |
+
<td>0.0649</td>
|
| 384 |
+
<td>6.49%</td>
|
| 385 |
+
<td>
|
| 386 |
+
<div class="importance-bar">
|
| 387 |
+
<div class="importance-fill" style="width: 15.614344234190247%"></div>
|
| 388 |
+
</div>
|
| 389 |
+
</td>
|
| 390 |
+
</tr>
|
| 391 |
+
|
| 392 |
+
<tr>
|
| 393 |
+
<td><strong>natural_gas</strong></td>
|
| 394 |
+
<td>0.0444</td>
|
| 395 |
+
<td>4.44%</td>
|
| 396 |
+
<td>
|
| 397 |
+
<div class="importance-bar">
|
| 398 |
+
<div class="importance-fill" style="width: 10.669995866214208%"></div>
|
| 399 |
+
</div>
|
| 400 |
+
</td>
|
| 401 |
+
</tr>
|
| 402 |
+
|
| 403 |
+
<tr>
|
| 404 |
+
<td><strong>neighborhood_id</strong></td>
|
| 405 |
+
<td>0.0368</td>
|
| 406 |
+
<td>3.68%</td>
|
| 407 |
+
<td>
|
| 408 |
+
<div class="importance-bar">
|
| 409 |
+
<div class="importance-fill" style="width: 8.854359706938872%"></div>
|
| 410 |
+
</div>
|
| 411 |
+
</td>
|
| 412 |
+
</tr>
|
| 413 |
+
|
| 414 |
+
<tr>
|
| 415 |
+
<td><strong>property_gfa_parking</strong></td>
|
| 416 |
+
<td>0.0209</td>
|
| 417 |
+
<td>2.09%</td>
|
| 418 |
+
<td>
|
| 419 |
+
<div class="importance-bar">
|
| 420 |
+
<div class="importance-fill" style="width: 5.020163876222049%"></div>
|
| 421 |
+
</div>
|
| 422 |
+
</td>
|
| 423 |
+
</tr>
|
| 424 |
+
|
| 425 |
+
<tr>
|
| 426 |
+
<td><strong>second_largest_property_use_type_id</strong></td>
|
| 427 |
+
<td>0.0206</td>
|
| 428 |
+
<td>2.06%</td>
|
| 429 |
+
<td>
|
| 430 |
+
<div class="importance-bar">
|
| 431 |
+
<div class="importance-fill" style="width: 4.95439652964564%"></div>
|
| 432 |
+
</div>
|
| 433 |
+
</td>
|
| 434 |
+
</tr>
|
| 435 |
+
|
| 436 |
+
</tbody>
|
| 437 |
+
</table>
|
| 438 |
+
</div>
|
| 439 |
+
</div>
|
| 440 |
+
|
| 441 |
+
</div>
|
| 442 |
+
|
| 443 |
+
<div class="footer">
|
| 444 |
+
<p>🏢 Project5 - API de gestion énergétique des bâtiments</p>
|
| 445 |
+
<p>Généré automatiquement le 21/09/2025 à 11:11:02</p>
|
| 446 |
+
</div>
|
| 447 |
+
</div>
|
| 448 |
+
</body>
|
| 449 |
+
</html>
|
poetry.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
pyproject.toml
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# pyproject.toml corrigé pour Poetry
|
| 2 |
+
[tool.poetry]
|
| 3 |
+
name = "project5"
|
| 4 |
+
version = "0.1.0"
|
| 5 |
+
description = "Prédiction de consommation énergétique des bâtiments"
|
| 6 |
+
authors = ["francois hellebuyck <formation.fhellebuyck@pm.me>"]
|
| 7 |
+
packages = [{include = "project5", from = "src"}]
|
| 8 |
+
|
| 9 |
+
[tool.poetry.group.docs.dependencies]
|
| 10 |
+
# Sphinx et extensions essentielles
|
| 11 |
+
sphinx = "^7.2.6"
|
| 12 |
+
sphinx-rtd-theme = "^1.3.0"
|
| 13 |
+
sphinx-autodoc-typehints = "^1.25.2"
|
| 14 |
+
sphinx-copybutton = "^0.5.2"
|
| 15 |
+
myst-parser = "^2.0.0"
|
| 16 |
+
sphinx-autobuild = "^2021.3.14"
|
| 17 |
+
sphinxcontrib-napoleon = "^0.7"
|
| 18 |
+
|
| 19 |
+
# Extensions optionnelles mais recommandées
|
| 20 |
+
sphinx-book-theme = "^1.0.1" # Thème moderne alternatif
|
| 21 |
+
sphinxext-rediraffe = "^0.2.7" # Redirections
|
| 22 |
+
sphinx-design = "^0.5.0" # Composants modernes
|
| 23 |
+
sphinx-tabs = "^3.4.4" # Onglets
|
| 24 |
+
sphinx-togglebutton = "^0.3.2" # Boutons pliables
|
| 25 |
+
|
| 26 |
+
[tool.poetry.dependencies]
|
| 27 |
+
python = "^3.11"
|
| 28 |
+
fastapi = "^0.116.1"
|
| 29 |
+
uvicorn = {extras = ["standard"], version = "^0.35.0"}
|
| 30 |
+
# Base de données
|
| 31 |
+
sqlalchemy = "^2.0.23"
|
| 32 |
+
alembic = "^1.12.1"
|
| 33 |
+
psycopg2-binary = "^2.9.9"
|
| 34 |
+
# Configuration et sécurité
|
| 35 |
+
pydantic = "^2.5.0"
|
| 36 |
+
pydantic-settings = "^2.1.0"
|
| 37 |
+
python-dotenv = "^1.0.0"
|
| 38 |
+
passlib = {extras = ["bcrypt"], version = "^1.7.4"}
|
| 39 |
+
# Utilitaires
|
| 40 |
+
httpx = "^0.25.0"
|
| 41 |
+
python-jwt = "^4.1.0"
|
| 42 |
+
joblib = "^1.5.2"
|
| 43 |
+
numpy = "^2.3.3"
|
| 44 |
+
scikit-learn = "1.7.1"
|
| 45 |
+
pandas = "^2.3.2"
|
| 46 |
+
shibuya = "^2025.8.16"
|
| 47 |
+
eralchemy = "^1.3.0"
|
| 48 |
+
graphviz = "^0.20.0"
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
[tool.poetry.group.dev.dependencies]
|
| 52 |
+
pylint = "^3.3.8"
|
| 53 |
+
pytest = "^7.4.0"
|
| 54 |
+
pytest-cov = "^4.1.0"
|
| 55 |
+
pytest-asyncio = "^0.21.0"
|
| 56 |
+
httpx = "^0.25.0"
|
| 57 |
+
black = "^24.0.0"
|
| 58 |
+
isort = "^5.12.0"
|
| 59 |
+
flake8 = "^6.1.0"
|
| 60 |
+
mypy = "^1.6.0"
|
| 61 |
+
bandit = "^1.7.5"
|
| 62 |
+
safety = "^2.3.0"
|
| 63 |
+
# Tests de base de données
|
| 64 |
+
pytest-postgresql = "^5.0.0"
|
| 65 |
+
aiosqlite = "^0.19.0"
|
| 66 |
+
sqlacodegen = "^3.1.0"
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
[build-system]
|
| 70 |
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
| 71 |
+
build-backend = "poetry.core.masonry.api"
|
| 72 |
+
|
| 73 |
+
[tool.black]
|
| 74 |
+
line-length = 88
|
| 75 |
+
target-version = ['py311']
|
| 76 |
+
include = '\.pyi?$'
|
| 77 |
+
extend-exclude = '''
|
| 78 |
+
/(
|
| 79 |
+
# directories
|
| 80 |
+
\.eggs
|
| 81 |
+
| \.git
|
| 82 |
+
| \.hg
|
| 83 |
+
| \.mypy_cache
|
| 84 |
+
| \.tox
|
| 85 |
+
| \.venv
|
| 86 |
+
| build
|
| 87 |
+
| dist
|
| 88 |
+
| alembic/versions
|
| 89 |
+
)/
|
| 90 |
+
'''
|
| 91 |
+
|
| 92 |
+
[tool.isort]
|
| 93 |
+
profile = "black"
|
| 94 |
+
multi_line_output = 3
|
| 95 |
+
include_trailing_comma = true
|
| 96 |
+
force_grid_wrap = 0
|
| 97 |
+
use_parentheses = true
|
| 98 |
+
ensure_newline_before_comments = true
|
| 99 |
+
line_length = 88
|
| 100 |
+
|
| 101 |
+
[tool.mypy]
|
| 102 |
+
python_version = "3.11"
|
| 103 |
+
warn_return_any = true
|
| 104 |
+
warn_unused_configs = true
|
| 105 |
+
disallow_untyped_defs = true
|
| 106 |
+
plugins = ["sqlalchemy.ext.mypy.plugin"]
|
| 107 |
+
|
| 108 |
+
[tool.pytest.ini_options]
|
| 109 |
+
minversion = "6.0"
|
| 110 |
+
addopts = "-ra -q --strict-markers"
|
| 111 |
+
testpaths = [
|
| 112 |
+
"tests",
|
| 113 |
+
]
|
| 114 |
+
pythonpath = ["./src"]
|
| 115 |
+
asyncio_mode = "auto"
|
| 116 |
+
markers = [
|
| 117 |
+
"unit: Unit tests",
|
| 118 |
+
"integration: Integration tests",
|
| 119 |
+
"database: Tests requiring database",
|
| 120 |
+
"slow: Slow running tests",
|
| 121 |
+
]
|
| 122 |
+
|
| 123 |
+
[tool.coverage.run]
|
| 124 |
+
source = ["src"]
|
| 125 |
+
omit = [
|
| 126 |
+
"*/tests/*",
|
| 127 |
+
"*/venv/*",
|
| 128 |
+
"*/.venv/*",
|
| 129 |
+
"*/env/*",
|
| 130 |
+
"*/alembic/*",
|
| 131 |
+
"*/migrations/*",
|
| 132 |
+
]
|
| 133 |
+
|
| 134 |
+
[tool.coverage.report]
|
| 135 |
+
exclude_lines = [
|
| 136 |
+
"pragma: no cover",
|
| 137 |
+
"def __repr__",
|
| 138 |
+
"if self.debug:",
|
| 139 |
+
"if settings.DEBUG",
|
| 140 |
+
"raise AssertionError",
|
| 141 |
+
"raise NotImplementedError",
|
| 142 |
+
"if 0:",
|
| 143 |
+
"if __name__ == .__main__.:",
|
| 144 |
+
"class .*\\bProtocol\\):",
|
| 145 |
+
"@(abc\\.)?abstractmethod",
|
| 146 |
+
]
|
| 147 |
+
|
| 148 |
+
[tool.flake8]
|
| 149 |
+
max-line-length = 88
|
| 150 |
+
extend-ignore = [
|
| 151 |
+
"E203",
|
| 152 |
+
"W503",
|
| 153 |
+
"E402",
|
| 154 |
+
"E501",
|
| 155 |
+
]
|
| 156 |
+
exclude = [
|
| 157 |
+
".git",
|
| 158 |
+
"__pycache__",
|
| 159 |
+
".venv",
|
| 160 |
+
"venv",
|
| 161 |
+
".jupyter",
|
| 162 |
+
"alembic/versions",
|
| 163 |
+
"migrations",
|
| 164 |
+
]
|
| 165 |
+
per-file-ignores = [
|
| 166 |
+
"*.ipynb:E402",
|
| 167 |
+
"alembic/*:E402,F401",
|
| 168 |
+
]
|
src/project5/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""API REST pour gérer les bâtiments"""
|
| 2 |
+
|
| 3 |
+
__version__ = "1.0.0"
|
| 4 |
+
__author__ = "François Hellebuyck"
|
| 5 |
+
|
| 6 |
+
# Exposer certaines classes au niveau du package
|
| 7 |
+
from .database import engine, get_db
|
| 8 |
+
from .models import Neighborhood
|
| 9 |
+
|
| 10 |
+
__all__ = ["get_db", "engine", "Neighborhood"]
|
src/project5/alembic.ini
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# A generic, single database configuration.
|
| 2 |
+
|
| 3 |
+
[alembic]
|
| 4 |
+
# path to migration scripts
|
| 5 |
+
script_location = alembic
|
| 6 |
+
|
| 7 |
+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
| 8 |
+
# Uncomment the line below if you want the files to be prepended with date and time
|
| 9 |
+
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s
|
| 10 |
+
|
| 11 |
+
# sys.path path, will be prepended to sys.path if present.
|
| 12 |
+
prepend_sys_path = .
|
| 13 |
+
|
| 14 |
+
# timezone to use when rendering the date within the migration file
|
| 15 |
+
# as well as the filename.
|
| 16 |
+
# If specified, requires the python-dateutil library that can be
|
| 17 |
+
# installed by adding `alembic[tz]` to the pip requirements
|
| 18 |
+
# string value is passed to dateutil.tz.gettz()
|
| 19 |
+
# leave blank for localtime
|
| 20 |
+
# timezone =
|
| 21 |
+
|
| 22 |
+
# max length of characters to apply to the
|
| 23 |
+
# "slug" field
|
| 24 |
+
# truncate_slug_length = 40
|
| 25 |
+
|
| 26 |
+
# set to 'true' to run the environment during
|
| 27 |
+
# the 'revision' command, regardless of autogenerate
|
| 28 |
+
# revision_environment = false
|
| 29 |
+
|
| 30 |
+
# set to 'true' to allow .pyc and .pyo files without
|
| 31 |
+
# a source .py file to be detected as revisions in the
|
| 32 |
+
# versions/ directory
|
| 33 |
+
# sourceless = false
|
| 34 |
+
|
| 35 |
+
# version number format. This value should be incrementing integer,
|
| 36 |
+
# typically starting at 1.
|
| 37 |
+
# version_num_format = %d
|
| 38 |
+
|
| 39 |
+
# Logging configuration
|
| 40 |
+
[loggers]
|
| 41 |
+
keys = root,sqlalchemy,alembic
|
| 42 |
+
|
| 43 |
+
[handlers]
|
| 44 |
+
keys = console
|
| 45 |
+
|
| 46 |
+
[formatters]
|
| 47 |
+
keys = generic
|
| 48 |
+
|
| 49 |
+
[logger_root]
|
| 50 |
+
level = WARN
|
| 51 |
+
handlers = console
|
| 52 |
+
qualname =
|
| 53 |
+
|
| 54 |
+
[logger_sqlalchemy]
|
| 55 |
+
level = WARN
|
| 56 |
+
handlers =
|
| 57 |
+
qualname = sqlalchemy.engine
|
| 58 |
+
|
| 59 |
+
[logger_alembic]
|
| 60 |
+
level = INFO
|
| 61 |
+
handlers =
|
| 62 |
+
qualname = alembic
|
| 63 |
+
|
| 64 |
+
[handler_console]
|
| 65 |
+
class = StreamHandler
|
| 66 |
+
args = (sys.stderr,)
|
| 67 |
+
level = NOTSET
|
| 68 |
+
formatter = generic
|
| 69 |
+
|
| 70 |
+
[formatter_generic]
|
| 71 |
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
| 72 |
+
datefmt = %H:%M:%S
|
src/project5/config.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Configuration de l'application."""
|
| 2 |
+
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
from pydantic import Field
|
| 6 |
+
from pydantic_settings import BaseSettings
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Settings(BaseSettings):
|
| 10 |
+
"""Configuration de l'application via variables d'environnement."""
|
| 11 |
+
|
| 12 |
+
# Configuration de l'application
|
| 13 |
+
app_name: str = Field(
|
| 14 |
+
default="API de gestion énergétique des bâtiments", env="APP_NAME"
|
| 15 |
+
)
|
| 16 |
+
app_version: str = Field(default="1.0.0", env="APP_VERSION")
|
| 17 |
+
debug: bool = Field(default=False, env="DEBUG")
|
| 18 |
+
environment: str = Field(default="production", env="ENVIRONMENT")
|
| 19 |
+
|
| 20 |
+
# Configuration de la base de données
|
| 21 |
+
database_url: str = Field(..., env="DATABASE_URL")
|
| 22 |
+
db_pool_size: int = Field(default=20, env="DB_POOL_SIZE")
|
| 23 |
+
db_max_overflow: int = Field(default=0, env="DB_MAX_OVERFLOW")
|
| 24 |
+
db_pool_pre_ping: bool = Field(default=True, env="DB_POOL_PRE_PING")
|
| 25 |
+
db_echo: bool = Field(default=False, env="DB_ECHO")
|
| 26 |
+
|
| 27 |
+
# Configuration de l'API
|
| 28 |
+
api_prefix: str = Field(default="/api/v1", env="API_PREFIX")
|
| 29 |
+
cors_origins: list = Field(default=["*"], env="CORS_ORIGINS")
|
| 30 |
+
cors_allow_credentials: bool = Field(default=True, env="CORS_ALLOW_CREDENTIALS")
|
| 31 |
+
cors_allow_methods: list = Field(default=["GET"], env="CORS_ALLOW_METHODS")
|
| 32 |
+
cors_allow_headers: list = Field(default=["*"], env="CORS_ALLOW_HEADERS")
|
| 33 |
+
|
| 34 |
+
# Configuration du logging
|
| 35 |
+
log_level: str = Field(default="INFO", env="LOG_LEVEL")
|
| 36 |
+
log_format: str = Field(
|
| 37 |
+
default="%(asctime)s - %(name)s - %(levelname)s - %(message)s", env="LOG_FORMAT"
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
# Configuration de sécurité
|
| 41 |
+
allowed_hosts: list = Field(default=["*"], env="ALLOWED_HOSTS")
|
| 42 |
+
|
| 43 |
+
# Configuration de cache (pour extensions futures)
|
| 44 |
+
redis_url: Optional[str] = Field(None, env="REDIS_URL")
|
| 45 |
+
cache_ttl: int = Field(default=300, env="CACHE_TTL") # 5 minutes
|
| 46 |
+
|
| 47 |
+
# Configuration de monitoring
|
| 48 |
+
enable_metrics: bool = Field(default=False, env="ENABLE_METRICS")
|
| 49 |
+
metrics_port: int = Field(default=9090, env="METRICS_PORT")
|
| 50 |
+
|
| 51 |
+
class Config:
|
| 52 |
+
env_file = ".env"
|
| 53 |
+
case_sensitive = False
|
| 54 |
+
extra = "ignore" # Ignore extra fields from .env
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# Instance globale des paramètres
|
| 58 |
+
settings = Settings()
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def get_settings() -> Settings:
|
| 62 |
+
"""Récupère l'instance des paramètres."""
|
| 63 |
+
return settings
|
src/project5/database.py
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Configuration et gestion de la base de données pour l'application de prédiction énergétique.
|
| 3 |
+
|
| 4 |
+
Ce module configure la connectivité à la base de données avec support adaptatif pour
|
| 5 |
+
multiples environnements : PostgreSQL pour la production standard, SQLite en mémoire
|
| 6 |
+
pour le déploiement HuggingFace Spaces, et SQLite fichier pour les tests.
|
| 7 |
+
|
| 8 |
+
Architecture de déploiement :
|
| 9 |
+
- Production normale : PostgreSQL avec pool de connexions optimisé
|
| 10 |
+
- HuggingFace Spaces : SQLite en mémoire partagée pour contourner les limitations
|
| 11 |
+
- Tests/Développement : SQLite fichier local pour isolation
|
| 12 |
+
|
| 13 |
+
Fonctionnalités principales :
|
| 14 |
+
- Configuration automatique basée sur variables d'environnement
|
| 15 |
+
- Pool de connexions PostgreSQL avec reconnexion automatique
|
| 16 |
+
- SQLite en mémoire partagée thread-safe pour HuggingFace
|
| 17 |
+
- Initialisation automatique des données en production
|
| 18 |
+
- Session management avec pattern Dependency Injection
|
| 19 |
+
- Migration et création de tables automatique
|
| 20 |
+
|
| 21 |
+
Gestion des environnements :
|
| 22 |
+
Le module détecte automatiquement l'environnement via DATABASE_URL et ENVIRONMENT :
|
| 23 |
+
- ENVIRONMENT="production" + SQLite -> Mode HuggingFace Spaces
|
| 24 |
+
- DATABASE_URL PostgreSQL -> Mode production standard
|
| 25 |
+
- Autres cas -> Mode développement/test
|
| 26 |
+
|
| 27 |
+
Thread Safety :
|
| 28 |
+
- PostgreSQL : Thread-safe natif avec pool de connexions
|
| 29 |
+
- SQLite en mémoire : Connexion partagée avec check_same_thread=False
|
| 30 |
+
- Sessions SQLAlchemy : Thread-safe par design
|
| 31 |
+
|
| 32 |
+
Performance :
|
| 33 |
+
- PostgreSQL : Pool configuré pour 20 connexions simultanées
|
| 34 |
+
- SQLite : Connexion unique partagée pour minimiser l'empreinte mémoire
|
| 35 |
+
- Pool pre-ping activé pour éviter les connexions fermées
|
| 36 |
+
|
| 37 |
+
Variables d'environnement :
|
| 38 |
+
DATABASE_URL : URL de connexion PostgreSQL ou SQLite
|
| 39 |
+
ENVIRONMENT : "production" pour activer le mode HuggingFace Spaces
|
| 40 |
+
|
| 41 |
+
Exemple d'usage :
|
| 42 |
+
# Dans une route FastAPI
|
| 43 |
+
@app.get("/buildings/")
|
| 44 |
+
def get_buildings(db: Session = Depends(get_db)):
|
| 45 |
+
return db.query(Building).all()
|
| 46 |
+
|
| 47 |
+
# Initialisation manuelle
|
| 48 |
+
from project5.database import create_tables
|
| 49 |
+
create_tables()
|
| 50 |
+
|
| 51 |
+
Note :
|
| 52 |
+
Le design de ce module prend en compte les contraintes spécifiques de
|
| 53 |
+
HuggingFace Spaces qui ne permet pas l'écriture sur disque persistante.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
import os
|
| 57 |
+
import sqlite3
|
| 58 |
+
import sys
|
| 59 |
+
|
| 60 |
+
from dotenv import load_dotenv
|
| 61 |
+
from sqlalchemy import create_engine
|
| 62 |
+
from sqlalchemy.ext.declarative import declarative_base
|
| 63 |
+
from sqlalchemy.orm import sessionmaker
|
| 64 |
+
|
| 65 |
+
# Charger les variables d'environnement
|
| 66 |
+
load_dotenv()
|
| 67 |
+
|
| 68 |
+
# URL de connexion à la base de données PostgreSQL
|
| 69 |
+
# Exemple : postgresql://user:password@localhost/dbname
|
| 70 |
+
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:password@localhost/dbname")
|
| 71 |
+
ENVIRONMENT = os.getenv("ENVIRONMENT", "dev")
|
| 72 |
+
|
| 73 |
+
# Connexion SQLite partagée pour la production
|
| 74 |
+
_shared_sqlite_conn = None
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def get_shared_sqlite_connection():
|
| 78 |
+
"""
|
| 79 |
+
Retourne la connexion SQLite partagée en mémoire pour HuggingFace Spaces.
|
| 80 |
+
|
| 81 |
+
Cette fonction implémente un pattern Singleton pour partager une unique
|
| 82 |
+
connexion SQLite en mémoire à travers toute l'application. Elle est
|
| 83 |
+
spécifiquement conçue pour le déploiement sur HuggingFace Spaces qui
|
| 84 |
+
ne permet pas l'écriture sur disque persistante.
|
| 85 |
+
|
| 86 |
+
Returns:
|
| 87 |
+
sqlite3.Connection: Connexion SQLite en mémoire partagée et thread-safe
|
| 88 |
+
|
| 89 |
+
Side Effects:
|
| 90 |
+
- Crée la connexion globale lors du premier appel
|
| 91 |
+
- Initialise automatiquement les données si ENVIRONMENT="production"
|
| 92 |
+
- Modifie la variable globale _shared_sqlite_conn
|
| 93 |
+
|
| 94 |
+
Thread Safety:
|
| 95 |
+
Connexion configurée avec check_same_thread=False pour permettre
|
| 96 |
+
l'utilisation depuis multiple threads FastAPI.
|
| 97 |
+
|
| 98 |
+
Performance:
|
| 99 |
+
- Base de données entièrement en RAM pour performances maximales
|
| 100 |
+
- Pas d'I/O disque, idéal pour HuggingFace Spaces
|
| 101 |
+
- Initialisation une seule fois au démarrage
|
| 102 |
+
|
| 103 |
+
Example:
|
| 104 |
+
>>> conn = get_shared_sqlite_connection()
|
| 105 |
+
>>> cursor = conn.cursor()
|
| 106 |
+
>>> cursor.execute("SELECT COUNT(*) FROM buildings")
|
| 107 |
+
>>> print(cursor.fetchone()[0])
|
| 108 |
+
9895
|
| 109 |
+
|
| 110 |
+
Note:
|
| 111 |
+
Cette fonction ne doit être utilisée qu'en mode production HuggingFace.
|
| 112 |
+
Pour les autres environnements, utiliser les sessions SQLAlchemy normales.
|
| 113 |
+
|
| 114 |
+
Raises:
|
| 115 |
+
Exception: Si l'initialisation des données de production échoue
|
| 116 |
+
"""
|
| 117 |
+
global _shared_sqlite_conn
|
| 118 |
+
if _shared_sqlite_conn is None:
|
| 119 |
+
_shared_sqlite_conn = sqlite3.connect(":memory:", check_same_thread=False)
|
| 120 |
+
# Initialiser les données si en production
|
| 121 |
+
if ENVIRONMENT == "production":
|
| 122 |
+
_initialize_production_data()
|
| 123 |
+
return _shared_sqlite_conn
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def _initialize_production_data():
|
| 127 |
+
"""
|
| 128 |
+
Initialise les données de production dans la base SQLite en mémoire.
|
| 129 |
+
|
| 130 |
+
Cette fonction privée charge automatiquement le dataset complet de
|
| 131 |
+
9895 bâtiments de Seattle dans la base de données en mémoire lors
|
| 132 |
+
du démarrage en mode production HuggingFace Spaces.
|
| 133 |
+
|
| 134 |
+
Architecture d'initialisation :
|
| 135 |
+
1. Modification dynamique du chemin Python pour accéder à init_db
|
| 136 |
+
2. Monkey-patching de get_db_connection pour utiliser la connexion partagée
|
| 137 |
+
3. Création des tables via create_sqlite_tables()
|
| 138 |
+
4. Insertion des données via init_sqlite_data()
|
| 139 |
+
|
| 140 |
+
Side Effects:
|
| 141 |
+
- Modifie sys.path temporairement
|
| 142 |
+
- Remplace la fonction get_db_connection dans le module init_db
|
| 143 |
+
- Crée toutes les tables nécessaires
|
| 144 |
+
- Insère 9895 enregistrements de bâtiments
|
| 145 |
+
- Affiche des messages de statut
|
| 146 |
+
|
| 147 |
+
Performance :
|
| 148 |
+
- Opération d'initialisation unique au démarrage
|
| 149 |
+
- Toutes les données chargées en RAM pour accès rapide
|
| 150 |
+
- Optimisé pour le déploiement HuggingFace Spaces
|
| 151 |
+
|
| 152 |
+
Error Handling :
|
| 153 |
+
- Capture toutes les exceptions et les re-lève après logging
|
| 154 |
+
- Empêche le démarrage de l'application en cas d'échec
|
| 155 |
+
- Messages d'erreur détaillés pour debugging
|
| 156 |
+
|
| 157 |
+
Example Output:
|
| 158 |
+
✅ Base de données SQLite en mémoire initialisée pour la production
|
| 159 |
+
|
| 160 |
+
Raises:
|
| 161 |
+
Exception: Toute erreur durant l'initialisation est propagée
|
| 162 |
+
après avoir été loggée
|
| 163 |
+
|
| 164 |
+
Note :
|
| 165 |
+
Cette fonction ne doit être appelée que depuis get_shared_sqlite_connection()
|
| 166 |
+
et uniquement en mode production.
|
| 167 |
+
"""
|
| 168 |
+
try:
|
| 169 |
+
# Import des fonctions d'initialisation
|
| 170 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
| 171 |
+
# Remplacer temporairement la fonction get_db_connection
|
| 172 |
+
import init_db
|
| 173 |
+
from init_db import create_sqlite_tables, init_sqlite_data
|
| 174 |
+
|
| 175 |
+
init_db.get_db_connection = get_shared_sqlite_connection
|
| 176 |
+
|
| 177 |
+
# Créer les tables et insérer les données
|
| 178 |
+
create_sqlite_tables()
|
| 179 |
+
init_sqlite_data()
|
| 180 |
+
|
| 181 |
+
print("✅ Base de données SQLite en mémoire initialisée pour la production")
|
| 182 |
+
|
| 183 |
+
except Exception as e:
|
| 184 |
+
print(f"❌ Erreur lors de l'initialisation des données de production: {e}")
|
| 185 |
+
raise
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
# Créer le moteur de base de données avec configuration adaptative
|
| 189 |
+
if DATABASE_URL.startswith("sqlite"):
|
| 190 |
+
if ENVIRONMENT == "production":
|
| 191 |
+
print(
|
| 192 |
+
"🐳 Utilisation de SQLite en mémoire partagée pour la production HuggingFace"
|
| 193 |
+
)
|
| 194 |
+
# Créer un moteur qui utilise la connexion partagée
|
| 195 |
+
engine = create_engine(
|
| 196 |
+
"sqlite:///:memory:",
|
| 197 |
+
echo=False,
|
| 198 |
+
connect_args={"check_same_thread": False},
|
| 199 |
+
creator=get_shared_sqlite_connection,
|
| 200 |
+
)
|
| 201 |
+
else:
|
| 202 |
+
print("⚠️ Utilisation de SQLite pour les tests uniquement.")
|
| 203 |
+
# Configuration pour SQLite (tests)
|
| 204 |
+
engine = create_engine(
|
| 205 |
+
DATABASE_URL, echo=False, connect_args={"check_same_thread": False}
|
| 206 |
+
)
|
| 207 |
+
else:
|
| 208 |
+
# Configuration pour PostgreSQL (production normale)
|
| 209 |
+
engine = create_engine(
|
| 210 |
+
DATABASE_URL,
|
| 211 |
+
pool_size=20,
|
| 212 |
+
max_overflow=0,
|
| 213 |
+
pool_pre_ping=True,
|
| 214 |
+
echo=False, # Mettre à True pour voir les requêtes SQL
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
# Créer la session
|
| 218 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 219 |
+
|
| 220 |
+
# Base pour nos modèles
|
| 221 |
+
Base = declarative_base()
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def get_db():
|
| 225 |
+
"""
|
| 226 |
+
Générateur de sessions de base de données pour injection de dépendance FastAPI.
|
| 227 |
+
|
| 228 |
+
Cette fonction implémente le pattern Dependency Injection de FastAPI pour
|
| 229 |
+
fournir des sessions de base de données aux routes. Elle garantit la fermeture
|
| 230 |
+
automatique des sessions même en cas d'exception.
|
| 231 |
+
|
| 232 |
+
Yields:
|
| 233 |
+
sqlalchemy.orm.Session: Session de base de données configurée et prête
|
| 234 |
+
|
| 235 |
+
Architecture :
|
| 236 |
+
- Pattern Generator pour gestion automatique du cycle de vie
|
| 237 |
+
- Utilise SessionLocal configuré selon l'environnement
|
| 238 |
+
- Fermeture garantie via bloc finally
|
| 239 |
+
- Compatible avec toutes les configurations (PostgreSQL/SQLite)
|
| 240 |
+
|
| 241 |
+
Usage dans FastAPI :
|
| 242 |
+
@app.get("/buildings/{building_id}")
|
| 243 |
+
def get_building(building_id: int, db: Session = Depends(get_db)):
|
| 244 |
+
return db.query(Building).filter(Building.id == building_id).first()
|
| 245 |
+
|
| 246 |
+
Thread Safety :
|
| 247 |
+
Chaque appel crée une nouvelle session isolée, garantissant
|
| 248 |
+
la thread-safety dans un environnement multi-threadé.
|
| 249 |
+
|
| 250 |
+
Performance :
|
| 251 |
+
- Sessions créées à la demande (lazy loading)
|
| 252 |
+
- Réutilisation du pool de connexions configuré
|
| 253 |
+
- Pas de connexions persistantes inutiles
|
| 254 |
+
|
| 255 |
+
Error Handling :
|
| 256 |
+
Les exceptions dans les routes n'empêchent pas la fermeture
|
| 257 |
+
de la session grâce au bloc finally.
|
| 258 |
+
|
| 259 |
+
Example:
|
| 260 |
+
# Utilisation directe (non recommandé)
|
| 261 |
+
for db in get_db():
|
| 262 |
+
buildings = db.query(Building).all()
|
| 263 |
+
break
|
| 264 |
+
|
| 265 |
+
# Utilisation recommandée avec FastAPI
|
| 266 |
+
@app.get("/predict")
|
| 267 |
+
def predict(data: PredictionRequest, db: Session = Depends(get_db)):
|
| 268 |
+
# db est automatiquement fournie et fermée
|
| 269 |
+
return prediction_service.predict(data, db)
|
| 270 |
+
|
| 271 |
+
Note :
|
| 272 |
+
Cette fonction est conçue pour être utilisée exclusivement comme
|
| 273 |
+
dépendance FastAPI. Pour un usage direct, considérer SessionLocal().
|
| 274 |
+
"""
|
| 275 |
+
db = SessionLocal()
|
| 276 |
+
try:
|
| 277 |
+
yield db
|
| 278 |
+
finally:
|
| 279 |
+
db.close()
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def create_tables():
|
| 283 |
+
"""
|
| 284 |
+
Crée toutes les tables dans la base de données selon les modèles définis.
|
| 285 |
+
|
| 286 |
+
Cette fonction utilise SQLAlchemy pour créer automatiquement toutes les
|
| 287 |
+
tables nécessaires à l'application basées sur les métadonnées des modèles.
|
| 288 |
+
Elle est idempotente et peut être appelée multiple fois sans effet de bord.
|
| 289 |
+
|
| 290 |
+
Architecture :
|
| 291 |
+
- Utilise Base.metadata.create_all() pour création déclarative
|
| 292 |
+
- Compatible avec toutes les configurations de base de données
|
| 293 |
+
- Respecte les contraintes et index définis dans les modèles
|
| 294 |
+
- Opération idempotente (pas d'erreur si tables existent)
|
| 295 |
+
|
| 296 |
+
Tables créées :
|
| 297 |
+
- buildings : Table principale des bâtiments avec 17 attributs
|
| 298 |
+
- Autres tables selon les modèles définis dans project5.models
|
| 299 |
+
|
| 300 |
+
Side Effects :
|
| 301 |
+
- Crée les tables physiques dans la base de données
|
| 302 |
+
- Affiche un message de confirmation
|
| 303 |
+
- Met à jour le schéma de base de données
|
| 304 |
+
|
| 305 |
+
Usage :
|
| 306 |
+
# Lors du déploiement initial
|
| 307 |
+
from project5.database import create_tables
|
| 308 |
+
create_tables()
|
| 309 |
+
|
| 310 |
+
# Dans un script d'initialisation
|
| 311 |
+
if __name__ == "__main__":
|
| 312 |
+
create_tables()
|
| 313 |
+
print("Base de données initialisée")
|
| 314 |
+
|
| 315 |
+
Compatibilité :
|
| 316 |
+
- PostgreSQL : Utilise les types natifs et contraintes
|
| 317 |
+
- SQLite : Adaptation automatique des types
|
| 318 |
+
- Tous environnements : dev, test, production
|
| 319 |
+
|
| 320 |
+
Performance :
|
| 321 |
+
- Opération rapide sur bases vides
|
| 322 |
+
- Sur bases existantes : vérification de schéma uniquement
|
| 323 |
+
- Pas de perte de données sur tables existantes
|
| 324 |
+
|
| 325 |
+
Example Output :
|
| 326 |
+
Tables created successfully!
|
| 327 |
+
|
| 328 |
+
Error Handling :
|
| 329 |
+
Les erreurs SQLAlchemy sont propagées vers l'appelant
|
| 330 |
+
pour gestion appropriée selon le contexte.
|
| 331 |
+
|
| 332 |
+
Note :
|
| 333 |
+
Cette fonction doit être appelée après l'importation des modèles
|
| 334 |
+
pour garantir que toutes les métadonnées sont disponibles.
|
| 335 |
+
"""
|
| 336 |
+
from project5.models.base import Base
|
| 337 |
+
|
| 338 |
+
Base.metadata.create_all(bind=engine)
|
| 339 |
+
print("Tables created successfully!")
|
src/project5/main.py
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Point d'entrée principal de l'API de prédiction énergétique des bâtiments de Seattle.
|
| 3 |
+
|
| 4 |
+
Ce module configure et lance l'application FastAPI complète pour la prédiction
|
| 5 |
+
de consommation énergétique des bâtiments. Il inclut la gestion du cycle de vie,
|
| 6 |
+
la configuration des middlewares, la gestion d'erreurs centralisée, et l'intégration
|
| 7 |
+
du modèle de machine learning.
|
| 8 |
+
|
| 9 |
+
Architecture de l'application:
|
| 10 |
+
- FastAPI avec OpenAPI/Swagger automatique
|
| 11 |
+
- Middleware CORS pour les appels cross-origin
|
| 12 |
+
- Gestion centralisée des exceptions avec mapping HTTP
|
| 13 |
+
- Logging structuré des requêtes et réponses
|
| 14 |
+
- Chargement automatique du modèle ML au démarrage
|
| 15 |
+
- Configuration adaptative selon l'environnement
|
| 16 |
+
|
| 17 |
+
Environnements supportés:
|
| 18 |
+
- Développement: Base PostgreSQL locale, logging verbose
|
| 19 |
+
- Production: Base SQLite en mémoire (HuggingFace Spaces)
|
| 20 |
+
- Test: Base SQLite locale pour les tests
|
| 21 |
+
|
| 22 |
+
Fonctionnalités principales:
|
| 23 |
+
- API REST complète pour la gestion des bâtiments et prédictions
|
| 24 |
+
- Modèle ML RandomForestRegressor pour prédictions énergétiques
|
| 25 |
+
- Authentification bearer token en production
|
| 26 |
+
- Documentation interactive Swagger/ReDoc
|
| 27 |
+
- Monitoring avec health checks
|
| 28 |
+
- Gestion d'erreurs robuste avec logging
|
| 29 |
+
|
| 30 |
+
Sécurité:
|
| 31 |
+
- Authentication bearer token en environnement production
|
| 32 |
+
- Validation des données avec Pydantic
|
| 33 |
+
- Gestion sécurisée des erreurs (pas d'exposition de détails techniques)
|
| 34 |
+
- Configuration CORS appropriée
|
| 35 |
+
|
| 36 |
+
Performance:
|
| 37 |
+
- Pool de connexions PostgreSQL optimisé
|
| 38 |
+
- Cache du modèle ML en mémoire
|
| 39 |
+
- Logging asynchrone pour minimiser l'impact
|
| 40 |
+
- Middleware de timing des requêtes
|
| 41 |
+
|
| 42 |
+
Routes principales:
|
| 43 |
+
- /: Informations sur le service
|
| 44 |
+
- /health: Health check pour monitoring
|
| 45 |
+
- /docs: Documentation Swagger interactive
|
| 46 |
+
- /redoc: Documentation ReDoc alternative
|
| 47 |
+
- /neighborhoods/*: Gestion des quartiers de Seattle
|
| 48 |
+
- /building-models/*: Gestion des modèles de bâtiments
|
| 49 |
+
- /predictions/*: Prédictions énergétiques ML
|
| 50 |
+
|
| 51 |
+
Note:
|
| 52 |
+
Configuration adaptative pour déploiement sur HuggingFace Spaces
|
| 53 |
+
avec base SQLite en mémoire et données pré-initialisées.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
import logging
|
| 57 |
+
import os
|
| 58 |
+
import time
|
| 59 |
+
from contextlib import asynccontextmanager
|
| 60 |
+
|
| 61 |
+
from fastapi import FastAPI, Request
|
| 62 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 63 |
+
from fastapi.openapi.utils import get_openapi
|
| 64 |
+
from fastapi.responses import JSONResponse
|
| 65 |
+
from sqlalchemy.exc import SQLAlchemyError
|
| 66 |
+
|
| 67 |
+
from project5.config import get_settings
|
| 68 |
+
from project5.database import Base, engine
|
| 69 |
+
from project5.schemas import ErrorResponse, ServiceInfo
|
| 70 |
+
from project5.utils.exceptions import NotFoundError, ServiceError, ValidationError
|
| 71 |
+
from project5.utils.ml_model import MLModel
|
| 72 |
+
from project5.utils.model_registry import get_ml_model, set_ml_model
|
| 73 |
+
|
| 74 |
+
# Configuration du logging
|
| 75 |
+
settings = get_settings()
|
| 76 |
+
logging.basicConfig(
|
| 77 |
+
level=getattr(logging, settings.log_level.upper()), format=settings.log_format
|
| 78 |
+
)
|
| 79 |
+
logger = logging.getLogger(__name__)
|
| 80 |
+
|
| 81 |
+
ML_MODEL_PATH = os.getenv("ML_MODEL_PATH", "../model/model.pkl")
|
| 82 |
+
|
| 83 |
+
ENVIRONMENT = os.getenv("ENVIRONMENT", "dev")
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
@asynccontextmanager
|
| 87 |
+
async def lifespan(app: FastAPI):
|
| 88 |
+
"""
|
| 89 |
+
Gestionnaire du cycle de vie de l'application FastAPI.
|
| 90 |
+
|
| 91 |
+
Cette fonction gère les phases de démarrage et d'arrêt de l'application,
|
| 92 |
+
incluant l'initialisation de la base de données et le chargement du modèle ML.
|
| 93 |
+
|
| 94 |
+
Phases de démarrage:
|
| 95 |
+
1. Création des tables de base de données
|
| 96 |
+
2. Chargement du modèle ML depuis le fichier
|
| 97 |
+
3. Enregistrement du modèle dans le registre global
|
| 98 |
+
4. Validation que l'application est prête
|
| 99 |
+
|
| 100 |
+
Phases d'arrêt:
|
| 101 |
+
1. Nettoyage des ressources
|
| 102 |
+
2. Logging de l'arrêt
|
| 103 |
+
|
| 104 |
+
Args:
|
| 105 |
+
app (FastAPI): Instance de l'application FastAPI
|
| 106 |
+
|
| 107 |
+
Yields:
|
| 108 |
+
None: Contrôle rendu à l'application pendant son exécution
|
| 109 |
+
|
| 110 |
+
Raises:
|
| 111 |
+
RuntimeError: Si le modèle ML ne peut pas être chargé
|
| 112 |
+
Exception: Toute erreur durant l'initialisation
|
| 113 |
+
|
| 114 |
+
Environment Variables:
|
| 115 |
+
ML_MODEL_PATH: Chemin vers le fichier du modèle (défaut: "../model/model.pkl")
|
| 116 |
+
|
| 117 |
+
Note:
|
| 118 |
+
En cas d'échec de démarrage, l'application s'arrête immédiatement
|
| 119 |
+
pour éviter un état incohérent (fail-fast pattern).
|
| 120 |
+
"""
|
| 121 |
+
# Startup
|
| 122 |
+
logger.info("🚀 Démarrage de l'application project5 API")
|
| 123 |
+
|
| 124 |
+
try:
|
| 125 |
+
# Créer les tables si nécessaire
|
| 126 |
+
Base.metadata.create_all(bind=engine)
|
| 127 |
+
|
| 128 |
+
"""Gestionnaire du cycle de vie de l'application"""
|
| 129 |
+
# Startup
|
| 130 |
+
model = MLModel()
|
| 131 |
+
if not model.load_model(ML_MODEL_PATH):
|
| 132 |
+
raise RuntimeError("❌ Impossible de charger le modèle")
|
| 133 |
+
# Instance globale du modèle
|
| 134 |
+
set_ml_model(model)
|
| 135 |
+
logger.info(f"✅ Setting ML Model {get_ml_model()}")
|
| 136 |
+
logger.info("🎯 Application prête à recevoir des requêtes")
|
| 137 |
+
|
| 138 |
+
except Exception as e:
|
| 139 |
+
logger.error(f"❌ Erreur lors du démarrage: {e}")
|
| 140 |
+
raise
|
| 141 |
+
|
| 142 |
+
yield
|
| 143 |
+
|
| 144 |
+
# Shutdown
|
| 145 |
+
logger.info("🛑 Arrêt de l'application")
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
# Création de l'application FastAPI
|
| 149 |
+
app = FastAPI(
|
| 150 |
+
title=settings.app_name,
|
| 151 |
+
description="API REST pour la gestion énergétique des bâtiments",
|
| 152 |
+
version=settings.app_version,
|
| 153 |
+
lifespan=lifespan,
|
| 154 |
+
docs_url="/docs",
|
| 155 |
+
redoc_url="/redoc",
|
| 156 |
+
openapi_url="/openapi.json",
|
| 157 |
+
contact={
|
| 158 |
+
"name": "François Hellebuyck",
|
| 159 |
+
"email": "formation@pm.me",
|
| 160 |
+
},
|
| 161 |
+
license_info={
|
| 162 |
+
"name": "",
|
| 163 |
+
},
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def custom_openapi():
|
| 168 |
+
"""
|
| 169 |
+
Génère un schéma OpenAPI personnalisé avec configuration de sécurité.
|
| 170 |
+
|
| 171 |
+
Cette fonction étend le schéma OpenAPI par défaut pour ajouter :
|
| 172 |
+
- Configuration des serveurs (développement et production)
|
| 173 |
+
- Schéma d'authentification Bearer Token
|
| 174 |
+
- Application automatique de la sécurité à tous les endpoints
|
| 175 |
+
|
| 176 |
+
Returns:
|
| 177 |
+
dict: Schéma OpenAPI complet avec extensions de sécurité
|
| 178 |
+
|
| 179 |
+
Configuration des serveurs:
|
| 180 |
+
- Serveur local: http://localhost:8000 (développement)
|
| 181 |
+
- Serveur production: https://FrancoisFormation-project5.hf.space
|
| 182 |
+
|
| 183 |
+
Authentification:
|
| 184 |
+
- Type: HTTP Bearer Token
|
| 185 |
+
- Appliquée automatiquement à tous les endpoints
|
| 186 |
+
- Affichée dans l'interface Swagger pour les tests
|
| 187 |
+
|
| 188 |
+
Caching:
|
| 189 |
+
Le schéma est généré une seule fois et mis en cache
|
| 190 |
+
pour optimiser les performances.
|
| 191 |
+
|
| 192 |
+
Note:
|
| 193 |
+
Cette configuration permet l'authentification en production
|
| 194 |
+
tout en gardant une interface de test claire.
|
| 195 |
+
"""
|
| 196 |
+
if app.openapi_schema:
|
| 197 |
+
return app.openapi_schema
|
| 198 |
+
|
| 199 |
+
openapi_schema = get_openapi(
|
| 200 |
+
title=app.title,
|
| 201 |
+
version=app.version,
|
| 202 |
+
description=app.description,
|
| 203 |
+
routes=app.routes,
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
openapi_schema["servers"] = [
|
| 207 |
+
{"url": "http://localhost:8000", "description": "Serveur de développement"},
|
| 208 |
+
{
|
| 209 |
+
"url": "https://FrancoisFormation-project5.hf.space",
|
| 210 |
+
"description": "Serveur de production",
|
| 211 |
+
},
|
| 212 |
+
]
|
| 213 |
+
|
| 214 |
+
openapi_schema["components"]["securitySchemes"] = {
|
| 215 |
+
"BearerAuth": {"type": "http", "scheme": "bearer"}
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
for path, path_item in openapi_schema["paths"].items():
|
| 219 |
+
for method, operation in path_item.items():
|
| 220 |
+
if isinstance(operation, dict):
|
| 221 |
+
operation["security"] = [{"BearerAuth": []}]
|
| 222 |
+
|
| 223 |
+
app.openapi_schema = openapi_schema
|
| 224 |
+
return app.openapi_schema
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
app.openapi = custom_openapi
|
| 228 |
+
|
| 229 |
+
# Configuration CORS
|
| 230 |
+
app.add_middleware(
|
| 231 |
+
CORSMiddleware,
|
| 232 |
+
allow_origins=settings.cors_origins,
|
| 233 |
+
allow_credentials=settings.cors_allow_credentials,
|
| 234 |
+
allow_methods=settings.cors_allow_methods,
|
| 235 |
+
allow_headers=settings.cors_allow_headers,
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
# Inclusion des routers
|
| 239 |
+
from project5.routers import ( # Importer les routers ici
|
| 240 |
+
building_energy_prediction_routes,
|
| 241 |
+
building_model_routes,
|
| 242 |
+
building_types_routes,
|
| 243 |
+
categories_routes,
|
| 244 |
+
neighborhoods_routes,
|
| 245 |
+
prediction_routes,
|
| 246 |
+
properties_routes,
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
app.include_router(neighborhoods_routes.router)
|
| 250 |
+
app.include_router(building_types_routes.router)
|
| 251 |
+
app.include_router(categories_routes.router)
|
| 252 |
+
app.include_router(properties_routes.router)
|
| 253 |
+
app.include_router(building_energy_prediction_routes.router)
|
| 254 |
+
app.include_router(building_model_routes.router)
|
| 255 |
+
app.include_router(prediction_routes.router)
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
# Gestionnaires d'exceptions personnalisés
|
| 259 |
+
@app.exception_handler(NotFoundError)
|
| 260 |
+
async def neighborhood_not_found_handler(request: Request, exc: NotFoundError):
|
| 261 |
+
"""
|
| 262 |
+
Gestionnaire d'exception pour les ressources non trouvées (404).
|
| 263 |
+
|
| 264 |
+
Transforme les NotFoundError en réponses HTTP 404 structurées
|
| 265 |
+
avec logging approprié et format de réponse standardisé.
|
| 266 |
+
|
| 267 |
+
Args:
|
| 268 |
+
request (Request): Requête FastAPI avec métadonnées (URL, méthode, etc.)
|
| 269 |
+
exc (NotFoundError): Exception personnalisée avec message et code d'erreur
|
| 270 |
+
|
| 271 |
+
Returns:
|
| 272 |
+
JSONResponse: Réponse HTTP 404 avec :
|
| 273 |
+
- detail: Message d'erreur descriptif
|
| 274 |
+
- error_code: Code d'erreur standardisé
|
| 275 |
+
- path: Chemin de l'endpoint qui a échoué
|
| 276 |
+
|
| 277 |
+
Logging:
|
| 278 |
+
Log niveau WARNING avec message et chemin pour traçabilité
|
| 279 |
+
|
| 280 |
+
Example response:
|
| 281 |
+
{
|
| 282 |
+
"detail": "Bâtiment avec l'ID 999 non trouvé",
|
| 283 |
+
"error_code": "NOT_FOUND",
|
| 284 |
+
"path": "/building-models/999"
|
| 285 |
+
}
|
| 286 |
+
"""
|
| 287 |
+
logger.warning(f"Entité non trouvé: {exc.message} - Path: {request.url.path}")
|
| 288 |
+
return JSONResponse(
|
| 289 |
+
status_code=404,
|
| 290 |
+
content=ErrorResponse(
|
| 291 |
+
detail=exc.message, error_code=exc.error_code, path=str(request.url.path)
|
| 292 |
+
).dict(),
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
@app.exception_handler(ValidationError)
|
| 297 |
+
async def validation_error_handler(request: Request, exc: ValidationError):
|
| 298 |
+
"""
|
| 299 |
+
Gestionnaire d'exception pour les erreurs de validation métier (422).
|
| 300 |
+
|
| 301 |
+
Transforme les ValidationError en réponses HTTP 422 pour indiquer
|
| 302 |
+
que les données sont syntaxiquement correctes mais violent des
|
| 303 |
+
règles métier.
|
| 304 |
+
|
| 305 |
+
Args:
|
| 306 |
+
request (Request): Requête FastAPI avec contexte
|
| 307 |
+
exc (ValidationError): Exception de validation avec détails
|
| 308 |
+
|
| 309 |
+
Returns:
|
| 310 |
+
JSONResponse: Réponse HTTP 422 avec format standardisé
|
| 311 |
+
|
| 312 |
+
Usage:
|
| 313 |
+
Utilisé pour les validations métier qui ne peuvent pas être
|
| 314 |
+
exprimées dans les schémas Pydantic (règles complexes,
|
| 315 |
+
validations cross-champs, contraintes temporelles).
|
| 316 |
+
|
| 317 |
+
Distinction HTTP:
|
| 318 |
+
- 400: Erreur de format/syntaxe
|
| 319 |
+
- 422: Erreur de validation métier
|
| 320 |
+
- 404: Ressource non trouvée
|
| 321 |
+
"""
|
| 322 |
+
logger.warning(f"Erreur de validation: {exc.message} - Path: {request.url.path}")
|
| 323 |
+
return JSONResponse(
|
| 324 |
+
status_code=422,
|
| 325 |
+
content=ErrorResponse(
|
| 326 |
+
detail=exc.message, error_code=exc.error_code, path=str(request.url.path)
|
| 327 |
+
).dict(),
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
@app.exception_handler(ServiceError)
|
| 332 |
+
async def service_error_handler(request: Request, exc: ServiceError):
|
| 333 |
+
"""
|
| 334 |
+
Gestionnaire d'exception pour les erreurs internes de service (500).
|
| 335 |
+
|
| 336 |
+
Transforme les ServiceError en réponses HTTP 500 avec masquage
|
| 337 |
+
des détails techniques pour la sécurité.
|
| 338 |
+
|
| 339 |
+
Args:
|
| 340 |
+
request (Request): Requête FastAPI avec contexte
|
| 341 |
+
exc (ServiceError): Exception de service avec détails internes
|
| 342 |
+
|
| 343 |
+
Returns:
|
| 344 |
+
JSONResponse: Réponse HTTP 500 avec message générique
|
| 345 |
+
|
| 346 |
+
Sécurité:
|
| 347 |
+
Le message détaillé est loggé mais pas exposé au client
|
| 348 |
+
pour éviter la fuite d'informations techniques.
|
| 349 |
+
|
| 350 |
+
Logging:
|
| 351 |
+
Log niveau ERROR avec stack trace complet pour debugging.
|
| 352 |
+
|
| 353 |
+
Monitoring:
|
| 354 |
+
Ces erreurs devraient déclencher des alertes de monitoring
|
| 355 |
+
car elles indiquent des problèmes techniques internes.
|
| 356 |
+
"""
|
| 357 |
+
logger.error(f"Erreur de service: {exc.message} - Path: {request.url.path}")
|
| 358 |
+
return JSONResponse(
|
| 359 |
+
status_code=500,
|
| 360 |
+
content=ErrorResponse(
|
| 361 |
+
detail="Erreur interne du service",
|
| 362 |
+
error_code=exc.error_code,
|
| 363 |
+
path=str(request.url.path),
|
| 364 |
+
).dict(),
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
@app.exception_handler(SQLAlchemyError)
|
| 369 |
+
async def sqlalchemy_error_handler(request: Request, exc: SQLAlchemyError):
|
| 370 |
+
"""
|
| 371 |
+
Gestionnaire d'exception pour les erreurs de base de données (503).
|
| 372 |
+
|
| 373 |
+
Transforme les erreurs SQLAlchemy en réponses HTTP 503 pour indiquer
|
| 374 |
+
une indisponibilité temporaire du service de persistance.
|
| 375 |
+
|
| 376 |
+
Args:
|
| 377 |
+
request (Request): Requête FastAPI avec contexte
|
| 378 |
+
exc (SQLAlchemyError): Exception SQLAlchemy (connexion, transaction, etc.)
|
| 379 |
+
|
| 380 |
+
Returns:
|
| 381 |
+
JSONResponse: Réponse HTTP 503 (Service Unavailable)
|
| 382 |
+
|
| 383 |
+
Rationale HTTP 503:
|
| 384 |
+
Les erreurs de base de données sont souvent temporaires
|
| 385 |
+
(surcharge, maintenance, réseau) et peuvent être résolues
|
| 386 |
+
en retentant la requête plus tard.
|
| 387 |
+
|
| 388 |
+
Sécurité:
|
| 389 |
+
Détails techniques de la base masqués pour éviter l'exposition
|
| 390 |
+
d'informations sur la structure de données.
|
| 391 |
+
|
| 392 |
+
Monitoring:
|
| 393 |
+
Ces erreurs nécessitent une attention immédiate car elles
|
| 394 |
+
peuvent indiquer des problèmes d'infrastructure critiques.
|
| 395 |
+
"""
|
| 396 |
+
logger.error(f"Erreur base de données: {str(exc)} - Path: {request.url.path}")
|
| 397 |
+
return JSONResponse(
|
| 398 |
+
status_code=503,
|
| 399 |
+
content=ErrorResponse(
|
| 400 |
+
detail="Service temporairement indisponible",
|
| 401 |
+
error_code="DATABASE_ERROR",
|
| 402 |
+
path=str(request.url.path),
|
| 403 |
+
).dict(),
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
@app.exception_handler(Exception)
|
| 408 |
+
async def general_exception_handler(request: Request, exc: Exception):
|
| 409 |
+
"""
|
| 410 |
+
Gestionnaire d'exception catch-all pour toutes les erreurs non capturées.
|
| 411 |
+
|
| 412 |
+
Ce gestionnaire sert de filet de sécurité pour capturer toutes les
|
| 413 |
+
exceptions non prévues et éviter les crashes de l'application.
|
| 414 |
+
|
| 415 |
+
Args:
|
| 416 |
+
request (Request): Requête FastAPI avec contexte complet
|
| 417 |
+
exc (Exception): Exception Python non capturée précédemment
|
| 418 |
+
|
| 419 |
+
Returns:
|
| 420 |
+
JSONResponse: Réponse HTTP 500 générique sécurisée
|
| 421 |
+
|
| 422 |
+
Logging:
|
| 423 |
+
Log niveau ERROR avec exc_info=True pour capturer la stack trace
|
| 424 |
+
complète, essentielle pour le debugging des erreurs inattendues.
|
| 425 |
+
|
| 426 |
+
Sécurité:
|
| 427 |
+
Aucun détail technique exposé au client pour éviter la fuite
|
| 428 |
+
d'informations potentiellement sensibles.
|
| 429 |
+
|
| 430 |
+
Monitoring:
|
| 431 |
+
Ces erreurs indiquent des bugs ou des cas limites non prévus
|
| 432 |
+
et nécessitent une investigation immédiate.
|
| 433 |
+
|
| 434 |
+
Note:
|
| 435 |
+
Ce gestionnaire est appelé en dernier recours après tous
|
| 436 |
+
les gestionnaires spécifiques.
|
| 437 |
+
"""
|
| 438 |
+
logger.error(
|
| 439 |
+
f"Erreur non gérée: {str(exc)} - Path: {request.url.path}", exc_info=True
|
| 440 |
+
)
|
| 441 |
+
return JSONResponse(
|
| 442 |
+
status_code=500,
|
| 443 |
+
content=ErrorResponse(
|
| 444 |
+
detail="Erreur interne du serveur",
|
| 445 |
+
error_code="INTERNAL_ERROR",
|
| 446 |
+
path=str(request.url.path),
|
| 447 |
+
).dict(),
|
| 448 |
+
)
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
# Middleware de logging des requêtes
|
| 452 |
+
@app.middleware("http")
|
| 453 |
+
async def log_requests(request: Request, call_next):
|
| 454 |
+
"""
|
| 455 |
+
Middleware pour le logging structuré de toutes les requêtes HTTP.
|
| 456 |
+
|
| 457 |
+
Ce middleware capture et log toutes les requêtes entrantes et sortantes
|
| 458 |
+
avec timing et métriques pour le monitoring et le debugging.
|
| 459 |
+
|
| 460 |
+
Args:
|
| 461 |
+
request (Request): Requête HTTP entrante avec métadonnées
|
| 462 |
+
call_next: Fonction pour passer au middleware/route suivant
|
| 463 |
+
|
| 464 |
+
Returns:
|
| 465 |
+
Response: Réponse HTTP avec headers et timing ajoutés
|
| 466 |
+
|
| 467 |
+
Métriques capturées:
|
| 468 |
+
- Méthode HTTP (GET, POST, etc.)
|
| 469 |
+
- Chemin de l'endpoint
|
| 470 |
+
- Adresse IP du client (si disponible)
|
| 471 |
+
- Code de statut de réponse
|
| 472 |
+
- Temps de traitement en secondes
|
| 473 |
+
|
| 474 |
+
Format des logs:
|
| 475 |
+
- Entrée: ➡️ GET /neighborhoods - 192.168.1.100
|
| 476 |
+
- Sortie: ⬅️ GET /neighborhoods - Status: 200 - Time: 0.045s
|
| 477 |
+
|
| 478 |
+
Performance:
|
| 479 |
+
Impact minimal grâce au logging asynchrone et à la
|
| 480 |
+
mesure de temps optimisée.
|
| 481 |
+
|
| 482 |
+
Monitoring:
|
| 483 |
+
Ces logs sont essentiels pour l'analyse des performances,
|
| 484 |
+
le debugging des problèmes, et la détection d'anomalies.
|
| 485 |
+
"""
|
| 486 |
+
start_time = time.time()
|
| 487 |
+
|
| 488 |
+
# Log de la requête entrante
|
| 489 |
+
logger.info(
|
| 490 |
+
f"➡️ {request.method} {request.url.path} - {request.client.host if request.client else 'unknown'}"
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
response = await call_next(request)
|
| 494 |
+
|
| 495 |
+
# Log de la réponse
|
| 496 |
+
process_time = time.time() - start_time
|
| 497 |
+
logger.info(
|
| 498 |
+
f"⬅️ {request.method} {request.url.path} - "
|
| 499 |
+
f"Status: {response.status_code} - "
|
| 500 |
+
f"Time: {process_time:.3f}s"
|
| 501 |
+
)
|
| 502 |
+
|
| 503 |
+
return response
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
# Routes principales
|
| 507 |
+
@app.get(
|
| 508 |
+
"/",
|
| 509 |
+
response_model=ServiceInfo,
|
| 510 |
+
tags=["Info"],
|
| 511 |
+
summary="Informations du service",
|
| 512 |
+
description="Informations générales sur l'API",
|
| 513 |
+
)
|
| 514 |
+
async def root():
|
| 515 |
+
"""
|
| 516 |
+
Endpoint racine fournissant les informations du service.
|
| 517 |
+
|
| 518 |
+
Retourne les métadonnées essentielles sur l'API pour la découverte
|
| 519 |
+
automatique des services et la documentation dynamique.
|
| 520 |
+
|
| 521 |
+
Returns:
|
| 522 |
+
ServiceInfo: Informations structurées du service contenant :
|
| 523 |
+
- name: Nom de l'application depuis la configuration
|
| 524 |
+
- version: Version actuelle de l'API
|
| 525 |
+
- description: Description fonctionnelle du service
|
| 526 |
+
- endpoints: Points d'entrée principaux de documentation
|
| 527 |
+
|
| 528 |
+
Usage:
|
| 529 |
+
- Point d'entrée pour la découverte du service
|
| 530 |
+
- Validation que l'API est opérationnelle
|
| 531 |
+
- Navigation vers la documentation interactive
|
| 532 |
+
|
| 533 |
+
Example response:
|
| 534 |
+
{
|
| 535 |
+
"name": "Project5 API",
|
| 536 |
+
"version": "1.0.0",
|
| 537 |
+
"description": "API REST pour la gestion énergétique des bâtiments",
|
| 538 |
+
"endpoints": {
|
| 539 |
+
"docs": "/docs",
|
| 540 |
+
"redoc": "/redoc"
|
| 541 |
+
}
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
Note:
|
| 545 |
+
Endpoint public accessible sans authentification pour faciliter
|
| 546 |
+
la découverte et les vérifications de santé automatiques.
|
| 547 |
+
"""
|
| 548 |
+
service_info = ServiceInfo(
|
| 549 |
+
name=settings.app_name,
|
| 550 |
+
version=settings.app_version,
|
| 551 |
+
description="API REST pour la gestion énergétique des bâtiments",
|
| 552 |
+
endpoints={
|
| 553 |
+
"docs": "/docs",
|
| 554 |
+
"redoc": "/redoc",
|
| 555 |
+
},
|
| 556 |
+
)
|
| 557 |
+
|
| 558 |
+
return service_info
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
@app.get(
|
| 562 |
+
"/health",
|
| 563 |
+
tags=["Info"],
|
| 564 |
+
summary="Vérification de l'état de santé",
|
| 565 |
+
description="Endpoint de santé pour les vérifications de disponibilité",
|
| 566 |
+
)
|
| 567 |
+
async def health_check():
|
| 568 |
+
"""
|
| 569 |
+
Endpoint de health check pour le monitoring et l'orchestration.
|
| 570 |
+
|
| 571 |
+
Fournit un point de contrôle rapide pour vérifier que l'API
|
| 572 |
+
est opérationnelle et prête à traiter les requêtes.
|
| 573 |
+
|
| 574 |
+
Returns:
|
| 575 |
+
dict: Statut de santé avec métadonnées d'environnement :
|
| 576 |
+
- status: "healthy" si le service fonctionne
|
| 577 |
+
- environment: Environnement actuel (dev/production)
|
| 578 |
+
- authentication_required: Booléen selon l'environnement
|
| 579 |
+
|
| 580 |
+
Usage:
|
| 581 |
+
- Monitoring automatique par les systèmes d'orchestration
|
| 582 |
+
- Health checks Kubernetes/Docker
|
| 583 |
+
- Load balancer health probes
|
| 584 |
+
- Surveillance continue des services
|
| 585 |
+
|
| 586 |
+
Performance:
|
| 587 |
+
Endpoint ultra-rapide sans opération coûteuse (pas de DB/ML)
|
| 588 |
+
pour des vérifications fréquentes sans impact.
|
| 589 |
+
|
| 590 |
+
Example response:
|
| 591 |
+
{
|
| 592 |
+
"status": "healthy",
|
| 593 |
+
"environment": "production",
|
| 594 |
+
"authentication_required": true
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
Note:
|
| 598 |
+
Endpoint public accessible sans authentification pour permettre
|
| 599 |
+
les vérifications de santé par l'infrastructure.
|
| 600 |
+
"""
|
| 601 |
+
return {
|
| 602 |
+
"status": "healthy",
|
| 603 |
+
"environment": ENVIRONMENT,
|
| 604 |
+
"authentication_required": ENVIRONMENT == "production",
|
| 605 |
+
}
|
src/project5/models/README.md
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
poetry add --group dev sqlacodegen
|
| 2 |
+
poetry run sqlacodegen postgresql://project5_user:project5_password@localhost:5432/project5_db --outfile models.py
|
src/project5/models/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Package des modèles SQLAlchemy pour l'API de prédiction énergétique.
|
| 3 |
+
|
| 4 |
+
Ce package contient tous les modèles de données utilisés par l'application
|
| 5 |
+
pour la gestion des bâtiments et des prédictions énergétiques.
|
| 6 |
+
|
| 7 |
+
Modèles disponibles :
|
| 8 |
+
- Base : Classe de base DeclarativeBase pour tous les modèles
|
| 9 |
+
- Neighborhood : Quartiers de Seattle pour la géolocalisation
|
| 10 |
+
- BuildingType : Types de bâtiments (résidentiel, commercial, etc.)
|
| 11 |
+
- Categorie : Catégories d'usage (bureaux, logement, éducation, etc.)
|
| 12 |
+
- Property : Propriétés d'usage spécifiques (office, restaurant, etc.)
|
| 13 |
+
- BuildingModels : Modèle principal des bâtiments avec toutes caractéristiques
|
| 14 |
+
- BuildingEnergyPredictions : Prédictions énergétiques calculées par le ML
|
| 15 |
+
|
| 16 |
+
Architecture relationnelle :
|
| 17 |
+
- Les bâtiments (BuildingModels) sont liés aux quartiers, types, et propriétés
|
| 18 |
+
- Les propriétés appartiennent à des catégories
|
| 19 |
+
- Les prédictions (BuildingEnergyPredictions) sont liées aux bâtiments
|
| 20 |
+
- Tous les modèles incluent des model_id pour l'encodage ML
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
# Import du modèle Neighborhood depuis le fichier approprié
|
| 24 |
+
from .base import Base
|
| 25 |
+
from .building_energy_prediction import BuildingEnergyPredictions
|
| 26 |
+
from .building_models import BuildingModels
|
| 27 |
+
from .building_type import BuildingType
|
| 28 |
+
from .categorie import Categorie
|
| 29 |
+
from .neighborhood import Neighborhood
|
| 30 |
+
from .property import Property
|
| 31 |
+
|
| 32 |
+
# Exposer les modèles au niveau du package
|
| 33 |
+
__all__ = [
|
| 34 |
+
"Base",
|
| 35 |
+
"Neighborhood",
|
| 36 |
+
"BuildingType",
|
| 37 |
+
"Categorie",
|
| 38 |
+
"Property",
|
| 39 |
+
"BuildingModels",
|
| 40 |
+
"BuildingEnergyPredictions",
|
| 41 |
+
]
|
src/project5/models/base.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Base SQLAlchemy pour tous les modèles de données.
|
| 3 |
+
|
| 4 |
+
Ce module définit la classe de base DeclarativeBase utilisée par tous
|
| 5 |
+
les modèles SQLAlchemy de l'application de prédiction énergétique.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from sqlalchemy.orm import DeclarativeBase, registry
|
| 9 |
+
|
| 10 |
+
mapper_registry = registry()
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class Base(DeclarativeBase):
|
| 14 |
+
"""
|
| 15 |
+
Classe de base DeclarativeBase pour tous les modèles SQLAlchemy.
|
| 16 |
+
|
| 17 |
+
Cette classe sert de base à tous les modèles de données de l'application.
|
| 18 |
+
Elle utilise le registre SQLAlchemy pour gérer les mappings entre
|
| 19 |
+
les classes Python et les tables de base de données.
|
| 20 |
+
|
| 21 |
+
Note:
|
| 22 |
+
Tous les modèles (Neighborhood, BuildingType, Property, etc.)
|
| 23 |
+
héritent de cette classe pour bénéficier de la fonctionnalité ORM.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
registry = mapper_registry
|
src/project5/models/building_energy_prediction.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Modèle SQLAlchemy pour les prédictions énergétiques des bâtiments.
|
| 3 |
+
|
| 4 |
+
Ce module définit le modèle de données pour stocker les prédictions de consommation
|
| 5 |
+
énergétique calculées par le modèle de machine learning.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import datetime
|
| 9 |
+
import decimal
|
| 10 |
+
from typing import Optional
|
| 11 |
+
|
| 12 |
+
from sqlalchemy import (
|
| 13 |
+
Boolean,
|
| 14 |
+
DateTime,
|
| 15 |
+
ForeignKeyConstraint,
|
| 16 |
+
Index,
|
| 17 |
+
Integer,
|
| 18 |
+
Numeric,
|
| 19 |
+
PrimaryKeyConstraint,
|
| 20 |
+
UniqueConstraint,
|
| 21 |
+
text,
|
| 22 |
+
)
|
| 23 |
+
from sqlalchemy.orm import Mapped, mapped_column # TODO: ,relationship
|
| 24 |
+
|
| 25 |
+
from .base import Base
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class BuildingEnergyPredictions(Base):
|
| 29 |
+
"""
|
| 30 |
+
Modèle représentant une prédiction de consommation énergétique.
|
| 31 |
+
|
| 32 |
+
Cette table stocke les résultats des prédictions énergétiques calculées
|
| 33 |
+
par le modèle de machine learning pour chaque bâtiment. Elle permet de
|
| 34 |
+
conserver l'historique des prédictions et de suivre leur évolution.
|
| 35 |
+
|
| 36 |
+
Attributes:
|
| 37 |
+
id (int): Identifiant unique de la prédiction (clé primaire)
|
| 38 |
+
building_id (int): Identifiant du bâtiment concerné (clé étrangère)
|
| 39 |
+
site_energy_use_wn_kbtu (Decimal, optional): Consommation énergétique prédite en kBTU
|
| 40 |
+
avec normalisation météorologique
|
| 41 |
+
predicted (bool, optional): Indique si la valeur est prédite (True) ou mesurée (False)
|
| 42 |
+
updated_at (datetime, optional): Date et heure de la prédiction
|
| 43 |
+
|
| 44 |
+
Note:
|
| 45 |
+
La consommation est exprimée en kBTU (thousand British Thermal Units)
|
| 46 |
+
avec normalisation météorologique pour compenser les variations climatiques.
|
| 47 |
+
Le champ 'predicted' permet de distinguer les prédictions ML des mesures réelles.
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
__tablename__ = "building_energy_predictions"
|
| 51 |
+
__table_args__ = (
|
| 52 |
+
ForeignKeyConstraint(
|
| 53 |
+
["building_id"],
|
| 54 |
+
["building_models.id"],
|
| 55 |
+
name="fk_building_energy_predictions",
|
| 56 |
+
),
|
| 57 |
+
PrimaryKeyConstraint("id", name="building_energy_predictions_pkey"),
|
| 58 |
+
UniqueConstraint(
|
| 59 |
+
"building_id", name="building_energy_predictions_building_id_key"
|
| 60 |
+
),
|
| 61 |
+
Index("idx_building_energy_predictions_building_id", "building_id"),
|
| 62 |
+
{"comment": "Table des prédictions de consommation énergétique des bâtiments"},
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
id: Mapped[int] = mapped_column(
|
| 66 |
+
Integer,
|
| 67 |
+
primary_key=True,
|
| 68 |
+
comment="Identifiant unique de la prédiction (clé primaire auto-incrémentée)",
|
| 69 |
+
)
|
| 70 |
+
building_id: Mapped[int] = mapped_column(
|
| 71 |
+
Integer,
|
| 72 |
+
nullable=False,
|
| 73 |
+
comment="Identifiant du bâtiment concerné (clé étrangère vers buildings.id)",
|
| 74 |
+
)
|
| 75 |
+
site_energy_use_wn_kbtu: Mapped[Optional[decimal.Decimal]] = mapped_column(
|
| 76 |
+
Numeric(15, 2),
|
| 77 |
+
comment="Consommation énergétique du site en kBTU avec normalisation météorologique (15 chiffres, 2 décimales)",
|
| 78 |
+
)
|
| 79 |
+
predicted: Mapped[Optional[bool]] = mapped_column(
|
| 80 |
+
Boolean,
|
| 81 |
+
server_default=text("true"),
|
| 82 |
+
comment="Indique si la valeur est prédite (TRUE) ou mesurée (FALSE)",
|
| 83 |
+
)
|
| 84 |
+
updated_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 85 |
+
DateTime(True),
|
| 86 |
+
server_default=text("CURRENT_TIMESTAMP"),
|
| 87 |
+
comment="Date et heure de la prédiction avec fuseau horaire",
|
| 88 |
+
)
|
src/project5/models/building_models.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Modèle SQLAlchemy principal pour les bâtiments.
|
| 3 |
+
|
| 4 |
+
Ce module définit le modèle de données central pour les bâtiments avec toutes
|
| 5 |
+
leurs caractéristiques utilisées pour les prédictions énergétiques.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import datetime
|
| 9 |
+
import decimal
|
| 10 |
+
from typing import TYPE_CHECKING, Optional
|
| 11 |
+
|
| 12 |
+
from sqlalchemy import (
|
| 13 |
+
CheckConstraint,
|
| 14 |
+
DateTime,
|
| 15 |
+
ForeignKeyConstraint,
|
| 16 |
+
Index,
|
| 17 |
+
Integer,
|
| 18 |
+
Numeric,
|
| 19 |
+
PrimaryKeyConstraint,
|
| 20 |
+
String,
|
| 21 |
+
Text,
|
| 22 |
+
UniqueConstraint,
|
| 23 |
+
text,
|
| 24 |
+
)
|
| 25 |
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
| 26 |
+
|
| 27 |
+
from .base import Base
|
| 28 |
+
|
| 29 |
+
# from project5.models import BuildingType, Neighborhood, Property
|
| 30 |
+
# Import seulement pour le type checking
|
| 31 |
+
if TYPE_CHECKING:
|
| 32 |
+
from .building_type import BuildingType
|
| 33 |
+
from .neighborhood import Neighborhood
|
| 34 |
+
from .property import Property
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class BuildingModels(Base):
|
| 38 |
+
"""
|
| 39 |
+
Modèle principal représentant un bâtiment avec toutes ses caractéristiques.
|
| 40 |
+
|
| 41 |
+
Cette table centrale contient toutes les informations d'un bâtiment nécessaires
|
| 42 |
+
pour les prédictions énergétiques : localisation, caractéristiques physiques,
|
| 43 |
+
types d'énergie utilisés, et références vers les tables de classification.
|
| 44 |
+
|
| 45 |
+
Attributes:
|
| 46 |
+
id (int): Identifiant unique du bâtiment (clé primaire)
|
| 47 |
+
ose_building_id (int): Identifiant OSE (Office of Sustainability & Environment)
|
| 48 |
+
address (str): Adresse complète du bâtiment
|
| 49 |
+
city (str, optional): Ville (généralement Seattle)
|
| 50 |
+
state (str, optional): État (généralement WA)
|
| 51 |
+
zip_code (str, optional): Code postal
|
| 52 |
+
tax_parcel_identification_number (str, optional): Numéro de parcelle fiscale
|
| 53 |
+
council_district_code (str, optional): Code du district municipal
|
| 54 |
+
latitude (Decimal, optional): Latitude GPS (-90 à 90)
|
| 55 |
+
longitude (Decimal, optional): Longitude GPS (-180 à 180)
|
| 56 |
+
year_built (int, optional): Année de construction
|
| 57 |
+
number_of_buildings (int, optional): Nombre de bâtiments sur la propriété
|
| 58 |
+
number_of_floors (int, optional): Nombre d'étages
|
| 59 |
+
property_gfa_total (Decimal, optional): Surface brute totale en pieds carrés
|
| 60 |
+
property_gfa_parking (Decimal, optional): Surface de parking en pieds carrés
|
| 61 |
+
second_largest_property_use_type_gfa (Decimal, optional): Surface du 2e usage
|
| 62 |
+
third_largest_property_use_type_gfa (Decimal, optional): Surface du 3e usage
|
| 63 |
+
multiusage (bool, optional): Indique si le bâtiment a plusieurs usages
|
| 64 |
+
steam (bool, optional): Utilise la vapeur comme source d'énergie
|
| 65 |
+
electricity (bool, optional): Utilise l'électricité
|
| 66 |
+
natural_gas (bool, optional): Utilise le gaz naturel
|
| 67 |
+
neighborhood_id (int, optional): Identifiant du quartier (clé étrangère)
|
| 68 |
+
building_type_id (int, optional): Identifiant du type de bâtiment (clé étrangère)
|
| 69 |
+
largest_property_use_type_id (int, optional): Usage principal (clé étrangère)
|
| 70 |
+
primary_property_type_id (int, optional): Type principal (clé étrangère)
|
| 71 |
+
second_largest_property_use_type_id (int, optional): Usage secondaire (clé étrangère)
|
| 72 |
+
third_largest_property_use_type_id (int, optional): Usage tertiaire (clé étrangère)
|
| 73 |
+
created_at (datetime): Date de création
|
| 74 |
+
updated_at (datetime): Date de modification
|
| 75 |
+
|
| 76 |
+
Relations:
|
| 77 |
+
neighborhood: Quartier associé
|
| 78 |
+
building_type: Type de bâtiment associé
|
| 79 |
+
largest_property_use_type: Usage principal
|
| 80 |
+
primary_property_type: Type principal
|
| 81 |
+
second_largest_property_use_type: Usage secondaire
|
| 82 |
+
third_largest_property_use_type: Usage tertiaire
|
| 83 |
+
|
| 84 |
+
Note:
|
| 85 |
+
Ce modèle est au centre du système de prédiction énergétique.
|
| 86 |
+
Toutes les caractéristiques sont utilisées par le modèle ML pour
|
| 87 |
+
calculer la consommation énergétique prédite.
|
| 88 |
+
"""
|
| 89 |
+
|
| 90 |
+
__tablename__ = "building_models"
|
| 91 |
+
__table_args__ = (
|
| 92 |
+
CheckConstraint(
|
| 93 |
+
"latitude IS NULL AND longitude IS NULL OR latitude >= '-90'::integer::numeric AND latitude <= 90::numeric AND longitude >= '-180'::integer::numeric AND longitude <= 180::numeric",
|
| 94 |
+
name="valid_coordinates",
|
| 95 |
+
),
|
| 96 |
+
ForeignKeyConstraint(
|
| 97 |
+
["building_type_id"], ["building_type.id"], name="fk_building_type"
|
| 98 |
+
),
|
| 99 |
+
ForeignKeyConstraint(
|
| 100 |
+
["largest_property_use_type_id"],
|
| 101 |
+
["property.id"],
|
| 102 |
+
name="fk_largest_property_use_type_id",
|
| 103 |
+
),
|
| 104 |
+
ForeignKeyConstraint(
|
| 105 |
+
["neighborhood_id"], ["neighborhood.id"], name="fk_neighborhood"
|
| 106 |
+
),
|
| 107 |
+
ForeignKeyConstraint(
|
| 108 |
+
["primary_property_type_id"],
|
| 109 |
+
["property.id"],
|
| 110 |
+
name="fk_primary_property_type",
|
| 111 |
+
),
|
| 112 |
+
ForeignKeyConstraint(
|
| 113 |
+
["second_largest_property_use_type_id"],
|
| 114 |
+
["property.id"],
|
| 115 |
+
name="fk_second_property_type",
|
| 116 |
+
),
|
| 117 |
+
ForeignKeyConstraint(
|
| 118 |
+
["third_largest_property_use_type_id"],
|
| 119 |
+
["property.id"],
|
| 120 |
+
name="fk_third_property_type",
|
| 121 |
+
),
|
| 122 |
+
PrimaryKeyConstraint("id", name="building_models_pkey"),
|
| 123 |
+
UniqueConstraint("ose_building_id", name="building_models_ose_building_id_key"),
|
| 124 |
+
Index("idx_building_models_building_type_id", "building_type_id"),
|
| 125 |
+
Index("idx_building_models_neighborhood_id", "neighborhood_id"),
|
| 126 |
+
Index(
|
| 127 |
+
"idx_building_models_primary_property_type_id", "primary_property_type_id"
|
| 128 |
+
),
|
| 129 |
+
Index(
|
| 130 |
+
"idx_building_models_second_property_use_type_id",
|
| 131 |
+
"second_largest_property_use_type_id",
|
| 132 |
+
),
|
| 133 |
+
Index(
|
| 134 |
+
"idx_building_models_third_property_use_type_id",
|
| 135 |
+
"third_largest_property_use_type_id",
|
| 136 |
+
),
|
| 137 |
+
Index("idx_largest_property_use_type_id", "largest_property_use_type_id"),
|
| 138 |
+
{
|
| 139 |
+
"comment": "Table contenant les données des modèles de bâtiments avec "
|
| 140 |
+
"informations énergétiques et structurelles"
|
| 141 |
+
},
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
id: Mapped[int] = mapped_column(
|
| 145 |
+
Integer,
|
| 146 |
+
primary_key=True,
|
| 147 |
+
comment="Identifiant du bâtiment concerné (clé étrangère vers buildings.id)",
|
| 148 |
+
)
|
| 149 |
+
ose_building_id: Mapped[str] = mapped_column(
|
| 150 |
+
String(50),
|
| 151 |
+
nullable=False,
|
| 152 |
+
comment="Identifiant unique du bâtiment dans le système OSE",
|
| 153 |
+
)
|
| 154 |
+
address: Mapped[Optional[str]] = mapped_column(Text, comment="Adresse du bâtiment")
|
| 155 |
+
city: Mapped[Optional[str]] = mapped_column(
|
| 156 |
+
String(100),
|
| 157 |
+
server_default=text("'Seattle'::character varying"),
|
| 158 |
+
comment="Ville",
|
| 159 |
+
)
|
| 160 |
+
state: Mapped[Optional[str]] = mapped_column(
|
| 161 |
+
String(10), server_default=text("'WA'::character varying"), comment="État"
|
| 162 |
+
)
|
| 163 |
+
zip_code: Mapped[Optional[str]] = mapped_column(String(20), comment="Code postal")
|
| 164 |
+
tax_parcel_identification_number: Mapped[Optional[str]] = mapped_column(
|
| 165 |
+
String(100), comment="Numéro d'identification fiscale de la parcelle"
|
| 166 |
+
)
|
| 167 |
+
council_district_code: Mapped[Optional[str]] = mapped_column(String(20))
|
| 168 |
+
latitude: Mapped[Optional[decimal.Decimal]] = mapped_column(
|
| 169 |
+
Numeric(10, 8), comment="Latitude géographique"
|
| 170 |
+
)
|
| 171 |
+
longitude: Mapped[Optional[decimal.Decimal]] = mapped_column(
|
| 172 |
+
Numeric(11, 8), comment="Longitude géographique"
|
| 173 |
+
)
|
| 174 |
+
year_built: Mapped[Optional[int]] = mapped_column(
|
| 175 |
+
Integer, comment="Année de construction du bâtiment"
|
| 176 |
+
)
|
| 177 |
+
number_of_buildings: Mapped[Optional[int]] = mapped_column(
|
| 178 |
+
Integer, comment="Nombre de bâtiments"
|
| 179 |
+
)
|
| 180 |
+
number_of_floors: Mapped[Optional[int]] = mapped_column(
|
| 181 |
+
Integer, comment="Nombre d'étages"
|
| 182 |
+
)
|
| 183 |
+
property_gfa_total: Mapped[Optional[decimal.Decimal]] = mapped_column(
|
| 184 |
+
Numeric(12, 2), comment="Surface brute totale (GFA) en pieds carrés"
|
| 185 |
+
)
|
| 186 |
+
property_gfa_parking: Mapped[Optional[decimal.Decimal]] = mapped_column(
|
| 187 |
+
Numeric(12, 2), comment="Surface brute de parking en pieds carrés"
|
| 188 |
+
)
|
| 189 |
+
second_largest_property_use_type_gfa: Mapped[Optional[decimal.Decimal]] = (
|
| 190 |
+
mapped_column(
|
| 191 |
+
Numeric(12, 2), comment="Surface GFA du deuxième type d'usage principal"
|
| 192 |
+
)
|
| 193 |
+
)
|
| 194 |
+
third_largest_property_use_type_gfa: Mapped[Optional[decimal.Decimal]] = (
|
| 195 |
+
mapped_column(
|
| 196 |
+
Numeric(12, 2), comment="Surface GFA du troisième type d'usage principal"
|
| 197 |
+
)
|
| 198 |
+
)
|
| 199 |
+
multiusage: Mapped[Optional[int]] = mapped_column(
|
| 200 |
+
Integer, comment="Indicateur si le bâtiment a plusieurs usages"
|
| 201 |
+
)
|
| 202 |
+
steam: Mapped[Optional[int]] = mapped_column(
|
| 203 |
+
Integer, comment="Indicateur d'utilisation de vapeur"
|
| 204 |
+
)
|
| 205 |
+
electricity: Mapped[Optional[int]] = mapped_column(
|
| 206 |
+
Integer, comment="Indicateur d'utilisation d'électricité"
|
| 207 |
+
)
|
| 208 |
+
natural_gas: Mapped[Optional[int]] = mapped_column(
|
| 209 |
+
Integer, comment="Indicateur d'utilisation de gaz naturel"
|
| 210 |
+
)
|
| 211 |
+
neighborhood_id: Mapped[Optional[int]] = mapped_column(
|
| 212 |
+
Integer, comment="Identifiant du quartier"
|
| 213 |
+
)
|
| 214 |
+
building_type_id: Mapped[Optional[int]] = mapped_column(
|
| 215 |
+
Integer, comment="Identifiant du type de bâtiment"
|
| 216 |
+
)
|
| 217 |
+
largest_property_use_type_id: Mapped[Optional[int]] = mapped_column(
|
| 218 |
+
Integer, comment="ID du type d'usage principal le plus important"
|
| 219 |
+
)
|
| 220 |
+
primary_property_type_id: Mapped[Optional[int]] = mapped_column(
|
| 221 |
+
Integer, comment="ID du type de propriété principal"
|
| 222 |
+
)
|
| 223 |
+
second_largest_property_use_type_id: Mapped[Optional[int]] = mapped_column(
|
| 224 |
+
Integer, comment="ID du deuxième type d'usage principal"
|
| 225 |
+
)
|
| 226 |
+
third_largest_property_use_type_id: Mapped[Optional[int]] = mapped_column(
|
| 227 |
+
Integer, comment="ID du troisième type d'usage principal"
|
| 228 |
+
)
|
| 229 |
+
created_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 230 |
+
DateTime, server_default=text("CURRENT_TIMESTAMP")
|
| 231 |
+
)
|
| 232 |
+
updated_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 233 |
+
DateTime, server_default=text("CURRENT_TIMESTAMP")
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
building_type: Mapped[Optional["BuildingType"]] = relationship(
|
| 237 |
+
"BuildingType", back_populates="building_models"
|
| 238 |
+
)
|
| 239 |
+
largest_property_use_type: Mapped[Optional["Property"]] = relationship(
|
| 240 |
+
"Property",
|
| 241 |
+
foreign_keys=[largest_property_use_type_id],
|
| 242 |
+
back_populates="building_models",
|
| 243 |
+
)
|
| 244 |
+
neighborhood: Mapped[Optional["Neighborhood"]] = relationship(
|
| 245 |
+
"Neighborhood", back_populates="building_models"
|
| 246 |
+
)
|
| 247 |
+
primary_property_type: Mapped[Optional["Property"]] = relationship(
|
| 248 |
+
"Property",
|
| 249 |
+
foreign_keys=[primary_property_type_id],
|
| 250 |
+
back_populates="building_models_",
|
| 251 |
+
)
|
| 252 |
+
second_largest_property_use_type: Mapped[Optional["Property"]] = relationship(
|
| 253 |
+
"Property",
|
| 254 |
+
foreign_keys=[second_largest_property_use_type_id],
|
| 255 |
+
back_populates="building_models1",
|
| 256 |
+
)
|
| 257 |
+
third_largest_property_use_type: Mapped[Optional["Property"]] = relationship(
|
| 258 |
+
"Property",
|
| 259 |
+
foreign_keys=[third_largest_property_use_type_id],
|
| 260 |
+
back_populates="building_models2",
|
| 261 |
+
)
|
src/project5/models/building_type.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Modèle SQLAlchemy pour les types de bâtiments.
|
| 3 |
+
|
| 4 |
+
Ce module définit le modèle de données pour les types de bâtiments utilisés
|
| 5 |
+
dans la classification des constructions pour les prédictions énergétiques.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import datetime
|
| 9 |
+
from typing import TYPE_CHECKING, Optional
|
| 10 |
+
|
| 11 |
+
from sqlalchemy import (
|
| 12 |
+
DateTime,
|
| 13 |
+
Integer,
|
| 14 |
+
PrimaryKeyConstraint,
|
| 15 |
+
String,
|
| 16 |
+
Text,
|
| 17 |
+
UniqueConstraint,
|
| 18 |
+
text,
|
| 19 |
+
)
|
| 20 |
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
| 21 |
+
|
| 22 |
+
if TYPE_CHECKING:
|
| 23 |
+
from .building_models import BuildingModels
|
| 24 |
+
|
| 25 |
+
from .base import Base
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class BuildingType(Base):
|
| 29 |
+
"""
|
| 30 |
+
Modèle représentant un type de bâtiment.
|
| 31 |
+
|
| 32 |
+
Les types de bâtiments sont utilisés pour catégoriser les constructions
|
| 33 |
+
selon leur structure et usage général (résidentiel, commercial, industriel, etc.).
|
| 34 |
+
Ils sont essentiels pour les prédictions énergétiques car ils déterminent
|
| 35 |
+
les caractéristiques de consommation typiques.
|
| 36 |
+
|
| 37 |
+
Attributes:
|
| 38 |
+
id (int): Identifiant unique du type de bâtiment (clé primaire)
|
| 39 |
+
model_id (int): Identifiant utilisé par le modèle ML pour l'encodage
|
| 40 |
+
building_type_name (str): Nom du type (CAMPUS, NONRESIDENTIAL, etc.)
|
| 41 |
+
description (str, optional): Description détaillée du type
|
| 42 |
+
created_at (datetime): Date de création de l'enregistrement
|
| 43 |
+
updated_at (datetime): Date de dernière modification
|
| 44 |
+
building_models (list[BuildingModels]): Bâtiments de ce type
|
| 45 |
+
|
| 46 |
+
Note:
|
| 47 |
+
Le model_id est utilisé par le modèle de machine learning pour encoder
|
| 48 |
+
numériquement le type de bâtiment lors des prédictions énergétiques.
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
__tablename__ = "building_type"
|
| 52 |
+
__table_args__ = (
|
| 53 |
+
PrimaryKeyConstraint("id", name="building_type_pkey"),
|
| 54 |
+
UniqueConstraint(
|
| 55 |
+
"building_type_name", name="building_type_building_type_name_key"
|
| 56 |
+
),
|
| 57 |
+
UniqueConstraint("model_id", name="building_type_model_id_key"),
|
| 58 |
+
{
|
| 59 |
+
"comment": "Table de référence des types de bâtiments pour la classification "
|
| 60 |
+
"des constructions"
|
| 61 |
+
},
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
id: Mapped[int] = mapped_column(
|
| 65 |
+
Integer,
|
| 66 |
+
primary_key=True,
|
| 67 |
+
comment="Identifiant unique du type de bâtiment (clé primaire auto-incrémentée)",
|
| 68 |
+
)
|
| 69 |
+
model_id: Mapped[int] = mapped_column(
|
| 70 |
+
Integer,
|
| 71 |
+
nullable=False,
|
| 72 |
+
comment="Identifiant au modèle associé (clé unique, obligatoire)",
|
| 73 |
+
)
|
| 74 |
+
building_type_name: Mapped[str] = mapped_column(
|
| 75 |
+
String(100),
|
| 76 |
+
nullable=False,
|
| 77 |
+
comment="Nom du type de bâtiment (unique, obligatoire, max 100 caractères)",
|
| 78 |
+
)
|
| 79 |
+
description: Mapped[Optional[str]] = mapped_column(
|
| 80 |
+
Text, comment="Description détaillée du type de bâtiment (optionnel)"
|
| 81 |
+
)
|
| 82 |
+
created_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 83 |
+
DateTime,
|
| 84 |
+
server_default=text("CURRENT_TIMESTAMP"),
|
| 85 |
+
comment="Date et heure de création de l'enregistrement",
|
| 86 |
+
)
|
| 87 |
+
updated_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 88 |
+
DateTime,
|
| 89 |
+
server_default=text("CURRENT_TIMESTAMP"),
|
| 90 |
+
comment="Date et heure de maj de l'enregistrement",
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
building_models: Mapped[list["BuildingModels"]] = relationship(
|
| 94 |
+
"BuildingModels", back_populates="building_type"
|
| 95 |
+
)
|
src/project5/models/categorie.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Modèle SQLAlchemy pour les catégories de propriétés.
|
| 3 |
+
|
| 4 |
+
Ce module définit le modèle de données pour les catégories d'usage des bâtiments
|
| 5 |
+
utilisées dans la classification pour les prédictions énergétiques.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import datetime
|
| 9 |
+
from typing import Optional
|
| 10 |
+
|
| 11 |
+
from sqlalchemy import (
|
| 12 |
+
DateTime,
|
| 13 |
+
Integer,
|
| 14 |
+
PrimaryKeyConstraint,
|
| 15 |
+
String,
|
| 16 |
+
Text,
|
| 17 |
+
UniqueConstraint,
|
| 18 |
+
text,
|
| 19 |
+
)
|
| 20 |
+
from sqlalchemy.orm import Mapped, mapped_column # TODO:, relationship
|
| 21 |
+
|
| 22 |
+
from .base import Base
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class Categorie(Base):
|
| 26 |
+
"""
|
| 27 |
+
Modèle représentant une catégorie d'usage de propriété.
|
| 28 |
+
|
| 29 |
+
Les catégories regroupent les différents usages des bâtiments par domaine
|
| 30 |
+
d'activité (résidentiel, commercial, bureaux, éducation, santé, etc.).
|
| 31 |
+
Elles servent à classifier les propriétés pour les prédictions énergétiques.
|
| 32 |
+
|
| 33 |
+
Attributes:
|
| 34 |
+
id (int): Identifiant unique de la catégorie (clé primaire)
|
| 35 |
+
category_code (str): Code court de la catégorie (OFFICE, RESIDENTIAL, etc.)
|
| 36 |
+
category_name (str): Nom complet en français (Bureaux, Résidentiel, etc.)
|
| 37 |
+
description (str, optional): Description détaillée de la catégorie
|
| 38 |
+
created_at (datetime): Date de création de l'enregistrement
|
| 39 |
+
updated_at (datetime): Date de dernière modification
|
| 40 |
+
|
| 41 |
+
Note:
|
| 42 |
+
Les catégories sont liées aux propriétés (Property) qui sont plus
|
| 43 |
+
granulaires et utilisées directement par le modèle ML.
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
__tablename__ = "categories"
|
| 47 |
+
__table_args__ = (
|
| 48 |
+
PrimaryKeyConstraint("id", name="categories_pkey"),
|
| 49 |
+
UniqueConstraint("category_code", name="categories_category_code_key"),
|
| 50 |
+
{"comment": "Table de référence des catégories de bâtiments"},
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
id: Mapped[int] = mapped_column(
|
| 54 |
+
Integer,
|
| 55 |
+
primary_key=True,
|
| 56 |
+
comment="Identifiant unique de la catégorie (clé primaire auto-incrémentée)",
|
| 57 |
+
)
|
| 58 |
+
category_code: Mapped[str] = mapped_column(
|
| 59 |
+
String(50),
|
| 60 |
+
nullable=False,
|
| 61 |
+
comment="Code unique de la catégorie (identifiant court, max 50 caractères)",
|
| 62 |
+
)
|
| 63 |
+
category_name: Mapped[str] = mapped_column(
|
| 64 |
+
String(100),
|
| 65 |
+
nullable=False,
|
| 66 |
+
comment="Nom complet de la catégorie (obligatoire, max 100 caractères)",
|
| 67 |
+
)
|
| 68 |
+
description: Mapped[Optional[str]] = mapped_column(
|
| 69 |
+
Text, comment="Description détaillée de la catégorie (optionnel)"
|
| 70 |
+
)
|
| 71 |
+
created_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 72 |
+
DateTime,
|
| 73 |
+
server_default=text("CURRENT_TIMESTAMP"),
|
| 74 |
+
comment="Date et heure de création de l'enregistrement",
|
| 75 |
+
)
|
| 76 |
+
updated_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 77 |
+
DateTime,
|
| 78 |
+
server_default=text("CURRENT_TIMESTAMP"),
|
| 79 |
+
comment="Date et heure de maj de l'enregistrement",
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
# TODO: property: Mapped[list["Property"]] = relationship(
|
| 83 |
+
# "Property", back_populates="category"
|
| 84 |
+
# )
|
src/project5/models/neighborhood.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Modèle SQLAlchemy pour les quartiers de Seattle.
|
| 3 |
+
|
| 4 |
+
Ce module définit le modèle de données pour les quartiers (neighborhoods) utilisés
|
| 5 |
+
dans la classification géographique des bâtiments pour les prédictions énergétiques.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import datetime
|
| 9 |
+
from typing import TYPE_CHECKING, Optional
|
| 10 |
+
|
| 11 |
+
from sqlalchemy import (
|
| 12 |
+
DateTime,
|
| 13 |
+
Integer,
|
| 14 |
+
PrimaryKeyConstraint,
|
| 15 |
+
String,
|
| 16 |
+
UniqueConstraint,
|
| 17 |
+
text,
|
| 18 |
+
)
|
| 19 |
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
| 20 |
+
|
| 21 |
+
if TYPE_CHECKING:
|
| 22 |
+
from .building_models import BuildingModels
|
| 23 |
+
|
| 24 |
+
from .base import Base
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class Neighborhood(Base):
|
| 28 |
+
"""
|
| 29 |
+
Modèle représentant un quartier de Seattle.
|
| 30 |
+
|
| 31 |
+
Les quartiers sont utilisés pour la géolocalisation et la classification
|
| 32 |
+
géographique des bâtiments. Ils influencent les prédictions énergétiques
|
| 33 |
+
car différents quartiers ont des caractéristiques climatiques et urbaines
|
| 34 |
+
distinctes.
|
| 35 |
+
|
| 36 |
+
Attributes:
|
| 37 |
+
id (int): Identifiant unique du quartier (clé primaire)
|
| 38 |
+
neighborhood_name (str): Nom du quartier (BALLARD, DOWNTOWN, etc.)
|
| 39 |
+
model_id (int): Identifiant utilisé par le modèle ML pour l'encodage
|
| 40 |
+
created_at (datetime): Date de création de l'enregistrement
|
| 41 |
+
updated_at (datetime): Date de dernière modification
|
| 42 |
+
building_models (list[BuildingModels]): Bâtiments associés à ce quartier
|
| 43 |
+
|
| 44 |
+
Note:
|
| 45 |
+
Le model_id est utilisé par le modèle de machine learning pour encoder
|
| 46 |
+
numériquement le quartier lors des prédictions énergétiques.
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
__tablename__ = "neighborhood"
|
| 50 |
+
__table_args__ = (
|
| 51 |
+
PrimaryKeyConstraint("id", name="neighborhood_pkey"),
|
| 52 |
+
UniqueConstraint("model_id", name="neighborhood_model_id_key"),
|
| 53 |
+
UniqueConstraint(
|
| 54 |
+
"neighborhood_name", name="neighborhood_neighborhood_name_key"
|
| 55 |
+
),
|
| 56 |
+
{
|
| 57 |
+
"comment": "Table des quartiers/arrondissements pour la classification "
|
| 58 |
+
"géographique des bâtiments"
|
| 59 |
+
},
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
id: Mapped[int] = mapped_column(
|
| 63 |
+
Integer,
|
| 64 |
+
primary_key=True,
|
| 65 |
+
comment="Identifiant unique du quartier (clé primaire auto-incrémentée)",
|
| 66 |
+
)
|
| 67 |
+
neighborhood_name: Mapped[str] = mapped_column(
|
| 68 |
+
String(50),
|
| 69 |
+
nullable=False,
|
| 70 |
+
comment="Nom du quartier (unique, obligatoire, max 50 caractères)",
|
| 71 |
+
)
|
| 72 |
+
model_id: Mapped[int] = mapped_column(
|
| 73 |
+
Integer,
|
| 74 |
+
nullable=False,
|
| 75 |
+
comment="Identifiant au modèle associé (clé unique, obligatoire)",
|
| 76 |
+
)
|
| 77 |
+
created_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 78 |
+
DateTime,
|
| 79 |
+
server_default=text("CURRENT_TIMESTAMP"),
|
| 80 |
+
comment="Date et heure de création de l'enregistrement",
|
| 81 |
+
)
|
| 82 |
+
updated_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 83 |
+
DateTime,
|
| 84 |
+
server_default=text("CURRENT_TIMESTAMP"),
|
| 85 |
+
comment="Date et heure de maj de l'enregistrement",
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
building_models: Mapped[list["BuildingModels"]] = relationship(
|
| 89 |
+
"BuildingModels", back_populates="neighborhood"
|
| 90 |
+
)
|
src/project5/models/property.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Modèle SQLAlchemy pour les propriétés d'usage des bâtiments.
|
| 3 |
+
|
| 4 |
+
Ce module définit le modèle de données pour les propriétés d'usage spécifiques
|
| 5 |
+
des bâtiments, utilisées pour une classification fine dans les prédictions énergétiques.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import datetime
|
| 9 |
+
from typing import TYPE_CHECKING, Optional
|
| 10 |
+
|
| 11 |
+
from sqlalchemy import (
|
| 12 |
+
DateTime,
|
| 13 |
+
ForeignKeyConstraint,
|
| 14 |
+
Index,
|
| 15 |
+
Integer,
|
| 16 |
+
PrimaryKeyConstraint,
|
| 17 |
+
String,
|
| 18 |
+
UniqueConstraint,
|
| 19 |
+
text,
|
| 20 |
+
)
|
| 21 |
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
| 22 |
+
|
| 23 |
+
if TYPE_CHECKING:
|
| 24 |
+
from .building_models import BuildingModels
|
| 25 |
+
|
| 26 |
+
from .base import Base
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class Property(Base):
|
| 30 |
+
"""
|
| 31 |
+
Modèle représentant une propriété d'usage spécifique d'un bâtiment.
|
| 32 |
+
|
| 33 |
+
Les propriétés sont plus granulaires que les catégories et permettent
|
| 34 |
+
une classification fine des usages (OFFICE, RETAIL STORE, RESTAURANT, etc.).
|
| 35 |
+
Elles sont directement utilisées par le modèle ML pour les prédictions
|
| 36 |
+
énergétiques car différents usages ont des profils de consommation distincts.
|
| 37 |
+
|
| 38 |
+
Attributes:
|
| 39 |
+
id (int): Identifiant unique de la propriété (clé primaire)
|
| 40 |
+
model_id (int): Identifiant utilisé par le modèle ML pour l'encodage
|
| 41 |
+
property_name (str): Nom de la propriété (OFFICE, RESTAURANT, etc.)
|
| 42 |
+
category_id (int): Identifiant de la catégorie parent (clé étrangère)
|
| 43 |
+
created_at (datetime): Date de création de l'enregistrement
|
| 44 |
+
updated_at (datetime): Date de dernière modification
|
| 45 |
+
building_models (list[BuildingModels]): Bâtiments avec cette propriété principale
|
| 46 |
+
building_models_ (list[BuildingModels]): Bâtiments avec cette propriété primaire
|
| 47 |
+
building_models1 (list[BuildingModels]): Bâtiments avec cette propriété secondaire
|
| 48 |
+
building_models2 (list[BuildingModels]): Bâtiments avec cette propriété tertiaire
|
| 49 |
+
|
| 50 |
+
Note:
|
| 51 |
+
Le model_id est utilisé directement par le modèle de machine learning
|
| 52 |
+
pour encoder numériquement l'usage lors des prédictions énergétiques.
|
| 53 |
+
Les relations multiples permettent de gérer les bâtiments multi-usages.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
__tablename__ = "property"
|
| 57 |
+
__table_args__ = (
|
| 58 |
+
ForeignKeyConstraint(
|
| 59 |
+
["category_id"], ["categories.id"], name="fk_property_category"
|
| 60 |
+
),
|
| 61 |
+
PrimaryKeyConstraint("id", name="property_pkey"),
|
| 62 |
+
UniqueConstraint("model_id", name="property_model_id_key"),
|
| 63 |
+
UniqueConstraint("property_name", name="property_property_name_key"),
|
| 64 |
+
Index("idx_property_category", "category_id"),
|
| 65 |
+
{"comment": "Table des propriétés/bâtiments avec leur catégorie associée"},
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
id: Mapped[int] = mapped_column(
|
| 69 |
+
Integer,
|
| 70 |
+
primary_key=True,
|
| 71 |
+
comment="Identifiant unique de la propriété (clé primaire auto-incrémentée)",
|
| 72 |
+
)
|
| 73 |
+
model_id: Mapped[int] = mapped_column(
|
| 74 |
+
Integer,
|
| 75 |
+
nullable=False,
|
| 76 |
+
comment="Identifiant au modèle associé (clé unique, obligatoire)",
|
| 77 |
+
)
|
| 78 |
+
property_name: Mapped[str] = mapped_column(
|
| 79 |
+
String(150),
|
| 80 |
+
nullable=False,
|
| 81 |
+
comment="Nom de la propriété (unique, obligatoire, max 150 caractères)",
|
| 82 |
+
)
|
| 83 |
+
category_id: Mapped[int] = mapped_column(
|
| 84 |
+
Integer,
|
| 85 |
+
nullable=False,
|
| 86 |
+
comment="Identifiant de la catégorie associée (clé étrangère vers categories.id)",
|
| 87 |
+
)
|
| 88 |
+
created_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 89 |
+
DateTime,
|
| 90 |
+
server_default=text("CURRENT_TIMESTAMP"),
|
| 91 |
+
comment="Date et heure de création de l'enregistrement",
|
| 92 |
+
)
|
| 93 |
+
updated_at: Mapped[Optional[datetime.datetime]] = mapped_column(
|
| 94 |
+
DateTime,
|
| 95 |
+
server_default=text("CURRENT_TIMESTAMP"),
|
| 96 |
+
comment="Date et heure de maj de l'enregistrement",
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
# category: Mapped['Categories'] = relationship('Categories', back_populates='property')
|
| 100 |
+
building_models: Mapped[list["BuildingModels"]] = relationship(
|
| 101 |
+
"BuildingModels",
|
| 102 |
+
foreign_keys="[BuildingModels.largest_property_use_type_id]",
|
| 103 |
+
back_populates="largest_property_use_type",
|
| 104 |
+
)
|
| 105 |
+
building_models_: Mapped[list["BuildingModels"]] = relationship(
|
| 106 |
+
"BuildingModels",
|
| 107 |
+
foreign_keys="[BuildingModels.primary_property_type_id]",
|
| 108 |
+
back_populates="primary_property_type",
|
| 109 |
+
)
|
| 110 |
+
building_models1: Mapped[list["BuildingModels"]] = relationship(
|
| 111 |
+
"BuildingModels",
|
| 112 |
+
foreign_keys="[BuildingModels.second_largest_property_use_type_id]",
|
| 113 |
+
back_populates="second_largest_property_use_type",
|
| 114 |
+
)
|
| 115 |
+
building_models2: Mapped[list["BuildingModels"]] = relationship(
|
| 116 |
+
"BuildingModels",
|
| 117 |
+
foreign_keys="[BuildingModels.third_largest_property_use_type_id]",
|
| 118 |
+
back_populates="third_largest_property_use_type",
|
| 119 |
+
)
|
src/project5/routers/__init__.py
ADDED
|
File without changes
|
src/project5/routers/building_energy_prediction_routes.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Routes API pour la gestion des prédictions énergétiques des bâtiments.
|
| 3 |
+
|
| 4 |
+
Ce module contient les endpoints FastAPI pour :
|
| 5 |
+
- Récupération de la liste des prédictions énergétiques avec pagination
|
| 6 |
+
- Récupération d'une prédiction spécifique par ID
|
| 7 |
+
- Gestion des erreurs et logging approprié
|
| 8 |
+
|
| 9 |
+
Endpoints disponibles :
|
| 10 |
+
- GET /api/v1/predictions/ : Liste paginée des prédictions
|
| 11 |
+
- GET /api/v1/predictions/{id} : Prédiction spécifique par ID
|
| 12 |
+
- POST /api/v1/predictions/building/{building_id} : Effectuer une prédiction pour un bâtiment et la sauvegarder
|
| 13 |
+
|
| 14 |
+
Auteur: François Hellebuyck
|
| 15 |
+
Projet: Building Energy Prediction API - OpenClassrooms Projet 5
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import logging
|
| 19 |
+
from typing import List
|
| 20 |
+
|
| 21 |
+
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
| 22 |
+
from fastapi.encoders import jsonable_encoder
|
| 23 |
+
from fastapi.responses import JSONResponse
|
| 24 |
+
from sqlalchemy.orm import Session
|
| 25 |
+
|
| 26 |
+
from project5.database import get_db
|
| 27 |
+
from project5.schemas.building_energy_prediction import (
|
| 28 |
+
BuildingEnergyPredictionsList,
|
| 29 |
+
BuildingEnergyPredictionsResponse,
|
| 30 |
+
)
|
| 31 |
+
from project5.schemas.building_model import PredictionResponse
|
| 32 |
+
from project5.services.building_ensergy_prediction_service import (
|
| 33 |
+
BuildingEnergyPredictionService,
|
| 34 |
+
)
|
| 35 |
+
from project5.services.building_model_service import (
|
| 36 |
+
BuildingModelsService,
|
| 37 |
+
PredictionService,
|
| 38 |
+
)
|
| 39 |
+
from project5.utils.exceptions import NotFoundError
|
| 40 |
+
|
| 41 |
+
logger = logging.getLogger(__name__)
|
| 42 |
+
|
| 43 |
+
router = APIRouter(prefix="/api/v1/predictions", tags=["predictions"])
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@router.get(
|
| 47 |
+
"/",
|
| 48 |
+
response_model=List[BuildingEnergyPredictionsList],
|
| 49 |
+
summary="Récupérer toutes les prédictions énergétiques des bâtiments",
|
| 50 |
+
description="""
|
| 51 |
+
Retourne la liste paginée de toutes les prédictions énergétiques des bâtiments.
|
| 52 |
+
|
| 53 |
+
Cette endpoint permet de récupérer l'ensemble des prédictions de consommation
|
| 54 |
+
énergétique calculées par le modèle de machine learning. Les résultats incluent
|
| 55 |
+
les données du bâtiment associé et la valeur prédite.
|
| 56 |
+
|
| 57 |
+
Paramètres de pagination :
|
| 58 |
+
- skip : nombre d'enregistrements à ignorer (par défaut 0)
|
| 59 |
+
- limit : nombre maximum d'enregistrements à retourner (par défaut 100, max 1000)
|
| 60 |
+
""",
|
| 61 |
+
responses={
|
| 62 |
+
200: {
|
| 63 |
+
"description": "Liste des prédictions récupérée avec succès",
|
| 64 |
+
"content": {
|
| 65 |
+
"application/json": {
|
| 66 |
+
"example": [
|
| 67 |
+
{
|
| 68 |
+
"id": 1,
|
| 69 |
+
"building_id": 123,
|
| 70 |
+
"site_energy_use_kbtu": 45678.9,
|
| 71 |
+
"predicted": True,
|
| 72 |
+
"updated_at": "2025-09-17T10:30:00Z",
|
| 73 |
+
}
|
| 74 |
+
]
|
| 75 |
+
}
|
| 76 |
+
},
|
| 77 |
+
},
|
| 78 |
+
500: {"description": "Erreur interne du serveur"},
|
| 79 |
+
},
|
| 80 |
+
)
|
| 81 |
+
def get_predictions(
|
| 82 |
+
skip: int = Query(0, ge=0, description="Nombre d'éléments à ignorer (pagination)"),
|
| 83 |
+
limit: int = Query(
|
| 84 |
+
100, ge=1, le=1000, description="Nombre maximum d'éléments à retourner"
|
| 85 |
+
),
|
| 86 |
+
db: Session = Depends(get_db),
|
| 87 |
+
):
|
| 88 |
+
"""
|
| 89 |
+
Récupère la liste paginée des prédictions énergétiques des bâtiments.
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
skip (int): Nombre d'enregistrements à ignorer pour la pagination
|
| 93 |
+
limit (int): Nombre maximum d'enregistrements à retourner
|
| 94 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 95 |
+
|
| 96 |
+
Returns:
|
| 97 |
+
JSONResponse: Liste des prédictions énergétiques au format JSON
|
| 98 |
+
|
| 99 |
+
Raises:
|
| 100 |
+
HTTPException: Erreur 500 si problème lors de la récupération des données
|
| 101 |
+
|
| 102 |
+
Example:
|
| 103 |
+
GET /api/v1/predictions/?skip=0&limit=50
|
| 104 |
+
"""
|
| 105 |
+
try:
|
| 106 |
+
predictions = BuildingEnergyPredictionService.get_all(
|
| 107 |
+
db=db, skip=skip, limit=limit
|
| 108 |
+
)
|
| 109 |
+
return JSONResponse(jsonable_encoder(predictions))
|
| 110 |
+
except Exception as e:
|
| 111 |
+
logger.error(
|
| 112 |
+
f"Erreur lors de la récupération des predictions énergétiques des bâtiments: {e}"
|
| 113 |
+
)
|
| 114 |
+
raise HTTPException(
|
| 115 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 116 |
+
detail="Erreur lors de la récupération des predictions énergétiques des bâtiments",
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
@router.get(
|
| 121 |
+
"/{id}",
|
| 122 |
+
response_model=BuildingEnergyPredictionsResponse,
|
| 123 |
+
summary="Récupérer une prédiction énergétique par ID",
|
| 124 |
+
description="""
|
| 125 |
+
Retourne les détails d'une prédiction énergétique spécifique.
|
| 126 |
+
|
| 127 |
+
Récupère une prédiction énergétique complète avec toutes les informations
|
| 128 |
+
associées au bâtiment et aux calculs de consommation énergétique.
|
| 129 |
+
|
| 130 |
+
L'ID doit correspondre à une prédiction existante dans la base de données.
|
| 131 |
+
""",
|
| 132 |
+
responses={
|
| 133 |
+
200: {
|
| 134 |
+
"description": "Prédiction trouvée et retournée avec succès",
|
| 135 |
+
"content": {
|
| 136 |
+
"application/json": {
|
| 137 |
+
"example": {
|
| 138 |
+
"id": 1,
|
| 139 |
+
"building_id": 123,
|
| 140 |
+
"site_energy_use_kbtu": 45678.9,
|
| 141 |
+
"predicted": True,
|
| 142 |
+
"updated_at": "2025-09-17T10:30:00Z",
|
| 143 |
+
"building": {
|
| 144 |
+
"id": 123,
|
| 145 |
+
"address": "123 Main St",
|
| 146 |
+
"neighborhood": "Downtown",
|
| 147 |
+
"building_type": "Office",
|
| 148 |
+
},
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
},
|
| 152 |
+
},
|
| 153 |
+
404: {
|
| 154 |
+
"description": "Prédiction non trouvée",
|
| 155 |
+
"content": {
|
| 156 |
+
"application/json": {
|
| 157 |
+
"example": {"detail": "Prédiction avec l'ID 999 non trouvée"}
|
| 158 |
+
}
|
| 159 |
+
},
|
| 160 |
+
},
|
| 161 |
+
500: {"description": "Erreur interne du serveur"},
|
| 162 |
+
},
|
| 163 |
+
)
|
| 164 |
+
def get_prediction(id: int, db: Session = Depends(get_db)):
|
| 165 |
+
"""
|
| 166 |
+
Récupère une prédiction énergétique spécifique par son ID.
|
| 167 |
+
|
| 168 |
+
Args:
|
| 169 |
+
id (int): Identifiant unique de la prédiction à récupérer
|
| 170 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 171 |
+
|
| 172 |
+
Returns:
|
| 173 |
+
BuildingEnergyPredictionsResponse: Prédiction énergétique avec détails complets
|
| 174 |
+
|
| 175 |
+
Raises:
|
| 176 |
+
HTTPException:
|
| 177 |
+
- 404 si la prédiction n'existe pas
|
| 178 |
+
- 500 si erreur lors de la récupération
|
| 179 |
+
|
| 180 |
+
Example:
|
| 181 |
+
GET /api/v1/predictions/123
|
| 182 |
+
"""
|
| 183 |
+
try:
|
| 184 |
+
pred = BuildingEnergyPredictionService.get_by_id(db=db, prediction_id=id)
|
| 185 |
+
return pred
|
| 186 |
+
except Exception as e:
|
| 187 |
+
logger.error(f"Erreur lors de la récupération de la prediction: {e}")
|
| 188 |
+
raise HTTPException(
|
| 189 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 190 |
+
detail="Erreur lors de la récupération de la prediction",
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
@router.post(
|
| 195 |
+
"/building/{building_id}",
|
| 196 |
+
response_model=PredictionResponse,
|
| 197 |
+
status_code=status.HTTP_201_CREATED,
|
| 198 |
+
summary="Effectuer une prédiction pour un bâtiment spécifique",
|
| 199 |
+
description="""
|
| 200 |
+
Effectue une prédiction énergétique pour un bâtiment spécifique et sauvegarde automatiquement le résultat.
|
| 201 |
+
|
| 202 |
+
Cette endpoint :
|
| 203 |
+
1. Récupère les données du bâtiment spécifié
|
| 204 |
+
2. Effectue une prédiction énergétique avec le modèle ML
|
| 205 |
+
3. Sauvegarde automatiquement le résultat dans building_energy_predictions avec predicted=True
|
| 206 |
+
4. Retourne la prédiction avec intervalle de confiance
|
| 207 |
+
|
| 208 |
+
Le bâtiment doit exister et avoir toutes les données nécessaires pour les prédictions ML.
|
| 209 |
+
""",
|
| 210 |
+
responses={
|
| 211 |
+
201: {
|
| 212 |
+
"description": "Prédiction effectuée et sauvegardée avec succès",
|
| 213 |
+
"content": {
|
| 214 |
+
"application/json": {
|
| 215 |
+
"example": {
|
| 216 |
+
"prediction": 65432.10,
|
| 217 |
+
"prediction_log": 11.09,
|
| 218 |
+
"model_version": "v1.0",
|
| 219 |
+
"confidence_interval_log": {"lower": 10.85, "upper": 11.33},
|
| 220 |
+
}
|
| 221 |
+
}
|
| 222 |
+
},
|
| 223 |
+
},
|
| 224 |
+
404: {
|
| 225 |
+
"description": "Bâtiment non trouvé",
|
| 226 |
+
"content": {
|
| 227 |
+
"application/json": {
|
| 228 |
+
"example": {"detail": "Bâtiment avec l'ID 999 non trouvé"}
|
| 229 |
+
}
|
| 230 |
+
},
|
| 231 |
+
},
|
| 232 |
+
422: {
|
| 233 |
+
"description": "Données du bâtiment insuffisantes pour la prédiction",
|
| 234 |
+
"content": {
|
| 235 |
+
"application/json": {
|
| 236 |
+
"example": {
|
| 237 |
+
"detail": "Le bâtiment n'a pas toutes les données nécessaires pour les prédictions ML"
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
},
|
| 241 |
+
},
|
| 242 |
+
500: {"description": "Erreur interne du serveur"},
|
| 243 |
+
},
|
| 244 |
+
)
|
| 245 |
+
async def predict_for_building(building_id: int, db: Session = Depends(get_db)):
|
| 246 |
+
"""
|
| 247 |
+
Effectue une prédiction énergétique pour un bâtiment spécifique et la sauvegarde.
|
| 248 |
+
|
| 249 |
+
Args:
|
| 250 |
+
building_id (int): Identifiant unique du bâtiment
|
| 251 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 252 |
+
|
| 253 |
+
Returns:
|
| 254 |
+
PredictionResponse: Prédiction avec consommation énergétique et intervalle de confiance
|
| 255 |
+
|
| 256 |
+
Raises:
|
| 257 |
+
HTTPException:
|
| 258 |
+
- 404 si le bâtiment n'existe pas
|
| 259 |
+
- 422 si les données du bâtiment sont insuffisantes
|
| 260 |
+
- 500 si erreur lors de la prédiction ou sauvegarde
|
| 261 |
+
|
| 262 |
+
Example:
|
| 263 |
+
POST /api/v1/predictions/building/123
|
| 264 |
+
|
| 265 |
+
Note:
|
| 266 |
+
La prédiction est automatiquement sauvegardée dans la table
|
| 267 |
+
building_energy_predictions avec le flag predicted=True
|
| 268 |
+
"""
|
| 269 |
+
try:
|
| 270 |
+
# Récupérer les données du bâtiment pour la prédiction
|
| 271 |
+
building_data = BuildingModelsService.get_model_data_by_id(
|
| 272 |
+
db=db, building_id=building_id
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
# Convertir en BuildingFeatures pour la prédiction
|
| 276 |
+
from project5.schemas.building_model import convert_query_result_to_schema
|
| 277 |
+
|
| 278 |
+
model_schema = convert_query_result_to_schema(building_data)
|
| 279 |
+
|
| 280 |
+
# Créer l'objet BuildingFeatures
|
| 281 |
+
from project5.schemas.building_model import BuildingFeatures
|
| 282 |
+
|
| 283 |
+
features = BuildingFeatures(
|
| 284 |
+
year_built=model_schema.year_built,
|
| 285 |
+
number_of_buildings=model_schema.number_of_buildings,
|
| 286 |
+
number_of_floors=model_schema.number_of_floors,
|
| 287 |
+
property_gfa_total=float(model_schema.property_gfa_total),
|
| 288 |
+
property_gfa_parking=float(model_schema.property_gfa_parking),
|
| 289 |
+
second_largest_property_use_type_gfa=float(
|
| 290 |
+
model_schema.second_largest_property_use_type_gfa
|
| 291 |
+
),
|
| 292 |
+
third_largest_property_use_type_gfa=float(
|
| 293 |
+
model_schema.third_largest_property_use_type_gfa
|
| 294 |
+
),
|
| 295 |
+
multiusage=int(model_schema.multiusage),
|
| 296 |
+
steam=int(model_schema.steam),
|
| 297 |
+
electricity=int(model_schema.electricity),
|
| 298 |
+
natural_gas=int(model_schema.natural_gas),
|
| 299 |
+
neighborhood_id=model_schema.neighborhood_id,
|
| 300 |
+
building_type_id=model_schema.building_type_id,
|
| 301 |
+
largest_property_use_type_id=model_schema.largest_property_use_type_id,
|
| 302 |
+
primary_property_type_id=model_schema.primary_property_type_id,
|
| 303 |
+
second_largest_property_use_type_id=model_schema.second_largest_property_use_type_id,
|
| 304 |
+
third_largest_property_use_type_id=model_schema.third_largest_property_use_type_id,
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
# Effectuer la prédiction avec sauvegarde automatique
|
| 308 |
+
service = PredictionService()
|
| 309 |
+
result = await service.predict(features, building_id=building_id, db=db)
|
| 310 |
+
|
| 311 |
+
logger.info(
|
| 312 |
+
f"Prédiction effectuée et sauvegardée pour le bâtiment {building_id}: {result['prediction']} kBTU"
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
return PredictionResponse(**result)
|
| 316 |
+
|
| 317 |
+
except NotFoundError as e:
|
| 318 |
+
# Bâtiment non trouvé - retourner 404
|
| 319 |
+
logger.warning(f"Bâtiment {building_id} non trouvé: {e}")
|
| 320 |
+
raise HTTPException(
|
| 321 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 322 |
+
detail=f"Bâtiment avec l'ID {building_id} non trouvé",
|
| 323 |
+
)
|
| 324 |
+
except HTTPException:
|
| 325 |
+
# Propager les erreurs HTTP (404, etc.)
|
| 326 |
+
raise
|
| 327 |
+
except Exception as e:
|
| 328 |
+
logger.error(
|
| 329 |
+
f"Erreur lors de la prédiction pour le bâtiment {building_id}: {e}"
|
| 330 |
+
)
|
| 331 |
+
raise HTTPException(
|
| 332 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 333 |
+
detail=f"Erreur lors de la prédiction: {str(e)}",
|
| 334 |
+
)
|
src/project5/routers/building_model_routes.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Routes API pour la gestion des modèles de bâtiments.
|
| 3 |
+
|
| 4 |
+
Ce module contient les endpoints FastAPI pour :
|
| 5 |
+
- Récupération de la liste des bâtiments avec pagination
|
| 6 |
+
- Récupération d'un bâtiment spécifique par ID
|
| 7 |
+
- Récupération des données formatées pour le modèle ML
|
| 8 |
+
- Gestion des erreurs et logging approprié
|
| 9 |
+
|
| 10 |
+
Endpoints disponibles :
|
| 11 |
+
- GET /api/v1/buildings/ : Liste paginée des bâtiments
|
| 12 |
+
- GET /api/v1/buildings/{id} : Bâtiment spécifique par ID
|
| 13 |
+
- GET /api/v1/buildings/model/{id} : Données formatées pour le modèle ML
|
| 14 |
+
- POST /api/v1/buildings/ : Créer un nouveau bâtiment
|
| 15 |
+
|
| 16 |
+
Auteur: François Hellebuyck
|
| 17 |
+
Projet: Building Energy Prediction API - OpenClassrooms Projet 5
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import logging
|
| 21 |
+
from typing import List
|
| 22 |
+
|
| 23 |
+
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
| 24 |
+
from fastapi.encoders import jsonable_encoder
|
| 25 |
+
from fastapi.responses import JSONResponse
|
| 26 |
+
from sqlalchemy.orm import Session
|
| 27 |
+
|
| 28 |
+
from project5.database import get_db
|
| 29 |
+
from project5.models.building_models import BuildingModels
|
| 30 |
+
from project5.schemas.building_model import (
|
| 31 |
+
BuildingModelsCreate,
|
| 32 |
+
BuildingModelsList,
|
| 33 |
+
BuildingModelsResponse,
|
| 34 |
+
ModelViewSchema,
|
| 35 |
+
convert_query_result_to_schema,
|
| 36 |
+
)
|
| 37 |
+
from project5.services.building_model_service import BuildingModelsService
|
| 38 |
+
from project5.utils.exceptions import ValidationError
|
| 39 |
+
|
| 40 |
+
logger = logging.getLogger(__name__)
|
| 41 |
+
|
| 42 |
+
router = APIRouter(prefix="/api/v1/buildings", tags=["buildings"])
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@router.get(
|
| 46 |
+
"/",
|
| 47 |
+
response_model=List[BuildingModelsList],
|
| 48 |
+
summary="Récupérer tous les bâtiments",
|
| 49 |
+
description="""
|
| 50 |
+
Retourne la liste paginée de tous les bâtiments enregistrés dans le système.
|
| 51 |
+
|
| 52 |
+
Cette endpoint permet de récupérer l'ensemble des bâtiments avec leurs
|
| 53 |
+
informations de base comme l'adresse, le quartier, le type de bâtiment,
|
| 54 |
+
et les caractéristiques principales utilisées pour les prédictions énergétiques.
|
| 55 |
+
|
| 56 |
+
Paramètres de pagination :
|
| 57 |
+
- skip : nombre d'enregistrements à ignorer (par défaut 0)
|
| 58 |
+
- limit : nombre maximum d'enregistrements à retourner (par défaut 100, max 1000)
|
| 59 |
+
""",
|
| 60 |
+
responses={
|
| 61 |
+
200: {
|
| 62 |
+
"description": "Liste des bâtiments récupérée avec succès",
|
| 63 |
+
"content": {
|
| 64 |
+
"application/json": {
|
| 65 |
+
"example": [
|
| 66 |
+
{
|
| 67 |
+
"id": 1,
|
| 68 |
+
"ose_building_id": 12345,
|
| 69 |
+
"address": "123 Main St",
|
| 70 |
+
"city": "Seattle",
|
| 71 |
+
"neighborhood": "Downtown",
|
| 72 |
+
"building_type": "Office",
|
| 73 |
+
"year_built": 1995,
|
| 74 |
+
"property_gfa_total": 50000.0,
|
| 75 |
+
}
|
| 76 |
+
]
|
| 77 |
+
}
|
| 78 |
+
},
|
| 79 |
+
},
|
| 80 |
+
500: {"description": "Erreur interne du serveur"},
|
| 81 |
+
},
|
| 82 |
+
)
|
| 83 |
+
def get_building_types(
|
| 84 |
+
skip: int = Query(0, ge=0, description="Nombre d'éléments à ignorer (pagination)"),
|
| 85 |
+
limit: int = Query(
|
| 86 |
+
100, ge=1, le=1000, description="Nombre maximum d'éléments à retourner"
|
| 87 |
+
),
|
| 88 |
+
db: Session = Depends(get_db),
|
| 89 |
+
):
|
| 90 |
+
"""
|
| 91 |
+
Récupère la liste paginée des bâtiments.
|
| 92 |
+
|
| 93 |
+
Args:
|
| 94 |
+
skip (int): Nombre d'enregistrements à ignorer pour la pagination
|
| 95 |
+
limit (int): Nombre maximum d'enregistrements à retourner
|
| 96 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 97 |
+
|
| 98 |
+
Returns:
|
| 99 |
+
JSONResponse: Liste des bâtiments au format JSON
|
| 100 |
+
|
| 101 |
+
Raises:
|
| 102 |
+
HTTPException: Erreur 500 si problème lors de la récupération des données
|
| 103 |
+
|
| 104 |
+
Example:
|
| 105 |
+
GET /api/v1/buildings/?skip=0&limit=50
|
| 106 |
+
"""
|
| 107 |
+
try:
|
| 108 |
+
buildings = BuildingModelsService.get_all(db=db, skip=skip, limit=limit)
|
| 109 |
+
return JSONResponse(jsonable_encoder(buildings))
|
| 110 |
+
except Exception as e:
|
| 111 |
+
logger.error(f"Erreur lors de la récupération des bâtiments: {e}")
|
| 112 |
+
raise HTTPException(
|
| 113 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 114 |
+
detail="Erreur lors de la récupération des bâtiments",
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
@router.get(
|
| 119 |
+
"/{id}",
|
| 120 |
+
response_model=BuildingModelsResponse,
|
| 121 |
+
summary="Récupérer un bâtiment par ID",
|
| 122 |
+
description="""
|
| 123 |
+
Retourne les détails complets d'un bâtiment spécifique.
|
| 124 |
+
|
| 125 |
+
Récupère toutes les informations détaillées d'un bâtiment incluant :
|
| 126 |
+
- Informations générales (adresse, ville, quartier)
|
| 127 |
+
- Caractéristiques techniques (année de construction, nombre d'étages)
|
| 128 |
+
- Données énergétiques (types d'énergie utilisés)
|
| 129 |
+
- Relations avec les types et catégories de propriétés
|
| 130 |
+
|
| 131 |
+
L'ID doit correspondre à un bâtiment existant dans la base de données.
|
| 132 |
+
""",
|
| 133 |
+
responses={
|
| 134 |
+
200: {
|
| 135 |
+
"description": "Bâtiment trouvé et retourné avec succès",
|
| 136 |
+
"content": {
|
| 137 |
+
"application/json": {
|
| 138 |
+
"example": {
|
| 139 |
+
"id": 1,
|
| 140 |
+
"ose_building_id": 12345,
|
| 141 |
+
"address": "123 Main St",
|
| 142 |
+
"city": "Seattle",
|
| 143 |
+
"state": "WA",
|
| 144 |
+
"zip_code": "98101",
|
| 145 |
+
"neighborhood": "Downtown",
|
| 146 |
+
"building_type": "Office",
|
| 147 |
+
"year_built": 1995,
|
| 148 |
+
"number_of_buildings": 1,
|
| 149 |
+
"number_of_floors": 15,
|
| 150 |
+
"property_gfa_total": 50000.0,
|
| 151 |
+
"multiusage": False,
|
| 152 |
+
"steam": True,
|
| 153 |
+
"electricity": True,
|
| 154 |
+
"natural_gas": True,
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
},
|
| 158 |
+
},
|
| 159 |
+
404: {
|
| 160 |
+
"description": "Bâtiment non trouvé",
|
| 161 |
+
"content": {
|
| 162 |
+
"application/json": {
|
| 163 |
+
"example": {"detail": "Bâtiment avec l'ID 999 non trouvé"}
|
| 164 |
+
}
|
| 165 |
+
},
|
| 166 |
+
},
|
| 167 |
+
500: {"description": "Erreur interne du serveur"},
|
| 168 |
+
},
|
| 169 |
+
)
|
| 170 |
+
def get_building(id: int, db: Session = Depends(get_db)):
|
| 171 |
+
"""
|
| 172 |
+
Récupère un bâtiment spécifique par son ID.
|
| 173 |
+
|
| 174 |
+
Args:
|
| 175 |
+
id (int): Identifiant unique du bâtiment à récupérer
|
| 176 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 177 |
+
|
| 178 |
+
Returns:
|
| 179 |
+
BuildingModelsResponse: Bâtiment avec détails complets
|
| 180 |
+
|
| 181 |
+
Raises:
|
| 182 |
+
HTTPException:
|
| 183 |
+
- 404 si le bâtiment n'existe pas
|
| 184 |
+
- 500 si erreur lors de la récupération
|
| 185 |
+
|
| 186 |
+
Example:
|
| 187 |
+
GET /api/v1/buildings/123
|
| 188 |
+
"""
|
| 189 |
+
try:
|
| 190 |
+
building = BuildingModelsService.get_by_id(db=db, building_id=id)
|
| 191 |
+
return building
|
| 192 |
+
except Exception as e:
|
| 193 |
+
logger.error(f"Erreur lors du bâtiment: {e}")
|
| 194 |
+
raise HTTPException(
|
| 195 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 196 |
+
detail="Erreur lors de la récupération du bâtiment",
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
@router.get(
|
| 201 |
+
"/model/{id}",
|
| 202 |
+
response_model=ModelViewSchema,
|
| 203 |
+
summary="Récupérer les données formatées pour le modèle ML",
|
| 204 |
+
description="""
|
| 205 |
+
Retourne les données d'un bâtiment formatées spécifiquement pour le modèle de machine learning.
|
| 206 |
+
|
| 207 |
+
Cette endpoint transforme les données brutes du bâtiment en format approprié
|
| 208 |
+
pour effectuer des prédictions énergétiques avec le modèle ML. Les données
|
| 209 |
+
sont pré-traitées et encodées selon les exigences du modèle :
|
| 210 |
+
|
| 211 |
+
- Variables catégorielles encodées numériquement
|
| 212 |
+
- Variables continues normalisées si nécessaire
|
| 213 |
+
- Format de données compatible avec scikit-learn
|
| 214 |
+
- Gestion des valeurs manquantes
|
| 215 |
+
|
| 216 |
+
L'ID doit correspondre à un bâtiment existant dans la base de données.
|
| 217 |
+
""",
|
| 218 |
+
responses={
|
| 219 |
+
200: {
|
| 220 |
+
"description": "Données du modèle récupérées avec succès",
|
| 221 |
+
"content": {
|
| 222 |
+
"application/json": {
|
| 223 |
+
"example": {
|
| 224 |
+
"building_id": 123,
|
| 225 |
+
"neighborhood_encoded": 3,
|
| 226 |
+
"building_type_encoded": 1,
|
| 227 |
+
"largest_property_use_type_encoded": 18,
|
| 228 |
+
"primary_property_type_encoded": 18,
|
| 229 |
+
"year_built": 1995,
|
| 230 |
+
"number_of_buildings": 1,
|
| 231 |
+
"number_of_floors": 15,
|
| 232 |
+
"property_gfa_total": 50000.0,
|
| 233 |
+
"steam": 1,
|
| 234 |
+
"electricity": 1,
|
| 235 |
+
"natural_gas": 1,
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
},
|
| 239 |
+
},
|
| 240 |
+
404: {
|
| 241 |
+
"description": "Bâtiment non trouvé",
|
| 242 |
+
"content": {
|
| 243 |
+
"application/json": {
|
| 244 |
+
"example": {"detail": "Bâtiment avec l'ID 999 non trouvé"}
|
| 245 |
+
}
|
| 246 |
+
},
|
| 247 |
+
},
|
| 248 |
+
500: {"description": "Erreur interne du serveur"},
|
| 249 |
+
},
|
| 250 |
+
)
|
| 251 |
+
def get_model_data_by_id(id: int, db: Session = Depends(get_db)):
|
| 252 |
+
"""
|
| 253 |
+
Récupère les données d'un bâtiment formatées pour le modèle ML.
|
| 254 |
+
|
| 255 |
+
Args:
|
| 256 |
+
id (int): Identifiant unique du bâtiment
|
| 257 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 258 |
+
|
| 259 |
+
Returns:
|
| 260 |
+
JSONResponse: Données formatées pour le modèle ML au format JSON
|
| 261 |
+
|
| 262 |
+
Raises:
|
| 263 |
+
HTTPException:
|
| 264 |
+
- 404 si le bâtiment n'existe pas
|
| 265 |
+
- 500 si erreur lors de la récupération ou du formatage
|
| 266 |
+
|
| 267 |
+
Example:
|
| 268 |
+
GET /api/v1/buildings/model/123
|
| 269 |
+
|
| 270 |
+
Note:
|
| 271 |
+
Cette endpoint est principalement utilisée par l'endpoint de prédiction
|
| 272 |
+
pour préparer les données avant d'appeler le modèle ML.
|
| 273 |
+
"""
|
| 274 |
+
try:
|
| 275 |
+
building = BuildingModelsService.get_model_data_by_id(db=db, building_id=id)
|
| 276 |
+
return JSONResponse(jsonable_encoder(convert_query_result_to_schema(building)))
|
| 277 |
+
except Exception as e:
|
| 278 |
+
logger.error(f"Erreur lors du bâtiment: {e}")
|
| 279 |
+
raise HTTPException(
|
| 280 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 281 |
+
detail="Erreur lors de la récupération du bâtiment",
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
@router.post(
|
| 286 |
+
"/",
|
| 287 |
+
response_model=BuildingModelsResponse,
|
| 288 |
+
status_code=status.HTTP_201_CREATED,
|
| 289 |
+
summary="Créer un nouveau bâtiment",
|
| 290 |
+
description="""
|
| 291 |
+
Ajoute un nouveau bâtiment dans le système.
|
| 292 |
+
|
| 293 |
+
Cette endpoint permet de créer un nouveau bâtiment avec ses caractéristiques
|
| 294 |
+
complètes. Les données sont validées avant l'insertion en base de données.
|
| 295 |
+
|
| 296 |
+
Champs obligatoires pour les prédictions ML (17 features) :
|
| 297 |
+
- ose_building_id : Identifiant unique du bâtiment OSE
|
| 298 |
+
- year_built : Année de construction
|
| 299 |
+
- number_of_buildings : Nombre de bâtiments
|
| 300 |
+
- number_of_floors : Nombre d'étages
|
| 301 |
+
- property_gfa_total : Surface totale GFA
|
| 302 |
+
- property_gfa_parking : Surface parking GFA
|
| 303 |
+
- second_largest_property_use_type_gfa : Surface 2e type d'usage
|
| 304 |
+
- third_largest_property_use_type_gfa : Surface 3e type d'usage
|
| 305 |
+
- multiusage : Indicateur multi-usage (0 ou 1)
|
| 306 |
+
- steam : Indicateur vapeur (0 ou 1)
|
| 307 |
+
- electricity : Indicateur électricité (0 ou 1)
|
| 308 |
+
- natural_gas : Indicateur gaz naturel (0 ou 1)
|
| 309 |
+
- neighborhood_id : ID du quartier
|
| 310 |
+
- building_type_id : ID du type de bâtiment
|
| 311 |
+
- largest_property_use_type_id : ID du plus grand type d'usage
|
| 312 |
+
- primary_property_type_id : ID du type de propriété principal
|
| 313 |
+
- second_largest_property_use_type_id : ID du 2e type d'usage
|
| 314 |
+
- third_largest_property_use_type_id : ID du 3e type d'usage
|
| 315 |
+
|
| 316 |
+
Validation automatique :
|
| 317 |
+
- Format et valeurs des champs
|
| 318 |
+
- Contraintes métier (années, surfaces positives)
|
| 319 |
+
- Unicité de l'identifiant OSE
|
| 320 |
+
""",
|
| 321 |
+
responses={
|
| 322 |
+
201: {
|
| 323 |
+
"description": "Bâtiment créé avec succès",
|
| 324 |
+
"content": {
|
| 325 |
+
"application/json": {
|
| 326 |
+
"example": {
|
| 327 |
+
"id": 1001,
|
| 328 |
+
"ose_building_id": "NEW12345",
|
| 329 |
+
"address": "456 New Street",
|
| 330 |
+
"city": "Seattle",
|
| 331 |
+
"state": "WA",
|
| 332 |
+
"year_built": 2020,
|
| 333 |
+
"number_of_buildings": 1,
|
| 334 |
+
"number_of_floors": 5,
|
| 335 |
+
"property_gfa_total": 25000.0,
|
| 336 |
+
"property_gfa_parking": 2000.0,
|
| 337 |
+
"second_largest_property_use_type_gfa": 0.0,
|
| 338 |
+
"third_largest_property_use_type_gfa": 0.0,
|
| 339 |
+
"multiusage": 0,
|
| 340 |
+
"steam": 0,
|
| 341 |
+
"electricity": 1,
|
| 342 |
+
"natural_gas": 1,
|
| 343 |
+
"neighborhood_id": 1,
|
| 344 |
+
"building_type_id": 1,
|
| 345 |
+
"largest_property_use_type_id": 18,
|
| 346 |
+
"primary_property_type_id": 18,
|
| 347 |
+
"second_largest_property_use_type_id": 1,
|
| 348 |
+
"third_largest_property_use_type_id": 1,
|
| 349 |
+
"created_at": "2025-09-21T12:00:00",
|
| 350 |
+
"updated_at": "2025-09-21T12:00:00",
|
| 351 |
+
}
|
| 352 |
+
}
|
| 353 |
+
},
|
| 354 |
+
},
|
| 355 |
+
400: {
|
| 356 |
+
"description": "Données invalides ou manquantes",
|
| 357 |
+
"content": {
|
| 358 |
+
"application/json": {
|
| 359 |
+
"example": {"detail": "L'année de construction est obligatoire"}
|
| 360 |
+
}
|
| 361 |
+
},
|
| 362 |
+
},
|
| 363 |
+
422: {
|
| 364 |
+
"description": "Erreur de validation des données",
|
| 365 |
+
"content": {
|
| 366 |
+
"application/json": {
|
| 367 |
+
"example": {"detail": "La surface totale GFA est obligatoire"}
|
| 368 |
+
}
|
| 369 |
+
},
|
| 370 |
+
},
|
| 371 |
+
500: {"description": "Erreur interne du serveur"},
|
| 372 |
+
},
|
| 373 |
+
)
|
| 374 |
+
def create_building(building_data: BuildingModelsCreate, db: Session = Depends(get_db)):
|
| 375 |
+
"""
|
| 376 |
+
Crée un nouveau bâtiment dans le système.
|
| 377 |
+
|
| 378 |
+
Args:
|
| 379 |
+
building_data (BuildingModelsCreate): Données du bâtiment à créer
|
| 380 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 381 |
+
|
| 382 |
+
Returns:
|
| 383 |
+
BuildingModelsResponse: Bâtiment créé avec son ID généré et timestamps
|
| 384 |
+
|
| 385 |
+
Raises:
|
| 386 |
+
HTTPException:
|
| 387 |
+
- 400 si les données obligatoires sont manquantes
|
| 388 |
+
- 422 si erreur de validation des données
|
| 389 |
+
- 500 si erreur lors de la création
|
| 390 |
+
|
| 391 |
+
Example:
|
| 392 |
+
POST /api/v1/buildings/
|
| 393 |
+
{
|
| 394 |
+
"ose_building_id": "NEW12345",
|
| 395 |
+
"address": "456 New Street",
|
| 396 |
+
"city": "Seattle",
|
| 397 |
+
"year_built": 2020,
|
| 398 |
+
"number_of_buildings": 1,
|
| 399 |
+
"number_of_floors": 5,
|
| 400 |
+
"property_gfa_total": 25000.0,
|
| 401 |
+
"property_gfa_parking": 2000.0,
|
| 402 |
+
"second_largest_property_use_type_gfa": 0.0,
|
| 403 |
+
"third_largest_property_use_type_gfa": 0.0,
|
| 404 |
+
"multiusage": 0,
|
| 405 |
+
"steam": 0,
|
| 406 |
+
"electricity": 1,
|
| 407 |
+
"natural_gas": 1,
|
| 408 |
+
"neighborhood_id": 1,
|
| 409 |
+
"building_type_id": 1,
|
| 410 |
+
"largest_property_use_type_id": 18,
|
| 411 |
+
"primary_property_type_id": 18,
|
| 412 |
+
"second_largest_property_use_type_id": 1,
|
| 413 |
+
"third_largest_property_use_type_id": 1
|
| 414 |
+
}
|
| 415 |
+
"""
|
| 416 |
+
try:
|
| 417 |
+
# Convertir le schéma Pydantic en modèle SQLAlchemy
|
| 418 |
+
building_model = BuildingModels(**building_data.model_dump(exclude_unset=True))
|
| 419 |
+
|
| 420 |
+
# Appeler le service pour créer le bâtiment
|
| 421 |
+
created_building = BuildingModelsService.set_new_building(
|
| 422 |
+
db=db, building_model=building_model
|
| 423 |
+
)
|
| 424 |
+
|
| 425 |
+
return created_building
|
| 426 |
+
|
| 427 |
+
except ValidationError as e:
|
| 428 |
+
logger.error(f"Erreur de validation lors de la création du bâtiment: {e}")
|
| 429 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
| 430 |
+
except ValueError as e:
|
| 431 |
+
logger.error(f"Erreur de validation lors de la création du bâtiment: {e}")
|
| 432 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
| 433 |
+
except Exception as e:
|
| 434 |
+
logger.error(f"Erreur lors de la création du bâtiment: {e}")
|
| 435 |
+
raise HTTPException(
|
| 436 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 437 |
+
detail="Erreur lors de la création du bâtiment",
|
| 438 |
+
)
|
src/project5/routers/building_types_routes.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Routes API pour la gestion des types de bâtiments.
|
| 3 |
+
|
| 4 |
+
Ce module contient les endpoints FastAPI pour :
|
| 5 |
+
- Récupération de la liste des types de bâtiments avec pagination
|
| 6 |
+
- Récupération d'un type de bâtiment spécifique par ID
|
| 7 |
+
- Gestion des erreurs et logging approprié
|
| 8 |
+
|
| 9 |
+
Les types de bâtiments sont utilisés pour catégoriser les bâtiments selon leur
|
| 10 |
+
usage principal (résidentiel, commercial, industriel, etc.) et sont essentiels
|
| 11 |
+
pour les prédictions énergétiques du modèle ML.
|
| 12 |
+
|
| 13 |
+
Endpoints disponibles :
|
| 14 |
+
- GET /api/v1/building-types/ : Liste paginée des types de bâtiments
|
| 15 |
+
- GET /api/v1/building-types/{id} : Type de bâtiment spécifique par ID
|
| 16 |
+
|
| 17 |
+
Auteur: François Hellebuyck
|
| 18 |
+
Projet: Building Energy Prediction API - OpenClassrooms Projet 5
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
import logging
|
| 22 |
+
from typing import List
|
| 23 |
+
|
| 24 |
+
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
| 25 |
+
from sqlalchemy.orm import Session
|
| 26 |
+
|
| 27 |
+
from project5.database import get_db
|
| 28 |
+
from project5.schemas.building_type import BuildingTypeList, BuildingTypeResponse
|
| 29 |
+
from project5.services.building_type_service import BuildingTypeService
|
| 30 |
+
|
| 31 |
+
logger = logging.getLogger(__name__)
|
| 32 |
+
|
| 33 |
+
router = APIRouter(prefix="/api/v1/building-types", tags=["building-types"])
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@router.get(
|
| 37 |
+
"/",
|
| 38 |
+
response_model=List[BuildingTypeList],
|
| 39 |
+
summary="Récupérer tous les types de bâtiments",
|
| 40 |
+
description="""
|
| 41 |
+
Retourne la liste paginée de tous les types de bâtiments disponibles dans le système.
|
| 42 |
+
|
| 43 |
+
Cette endpoint permet de récupérer l'ensemble des catégories de bâtiments
|
| 44 |
+
utilisées pour la classification et les prédictions énergétiques. Chaque type
|
| 45 |
+
de bâtiment correspond à un usage spécifique (résidentiel, commercial,
|
| 46 |
+
industriel, etc.) avec ses caractéristiques énergétiques propres.
|
| 47 |
+
|
| 48 |
+
Types de bâtiments disponibles :
|
| 49 |
+
- Campus building complex
|
| 50 |
+
- Non-residential building
|
| 51 |
+
- Seattle Public Schools District K-12
|
| 52 |
+
- Multifamily (Low-Rise, Mid-Rise, High-Rise)
|
| 53 |
+
- Et autres catégories spécialisées
|
| 54 |
+
|
| 55 |
+
Paramètres de pagination :
|
| 56 |
+
- skip : nombre d'enregistrements à ignorer (par défaut 0)
|
| 57 |
+
- limit : nombre maximum d'enregistrements à retourner (par défaut 100, max 1000)
|
| 58 |
+
""",
|
| 59 |
+
responses={
|
| 60 |
+
200: {
|
| 61 |
+
"description": "Liste des types de bâtiments récupérée avec succès",
|
| 62 |
+
"content": {
|
| 63 |
+
"application/json": {
|
| 64 |
+
"example": [
|
| 65 |
+
{
|
| 66 |
+
"id": 1,
|
| 67 |
+
"model_id": 0,
|
| 68 |
+
"building_type_name": "CAMPUS",
|
| 69 |
+
"description": "Campus building complex",
|
| 70 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 71 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"id": 2,
|
| 75 |
+
"model_id": 1,
|
| 76 |
+
"building_type_name": "NONRESIDENTIAL",
|
| 77 |
+
"description": "Non-residential building",
|
| 78 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 79 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 80 |
+
},
|
| 81 |
+
]
|
| 82 |
+
}
|
| 83 |
+
},
|
| 84 |
+
},
|
| 85 |
+
500: {"description": "Erreur interne du serveur"},
|
| 86 |
+
},
|
| 87 |
+
)
|
| 88 |
+
def get_building_types(
|
| 89 |
+
skip: int = Query(0, ge=0, description="Nombre d'éléments à ignorer (pagination)"),
|
| 90 |
+
limit: int = Query(
|
| 91 |
+
100, ge=1, le=1000, description="Nombre maximum d'éléments à retourner"
|
| 92 |
+
),
|
| 93 |
+
db: Session = Depends(get_db),
|
| 94 |
+
):
|
| 95 |
+
"""
|
| 96 |
+
Récupère la liste paginée des types de bâtiments.
|
| 97 |
+
|
| 98 |
+
Args:
|
| 99 |
+
skip (int): Nombre d'enregistrements à ignorer pour la pagination
|
| 100 |
+
limit (int): Nombre maximum d'enregistrements à retourner
|
| 101 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 102 |
+
|
| 103 |
+
Returns:
|
| 104 |
+
List[BuildingTypeList]: Liste des types de bâtiments
|
| 105 |
+
|
| 106 |
+
Raises:
|
| 107 |
+
HTTPException: Erreur 500 si problème lors de la récupération des données
|
| 108 |
+
|
| 109 |
+
Example:
|
| 110 |
+
GET /api/v1/building-types/?skip=0&limit=20
|
| 111 |
+
|
| 112 |
+
Note:
|
| 113 |
+
Les types de bâtiments sont référencés par leur model_id dans le modèle ML
|
| 114 |
+
pour effectuer les prédictions énergétiques.
|
| 115 |
+
"""
|
| 116 |
+
try:
|
| 117 |
+
buildingTypes = BuildingTypeService.get_all(db=db, skip=skip, limit=limit)
|
| 118 |
+
return buildingTypes
|
| 119 |
+
except Exception as e:
|
| 120 |
+
logger.error(f"Erreur lors de la récupération des des types de bâtiment: {e}")
|
| 121 |
+
raise HTTPException(
|
| 122 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 123 |
+
detail="Erreur lors de la récupération des types de bâtiment",
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
@router.get(
|
| 128 |
+
"/{building_type_id}",
|
| 129 |
+
response_model=BuildingTypeResponse,
|
| 130 |
+
summary="Récupérer un type de bâtiment par ID",
|
| 131 |
+
description="""
|
| 132 |
+
Retourne les détails complets d'un type de bâtiment spécifique.
|
| 133 |
+
|
| 134 |
+
Récupère toutes les informations d'un type de bâtiment incluant :
|
| 135 |
+
- Identifiant unique dans la base de données
|
| 136 |
+
- Identifiant utilisé par le modèle ML (model_id)
|
| 137 |
+
- Nom du type de bâtiment
|
| 138 |
+
- Description détaillée
|
| 139 |
+
- Horodatages de création et modification
|
| 140 |
+
|
| 141 |
+
Le type de bâtiment est un élément clé pour la classification et les
|
| 142 |
+
prédictions énergétiques, car il détermine les caractéristiques de
|
| 143 |
+
consommation énergétique typiques.
|
| 144 |
+
|
| 145 |
+
L'ID doit correspondre à un type de bâtiment existant dans la base de données.
|
| 146 |
+
""",
|
| 147 |
+
responses={
|
| 148 |
+
200: {
|
| 149 |
+
"description": "Type de bâtiment trouvé et retourné avec succès",
|
| 150 |
+
"content": {
|
| 151 |
+
"application/json": {
|
| 152 |
+
"example": {
|
| 153 |
+
"id": 3,
|
| 154 |
+
"model_id": 1,
|
| 155 |
+
"building_type_name": "NONRESIDENTIAL",
|
| 156 |
+
"description": "Non-residential building",
|
| 157 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 158 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
},
|
| 162 |
+
},
|
| 163 |
+
404: {
|
| 164 |
+
"description": "Type de bâtiment non trouvé",
|
| 165 |
+
"content": {
|
| 166 |
+
"application/json": {
|
| 167 |
+
"example": {"detail": "Type de bâtiment avec l'ID 999 non trouvé"}
|
| 168 |
+
}
|
| 169 |
+
},
|
| 170 |
+
},
|
| 171 |
+
500: {"description": "Erreur interne du serveur"},
|
| 172 |
+
},
|
| 173 |
+
)
|
| 174 |
+
def get_building_type(building_type_id: int, db: Session = Depends(get_db)):
|
| 175 |
+
"""
|
| 176 |
+
Récupère un type de bâtiment spécifique par son ID.
|
| 177 |
+
|
| 178 |
+
Args:
|
| 179 |
+
building_type_id (int): Identifiant unique du type de bâtiment à récupérer
|
| 180 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 181 |
+
|
| 182 |
+
Returns:
|
| 183 |
+
BuildingTypeResponse: Type de bâtiment avec détails complets
|
| 184 |
+
|
| 185 |
+
Raises:
|
| 186 |
+
HTTPException:
|
| 187 |
+
- 404 si le type de bâtiment n'existe pas
|
| 188 |
+
- 500 si erreur lors de la récupération
|
| 189 |
+
|
| 190 |
+
Example:
|
| 191 |
+
GET /api/v1/building-types/3
|
| 192 |
+
|
| 193 |
+
Note:
|
| 194 |
+
Le model_id retourné est utilisé par le modèle ML pour encoder
|
| 195 |
+
le type de bâtiment lors des prédictions énergétiques.
|
| 196 |
+
"""
|
| 197 |
+
try:
|
| 198 |
+
bt = BuildingTypeService.get_by_id(db=db, id=building_type_id)
|
| 199 |
+
return bt
|
| 200 |
+
except Exception as e:
|
| 201 |
+
logger.error(f"Erreur lors de la récupération du type de bâtiment: {e}")
|
| 202 |
+
raise HTTPException(
|
| 203 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 204 |
+
detail="Erreur lors de la récupération du type de bâtiment",
|
| 205 |
+
)
|
src/project5/routers/categories_routes.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Routes API pour la gestion des catégories de propriétés.
|
| 3 |
+
|
| 4 |
+
Ce module contient les endpoints FastAPI pour :
|
| 5 |
+
- Récupération de la liste des catégories de propriétés avec pagination
|
| 6 |
+
- Récupération d'une catégorie spécifique par ID
|
| 7 |
+
- Gestion des erreurs et logging approprié
|
| 8 |
+
|
| 9 |
+
Les catégories de propriétés regroupent les différents usages des bâtiments
|
| 10 |
+
(résidentiel, commercial, bureaux, éducation, santé, etc.) et servent à
|
| 11 |
+
classifier les propriétés pour les prédictions énergétiques du modèle ML.
|
| 12 |
+
|
| 13 |
+
Catégories principales disponibles :
|
| 14 |
+
- RESIDENTIAL : Logements et habitations
|
| 15 |
+
- OFFICE : Espaces de bureaux et administratifs
|
| 16 |
+
- EDUCATION : Établissements d'enseignement
|
| 17 |
+
- HEALTHCARE : Hôpitaux et établissements de soins
|
| 18 |
+
- RETAIL : Magasins et commerces
|
| 19 |
+
- INDUSTRIAL : Usines et installations industrielles
|
| 20 |
+
- Et autres catégories spécialisées
|
| 21 |
+
|
| 22 |
+
Endpoints disponibles :
|
| 23 |
+
- GET /api/v1/categories/ : Liste paginée des catégories
|
| 24 |
+
- GET /api/v1/categories/{id} : Catégorie spécifique par ID
|
| 25 |
+
|
| 26 |
+
Auteur: François Hellebuyck
|
| 27 |
+
Projet: Building Energy Prediction API - OpenClassrooms Projet 5
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
import logging
|
| 31 |
+
from typing import List
|
| 32 |
+
|
| 33 |
+
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
| 34 |
+
from sqlalchemy.orm import Session
|
| 35 |
+
|
| 36 |
+
from project5.database import get_db
|
| 37 |
+
from project5.schemas.categorie import CategoryList, CategoryResponse
|
| 38 |
+
from project5.services.categorie_service import CategorieService
|
| 39 |
+
|
| 40 |
+
logger = logging.getLogger(__name__)
|
| 41 |
+
|
| 42 |
+
router = APIRouter(prefix="/api/v1/categories", tags=["categories"])
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@router.get(
|
| 46 |
+
"/",
|
| 47 |
+
response_model=List[CategoryList],
|
| 48 |
+
summary="Récupérer toutes les catégories de propriétés",
|
| 49 |
+
description="""
|
| 50 |
+
Retourne la liste paginée de toutes les catégories de propriétés disponibles dans le système.
|
| 51 |
+
|
| 52 |
+
Cette endpoint permet de récupérer l'ensemble des catégories d'usage des bâtiments
|
| 53 |
+
utilisées pour la classification et les prédictions énergétiques. Chaque catégorie
|
| 54 |
+
regroupe des propriétés ayant des caractéristiques énergétiques similaires.
|
| 55 |
+
|
| 56 |
+
Catégories disponibles dans le système :
|
| 57 |
+
- RESIDENTIAL : Logements et habitations
|
| 58 |
+
- OFFICE : Espaces de bureaux et administratifs
|
| 59 |
+
- EDUCATION : Établissements d'enseignement et de formation
|
| 60 |
+
- HEALTHCARE : Hôpitaux, cliniques et établissements de soins médicaux
|
| 61 |
+
- RETAIL : Magasins et commerces de détail
|
| 62 |
+
- RESTAURANT : Restaurants et services alimentaires
|
| 63 |
+
- INDUSTRIAL : Usines et installations industrielles
|
| 64 |
+
- ENTERTAINMENT : Théâtres, cinémas et espaces de divertissement
|
| 65 |
+
- LODGING : Hôtels et logements temporaires
|
| 66 |
+
- PARKING : Structures et espaces de stationnement
|
| 67 |
+
- PUBLIC : Services gouvernementaux et publics
|
| 68 |
+
- RELIGIOUS : Églises et lieux de culte
|
| 69 |
+
- Et autres catégories spécialisées
|
| 70 |
+
|
| 71 |
+
Paramètres de pagination :
|
| 72 |
+
- skip : nombre d'enregistrements à ignorer (par défaut 0)
|
| 73 |
+
- limit : nombre maximum d'enregistrements à retourner (par défaut 100, max 1000)
|
| 74 |
+
""",
|
| 75 |
+
responses={
|
| 76 |
+
200: {
|
| 77 |
+
"description": "Liste des catégories récupérée avec succès",
|
| 78 |
+
"content": {
|
| 79 |
+
"application/json": {
|
| 80 |
+
"example": [
|
| 81 |
+
{
|
| 82 |
+
"id": 11,
|
| 83 |
+
"category_code": "OFFICE",
|
| 84 |
+
"category_name": "Bureaux",
|
| 85 |
+
"description": "Espaces de bureaux et administratifs",
|
| 86 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 87 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
"id": 16,
|
| 91 |
+
"category_code": "RESIDENTIAL",
|
| 92 |
+
"category_name": "Résidentiel",
|
| 93 |
+
"description": "Logements et habitations",
|
| 94 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 95 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 96 |
+
},
|
| 97 |
+
]
|
| 98 |
+
}
|
| 99 |
+
},
|
| 100 |
+
},
|
| 101 |
+
500: {"description": "Erreur interne du serveur"},
|
| 102 |
+
},
|
| 103 |
+
)
|
| 104 |
+
def get_categories(
|
| 105 |
+
skip: int = Query(0, ge=0, description="Nombre d'éléments à ignorer (pagination)"),
|
| 106 |
+
limit: int = Query(
|
| 107 |
+
100, ge=1, le=1000, description="Nombre maximum d'éléments à retourner"
|
| 108 |
+
),
|
| 109 |
+
db: Session = Depends(get_db),
|
| 110 |
+
):
|
| 111 |
+
"""
|
| 112 |
+
Récupère la liste paginée des catégories de propriétés.
|
| 113 |
+
|
| 114 |
+
Args:
|
| 115 |
+
skip (int): Nombre d'enregistrements à ignorer pour la pagination
|
| 116 |
+
limit (int): Nombre maximum d'enregistrements à retourner
|
| 117 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 118 |
+
|
| 119 |
+
Returns:
|
| 120 |
+
List[CategoryList]: Liste des catégories de propriétés
|
| 121 |
+
|
| 122 |
+
Raises:
|
| 123 |
+
HTTPException: Erreur 500 si problème lors de la récupération des données
|
| 124 |
+
|
| 125 |
+
Example:
|
| 126 |
+
GET /api/v1/categories/?skip=0&limit=20
|
| 127 |
+
|
| 128 |
+
Note:
|
| 129 |
+
Les catégories sont utilisées pour regrouper les propriétés par usage
|
| 130 |
+
et déterminer les caractéristiques énergétiques typiques lors des prédictions.
|
| 131 |
+
"""
|
| 132 |
+
try:
|
| 133 |
+
categories = CategorieService.get_all(db=db, skip=skip, limit=limit)
|
| 134 |
+
return categories
|
| 135 |
+
except Exception as e:
|
| 136 |
+
logger.error(f"Erreur lors de la récupération des categories: {e}")
|
| 137 |
+
raise HTTPException(
|
| 138 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 139 |
+
detail="Erreur lors de la récupération des categories",
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
@router.get(
|
| 144 |
+
"/{categorie_id}",
|
| 145 |
+
response_model=CategoryResponse,
|
| 146 |
+
summary="Récupérer une catégorie par ID",
|
| 147 |
+
description="""
|
| 148 |
+
Retourne les détails complets d'une catégorie de propriété spécifique.
|
| 149 |
+
|
| 150 |
+
Récupère toutes les informations d'une catégorie incluant :
|
| 151 |
+
- Identifiant unique dans la base de données
|
| 152 |
+
- Code de la catégorie (nom court en anglais)
|
| 153 |
+
- Nom complet de la catégorie (en français)
|
| 154 |
+
- Description détaillée de l'usage
|
| 155 |
+
- Horodatages de création et modification
|
| 156 |
+
|
| 157 |
+
Les catégories servent à classifier les propriétés selon leur usage principal
|
| 158 |
+
et sont essentielles pour déterminer les profils de consommation énergétique
|
| 159 |
+
lors des prédictions du modèle ML.
|
| 160 |
+
|
| 161 |
+
L'ID doit correspondre à une catégorie existante dans la base de données.
|
| 162 |
+
""",
|
| 163 |
+
responses={
|
| 164 |
+
200: {
|
| 165 |
+
"description": "Catégorie trouvée et retournée avec succès",
|
| 166 |
+
"content": {
|
| 167 |
+
"application/json": {
|
| 168 |
+
"example": {
|
| 169 |
+
"id": 11,
|
| 170 |
+
"category_code": "OFFICE",
|
| 171 |
+
"category_name": "Bureaux",
|
| 172 |
+
"description": "Espaces de bureaux et administratifs",
|
| 173 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 174 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
},
|
| 178 |
+
},
|
| 179 |
+
404: {
|
| 180 |
+
"description": "Catégorie non trouvée",
|
| 181 |
+
"content": {
|
| 182 |
+
"application/json": {
|
| 183 |
+
"example": {"detail": "Catégorie avec l'ID 999 non trouvée"}
|
| 184 |
+
}
|
| 185 |
+
},
|
| 186 |
+
},
|
| 187 |
+
500: {"description": "Erreur interne du serveur"},
|
| 188 |
+
},
|
| 189 |
+
)
|
| 190 |
+
def get_categorie(categorie_id: int, db: Session = Depends(get_db)):
|
| 191 |
+
"""
|
| 192 |
+
Récupère une catégorie spécifique par son ID.
|
| 193 |
+
|
| 194 |
+
Args:
|
| 195 |
+
categorie_id (int): Identifiant unique de la catégorie à récupérer
|
| 196 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 197 |
+
|
| 198 |
+
Returns:
|
| 199 |
+
CategoryResponse: Catégorie avec détails complets
|
| 200 |
+
|
| 201 |
+
Raises:
|
| 202 |
+
HTTPException:
|
| 203 |
+
- 404 si la catégorie n'existe pas
|
| 204 |
+
- 500 si erreur lors de la récupération
|
| 205 |
+
|
| 206 |
+
Example:
|
| 207 |
+
GET /api/v1/categories/11
|
| 208 |
+
|
| 209 |
+
Note:
|
| 210 |
+
Les catégories sont liées aux propriétés et influencent directement
|
| 211 |
+
les prédictions énergétiques basées sur l'usage du bâtiment.
|
| 212 |
+
"""
|
| 213 |
+
try:
|
| 214 |
+
categorie = CategorieService.get_by_id(db=db, categorie_id=categorie_id)
|
| 215 |
+
return categorie
|
| 216 |
+
except Exception as e:
|
| 217 |
+
logger.error(f"Erreur lors de la récupération de la category: {e}")
|
| 218 |
+
raise HTTPException(
|
| 219 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 220 |
+
detail="Erreur lors de la récupération de la category",
|
| 221 |
+
)
|
src/project5/routers/neighborhoods_routes.py
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Routes API pour la gestion des quartiers de Seattle.
|
| 3 |
+
|
| 4 |
+
Ce module contient les endpoints FastAPI pour :
|
| 5 |
+
- Récupération de la liste des quartiers avec pagination
|
| 6 |
+
- Récupération d'un quartier spécifique par ID
|
| 7 |
+
- Recherche de quartiers par nom
|
| 8 |
+
- Gestion des erreurs et logging approprié
|
| 9 |
+
|
| 10 |
+
Les quartiers (neighborhoods) représentent les divisions géographiques de Seattle
|
| 11 |
+
et sont un facteur important pour les prédictions énergétiques car ils influencent
|
| 12 |
+
les caractéristiques de consommation selon la localisation, le climat local, et
|
| 13 |
+
le type d'urbanisation.
|
| 14 |
+
|
| 15 |
+
Quartiers disponibles dans le système :
|
| 16 |
+
- BALLARD, CENTRAL, DELRIDGE, DOWNTOWN
|
| 17 |
+
- EAST, GREATER DUWAMISH, LAKE UNION
|
| 18 |
+
- MAGNOLIA / QUEEN ANNE, NORTH, NORTHEAST
|
| 19 |
+
- NORTHWEST, SOUTHEAST, SOUTHWEST, WEST
|
| 20 |
+
- UNKNOWN (pour les données non géolocalisées)
|
| 21 |
+
|
| 22 |
+
Endpoints disponibles :
|
| 23 |
+
- GET /api/v1/neighborhoods/ : Liste paginée des quartiers
|
| 24 |
+
- GET /api/v1/neighborhoods/{id} : Quartier spécifique par ID
|
| 25 |
+
- GET /api/v1/neighborhoods/search/ : Recherche par nom
|
| 26 |
+
|
| 27 |
+
Auteur: François Hellebuyck
|
| 28 |
+
Projet: Building Energy Prediction API - OpenClassrooms Projet 5
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
import logging
|
| 32 |
+
from typing import List
|
| 33 |
+
|
| 34 |
+
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
| 35 |
+
from sqlalchemy.orm import Session
|
| 36 |
+
|
| 37 |
+
from project5.database import get_db
|
| 38 |
+
from project5.models import Neighborhood as NeighborhoodModel
|
| 39 |
+
from project5.schemas import Neighborhood
|
| 40 |
+
from project5.services.neighborhood_service import NeighborhoodService
|
| 41 |
+
|
| 42 |
+
logger = logging.getLogger(__name__)
|
| 43 |
+
|
| 44 |
+
router = APIRouter(
|
| 45 |
+
prefix="/api/v1/neighborhoods",
|
| 46 |
+
tags=["neighborhoods"],
|
| 47 |
+
responses={404: {"description": "Quartier non trouvé"}},
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@router.get(
|
| 52 |
+
"/",
|
| 53 |
+
response_model=List[Neighborhood],
|
| 54 |
+
summary="Récupérer tous les quartiers de Seattle",
|
| 55 |
+
description="""
|
| 56 |
+
Retourne la liste paginée de tous les quartiers de Seattle disponibles dans le système.
|
| 57 |
+
|
| 58 |
+
Cette endpoint permet de récupérer l'ensemble des quartiers (neighborhoods) de Seattle
|
| 59 |
+
utilisés pour la géolocalisation et les prédictions énergétiques. Chaque quartier
|
| 60 |
+
représente une zone géographique avec ses caractéristiques climatiques et urbaines
|
| 61 |
+
propres qui influencent la consommation énergétique des bâtiments.
|
| 62 |
+
|
| 63 |
+
Quartiers de Seattle disponibles :
|
| 64 |
+
- BALLARD : Quartier nord-ouest, résidentiel et commercial
|
| 65 |
+
- CENTRAL : Zone centrale de Seattle
|
| 66 |
+
- DELRIDGE : Quartier sud-ouest, principalement résidentiel
|
| 67 |
+
- DOWNTOWN : Centre-ville, zone commerciale et de bureaux
|
| 68 |
+
- EAST : Quartiers est de Seattle
|
| 69 |
+
- GREATER DUWAMISH : Zone industrielle sud
|
| 70 |
+
- LAKE UNION : Zone autour du lac Union, mixte
|
| 71 |
+
- MAGNOLIA / QUEEN ANNE : Quartiers résidentiels aisés
|
| 72 |
+
- NORTH, NORTHEAST : Zones résidentielles nord
|
| 73 |
+
- NORTHWEST, SOUTHEAST, SOUTHWEST, WEST : Autres zones géographiques
|
| 74 |
+
- UNKNOWN : Pour les bâtiments sans géolocalisation précise
|
| 75 |
+
|
| 76 |
+
Paramètres de pagination :
|
| 77 |
+
- skip : nombre d'enregistrements à ignorer (par défaut 0)
|
| 78 |
+
- limit : nombre maximum d'enregistrements à retourner (par défaut 100, max 1000)
|
| 79 |
+
""",
|
| 80 |
+
responses={
|
| 81 |
+
200: {
|
| 82 |
+
"description": "Liste des quartiers récupérée avec succès",
|
| 83 |
+
"content": {
|
| 84 |
+
"application/json": {
|
| 85 |
+
"example": [
|
| 86 |
+
{
|
| 87 |
+
"id": 5,
|
| 88 |
+
"neighborhood_name": "DOWNTOWN",
|
| 89 |
+
"model_id": 3,
|
| 90 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 91 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"id": 2,
|
| 95 |
+
"neighborhood_name": "BALLARD",
|
| 96 |
+
"model_id": 0,
|
| 97 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 98 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 99 |
+
},
|
| 100 |
+
]
|
| 101 |
+
}
|
| 102 |
+
},
|
| 103 |
+
},
|
| 104 |
+
500: {"description": "Erreur interne du serveur"},
|
| 105 |
+
},
|
| 106 |
+
)
|
| 107 |
+
def get_neighborhoods(
|
| 108 |
+
skip: int = Query(0, ge=0, description="Nombre d'éléments à ignorer (pagination)"),
|
| 109 |
+
limit: int = Query(
|
| 110 |
+
100, ge=1, le=1000, description="Nombre maximum d'éléments à retourner"
|
| 111 |
+
),
|
| 112 |
+
db: Session = Depends(get_db),
|
| 113 |
+
):
|
| 114 |
+
"""
|
| 115 |
+
Récupère la liste paginée des quartiers de Seattle.
|
| 116 |
+
|
| 117 |
+
Args:
|
| 118 |
+
skip (int): Nombre d'enregistrements à ignorer pour la pagination
|
| 119 |
+
limit (int): Nombre maximum d'enregistrements à retourner
|
| 120 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 121 |
+
|
| 122 |
+
Returns:
|
| 123 |
+
List[Neighborhood]: Liste des quartiers de Seattle
|
| 124 |
+
|
| 125 |
+
Raises:
|
| 126 |
+
HTTPException: Erreur 500 si problème lors de la récupération des données
|
| 127 |
+
|
| 128 |
+
Example:
|
| 129 |
+
GET /api/v1/neighborhoods/?skip=0&limit=20
|
| 130 |
+
|
| 131 |
+
Note:
|
| 132 |
+
Les quartiers sont référencés par leur model_id dans le modèle ML
|
| 133 |
+
pour tenir compte de la localisation géographique dans les prédictions.
|
| 134 |
+
"""
|
| 135 |
+
try:
|
| 136 |
+
neighborhoods = NeighborhoodService.get_all(db=db, skip=skip, limit=limit)
|
| 137 |
+
return neighborhoods
|
| 138 |
+
except Exception as e:
|
| 139 |
+
logger.error(f"Erreur lors de la récupération des quartiers: {e}")
|
| 140 |
+
raise HTTPException(
|
| 141 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 142 |
+
detail="Erreur lors de la récupération des quartiers",
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
@router.get(
|
| 147 |
+
"/{neighborhood_id}",
|
| 148 |
+
response_model=Neighborhood,
|
| 149 |
+
summary="Récupérer un quartier par ID",
|
| 150 |
+
description="""
|
| 151 |
+
Retourne les détails complets d'un quartier de Seattle spécifique.
|
| 152 |
+
|
| 153 |
+
Récupère toutes les informations d'un quartier incluant :
|
| 154 |
+
- Identifiant unique dans la base de données
|
| 155 |
+
- Nom du quartier (neighborhood_name)
|
| 156 |
+
- Identifiant utilisé par le modèle ML (model_id)
|
| 157 |
+
- Horodatages de création et modification
|
| 158 |
+
|
| 159 |
+
Les quartiers sont des facteurs géographiques importants pour les prédictions
|
| 160 |
+
énergétiques car ils déterminent :
|
| 161 |
+
- Les conditions climatiques locales
|
| 162 |
+
- Le type d'urbanisation (résidentiel, commercial, industriel)
|
| 163 |
+
- Les réglementations énergétiques locales
|
| 164 |
+
- Les habitudes de consommation régionales
|
| 165 |
+
|
| 166 |
+
L'ID doit correspondre à un quartier existant dans la base de données.
|
| 167 |
+
""",
|
| 168 |
+
responses={
|
| 169 |
+
200: {
|
| 170 |
+
"description": "Quartier trouvé et retourné avec succès",
|
| 171 |
+
"content": {
|
| 172 |
+
"application/json": {
|
| 173 |
+
"example": {
|
| 174 |
+
"id": 5,
|
| 175 |
+
"neighborhood_name": "DOWNTOWN",
|
| 176 |
+
"model_id": 3,
|
| 177 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 178 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
},
|
| 182 |
+
},
|
| 183 |
+
404: {
|
| 184 |
+
"description": "Quartier non trouvé",
|
| 185 |
+
"content": {
|
| 186 |
+
"application/json": {
|
| 187 |
+
"example": {"detail": "Quartier avec l'ID 999 non trouvé"}
|
| 188 |
+
}
|
| 189 |
+
},
|
| 190 |
+
},
|
| 191 |
+
500: {"description": "Erreur interne du serveur"},
|
| 192 |
+
},
|
| 193 |
+
)
|
| 194 |
+
def get_neighborhood(neighborhood_id: int, db: Session = Depends(get_db)):
|
| 195 |
+
"""
|
| 196 |
+
Récupère un quartier spécifique par son ID.
|
| 197 |
+
|
| 198 |
+
Args:
|
| 199 |
+
neighborhood_id (int): Identifiant unique du quartier à récupérer
|
| 200 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 201 |
+
|
| 202 |
+
Returns:
|
| 203 |
+
Neighborhood: Quartier avec détails complets
|
| 204 |
+
|
| 205 |
+
Raises:
|
| 206 |
+
HTTPException:
|
| 207 |
+
- 404 si le quartier n'existe pas
|
| 208 |
+
- 500 si erreur lors de la récupération
|
| 209 |
+
|
| 210 |
+
Example:
|
| 211 |
+
GET /api/v1/neighborhoods/5
|
| 212 |
+
|
| 213 |
+
Note:
|
| 214 |
+
Le model_id retourné est utilisé par le modèle ML pour encoder
|
| 215 |
+
la localisation géographique lors des prédictions énergétiques.
|
| 216 |
+
"""
|
| 217 |
+
try:
|
| 218 |
+
neighborhood = NeighborhoodService.get_by_id(
|
| 219 |
+
db=db, neighborhood_id=neighborhood_id
|
| 220 |
+
)
|
| 221 |
+
return neighborhood
|
| 222 |
+
except Exception as e:
|
| 223 |
+
logger.error(f"Erreur lors de la récupération du quartier: {e}")
|
| 224 |
+
raise HTTPException(
|
| 225 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 226 |
+
detail="Erreur lors de la récupération du quartier",
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
@router.get(
|
| 231 |
+
"/search/",
|
| 232 |
+
response_model=List[Neighborhood],
|
| 233 |
+
summary="Rechercher des quartiers par nom",
|
| 234 |
+
description="""
|
| 235 |
+
Recherche des quartiers de Seattle par nom avec correspondance partielle.
|
| 236 |
+
|
| 237 |
+
Cette endpoint permet de rechercher des quartiers en utilisant une correspondance
|
| 238 |
+
partielle (insensible à la casse) sur le nom du quartier. Utile pour :
|
| 239 |
+
- Auto-complétion dans les interfaces utilisateur
|
| 240 |
+
- Recherche floue de quartiers
|
| 241 |
+
- Filtrage rapide de la liste des quartiers
|
| 242 |
+
|
| 243 |
+
La recherche s'effectue sur le champ neighborhood_name avec une correspondance
|
| 244 |
+
partielle utilisant l'opérateur SQL ILIKE (insensible à la casse).
|
| 245 |
+
|
| 246 |
+
Exemples de recherche :
|
| 247 |
+
- "down" trouvera "DOWNTOWN"
|
| 248 |
+
- "north" trouvera "NORTH", "NORTHEAST", "NORTHWEST"
|
| 249 |
+
- "queen" trouvera "MAGNOLIA / QUEEN ANNE"
|
| 250 |
+
|
| 251 |
+
Les résultats sont triés par ordre alphabétique du nom de quartier.
|
| 252 |
+
""",
|
| 253 |
+
responses={
|
| 254 |
+
200: {
|
| 255 |
+
"description": "Quartiers trouvés avec succès",
|
| 256 |
+
"content": {
|
| 257 |
+
"application/json": {
|
| 258 |
+
"example": [
|
| 259 |
+
{
|
| 260 |
+
"id": 10,
|
| 261 |
+
"neighborhood_name": "NORTH",
|
| 262 |
+
"model_id": 8,
|
| 263 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 264 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 265 |
+
},
|
| 266 |
+
{
|
| 267 |
+
"id": 11,
|
| 268 |
+
"neighborhood_name": "NORTHEAST",
|
| 269 |
+
"model_id": 9,
|
| 270 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 271 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 272 |
+
},
|
| 273 |
+
]
|
| 274 |
+
}
|
| 275 |
+
},
|
| 276 |
+
},
|
| 277 |
+
422: {
|
| 278 |
+
"description": "Paramètres de recherche invalides",
|
| 279 |
+
"content": {
|
| 280 |
+
"application/json": {
|
| 281 |
+
"example": {
|
| 282 |
+
"detail": "Le terme de recherche doit contenir au moins 1 caractère"
|
| 283 |
+
}
|
| 284 |
+
}
|
| 285 |
+
},
|
| 286 |
+
},
|
| 287 |
+
500: {"description": "Erreur interne du serveur"},
|
| 288 |
+
},
|
| 289 |
+
)
|
| 290 |
+
def search_neighborhoods(
|
| 291 |
+
q: str = Query(
|
| 292 |
+
..., min_length=1, description="Terme de recherche (insensible à la casse)"
|
| 293 |
+
),
|
| 294 |
+
limit: int = Query(50, ge=1, le=100, description="Nombre maximum de résultats"),
|
| 295 |
+
db: Session = Depends(get_db),
|
| 296 |
+
):
|
| 297 |
+
"""
|
| 298 |
+
Recherche des quartiers par nom avec correspondance partielle.
|
| 299 |
+
|
| 300 |
+
Args:
|
| 301 |
+
q (str): Terme de recherche à chercher dans les noms de quartiers
|
| 302 |
+
limit (int): Nombre maximum de résultats à retourner (par défaut 50, max 100)
|
| 303 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 304 |
+
|
| 305 |
+
Returns:
|
| 306 |
+
List[Neighborhood]: Liste des quartiers correspondant à la recherche
|
| 307 |
+
|
| 308 |
+
Raises:
|
| 309 |
+
HTTPException:
|
| 310 |
+
- 422 si les paramètres de recherche sont invalides
|
| 311 |
+
- 500 si erreur lors de la recherche
|
| 312 |
+
|
| 313 |
+
Example:
|
| 314 |
+
GET /api/v1/neighborhoods/search/?q=north&limit=10
|
| 315 |
+
|
| 316 |
+
Note:
|
| 317 |
+
La recherche utilise ILIKE pour une correspondance insensible à la casse
|
| 318 |
+
et retourne les résultats triés par ordre alphabétique.
|
| 319 |
+
"""
|
| 320 |
+
neighborhoods = (
|
| 321 |
+
db.query(NeighborhoodModel)
|
| 322 |
+
.filter(NeighborhoodModel.neighborhood_name.ilike(f"%{q.upper()}%"))
|
| 323 |
+
.order_by(NeighborhoodModel.neighborhood_name)
|
| 324 |
+
.limit(limit)
|
| 325 |
+
.all()
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
return neighborhoods
|
src/project5/routers/prediction_routes.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Routes API pour les prédictions énergétiques des bâtiments.
|
| 3 |
+
|
| 4 |
+
Ce module contient les endpoints FastAPI pour :
|
| 5 |
+
- Prédiction de consommation énergétique basée sur les caractéristiques du bâtiment
|
| 6 |
+
- Récupération d'informations sur le modèle de machine learning
|
| 7 |
+
- Gestion des erreurs et logging approprié
|
| 8 |
+
|
| 9 |
+
Le modèle de prédiction utilise un RandomForestRegressor entraîné sur les données
|
| 10 |
+
énergétiques de Seattle pour prédire la consommation en kBTU/an. Les caractéristiques
|
| 11 |
+
principales incluent :
|
| 12 |
+
- Localisation (quartier)
|
| 13 |
+
- Type de bâtiment et usage principal
|
| 14 |
+
- Caractéristiques physiques (surface, étages, année de construction)
|
| 15 |
+
- Types d'énergie utilisés (électricité, gaz naturel, vapeur)
|
| 16 |
+
|
| 17 |
+
Endpoints disponibles :
|
| 18 |
+
- POST /api/v1/predict : Prédiction énergétique pour un bâtiment
|
| 19 |
+
- GET /api/v1/model/info : Informations sur le modèle ML
|
| 20 |
+
|
| 21 |
+
Auteur: François Hellebuyck
|
| 22 |
+
Projet: Building Energy Prediction API - OpenClassrooms Projet 5
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
import logging
|
| 26 |
+
|
| 27 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 28 |
+
|
| 29 |
+
from project5.schemas.building_model import BuildingFeatures, PredictionResponse
|
| 30 |
+
from project5.services.building_model_service import PredictionService
|
| 31 |
+
|
| 32 |
+
logger = logging.getLogger(__name__)
|
| 33 |
+
|
| 34 |
+
# Instance globale du service (peut être injectée via des dépendances)
|
| 35 |
+
prediction_service = PredictionService()
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def get_prediction_service() -> PredictionService:
|
| 39 |
+
"""
|
| 40 |
+
Injection de dépendance pour le service de prédiction.
|
| 41 |
+
|
| 42 |
+
Returns:
|
| 43 |
+
PredictionService: Instance du service de prédiction configuré
|
| 44 |
+
|
| 45 |
+
Note:
|
| 46 |
+
Cette fonction permet l'injection de dépendance FastAPI pour le service
|
| 47 |
+
de prédiction, facilitant les tests et la modularité.
|
| 48 |
+
"""
|
| 49 |
+
return prediction_service
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
router = APIRouter(prefix="/api/v1", tags=["predictions"])
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@router.post(
|
| 56 |
+
"/predict",
|
| 57 |
+
response_model=PredictionResponse,
|
| 58 |
+
summary="Prédire la consommation énergétique d'un bâtiment",
|
| 59 |
+
description="""
|
| 60 |
+
Effectue une prédiction de consommation énergétique basée sur les caractéristiques du bâtiment.
|
| 61 |
+
|
| 62 |
+
Cette endpoint utilise un modèle de machine learning (RandomForestRegressor) entraîné
|
| 63 |
+
sur les données énergétiques de Seattle pour prédire la consommation annuelle en kBTU.
|
| 64 |
+
|
| 65 |
+
Caractéristiques requises pour la prédiction :
|
| 66 |
+
- **neighborhood_encoded** : Quartier encodé numériquement (0-13, -1 pour UNKNOWN)
|
| 67 |
+
- **building_type_encoded** : Type de bâtiment encodé (0-7, -1 pour UNKNOWN)
|
| 68 |
+
- **largest_property_use_type_encoded** : Usage principal encodé (0-27, -1 pour UNKNOWN)
|
| 69 |
+
- **primary_property_type_encoded** : Type de propriété principal encodé
|
| 70 |
+
- **year_built** : Année de construction du bâtiment
|
| 71 |
+
- **number_of_buildings** : Nombre de bâtiments sur la propriété
|
| 72 |
+
- **number_of_floors** : Nombre d'étages
|
| 73 |
+
- **property_gfa_total** : Surface totale brute en pieds carrés
|
| 74 |
+
- **steam** : Utilisation de vapeur (0/1)
|
| 75 |
+
- **electricity** : Utilisation d'électricité (0/1)
|
| 76 |
+
- **natural_gas** : Utilisation de gaz naturel (0/1)
|
| 77 |
+
|
| 78 |
+
Le modèle retourne une prédiction de consommation énergétique avec un intervalle
|
| 79 |
+
de confiance basé sur la variance des arbres de la forêt aléatoire.
|
| 80 |
+
""",
|
| 81 |
+
responses={
|
| 82 |
+
200: {
|
| 83 |
+
"description": "Prédiction effectuée avec succès",
|
| 84 |
+
"content": {
|
| 85 |
+
"application/json": {
|
| 86 |
+
"example": {
|
| 87 |
+
"predicted_energy_use": 45678.9,
|
| 88 |
+
"confidence_interval": {"lower": 42000.5, "upper": 49357.3},
|
| 89 |
+
"model_version": "1.0",
|
| 90 |
+
"features_used": [
|
| 91 |
+
"neighborhood_encoded",
|
| 92 |
+
"building_type_encoded",
|
| 93 |
+
"year_built",
|
| 94 |
+
"property_gfa_total",
|
| 95 |
+
],
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
},
|
| 99 |
+
},
|
| 100 |
+
422: {
|
| 101 |
+
"description": "Données d'entrée invalides",
|
| 102 |
+
"content": {
|
| 103 |
+
"application/json": {
|
| 104 |
+
"example": {
|
| 105 |
+
"detail": [
|
| 106 |
+
{
|
| 107 |
+
"loc": ["body", "year_built"],
|
| 108 |
+
"msg": "ensure this value is greater than 1800",
|
| 109 |
+
"type": "value_error.number.not_gt",
|
| 110 |
+
}
|
| 111 |
+
]
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
},
|
| 115 |
+
},
|
| 116 |
+
500: {"description": "Erreur interne du serveur lors de la prédiction"},
|
| 117 |
+
},
|
| 118 |
+
)
|
| 119 |
+
async def predict_building_value(
|
| 120 |
+
features: BuildingFeatures,
|
| 121 |
+
service: PredictionService = Depends(get_prediction_service),
|
| 122 |
+
):
|
| 123 |
+
"""
|
| 124 |
+
Effectue une prédiction de consommation énergétique basée sur les caractéristiques du bâtiment.
|
| 125 |
+
|
| 126 |
+
Args:
|
| 127 |
+
features (BuildingFeatures): Caractéristiques encodées du bâtiment pour la prédiction
|
| 128 |
+
service (PredictionService): Service de prédiction injecté par FastAPI
|
| 129 |
+
|
| 130 |
+
Returns:
|
| 131 |
+
PredictionResponse: Prédiction avec consommation énergétique et intervalle de confiance
|
| 132 |
+
|
| 133 |
+
Raises:
|
| 134 |
+
HTTPException:
|
| 135 |
+
- 422 si les données d'entrée sont invalides
|
| 136 |
+
- 500 si erreur lors de la prédiction ML
|
| 137 |
+
|
| 138 |
+
Example:
|
| 139 |
+
POST /api/v1/predict
|
| 140 |
+
{
|
| 141 |
+
"neighborhood_encoded": 3,
|
| 142 |
+
"building_type_encoded": 1,
|
| 143 |
+
"largest_property_use_type_encoded": 18,
|
| 144 |
+
"primary_property_type_encoded": 18,
|
| 145 |
+
"year_built": 1995,
|
| 146 |
+
"number_of_buildings": 1,
|
| 147 |
+
"number_of_floors": 15,
|
| 148 |
+
"property_gfa_total": 50000.0,
|
| 149 |
+
"steam": 1,
|
| 150 |
+
"electricity": 1,
|
| 151 |
+
"natural_gas": 1
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
Note:
|
| 155 |
+
Les valeurs encodées doivent correspondre aux encodages utilisés lors
|
| 156 |
+
de l'entraînement du modèle. Utilisez l'endpoint /buildings/model/{id}
|
| 157 |
+
pour obtenir les données pré-encodées d'un bâtiment existant.
|
| 158 |
+
"""
|
| 159 |
+
try:
|
| 160 |
+
result = await service.predict(features)
|
| 161 |
+
return PredictionResponse(**result)
|
| 162 |
+
|
| 163 |
+
except Exception as e:
|
| 164 |
+
logger.error(f"Erreur dans la prédiction: {e}")
|
| 165 |
+
raise HTTPException(
|
| 166 |
+
status_code=500, detail=f"Erreur lors de la prédiction: {str(e)}"
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
@router.get(
|
| 171 |
+
"/model/info",
|
| 172 |
+
summary="Informations sur le modèle de machine learning",
|
| 173 |
+
description="""
|
| 174 |
+
Retourne des informations détaillées sur le modèle de prédiction énergétique.
|
| 175 |
+
|
| 176 |
+
Cette endpoint fournit des métadonnées essentielles sur le modèle ML utilisé :
|
| 177 |
+
- Version du modèle déployé
|
| 178 |
+
- Type d'algorithme (RandomForestRegressor)
|
| 179 |
+
- Nombre de caractéristiques utilisées
|
| 180 |
+
- Importance des caractéristiques (feature importance)
|
| 181 |
+
|
| 182 |
+
L'importance des caractéristiques indique quelles variables ont le plus
|
| 183 |
+
d'influence sur les prédictions :
|
| 184 |
+
- Valeurs plus élevées = plus d'importance
|
| 185 |
+
- Somme totale des importances = 1.0
|
| 186 |
+
- Utile pour comprendre quels facteurs influencent le plus la consommation
|
| 187 |
+
|
| 188 |
+
Ces informations sont utiles pour :
|
| 189 |
+
- Validation du modèle en production
|
| 190 |
+
- Debugging des prédictions
|
| 191 |
+
- Analyse de l'importance des variables
|
| 192 |
+
- Monitoring de la performance du modèle
|
| 193 |
+
""",
|
| 194 |
+
responses={
|
| 195 |
+
200: {
|
| 196 |
+
"description": "Informations du modèle récupérées avec succès",
|
| 197 |
+
"content": {
|
| 198 |
+
"application/json": {
|
| 199 |
+
"example": {
|
| 200 |
+
"model_version": "1.0",
|
| 201 |
+
"model_type": "RandomForestRegressor",
|
| 202 |
+
"feature_count": 11,
|
| 203 |
+
"feature_importance": {
|
| 204 |
+
"property_gfa_total": 0.45,
|
| 205 |
+
"year_built": 0.18,
|
| 206 |
+
"neighborhood_encoded": 0.12,
|
| 207 |
+
"building_type_encoded": 0.08,
|
| 208 |
+
"number_of_floors": 0.07,
|
| 209 |
+
"largest_property_use_type_encoded": 0.05,
|
| 210 |
+
"electricity": 0.02,
|
| 211 |
+
"natural_gas": 0.02,
|
| 212 |
+
"steam": 0.01,
|
| 213 |
+
},
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
},
|
| 217 |
+
},
|
| 218 |
+
500: {"description": "Erreur interne du serveur"},
|
| 219 |
+
},
|
| 220 |
+
)
|
| 221 |
+
async def get_model_info(service: PredictionService = Depends(get_prediction_service)):
|
| 222 |
+
"""
|
| 223 |
+
Retourne des informations détaillées sur le modèle de machine learning.
|
| 224 |
+
|
| 225 |
+
Args:
|
| 226 |
+
service (PredictionService): Service de prédiction injecté par FastAPI
|
| 227 |
+
|
| 228 |
+
Returns:
|
| 229 |
+
dict: Informations sur le modèle incluant version, type, et importance des caractéristiques
|
| 230 |
+
|
| 231 |
+
Raises:
|
| 232 |
+
HTTPException: Erreur 500 si problème lors de la récupération des informations
|
| 233 |
+
|
| 234 |
+
Example:
|
| 235 |
+
GET /api/v1/model/info
|
| 236 |
+
|
| 237 |
+
Note:
|
| 238 |
+
L'importance des caractéristiques est calculée par le RandomForestRegressor
|
| 239 |
+
et indique la contribution relative de chaque variable aux prédictions.
|
| 240 |
+
"""
|
| 241 |
+
try:
|
| 242 |
+
feature_importance = service.get_feature_importance()
|
| 243 |
+
return {
|
| 244 |
+
"model_version": service.model_version,
|
| 245 |
+
"model_type": "RandomForestRegressor",
|
| 246 |
+
"feature_count": len(service.feature_names),
|
| 247 |
+
"feature_importance": feature_importance,
|
| 248 |
+
}
|
| 249 |
+
except Exception as e:
|
| 250 |
+
logger.error(f"Erreur lors de la récupération des infos du modèle: {e}")
|
| 251 |
+
raise HTTPException(status_code=500, detail=f"Erreur: {str(e)}")
|
src/project5/routers/properties_routes.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Routes API pour la gestion des propriétés d'usage des bâtiments.
|
| 3 |
+
|
| 4 |
+
Ce module contient les endpoints FastAPI pour :
|
| 5 |
+
- Récupération de la liste des propriétés d'usage avec pagination
|
| 6 |
+
- Récupération d'une propriété spécifique par ID
|
| 7 |
+
- Gestion des erreurs et logging approprié
|
| 8 |
+
|
| 9 |
+
Les propriétés d'usage représentent les utilisations spécifiques des bâtiments
|
| 10 |
+
et sont plus granulaires que les catégories. Elles permettent une classification
|
| 11 |
+
fine pour les prédictions énergétiques du modèle ML.
|
| 12 |
+
|
| 13 |
+
Propriétés principales disponibles :
|
| 14 |
+
- OFFICE : Bureaux
|
| 15 |
+
- RETAIL STORE : Magasin de détail
|
| 16 |
+
- RESTAURANT : Restaurant
|
| 17 |
+
- K-12 SCHOOL : École primaire/secondaire
|
| 18 |
+
- COLLEGE/UNIVERSITY : Enseignement supérieur
|
| 19 |
+
- HOSPITAL : Hôpital
|
| 20 |
+
- HOTEL : Hôtel
|
| 21 |
+
- MULTIFAMILY HOUSING : Logement collectif
|
| 22 |
+
- WAREHOUSE : Entrepôt
|
| 23 |
+
- DATA CENTER : Centre de données
|
| 24 |
+
- Et autres usages spécialisés
|
| 25 |
+
|
| 26 |
+
Chaque propriété est liée à une catégorie parent et possède un model_id
|
| 27 |
+
utilisé par le modèle ML pour l'encodage numérique.
|
| 28 |
+
|
| 29 |
+
Endpoints disponibles :
|
| 30 |
+
- GET /api/v1/properties/ : Liste paginée des propriétés
|
| 31 |
+
- GET /api/v1/properties/{id} : Propriété spécifique par ID
|
| 32 |
+
|
| 33 |
+
Auteur: François Hellebuyck
|
| 34 |
+
Projet: Building Energy Prediction API - OpenClassrooms Projet 5
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
import logging
|
| 38 |
+
from typing import List
|
| 39 |
+
|
| 40 |
+
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
| 41 |
+
from sqlalchemy.orm import Session
|
| 42 |
+
|
| 43 |
+
from project5.database import get_db
|
| 44 |
+
from project5.schemas.property import PropertyList, PropertyResponse
|
| 45 |
+
from project5.services.property_service import PropertyService
|
| 46 |
+
|
| 47 |
+
logger = logging.getLogger(__name__)
|
| 48 |
+
|
| 49 |
+
router = APIRouter(prefix="/api/v1/properties", tags=["properties"])
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
@router.get(
|
| 53 |
+
"/",
|
| 54 |
+
response_model=List[PropertyList],
|
| 55 |
+
summary="Récupérer toutes les propriétés d'usage",
|
| 56 |
+
description="""
|
| 57 |
+
Retourne la liste paginée de toutes les propriétés d'usage des bâtiments disponibles dans le système.
|
| 58 |
+
|
| 59 |
+
Cette endpoint permet de récupérer l'ensemble des types d'usage spécifiques des
|
| 60 |
+
bâtiments utilisés pour une classification fine lors des prédictions énergétiques.
|
| 61 |
+
Les propriétés sont plus granulaires que les catégories et permettent de distinguer
|
| 62 |
+
des usages précis ayant des profils énergétiques différents.
|
| 63 |
+
|
| 64 |
+
Propriétés d'usage disponibles dans le système :
|
| 65 |
+
- **OFFICE** : Bureaux et espaces administratifs
|
| 66 |
+
- **RETAIL STORE** : Magasins et commerces de détail
|
| 67 |
+
- **RESTAURANT** : Restaurants et services de restauration
|
| 68 |
+
- **K-12 SCHOOL** : Écoles primaires et secondaires
|
| 69 |
+
- **COLLEGE/UNIVERSITY** : Établissements d'enseignement supérieur
|
| 70 |
+
- **HOSPITAL** : Hôpitaux et centres médicaux
|
| 71 |
+
- **HOTEL** : Hôtels et hébergements
|
| 72 |
+
- **MULTIFAMILY HOUSING** : Logements collectifs et appartements
|
| 73 |
+
- **WAREHOUSE** : Entrepôts et stockage
|
| 74 |
+
- **DATA CENTER** : Centres de données et serveurs
|
| 75 |
+
- **BANK BRANCH** : Agences bancaires
|
| 76 |
+
- **COURTHOUSE** : Tribunaux et bâtiments judiciaires
|
| 77 |
+
- **LIBRARY** : Bibliothèques
|
| 78 |
+
- **MEDICAL OFFICE** : Cabinets médicaux
|
| 79 |
+
- **PARKING** : Structures de stationnement
|
| 80 |
+
- Et autres usages spécialisés
|
| 81 |
+
|
| 82 |
+
Chaque propriété appartient à une catégorie parent et possède un model_id
|
| 83 |
+
unique utilisé par le modèle ML pour l'encodage numérique lors des prédictions.
|
| 84 |
+
|
| 85 |
+
Paramètres de pagination :
|
| 86 |
+
- skip : nombre d'enregistrements à ignorer (par défaut 0)
|
| 87 |
+
- limit : nombre maximum d'enregistrements à retourner (par défaut 100, max 1000)
|
| 88 |
+
""",
|
| 89 |
+
responses={
|
| 90 |
+
200: {
|
| 91 |
+
"description": "Liste des propriétés récupérée avec succès",
|
| 92 |
+
"content": {
|
| 93 |
+
"application/json": {
|
| 94 |
+
"example": [
|
| 95 |
+
{
|
| 96 |
+
"id": 20,
|
| 97 |
+
"model_id": 18,
|
| 98 |
+
"property_name": "OFFICE",
|
| 99 |
+
"category_id": 11,
|
| 100 |
+
"category_name": "Bureaux",
|
| 101 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 102 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"id": 24,
|
| 106 |
+
"model_id": 22,
|
| 107 |
+
"property_name": "RETAIL STORE",
|
| 108 |
+
"category_id": 18,
|
| 109 |
+
"category_name": "Commerce de détail",
|
| 110 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 111 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 112 |
+
},
|
| 113 |
+
]
|
| 114 |
+
}
|
| 115 |
+
},
|
| 116 |
+
},
|
| 117 |
+
500: {"description": "Erreur interne du serveur"},
|
| 118 |
+
},
|
| 119 |
+
)
|
| 120 |
+
def get_properties(
|
| 121 |
+
skip: int = Query(0, ge=0, description="Nombre d'éléments à ignorer (pagination)"),
|
| 122 |
+
limit: int = Query(
|
| 123 |
+
100, ge=1, le=1000, description="Nombre maximum d'éléments à retourner"
|
| 124 |
+
),
|
| 125 |
+
db: Session = Depends(get_db),
|
| 126 |
+
):
|
| 127 |
+
"""
|
| 128 |
+
Récupère la liste paginée des propriétés d'usage des bâtiments.
|
| 129 |
+
|
| 130 |
+
Args:
|
| 131 |
+
skip (int): Nombre d'enregistrements à ignorer pour la pagination
|
| 132 |
+
limit (int): Nombre maximum d'enregistrements à retourner
|
| 133 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 134 |
+
|
| 135 |
+
Returns:
|
| 136 |
+
List[PropertyList]: Liste des propriétés d'usage avec informations de catégorie
|
| 137 |
+
|
| 138 |
+
Raises:
|
| 139 |
+
HTTPException: Erreur 500 si problème lors de la récupération des données
|
| 140 |
+
|
| 141 |
+
Example:
|
| 142 |
+
GET /api/v1/properties/?skip=0&limit=30
|
| 143 |
+
|
| 144 |
+
Note:
|
| 145 |
+
Les propriétés sont référencées par leur model_id dans le modèle ML
|
| 146 |
+
pour effectuer une classification fine des usages lors des prédictions.
|
| 147 |
+
Chaque propriété hérite des caractéristiques de sa catégorie parent.
|
| 148 |
+
"""
|
| 149 |
+
try:
|
| 150 |
+
categories = PropertyService.get_all(db=db, skip=skip, limit=limit)
|
| 151 |
+
return categories
|
| 152 |
+
except Exception as e:
|
| 153 |
+
logger.error(f"Erreur lors de la récupération des properties: {e}")
|
| 154 |
+
raise HTTPException(
|
| 155 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 156 |
+
detail="Erreur lors de la récupération des properties",
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
@router.get(
|
| 161 |
+
"/{id}",
|
| 162 |
+
response_model=PropertyResponse,
|
| 163 |
+
summary="Récupérer une propriété d'usage par ID",
|
| 164 |
+
description="""
|
| 165 |
+
Retourne les détails complets d'une propriété d'usage spécifique.
|
| 166 |
+
|
| 167 |
+
Récupère toutes les informations d'une propriété d'usage incluant :
|
| 168 |
+
- Identifiant unique dans la base de données
|
| 169 |
+
- Identifiant utilisé par le modèle ML (model_id)
|
| 170 |
+
- Nom de la propriété d'usage
|
| 171 |
+
- Relation avec la catégorie parent (category_id et nom)
|
| 172 |
+
- Horodatages de création et modification
|
| 173 |
+
|
| 174 |
+
Les propriétés d'usage permettent une classification fine des bâtiments
|
| 175 |
+
selon leur utilisation spécifique, au-delà des catégories générales.
|
| 176 |
+
Elles sont essentielles pour les prédictions énergétiques car différents
|
| 177 |
+
usages ont des profils de consommation très différents :
|
| 178 |
+
|
| 179 |
+
- **OFFICE** vs **DATA CENTER** : consommation informatique différente
|
| 180 |
+
- **RESTAURANT** vs **RETAIL STORE** : besoins énergétiques distincts
|
| 181 |
+
- **HOSPITAL** vs **MEDICAL OFFICE** : intensité énergétique variable
|
| 182 |
+
- **K-12 SCHOOL** vs **COLLEGE/UNIVERSITY** : profils d'occupation différents
|
| 183 |
+
|
| 184 |
+
L'ID doit correspondre à une propriété existante dans la base de données.
|
| 185 |
+
""",
|
| 186 |
+
responses={
|
| 187 |
+
200: {
|
| 188 |
+
"description": "Propriété trouvée et retournée avec succès",
|
| 189 |
+
"content": {
|
| 190 |
+
"application/json": {
|
| 191 |
+
"example": {
|
| 192 |
+
"id": 20,
|
| 193 |
+
"model_id": 18,
|
| 194 |
+
"property_name": "OFFICE",
|
| 195 |
+
"category_id": 11,
|
| 196 |
+
"category_name": "Bureaux",
|
| 197 |
+
"created_at": "2025-09-09T09:56:21Z",
|
| 198 |
+
"updated_at": "2025-09-09T09:56:21Z",
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
},
|
| 202 |
+
},
|
| 203 |
+
404: {
|
| 204 |
+
"description": "Propriété non trouvée",
|
| 205 |
+
"content": {
|
| 206 |
+
"application/json": {
|
| 207 |
+
"example": {"detail": "Propriété avec l'ID 999 non trouvée"}
|
| 208 |
+
}
|
| 209 |
+
},
|
| 210 |
+
},
|
| 211 |
+
500: {"description": "Erreur interne du serveur"},
|
| 212 |
+
},
|
| 213 |
+
)
|
| 214 |
+
def get_property(id: int, db: Session = Depends(get_db)):
|
| 215 |
+
"""
|
| 216 |
+
Récupère une propriété d'usage spécifique par son ID.
|
| 217 |
+
|
| 218 |
+
Args:
|
| 219 |
+
id (int): Identifiant unique de la propriété à récupérer
|
| 220 |
+
db (Session): Session de base de données injectée par FastAPI
|
| 221 |
+
|
| 222 |
+
Returns:
|
| 223 |
+
PropertyResponse: Propriété d'usage avec détails complets et catégorie parent
|
| 224 |
+
|
| 225 |
+
Raises:
|
| 226 |
+
HTTPException:
|
| 227 |
+
- 404 si la propriété n'existe pas
|
| 228 |
+
- 500 si erreur lors de la récupération
|
| 229 |
+
|
| 230 |
+
Example:
|
| 231 |
+
GET /api/v1/properties/20
|
| 232 |
+
|
| 233 |
+
Note:
|
| 234 |
+
Le model_id retourné est utilisé par le modèle ML pour encoder
|
| 235 |
+
l'usage spécifique lors des prédictions énergétiques. Cette granularité
|
| 236 |
+
permet des prédictions plus précises que les catégories générales.
|
| 237 |
+
"""
|
| 238 |
+
try:
|
| 239 |
+
property = PropertyService.get_by_id(db=db, property_id=id)
|
| 240 |
+
return property
|
| 241 |
+
except Exception as e:
|
| 242 |
+
logger.error(f"Erreur lors de la récupération de la propriété: {e}")
|
| 243 |
+
raise HTTPException(
|
| 244 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 245 |
+
detail="Erreur lors de la récupération de la propriété",
|
| 246 |
+
)
|
src/project5/schemas/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Package des schémas Pydantic pour l'API de prédiction énergétique.
|
| 3 |
+
|
| 4 |
+
Ce package contient tous les schémas de validation et sérialisation des données
|
| 5 |
+
utilisés par l'API FastAPI pour la gestion des bâtiments et prédictions énergétiques.
|
| 6 |
+
|
| 7 |
+
Schémas disponibles :
|
| 8 |
+
- Neighborhood : Schémas pour les quartiers de Seattle
|
| 9 |
+
- NeighborhoodExists : Validation d'existence de quartier
|
| 10 |
+
- PaginatedNeighborhoods : Réponses paginées de quartiers
|
| 11 |
+
- HealthCheck : Schéma de vérification de l'état de l'API
|
| 12 |
+
- ServiceInfo : Informations sur le service API
|
| 13 |
+
- ErrorResponse : Schéma de réponse d'erreur standardisé
|
| 14 |
+
|
| 15 |
+
Architecture des schémas :
|
| 16 |
+
- Validation des données d'entrée avec Pydantic Field contraints
|
| 17 |
+
- Sérialisation automatique depuis les modèles SQLAlchemy
|
| 18 |
+
- Support de la pagination pour les listes d'éléments
|
| 19 |
+
- Gestion des erreurs avec messages structurés
|
| 20 |
+
- Configuration de JSON encoders pour les types spéciaux (Decimal, datetime)
|
| 21 |
+
|
| 22 |
+
Note :
|
| 23 |
+
Ces schémas assurent la validation côté API et la documentation automatique
|
| 24 |
+
OpenAPI/Swagger. Ils définissent les contrats d'interface entre clients et serveur.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
# Import du schema Neighborhood depuis le fichier approprié
|
| 28 |
+
from .neighborhood import (
|
| 29 |
+
ErrorResponse,
|
| 30 |
+
HealthCheck,
|
| 31 |
+
Neighborhood,
|
| 32 |
+
NeighborhoodExists,
|
| 33 |
+
PaginatedNeighborhoods,
|
| 34 |
+
ServiceInfo,
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
# Exposer les schemas au niveau du package
|
| 38 |
+
__all__ = [
|
| 39 |
+
"Neighborhood",
|
| 40 |
+
"NeighborhoodExists",
|
| 41 |
+
"PaginatedNeighborhoods",
|
| 42 |
+
"HealthCheck",
|
| 43 |
+
"ServiceInfo",
|
| 44 |
+
"ErrorResponse",
|
| 45 |
+
]
|
src/project5/schemas/building_energy_prediction.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Schémas Pydantic pour les prédictions énergétiques des bâtiments.
|
| 3 |
+
|
| 4 |
+
Ce module définit tous les schémas de validation et sérialisation pour les données
|
| 5 |
+
de prédictions énergétiques calculées par le modèle de machine learning.
|
| 6 |
+
|
| 7 |
+
Classes disponibles :
|
| 8 |
+
- BuildingEnergyPredictionsBase : Schéma de base avec les champs communs
|
| 9 |
+
- BuildingEnergyPredictionsCreate : Schéma pour la création de nouvelles prédictions
|
| 10 |
+
- BuildingEnergyPredictionsUpdate : Schéma pour la mise à jour de prédictions
|
| 11 |
+
- BuildingEnergyPredictionsResponse : Schéma de réponse avec ID et métadonnées
|
| 12 |
+
- BuildingEnergyPredictionsList : Schéma pour les réponses paginées
|
| 13 |
+
- BuildingEnergyPredictionsStats : Schéma pour les statistiques agrégées
|
| 14 |
+
|
| 15 |
+
Fonctionnalités :
|
| 16 |
+
- Validation automatique des types et contraintes (valeurs positives, etc.)
|
| 17 |
+
- Sérialisation automatique depuis les modèles SQLAlchemy
|
| 18 |
+
- Encodage JSON personnalisé pour Decimal et datetime
|
| 19 |
+
- Support de la pagination avec métadonnées (total, pages, etc.)
|
| 20 |
+
- Calculs statistiques (moyennes, min/max, compteurs)
|
| 21 |
+
|
| 22 |
+
Note :
|
| 23 |
+
Les prédictions énergétiques sont exprimées en kBTU (thousand British Thermal Units)
|
| 24 |
+
avec normalisation météorologique pour compenser les variations climatiques.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
from datetime import datetime
|
| 28 |
+
from decimal import Decimal
|
| 29 |
+
from typing import Optional
|
| 30 |
+
|
| 31 |
+
from pydantic import BaseModel, ConfigDict, Field
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class BuildingEnergyPredictionsBase(BaseModel):
|
| 35 |
+
"""
|
| 36 |
+
Schéma de base pour les prédictions énergétiques des bâtiments.
|
| 37 |
+
|
| 38 |
+
Cette classe définit les champs communs utilisés par tous les schémas
|
| 39 |
+
de prédictions énergétiques (création, mise à jour, réponse).
|
| 40 |
+
|
| 41 |
+
Attributes:
|
| 42 |
+
building_id (int): Identifiant du bâtiment concerné (obligatoire, > 0)
|
| 43 |
+
site_energy_use_wn_kbtu (Decimal, optional): Consommation énergétique
|
| 44 |
+
en kBTU avec normalisation météorologique (>= 0)
|
| 45 |
+
predicted (bool, optional): Indique si la valeur est prédite (True)
|
| 46 |
+
ou mesurée (False). Par défaut True.
|
| 47 |
+
updated_at (datetime, optional): Date et heure de la prédiction
|
| 48 |
+
avec fuseau horaire
|
| 49 |
+
|
| 50 |
+
Note:
|
| 51 |
+
La normalisation météorologique ajuste les données de consommation
|
| 52 |
+
pour compenser les variations climatiques saisonnières.
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
building_id: int = Field(
|
| 56 |
+
...,
|
| 57 |
+
description="Identifiant du bâtiment concerné (clé étrangère vers buildings.id)",
|
| 58 |
+
gt=0,
|
| 59 |
+
)
|
| 60 |
+
site_energy_use_wn_kbtu: Optional[Decimal] = Field(
|
| 61 |
+
None,
|
| 62 |
+
description="Consommation énergétique du site en kBTU avec normalisation météorologique",
|
| 63 |
+
ge=0,
|
| 64 |
+
)
|
| 65 |
+
predicted: Optional[bool] = Field(
|
| 66 |
+
True, description="Indique si la valeur est prédite (TRUE) ou mesurée (FALSE)"
|
| 67 |
+
)
|
| 68 |
+
updated_at: Optional[datetime] = Field(
|
| 69 |
+
None, description="Date et heure de la prédiction avec fuseau horaire"
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class BuildingEnergyPredictionsCreate(BuildingEnergyPredictionsBase):
|
| 74 |
+
"""
|
| 75 |
+
Schéma pour la création d'une nouvelle prédiction énergétique.
|
| 76 |
+
|
| 77 |
+
Hérite de BuildingEnergyPredictionsBase et utilise tous ses champs
|
| 78 |
+
sans modification. Ce schéma est utilisé lors des requêtes POST
|
| 79 |
+
pour créer de nouvelles prédictions dans la base de données.
|
| 80 |
+
|
| 81 |
+
Usage:
|
| 82 |
+
- Endpoint POST /building-energy-predictions/
|
| 83 |
+
- Validation automatique des contraintes de la classe de base
|
| 84 |
+
- Génération automatique de l'ID lors de l'insertion en base
|
| 85 |
+
|
| 86 |
+
Example:
|
| 87 |
+
{
|
| 88 |
+
"building_id": 12345,
|
| 89 |
+
"site_energy_use_wn_kbtu": 2500.75,
|
| 90 |
+
"predicted": true,
|
| 91 |
+
"updated_at": "2024-01-15T10:30:00Z"
|
| 92 |
+
}
|
| 93 |
+
"""
|
| 94 |
+
|
| 95 |
+
pass
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class BuildingEnergyPredictionsUpdate(BaseModel):
|
| 99 |
+
"""
|
| 100 |
+
Schéma pour la mise à jour partielle d'une prédiction énergétique.
|
| 101 |
+
|
| 102 |
+
Tous les champs sont optionnels pour permettre les mises à jour partielles.
|
| 103 |
+
Seuls les champs fournis seront mis à jour dans la base de données.
|
| 104 |
+
|
| 105 |
+
Attributes:
|
| 106 |
+
site_energy_use_wn_kbtu (Decimal, optional): Nouvelle consommation
|
| 107 |
+
énergétique en kBTU (>= 0)
|
| 108 |
+
predicted (bool, optional): Nouveau statut prédiction/mesure
|
| 109 |
+
updated_at (datetime, optional): Nouvelle date de prédiction
|
| 110 |
+
|
| 111 |
+
Usage:
|
| 112 |
+
- Endpoint PATCH /building-energy-predictions/{id}
|
| 113 |
+
- Permet la mise à jour de champs individuels
|
| 114 |
+
- building_id non modifiable (contrainte métier)
|
| 115 |
+
|
| 116 |
+
Note:
|
| 117 |
+
L'ID du bâtiment (building_id) n'est pas modifiable pour préserver
|
| 118 |
+
l'intégrité référentielle des données.
|
| 119 |
+
"""
|
| 120 |
+
|
| 121 |
+
site_energy_use_wn_kbtu: Optional[Decimal] = Field(
|
| 122 |
+
None,
|
| 123 |
+
description="Consommation énergétique du site en kBTU avec normalisation météorologique",
|
| 124 |
+
ge=0,
|
| 125 |
+
)
|
| 126 |
+
predicted: Optional[bool] = Field(
|
| 127 |
+
None, description="Indique si la valeur est prédite (TRUE) ou mesurée (FALSE)"
|
| 128 |
+
)
|
| 129 |
+
updated_at: Optional[datetime] = Field(
|
| 130 |
+
None, description="Date et heure de la prédiction avec fuseau horaire"
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
class BuildingEnergyPredictionsResponse(BuildingEnergyPredictionsBase):
|
| 135 |
+
"""
|
| 136 |
+
Schéma de réponse pour une prédiction énergétique individuelle.
|
| 137 |
+
|
| 138 |
+
Hérite de BuildingEnergyPredictionsBase et ajoute l'ID généré
|
| 139 |
+
automatiquement par la base de données. Inclut la configuration
|
| 140 |
+
de sérialisation pour la conversion depuis les modèles SQLAlchemy.
|
| 141 |
+
|
| 142 |
+
Attributes:
|
| 143 |
+
id (int): Identifiant unique de la prédiction (clé primaire)
|
| 144 |
+
+ tous les champs de BuildingEnergyPredictionsBase
|
| 145 |
+
|
| 146 |
+
Configuration:
|
| 147 |
+
- from_attributes=True : Conversion automatique depuis SQLAlchemy
|
| 148 |
+
- str_strip_whitespace=True : Nettoyage automatique des chaînes
|
| 149 |
+
- validate_assignment=True : Validation lors des assignations
|
| 150 |
+
- json_encoders : Encodage personnalisé Decimal->float, datetime->ISO
|
| 151 |
+
|
| 152 |
+
Usage:
|
| 153 |
+
- Réponses des endpoints GET /building-energy-predictions/
|
| 154 |
+
- Sérialisation automatique des objets BuildingEnergyPredictions
|
| 155 |
+
"""
|
| 156 |
+
|
| 157 |
+
id: int = Field(
|
| 158 |
+
...,
|
| 159 |
+
description="Identifiant unique de la prédiction (clé primaire auto-incrémentée)",
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
model_config = ConfigDict(
|
| 163 |
+
from_attributes=True,
|
| 164 |
+
# Permet la conversion automatique depuis les objets SQLAlchemy
|
| 165 |
+
str_strip_whitespace=True,
|
| 166 |
+
# Validation des types
|
| 167 |
+
validate_assignment=True,
|
| 168 |
+
# Configuration pour les décimales
|
| 169 |
+
json_encoders={
|
| 170 |
+
Decimal: lambda v: float(v) if v is not None else None,
|
| 171 |
+
datetime: lambda v: v.isoformat() if v is not None else None,
|
| 172 |
+
},
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
class BuildingEnergyPredictionsList(BaseModel):
|
| 177 |
+
"""
|
| 178 |
+
Schéma pour une liste paginée de prédictions énergétiques.
|
| 179 |
+
|
| 180 |
+
Structure de réponse standardisée pour les endpoints retournant
|
| 181 |
+
plusieurs prédictions avec support de la pagination.
|
| 182 |
+
|
| 183 |
+
Attributes:
|
| 184 |
+
items (list[BuildingEnergyPredictionsResponse]): Liste des prédictions
|
| 185 |
+
total (int): Nombre total d'éléments dans la base (toutes pages)
|
| 186 |
+
page (int): Numéro de la page actuelle (commence à 1)
|
| 187 |
+
size (int): Nombre d'éléments par page (1-100)
|
| 188 |
+
pages (int): Nombre total de pages calculé
|
| 189 |
+
|
| 190 |
+
Usage:
|
| 191 |
+
- Endpoint GET /building-energy-predictions/ avec paramètres de pagination
|
| 192 |
+
- Calcul automatique du nombre de pages : ceil(total / size)
|
| 193 |
+
- Navigation facilitée avec métadonnées de pagination
|
| 194 |
+
|
| 195 |
+
Example de réponse:
|
| 196 |
+
{
|
| 197 |
+
"items": [...],
|
| 198 |
+
"total": 1500,
|
| 199 |
+
"page": 2,
|
| 200 |
+
"size": 50,
|
| 201 |
+
"pages": 30
|
| 202 |
+
}
|
| 203 |
+
"""
|
| 204 |
+
|
| 205 |
+
items: list[BuildingEnergyPredictionsResponse]
|
| 206 |
+
total: int = Field(..., description="Nombre total d'éléments")
|
| 207 |
+
page: int = Field(..., description="Page actuelle", ge=1)
|
| 208 |
+
size: int = Field(..., description="Taille de la page", ge=1, le=100)
|
| 209 |
+
pages: int = Field(..., description="Nombre total de pages")
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
class BuildingEnergyPredictionsStats(BaseModel):
|
| 213 |
+
"""
|
| 214 |
+
Schéma pour les statistiques agrégées des prédictions énergétiques.
|
| 215 |
+
|
| 216 |
+
Fournit une vue d'ensemble des données de prédictions avec des
|
| 217 |
+
métriques statistiques calculées côté base de données.
|
| 218 |
+
|
| 219 |
+
Attributes:
|
| 220 |
+
total_predictions (int): Nombre total de prédictions en base
|
| 221 |
+
predicted_count (int): Nombre de valeurs prédites (ML)
|
| 222 |
+
measured_count (int): Nombre de valeurs mesurées (réelles)
|
| 223 |
+
avg_energy_consumption (Decimal, optional): Consommation moyenne en kBTU
|
| 224 |
+
min_energy_consumption (Decimal, optional): Consommation minimale en kBTU
|
| 225 |
+
max_energy_consumption (Decimal, optional): Consommation maximale en kBTU
|
| 226 |
+
latest_updated_at (datetime, optional): Date de la prédiction
|
| 227 |
+
la plus récente
|
| 228 |
+
|
| 229 |
+
Usage:
|
| 230 |
+
- Endpoint GET /building-energy-predictions/stats
|
| 231 |
+
- Tableaux de bord et reporting
|
| 232 |
+
- Monitoring de la qualité des prédictions
|
| 233 |
+
|
| 234 |
+
Note:
|
| 235 |
+
Les statistiques sont calculées en temps réel via des requêtes
|
| 236 |
+
d'agrégation SQL pour garantir la fraîcheur des données.
|
| 237 |
+
"""
|
| 238 |
+
|
| 239 |
+
total_predictions: int = Field(..., description="Nombre total de prédictions")
|
| 240 |
+
predicted_count: int = Field(..., description="Nombre de valeurs prédites")
|
| 241 |
+
measured_count: int = Field(..., description="Nombre de valeurs mesurées")
|
| 242 |
+
avg_energy_consumption: Optional[Decimal] = Field(
|
| 243 |
+
None, description="Consommation énergétique moyenne en kBTU"
|
| 244 |
+
)
|
| 245 |
+
min_energy_consumption: Optional[Decimal] = Field(
|
| 246 |
+
None, description="Consommation énergétique minimale en kBTU"
|
| 247 |
+
)
|
| 248 |
+
max_energy_consumption: Optional[Decimal] = Field(
|
| 249 |
+
None, description="Consommation énergétique maximale en kBTU"
|
| 250 |
+
)
|
| 251 |
+
latest_updated_at: Optional[datetime] = Field(
|
| 252 |
+
None, description="Date de la prédiction la plus récente"
|
| 253 |
+
)
|