Plateforme d'analyse de conformité de devis pour accélérer la rénovation énergétique des logements en simplifiant l'instruction des dossiers d'aide.
- Git pour cloner le repository
- Docker Desktop (recommandé, pour l'exécution avec Docker)
si pas Docker :
- Ruby 3.x voir
.ruby-version
- Node.js >= 18 voir
package.json
- PostgreSQL 16 voir
docker-compose.yml
Clonez le repository et installez les dépendances :
git clone https://github.com/MTES-MCT/mon-devis-sans-oublis-backend.git
cd mon-devis-sans-oublis-backend
docker compose up
Configurez les variables d'environnement selon votre méthode d'exécution :
- Copiez le fichier
.env.example
en.env.local
:
cp .env.example .env.local
- Éditez le fichier
.env.local
avec les valeurs réelles pour votre environnement de développement.
VARIABLE=
). Si vous n'avez pas besoin d'une variable, commentez-la avec #
ou supprimez la ligne complètement.
- Copiez le fichier
.env.example
en.env.docker
:
cp .env.example .env.docker
- Éditez le fichier
.env.docker
avec les valeurs appropriées pour l'environnement Docker.
VARIABLE=
). Si vous n'avez pas besoin d'une variable, commentez-la avec #
ou supprimez la ligne complètement.
Variable | Description | Exemple | Requis |
---|---|---|---|
ADMIN_EMAILS |
Mail ProConnect pouvant accédant aux Back Office | toto@gouv.fr,tata@gouv.fr |
Optionnel |
ALBERT_API_KEY |
longueClé |
Requis | |
ALBERT_MODEL |
Modèle Albert utilisé par défaut si disponible | neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8 |
Optionnel |
APPLICATION_HOST |
Host du backend pour générer des liens et la connexion OAuth | http://localhost:3000 , https://api.mon-devis-sans-oublis.beta.gouv.fr |
Requis |
APP_ENV |
Environnement applicatif, différent du RAILS_ENV technique | development , staging , production |
Requis |
BREVO_API_KEY |
Pour envoi de mails | longueClé |
Optionnel |
BREVO_SMTP_USER_NAME |
longueClé |
Optionnel | |
BREVO_SMTP_USER_PASSWORD |
longueClé |
Optionnel | |
DATABASE_URL |
URI de connexion à la base PostgreSQL | postgresql://postgres:dummy@localhost:5433/development , $SCALINGO_POSTGRESQL_URL |
Requis |
DEFAULT_EMAIL_FROM |
toto@gouv.fr |
Optionnel | |
FRONTEND_APPLICATION_HOST |
Host du frontend pour autoriser API | http://localhost:3001 , https://mon-devis-sans-oublis.beta.gouv.fr |
Optionnel |
GOOD_JOB_PASSWORD |
Mot de passe accès au Back Office Jobs | secret |
Requis |
GOOD_JOB_USERNAME |
Utilisateur accès au Back Office Jobs | secret |
Requis |
MATOMO_SITE_ID |
123 |
Optionnel | |
MATOMO_TOKEN_AUTH |
hash |
Optionnel | |
MDSO_API_KEY_FOR_MDSO |
Clé API pour frontend | hash via rake secret |
Optionnel |
MDSO_API_KEY_FOR_PARTNER1 |
Clé API pour PARTNER1 | hash via rake secret |
Optionnel |
MDSO_API_KEY_FOR_PARTNER2 |
Clé API pour PARTNER2 | hash via rake secret |
Optionnel |
MDSO_API_PASSWORD |
Ancienne clé API pour frontend | hash via rake secret |
Optionnel |
MDSO_OCR_API_KEY |
Clé API du système OCR MDSO | Optionnel | |
MDSO_OCR_HOST |
Host du système OCR MDSO | Optionnel | |
MDSO_OCR_MODEL |
Modèle du système OCR MDSO utilisé par défaut si disponible | Optionnel | |
MDSO_SITE_PASSWORD |
Ancienne clé accès au Back Office | hash via rake secret |
Optionnel |
MISTRAL_API_KEY |
longueClé |
Requis | |
MISTRAL_MODEL |
Modèle Mistral utilisé par défaut si disponible | mistral-large-latest |
Optionnel |
PROCONNECT_CLIENT_ID |
hash |
Optionnel | |
PROCONNECT_CLIENT_SECRET |
hash |
Optionnel | |
PROCONNECT_DOMAIN |
https://auth.agentconnect.gouv.fr/api/v2 , https://fca.integ01.dev-agentconnect.fr/api/v2 |
Optionnel | |
QUOTE_CHECK_EMAIL_RECIPIENTS |
Emails pour être informé des dépôts | toto@gouv.fr,tata@gouv.fr |
Optionnel |
SENTRY_DSN |
DSN Sentry pour le tracking d'erreurs | https://xxx@sentry.io/xxx |
Optionnel |
SENTRY_ENVIRONMENT |
Environnement Sentry pour le tracking d'erreurs | $APP_ENV |
Optionnel |
Scalingo est notre hébergeur type PaaS applicatif :
APPLICATION_HOST=https://api.mon-devis-sans-oublis.beta.gouv.fr
APP_ENV=staging
DATABASE_URL=$SCALINGO_POSTGRESQL_URL
FRONTEND_APPLICATION_HOST=https://staging.mon-devis-sans-oublis.beta.gouv.fr
# SCALINGO_POSTGRESQL_URL=générer par Scalingo
APPLICATION_HOST=https://api.staging.mon-devis-sans-oublis.beta.gouv.fr
APP_ENV=production
DATABASE_URL=$SCALINGO_POSTGRESQL_URL
FRONTEND_APPLICATION_HOST=https://mon-devis-sans-oublis.beta.gouv.fr
# SCALINGO_POSTGRESQL_URL=générer par Scalingo
- Ruby on Rails version 8 comme boîte à outil et socle technique applicatif ;
- le DSFR pour réutiliser les éléments graphiques officiels via la librairie de composants DSFR
- PostgreSQL comme base de données pour stocker les données ;
- des solutions de LLM pour interroger les devis, via la boîte à outils LangChain ***Albert API d'Etalab *** Mistral.ai : données publiques et/ou anonymisées *** Ollama : un modèle Llama local
- l'API Data de l'ADEME pour croiser les données d'entreprises qualifiées ;
- des annuaires officiels de professionnels pour croiser des données ;
Publi.codes pour un moteur de validation basé sur des règles(plus utilisé pour le moment) ;- Sentry pour monitorer et être alerté en cas d'erreur ;
- Matomo pour mesurer et comprendre l'usage via des analytics ;
- RSpec comme framework de tests ;
- Rswag comme outil de documentation au format Swagger/ OpenAPI de l'API à travers des tests ;
- Rubocop (RSpec et Rails) pour le linting ;
- Docker pour avoir un environnement de développement ;
- ClamAV pour scanner les fichiers déposés.
sequenceDiagram
actor User as Usager
participant Frontend as Interface MDSO Frontend
participant Backend as Interface MDSO Backend
participant QuoteCheckCheckJob as Process traitement
participant Albert LLM as API Albert AI LLM
participant Albert OCR as API Albert AI OCR LLM
participant MDSO OCR
participant Mistral LLM as API Mistral AI LLM
participant Tesseract as Tesseract OCR
participant BO as Back Office MDSO
User->>Frontend: Choisi un dossier de rénovaiton d'ampleur donc multi-devis
Frontend->>Backend: Créer un QuotesCase pour rassembler le dossier et les documents
Frontend->>Backend: Transmet les fichiers un à un
Backend->>Backend: Sauvegarde les fichiers QuoteFiles et génère des QuoteChecks associé au QuotesCase commun
Backend->>Frontend: Identifiant pour suivre les statuts du QuotesCase global et des QuoteChecks
User->>Frontend: Dépose un document type PDF
Frontend->>Backend: Transmet le fichier
Backend->>Backend: Sauvegarde le fichier QuoteFile et génère un QuoteCheck
Backend->>Frontend: Identifiant pour suivre le statut du QuoteCheck
activate QuoteCheckCheckJob
Backend->>QuoteCheckCheckJob: process asynchrone démarrage
Backend-->>Backend: Transformation du PDF en images par page (QuoteFileImagifyPdfJob)
Backend-->>Backend: Vérification de la non présence de virus (QuoteFileSecurityScanJob)
QuoteCheckCheckJob->>QuoteCheckCheckJob: Extraction automatique du texte du PDF si bien formatté
QuoteCheckCheckJob->>QuoteCheckCheckJob: Sinon extraction du texte via OCR (Albert / MDSO / Mistral / Tesseract) UNIQUEMENT VIA BO
QuoteCheckCheckJob->>QuoteCheckCheckJob: Extraction des données du texte via méthode naïve
QuoteCheckCheckJob->>QuoteCheckCheckJob: Réduction du texte si conditions générales
QuoteCheckCheckJob<<->>Albert LLM: Extraction des données personnelles et administratives
QuoteCheckCheckJob<<->>SIRENE API: Extension des données commerciales via recherche SIRET
QuoteCheckCheckJob<<->>ADEME API: Extension des données commerciales et certifications via recherche SIRET
QuoteCheckCheckJob->>QuoteCheckCheckJob: Anonymisation du texte
QuoteCheckCheckJob->>Frontend: Si erreur lecture ou texte vide renvoit d'une erreur
QuoteCheckCheckJob<<->>Mistral LLM: Extraction des données gestes et caractéristiques du texte anonymisé
QuoteCheckCheckJob->>QuoteCheckCheckJob: Validation des données selon algorithme Ruby maison et ajout d'erreurs, dont vérification cohérence avec données externes (SIRENE, ADEME)
QuoteCheckCheckJob->>Backend: retours avec données et erreurs
deactivate QuoteCheckCheckJob
Backend->>Frontend: Retour API QuotesCase et/ou QuoteCheck(s) avec erreurs cohérence dossier à la volée et affichage du résultat
Nous suivons les recommendations et les conventions du framework Ruby on Rails et de la communauté.
- dossier
lib
: pour les parties isolées qui pourraient être externalisées, comme la communication avec des services externes - dossier
app/services
: pour organiser la logique métier propre et interne à notre projet
Les fichiers devis sont traités par le QuoteChecksController
qui les envoient aux services:
QuoteReader
lisant le devis brut puis extractant les information du devis de manière naïve en se basant sur le texte du PDF et via solutions LLM avec croisement de données d'annuaires publiques de la rénovation- puis ces attributs de devis sont vérifier par le
QuoteValdiator
qui controlle un ensemble de règles et renvoit les erreurs correspondantes
Différentes briques sont mises à contribution et encore en évaluation via le projet dédié mon-devis-sans-oublis-backend-ocr
- pour la reconnaissance des images et lire leur contenu via OCR
- Surya (Python)
- tesseract (natif)
- pour transformer les PDF en images
- librairie Poppler
pdftoppm
(natif) - la gem MiniMagick (IM)
mini_magick
avec ImageMagick 6.9 (comme sur Scalingo) (natif)
- librairie Poppler
- vérifier la disponibilité du service via
QuoteReader::Image::MdsoOcr.new("", "").models
- ajouter une class dédiée type
QuoteReader::Image:MdsoOcrMarker
- ajouter le nom de la classe dans
config/custom.rb
docker compose exec web rake 'quote_checks:create[tmp/devis_tests/DC004200PAC-Aireau+Chauffe eau thermo.pdf]' | less
quote_check_id = "b9705194-02aa-4db7-bc38-5fc2dcb6ce58"
QuoteCheckCheckJob.perform_later(quote_check_id)
quote_check_id = "47db654b-a9fb-453b-b36f-b0c362279233"
quote_check = QuoteCheck.find(quote_check_id)
file_text = quote_check.file_text || quote_check.text
quote_reader = QuoteReader::Global.new(
quote_check.file.content,
quote_check.file.content_type,
quote_file: quote_check.file
)
private_attributes = quote_check.private_data_qa_attributes || {}
private_extended_attributes = TrackingHash.deep_merge_if_absent(
private_attributes,
ExtendedData.new(private_attributes).extended_attributes
)
# From quote_reader.read(file_text:)
anonymised_text = QuoteReader::Anonymiser.new(file_text).anonymised_text(private_extended_attributes)
quote_check_id = "76c35e1c-4d8d-479d-a62a-4f36511a5041"
QuoteCheck.find(quote_check_id).update!(validation_errors: nil, validation_error_edits: nil)
- au format REST JSON
- protéger via authentification HTTP Basic avec Bearer hashé
- voir fichier de documentation de l'API au format OpenAPI Swagger et interface bac à sable interractif sur
/api-docs
- regénération et mise à jour de la documentation à partir des spécifications tests via
make doc
- ajouter ou modifier la variable d'environnement type
MDSO_API_KEY_FOR_[PARTNER]
exempleMDSO_API_KEY_FOR_AMI
via le dashboard Scalingo onglet Environnement dans le contexte souhaitéstaging
/production
avec une valeur générée viarails secret
par exemple - redémarrer l'application via le dashboard Scalingo onglet Ressources
- vérifier sur le back office MDSO onglet "API Keys" la présence de l'accès
- tester si besoin via le playground API doc du contexte correspondant
brew install tesseract tesseract-lang
mkdir -p /opt/homebrew/share/tessdata
cd /opt/homebrew/share/tessdata
curl -O https://github.com/tesseract-ocr/tessdata_best/raw/main/fra.traineddata
# check that you really download the file and it's not empty
Un tableau de suivis des devis soumis est disponible sur /mdso/admin sous mot de passe hors développement.
Elles sont listées dans la base de données PostgreSQL via le librairie good_job
.
Un panneau de suivis est disponible sur /mdso_good_job/ sous mot de passe hors développement.
Ils sont envoyés en asynchrones via le service Brevo.
Une cinématique GitHub Action est founie qui lance :
- le linting via Rubocop ;
- les tests unitaires ia RSpec ;
- les tests d'intégration.
Cette cinématique commence d'abord par construire l'image Docker qu'elle transmet ensuite aux trois étapes ci-dessus, ce qui évite de répéter trois fois l'installation et la configuration du projet sans sacrifier le parallèlisme de ces étapes.
Le back-end dispose d'un système d'export automatisé qui permet de copier et anonymiser les données de production vers une base de données dédiée à Metabase pour les analyses et tableaux de bord.
Les données sont exportées dans un schéma dédié mdso_analytics
pour une organisation claire dans Metabase.
DB Source (Production)
↓ (anonymisation)
Schéma temporaire export_anonymized
↓ (export CSV)
Fichiers CSV temporaires
↓ (import)
DB Metabase → Schéma mdso_analytics
Le processus d'anonymisation est géré par 6 scripts situés dans le dossier db/scripts
:
export-db-metabase.sh
: Script bash d'orchestration complète
anonymize-data.sql
: Création des tables avec données anonymisées dans un schéma temporaireexport-anonymized-data.sql
: Export des données anonymisées vers fichiers CSVcleanup-metabase.sql
: Nettoyage et recréation du schémamdso_analytics
import-csv-to-metabase.sql
: Import des CSV vers le schémamdso_analytics
cleanup-anonymized-source-data.sql
: Suppression du schéma temporaire dans la DB source
L'export nécessite ces variables d'environnement sur l'application backend :
Variable | Description | Source | Requis |
---|---|---|---|
DATABASE_URL |
URL de la base de données backend | Automatiquement configurée par Scalingo | ✅ |
METABASE_DATA_DB_URL |
URL de la base de données Metabase | À configurer manuellement | ✅ |
ENABLE_METABASE_EXPORT |
Active/désactive l'export automatique | true ou false |
✅ |
# Activer l'export (recommandé pour la production)
scalingo --app mon-devis-sans-oublis-backend-prod env-set ENABLE_METABASE_EXPORT="true"
# Désactiver l'export (recommandé pour staging/dev)
scalingo --app mon-devis-sans-oublis-backend-staging env-set ENABLE_METABASE_EXPORT="false"
Comportement :
- Si
ENABLE_METABASE_EXPORT=true
: L'export s'exécute normalement - Si
ENABLE_METABASE_EXPORT=false
ou non définie : L'export se termine proprement sans erreur - Message explicite dans les logs pour indiquer l'état (activé/désactivé)
# 1. Récupérer l'URL de la DB Metabase
scalingo --app mon-devis-metabase env | grep DATABASE_URL
# 2. Configurer cette URL sur l'app backend
scalingo --app mon-devis-sans-oublis-backend-staging env-set \
METABASE_DATA_DB_URL="postgresql://user:password@host:port/database"
# 3. Vérifier la configuration
scalingo --app mon-devis-sans-oublis-backend-staging env | grep DATABASE
quote_checks
- Analyses de devis principalesquotes_cases
- Dossiers/cas de rénovationquote_check_feedbacks
- Retours utilisateursquote_error_edits
- Historique des corrections
Pour respecter la confidentialité, les données sensibles sont automatiquement anonymisées :
Type de données | Anonymisation |
---|---|
Contenu des devis | Remplacé par "Contenu anonymisé pour export Metabase" |
Commentaires utilisateurs | Remplacés par "Commentaire anonymisé" |
Emails utilisateurs | Remplacés par email-anonymise@example.com |
Texte OCR/PDF | Exclu de l'export |
Les données analytiques (dates, statuts, codes d'erreur, métriques, profils utilisateurs) sont conservées pour permettre les analyses.
Les données sont importées dans le schéma mdso_analytics
avec la structure :
mdso_analytics.quote_checks
mdso_analytics.quotes_cases
mdso_analytics.quote_check_feedbacks
mdso_analytics.quote_error_edits
- Fichiers CSV temporaires : Automatiquement supprimés après chaque exécution
- Schéma temporaire : Nettoyé après export
- Protection Git : Fichiers
.csv
exclus via.gitignore
- Gestion d'erreur : Nettoyage automatique même en cas d'échec
L'export est automatisé via un CRON (défini dans cron.json
) qui s'exécute tous les matins à 9h pour maintenir les données Metabase à jour avec les dernières données anonymisées.
Important : Le CRON ne s'exécute que si ENABLE_METABASE_EXPORT=true
.
# Pré-requis : Vérifier que les variables sont configurées
scalingo --app mon-devis-sans-oublis-backend-staging env | grep -E "(METABASE_DATA_DB_URL|ENABLE_METABASE_EXPORT)"
# Lancement de l'export
scalingo --app mon-devis-sans-oublis-backend-staging run db/scripts/export-db-metabase.sh
# Voir les tâches programmées
scalingo --app mon-devis-sans-oublis-backend-staging cron-tasks
# Consulter les logs d'exécution
scalingo --app mon-devis-sans-oublis-backend-staging logs --filter cron
# Vérifier les logs d'export dans la DB
psql $DATABASE_URL -c "SELECT * FROM export_logs ORDER BY created_at DESC LIMIT 10;"