L'API exposait /metrics, mais aucun collecteur ne le lisait : monitoring/ ne contenait que des .gitkeep. Sous le profil Compose `monitoring` : prometheus, alertmanager, grafana, postgres-exporter, node-exporter et cadvisor. Tous ont un mem_limit, pour environ 700 Mo au total sur la VM de 8 Go, et leurs interfaces n'écoutent que sur 127.0.0.1. Le profil est actif en prod via COMPOSE_PROFILES, donc à chaque déploiement, et se lance à la demande ailleurs (make monitoring-up). - Neuf règles d'alerte (API, base, hôte, cibles). Chacune a un cas dans les tests joués par `promtool test rules`, en CI comme par make monitoring-check. - Alertmanager route les alertes par courriel vers Mailpit ; un critical masque le warning de la même cible. - Grafana est provisionné : sources Prometheus et TimescaleDB, et trois tableaux de bord (API, données et dérive du modèle, infrastructure). - Le rôle PostgreSQL `supervision` est en lecture seule sur les seules tables métier (db/roles/supervision.sql), posé par make db-ensure-supervision et par stack-up quand le profil est actif. - Le jeton de /metrics passe à Prometheus en secret Compose (APP_METRICS_TOKEN) ; provision-host.sh génère ce secret et les deux autres. Backend : - un APP_METRICS_TOKEN vide vaut absent ; - les sondes de santé ne comptent plus dans les métriques ; - seaux de latence fins autour de 500 ms ; - un registre Prometheus par application, sans quoi toute application créée après la première (dans les tests) ne mesurait rien. Réf : #26
338 lines
17 KiB
Makefile
338 lines
17 KiB
Makefile
BACKEND := apps/backend
|
|
FRONTEND := apps/frontend
|
|
ML := ml
|
|
AIRFLOW := etl/airflow
|
|
E2E := tests/e2e
|
|
COMPOSE_PROD := docker compose -f docker-compose.yml -f docker-compose.prod.yml
|
|
|
|
# Piège : sans `export`, une valeur passée en ligne de commande n'atteindrait pas docker compose.
|
|
# PUBLIC_HOST retombe sur le `.env`, que make ne lit pas, puis sur la valeur de `.env.example`.
|
|
PUBLIC_HOST ?= $(shell sed -n 's/^PUBLIC_HOST=//p' .env 2>/dev/null | tail -1)
|
|
PUBLIC_HOST := $(or $(strip $(PUBLIC_HOST)),enervision.local)
|
|
export PUBLIC_HOST
|
|
ifdef ACME_EMAIL
|
|
export ACME_EMAIL
|
|
endif
|
|
|
|
# Piege : make ne lit pas `.env`, que seul docker compose interpole. Les cibles hors conteneur
|
|
# (ml-*, demo-data, db-wait) joignent la base par le port publie et ont besoin de ces valeurs.
|
|
env-val = $(shell sed -n 's/^$(1)=//p' .env 2>/dev/null | tail -1)
|
|
PG_USER := $(or $(strip $(call env-val,POSTGRES_USER)),enervision)
|
|
PG_PASSWORD := $(or $(strip $(call env-val,POSTGRES_PASSWORD)),change_me)
|
|
PG_DB := $(or $(strip $(call env-val,POSTGRES_DB)),enervision)
|
|
PG_PORT := $(or $(strip $(call env-val,POSTGRES_PORT)),5433)
|
|
ml-env-val = $(shell sed -n 's/^$(1)=//p' ml/.env 2>/dev/null | tail -1)
|
|
ML_ENV_DB_PASSWORD := $(call ml-env-val,MLFLOW_DB_PASSWORD)
|
|
AIRFLOW_PORT := $(or $(strip $(call env-val,AIRFLOW_PORT)),8080)
|
|
MAILPIT_UI_PORT := $(or $(strip $(call env-val,MAILPIT_UI_PORT)),8025)
|
|
ML_DATABASE_URL ?= postgresql+psycopg://$(PG_USER):$(PG_PASSWORD)@localhost:$(PG_PORT)/$(PG_DB)
|
|
export ML_DATABASE_URL
|
|
|
|
# Piege : la base des tests d'integration n'est pas la base de developpement. Ces tests ecrivent
|
|
# et suppriment des lignes, et leurs fixtures refusent de demarrer ailleurs que sur
|
|
# `enervision_test` (garde sur le nom, cf. ml/tests/conftest.py).
|
|
PG_TEST_DB ?= enervision_test
|
|
TEST_DATABASE_URL ?= postgresql+asyncpg://$(PG_USER):$(PG_PASSWORD)@localhost:$(PG_PORT)/$(PG_TEST_DB)
|
|
ML_TEST_DATABASE_URL ?= postgresql+psycopg://$(PG_USER):$(PG_PASSWORD)@localhost:$(PG_PORT)/$(PG_TEST_DB)
|
|
|
|
# Piege : ni make ni ces cibles ne lisent `.env` pour COMPOSE_PROFILES, que docker compose y lit
|
|
# seul. `stack-up` le relit ici pour savoir s'il doit poser le role `supervision` apres migration.
|
|
SUPERVISION := $(findstring monitoring,$(COMPOSE_PROFILES) $(call env-val,COMPOSE_PROFILES))
|
|
SERVICES_SUPERVISION := prometheus alertmanager grafana postgres-exporter node-exporter cadvisor
|
|
GRAFANA_PORT := $(or $(strip $(call env-val,GRAFANA_PORT)),3001)
|
|
PROMETHEUS_PORT := $(or $(strip $(call env-val,PROMETHEUS_PORT)),9090)
|
|
supervision-garde = for cle in APP_METRICS_TOKEN GRAFANA_ADMIN_PASSWORD SUPERVISION_DB_PASSWORD; do \
|
|
sed -n "s/^$$cle=//p" .env 2>/dev/null | tail -1 | grep -q . \
|
|
|| { echo "$$cle manquant dans .env, requis par la supervision (cf. .env.example)"; exit 1; }; \
|
|
done
|
|
MONITORING := docker compose --profile monitoring
|
|
PROMTOOL := $(MONITORING) run --rm --no-deps --entrypoint promtool prometheus
|
|
|
|
# Piege : `e2e-prepare` ajoute trois sites `demo-*` et des comptes `test-*` a la base visee. Elle
|
|
# vise la base de `make dev` ; ne jamais la lancer contre la recette ou la prod.
|
|
E2E_COMPTES ?= $(CURDIR)/$(E2E)/.comptes.json
|
|
E2E_API ?= http://localhost:$(or $(strip $(call env-val,BACKEND_PORT)),8000)
|
|
|
|
# Piege : `run` ne demarre que k6, la stack doit deja tourner. `--user` fait ecrire les rapports
|
|
# de tests/load/results avec l'uid du poste, pas celui de l'image (12345), qui n'y a pas acces.
|
|
k6-run = mkdir -p tests/load/results && $(COMPOSE_PROD) --profile load run --rm \
|
|
--user "$$(id -u):$$(id -g)" -e K6_WEB_DASHBOARD=true \
|
|
-e K6_WEB_DASHBOARD_EXPORT=/results/$(1)-$$(date +%Y%m%dT%H%M%S).html \
|
|
k6 run /scripts/$(1).js
|
|
|
|
# Le jeu historique s'arrete au 31/12/2024 : score et detection ancres a l'horloge reelle ne
|
|
# verraient qu'un parc muet depuis des mois. Cf. `--now` de enervision_ml.score.
|
|
DEMO_NOW ?= 2024-12-31T00:00:00Z
|
|
|
|
.DEFAULT_GOAL := help
|
|
.PHONY: help install install-backend install-frontend install-ml install-airflow \
|
|
dev dev-backend dev-frontend \
|
|
lint format typecheck test test-cov test-integration ml-test-integration \
|
|
test-chaine check \
|
|
openapi docker-build db-up db-down db-reset db-logs db-psql db-wait db-ensure-airflow \
|
|
migrate migrate-test bootstrap-admin services-up demo-data demo-data-force \
|
|
ml-lint ml-typecheck ml-test ml-check ml-train ml-score mlflow-up detect-alerts recommendations \
|
|
airflow-lint airflow-test airflow-check airflow-up airflow-down airflow-logs \
|
|
tls-selfsigned tls-acme tls-renew stack-up stack-down stack-logs \
|
|
e2e-install e2e-prepare e2e load-smoke load-test load-stress load-limits \
|
|
db-ensure-supervision monitoring-up monitoring-down monitoring-logs monitoring-check
|
|
|
|
help: ## Liste les cibles disponibles
|
|
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}'
|
|
|
|
install: install-backend install-frontend install-ml install-airflow ## Installe les dépendances backend, frontend, ML et Airflow
|
|
|
|
install-backend: ## Installe les dépendances du backend
|
|
cd $(BACKEND) && uv sync --all-groups
|
|
|
|
install-frontend: ## Installe les dépendances du frontend
|
|
cd $(FRONTEND) && npm ci
|
|
|
|
install-ml: ## Installe les dépendances du pipeline ML
|
|
cd $(ML) && uv sync --all-groups
|
|
|
|
install-airflow: ## Installe les dépendances de lint/test des DAGs Airflow
|
|
cd $(AIRFLOW) && uv sync --all-groups
|
|
|
|
dev: services-up migrate demo-data ## Lance toute la stack : base, Mailpit, Airflow, puis backend et frontend
|
|
@echo "airflow -> http://localhost:$(AIRFLOW_PORT) mailpit -> http://localhost:$(MAILPIT_UI_PORT)"
|
|
@trap 'kill 0' EXIT INT TERM; \
|
|
$(MAKE) --no-print-directory dev-backend & \
|
|
$(MAKE) --no-print-directory dev-frontend & \
|
|
wait
|
|
|
|
services-up: ## Démarre les services conteneurisés dont `make dev` dépend (base, Mailpit, Airflow)
|
|
docker compose up -d db mailpit
|
|
@$(MAKE) --no-print-directory db-wait
|
|
@$(MAKE) --no-print-directory db-ensure-airflow
|
|
docker compose up -d airflow-init airflow-apiserver airflow-scheduler airflow-dag-processor
|
|
|
|
dev-backend: ## Lance l'API seule en rechargement à chaud
|
|
@echo "backend -> http://localhost:8000 (docs sur /docs)"
|
|
cd $(BACKEND) && uv run uvicorn app.main:create_app --factory --reload --host 0.0.0.0 --port 8000
|
|
|
|
dev-frontend: ## Lance le frontend seul en rechargement à chaud
|
|
@echo "frontend -> http://localhost:4200"
|
|
cd $(FRONTEND) && npm start
|
|
|
|
lint: ## Analyse statique du backend
|
|
cd $(BACKEND) && uv run ruff check .
|
|
|
|
format: ## Formate et corrige le backend
|
|
cd $(BACKEND) && uv run ruff format . && uv run ruff check --fix .
|
|
|
|
typecheck: ## Vérifie le typage du backend
|
|
cd $(BACKEND) && uv run mypy app
|
|
|
|
test: ## Exécute les tests backend ne demandant pas de base
|
|
cd $(BACKEND) && uv run pytest --cov-fail-under=85
|
|
|
|
test-cov: ## Rapports de couverture HTML et XML, plus les résultats au format JUnit
|
|
cd $(BACKEND) && uv run pytest --cov-fail-under=85 --cov-report=html \
|
|
--cov-report=xml --junitxml=test-results/junit.xml
|
|
|
|
test-integration: ## Exécute les tests exigeant une base joignable
|
|
cd $(BACKEND) && uv run pytest -m integration
|
|
|
|
check: lint typecheck test ## Chaîne de vérification complète
|
|
|
|
openapi: ## Régénère apps/backend/openapi.json depuis les routes déclarées
|
|
cd $(BACKEND) && uv run python -m app.cli export-openapi
|
|
|
|
ml-lint: ## Analyse statique du pipeline ML
|
|
cd $(ML) && uv run ruff check .
|
|
|
|
ml-typecheck: ## Vérifie le typage du pipeline ML
|
|
cd $(ML) && uv run mypy enervision_ml tests
|
|
|
|
ml-test: ## Exécute les tests du pipeline ML (donnees synthetiques, sans base ni serveur MLflow)
|
|
cd $(ML) && uv run pytest
|
|
|
|
ml-check: ml-lint ml-typecheck ml-test ## Chaîne de vérification complète du pipeline ML
|
|
|
|
# La cible surcharge ML_DATABASE_URL, que ce Makefile exporte vers la base de développement : la
|
|
# garde du conftest ferait échouer la cible sans cette surcharge.
|
|
ml-test-integration: ML_DATABASE_URL := $(ML_TEST_DATABASE_URL)
|
|
ml-test-integration: ## Tests ML exigeant une base migrée. Faire `make db-up migrate-test` avant
|
|
cd $(ML) && uv run pytest -m integration --no-cov
|
|
|
|
test-chaine: ## Chaîne ML -> DB -> API, vrais binaires. Exige les deux environnements uv
|
|
cd $(BACKEND) && DATABASE_URL=$(TEST_DATABASE_URL) ML_PYTHON=$(CURDIR)/$(ML)/.venv/bin/python \
|
|
uv run pytest -m chaine --no-cov
|
|
|
|
ml-train: ## Entraine le modele LightGBM. CSV=chemin optionnel, sinon lit ML_DATABASE_URL
|
|
cd $(ML) && uv run python -m enervision_ml.train $(if $(CSV),--csv $(CSV),)
|
|
|
|
ml-score: ## Score le prochain pas horaire et l'ecrit dans `prediction`. CSV= et NOW= optionnels
|
|
cd $(ML) && uv run python -m enervision_ml.score $(if $(CSV),--csv $(CSV),) $(if $(NOW),--now $(NOW),)
|
|
|
|
mlflow-up: ## Démarre le serveur MLflow (tracking + registry) en conteneur. ml/.env requis
|
|
@test -n "$(strip $(ML_ENV_DB_PASSWORD))" \
|
|
|| { echo "MLFLOW_DB_PASSWORD absente de ml/.env (copier ml/.env.example)"; exit 1; }
|
|
@echo "$(ML_ENV_DB_PASSWORD)" | grep -qE '^[A-Za-z0-9]+$$' \
|
|
|| { echo "MLFLOW_DB_PASSWORD doit contenir uniquement lettres et chiffres (interpolee dans l'URI postgresql://)"; exit 1; }
|
|
cd $(ML) && docker compose -f docker-compose.mlflow.yml up -d --build
|
|
@echo "mlflow -> http://localhost:5000"
|
|
|
|
detect-alerts: ## Détecte les alertes internes depuis les lectures en base. SITE= et NOW= optionnels
|
|
cd $(BACKEND) && uv run python -m app.detection.internal_alerts $(if $(SITE),--site-id $(SITE),) $(if $(NOW),--now $(NOW),)
|
|
|
|
recommendations: ## Genere les recommandations depuis les alertes en base. SITE=identifiant optionnel
|
|
cd $(BACKEND) && uv run python -m app.cli generate-recommendations $(if $(SITE),--site-id $(SITE),)
|
|
|
|
airflow-lint: ## Analyse statique des DAGs Airflow
|
|
cd $(AIRFLOW) && uv run ruff check .
|
|
|
|
airflow-test: ## Verifie que les DAGs s'importent sans erreur et ont la structure attendue
|
|
cd $(AIRFLOW) && uv run pytest
|
|
|
|
airflow-check: airflow-lint airflow-test ## Chaîne de vérification complète des DAGs Airflow
|
|
|
|
airflow-up: db-ensure-airflow ## Démarre Airflow (api-server + scheduler + dag-processor, LocalExecutor). db-up requis avant.
|
|
docker compose up -d airflow-init airflow-apiserver airflow-scheduler airflow-dag-processor
|
|
@echo "airflow -> http://localhost:$${AIRFLOW_PORT:-8080}"
|
|
|
|
airflow-down: ## Arrête l'api-server, le scheduler et le dag-processor Airflow
|
|
docker compose stop airflow-apiserver airflow-scheduler airflow-dag-processor
|
|
|
|
airflow-logs: ## Suit les journaux du scheduler Airflow (où tournent les tâches, LocalExecutor)
|
|
docker compose logs -f airflow-scheduler
|
|
|
|
docker-build: ## Construit l'image du backend
|
|
docker build -t enervision-backend:local $(BACKEND)
|
|
|
|
tls-selfsigned: ## Génère le certificat de démonstration. PUBLIC_HOST=..., FORCE=1 pour écraser
|
|
./scripts/tls-selfsigned.sh $(if $(FORCE),--force,)
|
|
|
|
# Piège : l'image backend ne migre pas au démarrage, et `/health/ready` ne teste que la connexion
|
|
# et l'extension. Sans `alembic upgrade head`, la stack démarre verte sur une base sans schéma.
|
|
stack-up: ## Démarre la stack derrière le reverse proxy, puis migre la base. PUBLIC_HOST=... au besoin
|
|
@test -f infra/proxy/tls/fullchain.pem \
|
|
|| { echo "Aucun certificat dans infra/proxy/tls. Lancer d'abord make tls-selfsigned"; exit 1; }
|
|
@openssl x509 -in infra/proxy/tls/fullchain.pem -noout -checkhost "$(PUBLIC_HOST)" >/dev/null \
|
|
|| { echo "Le certificat ne couvre pas $(PUBLIC_HOST). Relancer make tls-selfsigned PUBLIC_HOST=$(PUBLIC_HOST) FORCE=1"; exit 1; }
|
|
@$(if $(SUPERVISION),$(supervision-garde),true)
|
|
$(COMPOSE_PROD) up -d --build
|
|
$(COMPOSE_PROD) exec -T backend alembic upgrade head
|
|
@$(if $(SUPERVISION),$(MAKE) --no-print-directory db-ensure-supervision,true)
|
|
|
|
stack-down: ## Arrête la stack complète en conservant les données
|
|
$(COMPOSE_PROD) stop
|
|
|
|
stack-logs: ## Suit les journaux du reverse proxy
|
|
$(COMPOSE_PROD) logs -f proxy
|
|
|
|
tls-acme: ## Demande un certificat Let's Encrypt. PUBLIC_HOST public et ACME_EMAIL requis
|
|
@test "$(PUBLIC_HOST)" != enervision.local \
|
|
|| { echo "PUBLIC_HOST doit être un domaine public résolvable, pas le nom de démonstration"; exit 1; }
|
|
$(COMPOSE_PROD) --profile acme run --rm certbot certonly --webroot -w /var/www/certbot \
|
|
-d $(PUBLIC_HOST) \
|
|
--email $${ACME_EMAIL:?ACME_EMAIL=... requis} \
|
|
--agree-tos --no-eff-email --deploy-hook /deploy-hook.sh
|
|
$(COMPOSE_PROD) exec proxy nginx -s reload
|
|
|
|
tls-renew: ## Renouvelle les certificats Let's Encrypt et recharge le proxy
|
|
$(COMPOSE_PROD) --profile acme run --rm certbot renew --deploy-hook /deploy-hook.sh
|
|
$(COMPOSE_PROD) exec proxy nginx -s reload
|
|
|
|
e2e-install: ## Installe Playwright et Chromium pour les tests de bout en bout
|
|
cd $(E2E) && npm ci && npx playwright install chromium
|
|
|
|
e2e-prepare: ## Sème le jeu de démonstration et crée les comptes de test sur la base de `make dev`
|
|
docker compose exec -T db psql -U $(PG_USER) -d $(PG_DB) -v ON_ERROR_STOP=1 < db/seeds/demo.sql
|
|
cd $(BACKEND) && BASE_URL=$(E2E_API) COMPTES_FICHIER=$(E2E_COMPTES) ADMIN_SUPPLEMENTAIRE=1 \
|
|
../../scripts/comptes-test.sh
|
|
|
|
e2e: ## Joue les parcours Playwright. E2E_BASE_URL= optionnel (défaut http://localhost:4200)
|
|
cd $(E2E) && E2E_COMPTES=$(E2E_COMPTES) npx playwright test
|
|
|
|
load-smoke: ## Tir k6 d'une minute. K6_EMAIL= et K6_PASSWORD= d'un lecteur, K6_BASE_URL= optionnel
|
|
$(call k6-run,smoke)
|
|
|
|
load-test: ## Charge nominale k6, 50 utilisateurs pendant 8 minutes. Rapport HTML dans tests/load/results
|
|
$(call k6-run,charge)
|
|
|
|
load-stress: ## Monte le débit jusqu'à la rupture de l'API. Sur la VM, la prod partage la machine
|
|
$(call k6-run,stress)
|
|
|
|
load-limits: ## Vérifie par le proxy que nginx limite le débit d'une même adresse (429)
|
|
$(call k6-run,limitation-debit)
|
|
|
|
# Piege : le mot de passe est lu dans `.env` par le shell et passe a psql sur son entree
|
|
# standard. Developpe par make, il apparaitrait en clair dans la ligne de commande (`ps`).
|
|
db-ensure-supervision: ## Crée ou réaligne le rôle `supervision`, en lecture seule, de Grafana et de l'exportateur
|
|
@mdp="$$(sed -n 's/^SUPERVISION_DB_PASSWORD=//p' .env 2>/dev/null | tail -1)"; \
|
|
[ -n "$$mdp" ] || { echo "SUPERVISION_DB_PASSWORD manquant dans .env"; exit 1; }; \
|
|
{ printf '\\set mot_de_passe %s\n' "$$mdp"; cat db/roles/supervision.sql; } \
|
|
| docker compose exec -T db psql -U $(PG_USER) -d $(PG_DB) -v ON_ERROR_STOP=1 -v base=$(PG_DB) -q
|
|
|
|
monitoring-up: ## Démarre la supervision sur la stack en cours : Prometheus, Alertmanager, Grafana, exporteurs
|
|
@$(supervision-garde)
|
|
$(MONITORING) up -d --no-deps $(SERVICES_SUPERVISION)
|
|
@$(MAKE) --no-print-directory db-ensure-supervision
|
|
@echo "grafana -> http://localhost:$(GRAFANA_PORT) prometheus -> http://localhost:$(PROMETHEUS_PORT)"
|
|
|
|
monitoring-down: ## Arrête la supervision en conservant ses données
|
|
$(MONITORING) stop $(SERVICES_SUPERVISION)
|
|
|
|
monitoring-logs: ## Suit les journaux de Prometheus, Alertmanager et Grafana
|
|
$(MONITORING) logs -f prometheus alertmanager grafana
|
|
|
|
monitoring-check: ## Valide la configuration de supervision et joue les tests des règles d'alerte, comme la CI
|
|
$(PROMTOOL) check config /etc/prometheus/prometheus.yml
|
|
$(PROMTOOL) test rules /etc/prometheus/tests/enervision.test.yml
|
|
$(MONITORING) run --rm --no-deps --entrypoint amtool alertmanager check-config /etc/alertmanager/alertmanager.yml
|
|
@for tableau in monitoring/grafana/dashboards/*.json; do jq empty "$$tableau" || exit 1; done
|
|
|
|
db-up: ## Démarre la base PostgreSQL TimescaleDB
|
|
docker compose up -d db
|
|
|
|
db-down: ## Arrête la base en conservant ses données
|
|
docker compose stop db
|
|
|
|
db-reset: ## Détruit la base et rejoue db/init
|
|
docker compose down -v && docker compose up -d db
|
|
|
|
db-logs: ## Suit les journaux de la base
|
|
docker compose logs -f db
|
|
|
|
db-psql: ## Ouvre une session psql sur la base applicative
|
|
docker compose exec db psql -U $${POSTGRES_USER:-enervision} -d $${POSTGRES_DB:-enervision}
|
|
|
|
db-wait: ## Attend que la base accepte les connexions
|
|
@for _ in $$(seq 1 60); do \
|
|
docker compose exec -T db pg_isready -U $(PG_USER) -d $(PG_DB) >/dev/null 2>&1 && exit 0; \
|
|
sleep 1; \
|
|
done; \
|
|
echo "La base n'accepte toujours pas de connexion apres 60s"; exit 1
|
|
|
|
# Piege : db/init ne rejoue qu'a la premiere initialisation du volume. Un `pgdata` cree avant
|
|
# db/init/120-airflow-database.sql n'a pas de base `airflow`, et airflow-init boucle dessus.
|
|
db-ensure-airflow: ## Crée la base de métadonnées Airflow si le volume pgdata est antérieur à db/init/120
|
|
@docker compose exec -T db psql -U $(PG_USER) -d postgres -tAc \
|
|
"SELECT 1 FROM pg_database WHERE datname = 'airflow'" | grep -q 1 \
|
|
|| docker compose exec -T db psql -U $(PG_USER) -d postgres -c "CREATE DATABASE airflow"
|
|
|
|
migrate: ## Applique les migrations Alembic
|
|
cd $(BACKEND) && uv run alembic upgrade head
|
|
|
|
migrate-test: ## Applique les migrations sur enervision_test, la base des tests d'intégration
|
|
cd $(BACKEND) && DATABASE_URL=$(TEST_DATABASE_URL) uv run alembic upgrade head
|
|
|
|
bootstrap-admin: ## Crée le premier administrateur, mot de passe saisi au clavier
|
|
cd $(BACKEND) && uv run python -m app.cli create-admin --email $${EMAIL:?EMAIL=... requis}
|
|
|
|
demo-data: ## Renseigne prédictions, alertes et recommandations si elles manquent. NOW= optionnel
|
|
@nombre=$$(docker compose exec -T db psql -U $(PG_USER) -d $(PG_DB) -tAc 'SELECT count(*) FROM alert') \
|
|
|| { echo "demo-data : base injoignable ou migrations non appliquees"; exit 1; }; \
|
|
if [ "$$nombre" = 0 ]; then \
|
|
$(MAKE) --no-print-directory demo-data-force; \
|
|
else \
|
|
echo "demo-data : $$nombre alerte(s) deja en base (make demo-data-force pour rejouer)"; \
|
|
fi
|
|
|
|
demo-data-force: ## Rejoue le peuplement sans regarder l'existant. Les trois etapes sont idempotentes
|
|
$(MAKE) --no-print-directory ml-score NOW=$(DEMO_NOW)
|
|
$(MAKE) --no-print-directory detect-alerts NOW=$(DEMO_NOW)
|
|
$(MAKE) --no-print-directory recommendations
|