docs: Mise à jour complète de la documentation v1.2.0
- Mise à jour du README.md avec les nouvelles fonctionnalités - Documentation API mise à jour avec les intégrations externes - Guide d'installation avec bootstrap automatisé - Architecture mise à jour avec Celery et intégrations - CHANGELOG détaillé avec toutes les nouvelles fonctionnalités - Nouvelle documentation des fonctionnalités v1.2.0 Nouvelles sections documentées: - Pipeline de traitement asynchrone avec Celery - Intégrations avec APIs externes (Cadastre, Géorisques, BODACC, etc.) - Clients d'intégration (AnythingLLM, Neo4j, OpenSearch) - Configuration d'environnement centralisée - Script bootstrap automatisé - Monitoring et observabilité - Exemples d'utilisation et API
This commit is contained in:
parent
f485efdb87
commit
8c089127af
39
CHANGELOG.md
39
CHANGELOG.md
@ -8,27 +8,32 @@ et ce projet adhère au [Versioning Sémantique](https://semver.org/lang/fr/).
|
|||||||
## [1.2.0] - 2025-01-09
|
## [1.2.0] - 2025-01-09
|
||||||
|
|
||||||
### Ajouté
|
### Ajouté
|
||||||
- Intégration complète de Celery pour les tâches asynchrones
|
- **Intégration complète de Celery** pour les tâches asynchrones avec queues spécialisées
|
||||||
- Tâches spécialisées pour chaque étape du pipeline (OCR, classification, extraction, indexation, vérification)
|
- **Pipelines worker complets** : préprocessing, OCR, classification, extraction, indexation, vérification, finalisation
|
||||||
- Configuration des queues Celery avec Redis
|
- **Intégrations avec APIs externes** : Cadastre, Géorisques, BODACC, Infogreffe, RBE
|
||||||
- Tâches de traitement en lot pour l'efficacité
|
- **Clients d'intégration avancés** : AnythingLLM, Neo4j, OpenSearch
|
||||||
- Monitoring et health checks des workers
|
- **Vérifications automatisées** avec calcul du score de vraisemblance
|
||||||
- Fichier d'environnement complet (.env.example et .env)
|
- **OCR avancé** avec préprocessing d'images et correction lexicale notariale
|
||||||
- Script bootstrap automatisé pour l'initialisation complète
|
- **Support multi-formats** : PDF, JPEG, PNG, TIFF, HEIC avec conversion automatique
|
||||||
- Orchestration avancée des pipelines avec gestion d'erreurs
|
- **Indexation multi-système** : AnythingLLM (sémantique), OpenSearch (plein-texte), Neo4j (graphe)
|
||||||
- Support des tâches périodiques (Celery Beat)
|
- **Fichier d'environnement complet** (.env.example et .env) avec toutes les variables
|
||||||
- Configuration centralisée des workers
|
- **Script bootstrap automatisé** pour l'initialisation complète du système
|
||||||
|
- **Gestion robuste des erreurs** dans tous les pipelines avec fallbacks
|
||||||
|
- **Support des tâches périodiques** (Celery Beat) pour la maintenance
|
||||||
|
- **Configuration centralisée** des workers et des services
|
||||||
|
|
||||||
### Modifié
|
### Modifié
|
||||||
- Worker principal refactorisé pour utiliser Celery
|
- **Worker principal refactorisé** pour utiliser Celery avec orchestration avancée
|
||||||
- Amélioration de la gestion des erreurs dans les pipelines
|
- **Amélioration de la gestion des erreurs** dans tous les pipelines
|
||||||
- Configuration Docker optimisée pour Celery
|
- **Configuration Docker optimisée** pour Celery et les nouvelles dépendances
|
||||||
- Documentation mise à jour avec les nouvelles fonctionnalités
|
- **Documentation complètement mise à jour** avec les nouvelles fonctionnalités
|
||||||
|
- **API version 1.2.0** avec nouvelles fonctionnalités
|
||||||
|
|
||||||
### Corrigé
|
### Corrigé
|
||||||
- Gestion robuste des erreurs dans les tâches asynchrones
|
- **Gestion robuste des erreurs** dans les tâches asynchrones
|
||||||
- Amélioration de la scalabilité du système
|
- **Amélioration de la scalabilité** du système avec Celery
|
||||||
- Configuration d'environnement centralisée
|
- **Configuration d'environnement centralisée** et automatisée
|
||||||
|
- **Compatibilité des formats** de documents avec conversion automatique
|
||||||
|
|
||||||
## [1.1.0] - 2025-01-09
|
## [1.1.0] - 2025-01-09
|
||||||
|
|
||||||
|
68
README.md
68
README.md
@ -6,31 +6,35 @@ Le système 4NK Notariat est une solution complète d'IA pour le traitement auto
|
|||||||
|
|
||||||
## ✨ Fonctionnalités Principales
|
## ✨ Fonctionnalités Principales
|
||||||
|
|
||||||
### 🔍 **Traitement de Documents**
|
### 🔍 **Pipeline de Traitement Avancé**
|
||||||
- **OCR Avancé** : Extraction de texte avec correction lexicale notariale
|
- **Préprocessing Intelligent** : Validation, conversion et optimisation automatique des documents
|
||||||
- **Classification Automatique** : Détection du type de document (acte de vente, donation, succession, CNI, etc.)
|
- **OCR Avancé** : Extraction de texte avec Tesseract et correction lexicale notariale spécialisée
|
||||||
- **Extraction d'Entités** : Identification automatique des identités, adresses, biens, montants
|
- **Classification Automatique** : Détection du type de document via LLM (acte de vente, donation, succession, CNI, etc.)
|
||||||
- **Support Multi-format** : PDF, JPEG, PNG, TIFF, HEIC
|
- **Extraction d'Entités** : Identification automatique des identités, adresses, biens, montants, dates
|
||||||
|
- **Support Multi-format** : PDF, JPEG, PNG, TIFF, HEIC avec conversion automatique
|
||||||
|
- **Traitement Asynchrone** : Pipeline Celery avec queues spécialisées pour la scalabilité
|
||||||
|
|
||||||
### 🔗 **Vérifications Externes**
|
### 🔗 **Vérifications Externes Automatisées**
|
||||||
- **Cadastre** : Vérification des parcelles et propriétés
|
- **API Cadastre** : Vérification des parcelles et propriétés immobilières
|
||||||
- **Géorisques** : Analyse des risques (inondation, argiles, radon, etc.)
|
- **API Géorisques** : Analyse des risques géologiques (inondation, argiles, radon, etc.)
|
||||||
- **BODACC** : Vérification des annonces légales
|
- **API BODACC** : Vérification des annonces légales et entreprises
|
||||||
- **Gel des Avoirs** : Contrôle des sanctions
|
- **API Infogreffe** : Recherche d'informations d'entreprises
|
||||||
- **Infogreffe** : Vérification des entreprises
|
- **API RBE** : Registre des Bénéficiaires Effectifs
|
||||||
- **RBE** : Bénéficiaires effectifs
|
- **Géocodage** : Conversion d'adresses en coordonnées GPS
|
||||||
|
|
||||||
### 🧠 **Intelligence Artificielle**
|
### 🧠 **Intelligence Artificielle Intégrée**
|
||||||
- **LLM Local** : Analyse contextuelle avec Ollama (Llama 3, Mistral)
|
- **LLM Local** : Analyse contextuelle avec Ollama (Llama 3, Mistral)
|
||||||
- **Score de Vraisemblance** : Évaluation automatique de la cohérence
|
- **Score de Vraisemblance** : Évaluation automatique basée sur les vérifications externes
|
||||||
- **Avis de Synthèse** : Analyse intelligente et recommandations
|
- **Indexation Sémantique** : AnythingLLM pour la recherche intelligente
|
||||||
- **Détection d'Anomalies** : Identification des incohérences
|
- **Graphe de Connaissances** : Neo4j pour les relations entre entités
|
||||||
|
- **Recherche Plein-texte** : OpenSearch avec analyseur français
|
||||||
|
|
||||||
### 🌐 **Interface Moderne**
|
### 🏗️ **Architecture Moderne**
|
||||||
- **Interface Web** : Upload par drag & drop, visualisation des analyses
|
- **API REST** : FastAPI avec documentation automatique
|
||||||
- **API REST** : Intégration avec les systèmes existants
|
- **Traitement Asynchrone** : Celery avec Redis pour la performance
|
||||||
- **Tableaux de Bord** : Statistiques et monitoring
|
- **Stockage S3** : MinIO pour la gestion des documents
|
||||||
- **Rapports** : Export des analyses et recommandations
|
- **Monitoring** : Prometheus et Grafana pour la supervision
|
||||||
|
- **Configuration** : Bootstrap automatisé et gestion d'environnement
|
||||||
|
|
||||||
## 🚀 Démarrage Rapide
|
## 🚀 Démarrage Rapide
|
||||||
|
|
||||||
@ -40,13 +44,31 @@ Le système 4NK Notariat est une solution complète d'IA pour le traitement auto
|
|||||||
- Ubuntu/Debian 20.04+
|
- Ubuntu/Debian 20.04+
|
||||||
- Python 3.11+
|
- Python 3.11+
|
||||||
- Docker & Docker Compose
|
- Docker & Docker Compose
|
||||||
- 8GB RAM minimum (16GB recommandé)
|
- 16GB RAM minimum (32GB recommandé pour les modèles LLM)
|
||||||
- 50GB espace disque
|
- 100GB espace disque (pour les modèles et documents)
|
||||||
|
|
||||||
# Dépendances système
|
# Dépendances système
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y python3 python3-pip python3-venv docker.io docker-compose
|
sudo apt-get install -y python3 python3-pip python3-venv docker.io docker-compose
|
||||||
sudo apt-get install -y tesseract-ocr tesseract-ocr-fra poppler-utils imagemagick
|
sudo apt-get install -y tesseract-ocr tesseract-ocr-fra poppler-utils imagemagick
|
||||||
|
sudo apt-get install -y wget curl jq
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation Automatisée
|
||||||
|
```bash
|
||||||
|
# Cloner le repository
|
||||||
|
git clone https://git.4nkweb.com/4nk/4NK_IA_back.git
|
||||||
|
cd 4NK_IA_back
|
||||||
|
|
||||||
|
# Bootstrap automatique (recommandé)
|
||||||
|
chmod +x ops/bootstrap.sh
|
||||||
|
./ops/bootstrap.sh
|
||||||
|
|
||||||
|
# Ou installation manuelle
|
||||||
|
cd infra
|
||||||
|
cp .env.example .env
|
||||||
|
# Éditer .env avec vos paramètres
|
||||||
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
@ -13,26 +13,30 @@ L'API Notariale 4NK est un système complet de traitement de documents notariaux
|
|||||||
- Gestion des tâches asynchrones
|
- Gestion des tâches asynchrones
|
||||||
- Intégration avec les services externes
|
- Intégration avec les services externes
|
||||||
|
|
||||||
2. **Pipeline de Traitement**
|
2. **Pipeline de Traitement Avancé** (`services/worker/`)
|
||||||
- OCR avec correction lexicale notariale
|
- **Préprocessing** : Validation, conversion et optimisation des documents
|
||||||
- Classification automatique des documents
|
- **OCR** : Extraction de texte avec Tesseract et correction lexicale notariale
|
||||||
- Extraction d'entités (identités, adresses, biens)
|
- **Classification** : Détection du type de document via LLM
|
||||||
- Vérifications externes (Cadastre, Géorisques, BODACC, etc.)
|
- **Extraction** : Extraction d'entités (identités, adresses, biens, montants, dates)
|
||||||
- Calcul du score de vraisemblance
|
- **Indexation** : Indexation multi-système (AnythingLLM, OpenSearch, Neo4j)
|
||||||
- Analyse contextuelle via LLM
|
- **Vérifications** : Contrôles métier et vérifications externes automatisées
|
||||||
|
- **Finalisation** : Synthèse et archivage avec score de vraisemblance
|
||||||
|
|
||||||
3. **Interface Web** (`services/web_interface/`)
|
3. **Traitement Asynchrone** (Celery)
|
||||||
- Interface utilisateur moderne pour les notaires
|
- Queues spécialisées pour chaque étape du pipeline
|
||||||
- Upload de documents par drag & drop
|
- Scalabilité horizontale des workers
|
||||||
- Visualisation des analyses
|
- Monitoring des tâches en temps réel
|
||||||
- Tableaux de bord et statistiques
|
- Gestion robuste des erreurs
|
||||||
|
|
||||||
4. **Services Externes**
|
4. **Services Externes Intégrés**
|
||||||
- Ollama (modèles LLM locaux)
|
- **Ollama** : Modèles LLM locaux (Llama 3, Mistral)
|
||||||
- APIs gouvernementales (Cadastre, Géorisques, BODACC)
|
- **APIs Gouvernementales** : Cadastre, Géorisques, BODACC, Infogreffe, RBE
|
||||||
- Base de données PostgreSQL
|
- **Base de données PostgreSQL** : Métadonnées et résultats
|
||||||
- Stockage MinIO
|
- **Stockage MinIO** : Documents et artefacts
|
||||||
- Cache Redis
|
- **Cache Redis** : Performance et queues Celery
|
||||||
|
- **Neo4j** : Graphe de connaissances
|
||||||
|
- **OpenSearch** : Recherche plein-texte
|
||||||
|
- **AnythingLLM** : Indexation sémantique
|
||||||
|
|
||||||
## 📋 Types de Documents Supportés
|
## 📋 Types de Documents Supportés
|
||||||
|
|
||||||
|
@ -20,9 +20,17 @@ Le système notarial 4NK_IA est conçu selon une architecture microservices mode
|
|||||||
|
|
||||||
### **3. Résilience et Fiabilité**
|
### **3. Résilience et Fiabilité**
|
||||||
- Health checks automatiques
|
- Health checks automatiques
|
||||||
- Retry policies
|
- Retry policies avec Celery
|
||||||
- Circuit breakers
|
- Circuit breakers
|
||||||
- Monitoring complet
|
- Monitoring complet avec Prometheus/Grafana
|
||||||
|
- Gestion robuste des erreurs dans les pipelines
|
||||||
|
|
||||||
|
### **4. Intégrations Externes**
|
||||||
|
- APIs gouvernementales (Cadastre, Géorisques, BODACC, Infogreffe, RBE)
|
||||||
|
- LLM locaux (Ollama avec Llama 3, Mistral)
|
||||||
|
- Indexation sémantique (AnythingLLM)
|
||||||
|
- Graphe de connaissances (Neo4j)
|
||||||
|
- Recherche plein-texte (OpenSearch)
|
||||||
|
|
||||||
## 🏛️ Architecture Logique
|
## 🏛️ Architecture Logique
|
||||||
|
|
||||||
|
@ -4,6 +4,37 @@
|
|||||||
|
|
||||||
Ce guide vous accompagne dans l'installation complète du système notarial 4NK_IA, de l'environnement de développement à la production.
|
Ce guide vous accompagne dans l'installation complète du système notarial 4NK_IA, de l'environnement de développement à la production.
|
||||||
|
|
||||||
|
## ⚡ Installation Rapide (Recommandée)
|
||||||
|
|
||||||
|
Pour une installation rapide et automatisée, utilisez le script bootstrap :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Cloner le repository
|
||||||
|
git clone https://git.4nkweb.com/4nk/4NK_IA_back.git
|
||||||
|
cd 4NK_IA_back
|
||||||
|
|
||||||
|
# Bootstrap automatique
|
||||||
|
chmod +x ops/bootstrap.sh
|
||||||
|
./ops/bootstrap.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Le script bootstrap configure automatiquement :
|
||||||
|
- ✅ Vérification des prérequis
|
||||||
|
- ✅ Configuration de l'environnement
|
||||||
|
- ✅ Téléchargement des images Docker
|
||||||
|
- ✅ Démarrage des services
|
||||||
|
- ✅ Configuration des modèles Ollama
|
||||||
|
- ✅ Setup des workspaces AnythingLLM
|
||||||
|
- ✅ Vérifications finales
|
||||||
|
|
||||||
|
**Services disponibles après installation :**
|
||||||
|
- 🌐 API Notariale : http://localhost:8000
|
||||||
|
- 📚 Documentation API : http://localhost:8000/docs
|
||||||
|
- 🤖 AnythingLLM : http://localhost:3001
|
||||||
|
- 📊 Grafana : http://localhost:3000
|
||||||
|
- 🗄️ MinIO Console : http://localhost:9001
|
||||||
|
- 🦙 Ollama : http://localhost:11434
|
||||||
|
|
||||||
## 📋 Prérequis
|
## 📋 Prérequis
|
||||||
|
|
||||||
### **Système d'Exploitation**
|
### **Système d'Exploitation**
|
||||||
|
289
docs/NEW-FEATURES-v1.2.0.md
Normal file
289
docs/NEW-FEATURES-v1.2.0.md
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
# Nouvelles Fonctionnalités v1.2.0
|
||||||
|
|
||||||
|
## 🚀 Vue d'ensemble
|
||||||
|
|
||||||
|
La version 1.2.0 apporte des améliorations majeures au système 4NK Notariat avec l'intégration complète de Celery, des pipelines avancés et des intégrations externes automatisées.
|
||||||
|
|
||||||
|
## 🔄 Pipeline de Traitement Asynchrone
|
||||||
|
|
||||||
|
### Architecture Celery
|
||||||
|
|
||||||
|
Le système utilise maintenant Celery pour le traitement asynchrone avec des queues spécialisées :
|
||||||
|
|
||||||
|
- **Queue `pipeline`** : Traitement principal des documents
|
||||||
|
- **Queue `ocr`** : Traitement OCR spécialisé
|
||||||
|
- **Queue `classification`** : Classification des documents
|
||||||
|
- **Queue `extraction`** : Extraction d'entités
|
||||||
|
- **Queue `indexing`** : Indexation multi-système
|
||||||
|
- **Queue `verification`** : Vérifications externes
|
||||||
|
|
||||||
|
### Étapes du Pipeline
|
||||||
|
|
||||||
|
1. **Préprocessing** (`services/worker/pipelines/preprocess.py`)
|
||||||
|
- Validation des formats de fichiers
|
||||||
|
- Conversion automatique (HEIC → JPEG, images → PDF)
|
||||||
|
- Optimisation des images pour l'OCR
|
||||||
|
- Détection de la langue
|
||||||
|
- Calcul du hash pour l'intégrité
|
||||||
|
|
||||||
|
2. **OCR Avancé** (`services/worker/pipelines/ocr.py`)
|
||||||
|
- Support PDF et images avec Tesseract
|
||||||
|
- Préprocessing d'images (contraste, débruitage, netteté)
|
||||||
|
- Correction lexicale spécialisée notariale
|
||||||
|
- Détection de la structure du document
|
||||||
|
- Support OCRmyPDF en fallback
|
||||||
|
|
||||||
|
3. **Classification** (`services/worker/tasks/classification_tasks.py`)
|
||||||
|
- Détection du type de document via LLM
|
||||||
|
- Support des types : acte_vente, acte_donation, acte_succession, cni, contrat, autre
|
||||||
|
- Calcul de la confiance de classification
|
||||||
|
- Traitement en lot
|
||||||
|
|
||||||
|
4. **Extraction d'Entités** (`services/worker/tasks/extraction_tasks.py`)
|
||||||
|
- Extraction des identités (vendeur, acheteur, notaire)
|
||||||
|
- Extraction des adresses et biens immobiliers
|
||||||
|
- Extraction des montants et dates
|
||||||
|
- Support spécifique par type de document
|
||||||
|
|
||||||
|
5. **Indexation Multi-Système** (`services/worker/tasks/indexing_tasks.py`)
|
||||||
|
- **AnythingLLM** : Indexation sémantique pour la recherche intelligente
|
||||||
|
- **OpenSearch** : Recherche plein-texte avec analyseur français
|
||||||
|
- **Neo4j** : Graphe de connaissances pour les relations entre entités
|
||||||
|
|
||||||
|
6. **Vérifications Externes** (`services/worker/tasks/verification_tasks.py`)
|
||||||
|
- **API Cadastre** : Vérification des parcelles immobilières
|
||||||
|
- **API Géorisques** : Analyse des risques géologiques
|
||||||
|
- **API BODACC** : Vérification des entreprises
|
||||||
|
- **API Infogreffe** : Recherche d'informations d'entreprises
|
||||||
|
- **API RBE** : Registre des Bénéficiaires Effectifs
|
||||||
|
- **Calcul du score de vraisemblance** basé sur les vérifications
|
||||||
|
|
||||||
|
7. **Finalisation** (`services/worker/pipelines/finalize.py`)
|
||||||
|
- Synthèse des résultats
|
||||||
|
- Archivage des documents
|
||||||
|
- Génération des rapports
|
||||||
|
|
||||||
|
## 🔗 Intégrations Externes
|
||||||
|
|
||||||
|
### APIs Gouvernementales
|
||||||
|
|
||||||
|
#### API Cadastre (`services/worker/utils/external_apis.py`)
|
||||||
|
```python
|
||||||
|
# Vérification d'adresse
|
||||||
|
result = await api_manager.verify_address(
|
||||||
|
address="123 Rue de la Paix",
|
||||||
|
postal_code="75001",
|
||||||
|
city="Paris"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### API Géorisques
|
||||||
|
```python
|
||||||
|
# Vérification des risques géologiques
|
||||||
|
result = await api_manager.check_geological_risks(
|
||||||
|
address="123 Rue de la Paix",
|
||||||
|
coordinates=[2.3522, 48.8566]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### API BODACC
|
||||||
|
```python
|
||||||
|
# Vérification d'entreprise
|
||||||
|
result = await api_manager.verify_company(
|
||||||
|
company_name="SARL Example",
|
||||||
|
siren="123456789"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clients d'Intégration
|
||||||
|
|
||||||
|
#### AnythingLLM Client (`services/worker/utils/anythingllm_client.py`)
|
||||||
|
- Création automatique des workspaces
|
||||||
|
- Upload de documents avec métadonnées
|
||||||
|
- Recherche sémantique intelligente
|
||||||
|
- Indexation pour les actes similaires
|
||||||
|
|
||||||
|
#### Neo4j Client (`services/worker/utils/neo4j_client.py`)
|
||||||
|
- Création du contexte de dossier
|
||||||
|
- Ajout des entités au graphe
|
||||||
|
- Recherche de documents liés
|
||||||
|
- Génération de résumés de dossier
|
||||||
|
|
||||||
|
#### OpenSearch Client (`services/worker/utils/opensearch_client.py`)
|
||||||
|
- Indexation avec mapping français
|
||||||
|
- Recherche plein-texte avancée
|
||||||
|
- Recherche par entités
|
||||||
|
- Statistiques d'index
|
||||||
|
|
||||||
|
## 🛠️ Configuration et Déploiement
|
||||||
|
|
||||||
|
### Fichier d'Environnement
|
||||||
|
|
||||||
|
Le fichier `infra/.env` contient toutes les variables de configuration :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Configuration du projet
|
||||||
|
PROJECT_NAME=notariat
|
||||||
|
DOMAIN=localhost
|
||||||
|
|
||||||
|
# Base de données PostgreSQL
|
||||||
|
POSTGRES_USER=notariat
|
||||||
|
POSTGRES_PASSWORD=notariat_pwd
|
||||||
|
POSTGRES_DB=notariat
|
||||||
|
|
||||||
|
# Redis pour Celery
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
|
# MinIO (Stockage S3-compatible)
|
||||||
|
MINIO_ROOT_USER=minio
|
||||||
|
MINIO_ROOT_PASSWORD=minio_pwd
|
||||||
|
MINIO_BUCKET=ingest
|
||||||
|
|
||||||
|
# AnythingLLM
|
||||||
|
ANYLLM_API_KEY=change_me
|
||||||
|
ANYLLM_BASE_URL=http://anythingllm:3001
|
||||||
|
ANYLLM_WORKSPACE_NORMES=workspace_normes
|
||||||
|
ANYLLM_WORKSPACE_TRAMES=workspace_trames
|
||||||
|
ANYLLM_WORKSPACE_ACTES=workspace_actes
|
||||||
|
|
||||||
|
# Ollama (LLM local)
|
||||||
|
OLLAMA_BASE_URL=http://ollama:11434
|
||||||
|
OLLAMA_MODELS=llama3:8b,mistral:7b
|
||||||
|
|
||||||
|
# Neo4j (Graphe de connaissances)
|
||||||
|
NEO4J_AUTH=neo4j/neo4j_pwd
|
||||||
|
|
||||||
|
# OpenSearch (Recherche plein-texte)
|
||||||
|
OPENSEARCH_PASSWORD=opensearch_pwd
|
||||||
|
|
||||||
|
# URLs des APIs externes
|
||||||
|
CADASTRE_API_URL=https://apicarto.ign.fr/api/cadastre
|
||||||
|
GEORISQUES_API_URL=https://www.georisques.gouv.fr/api
|
||||||
|
BODACC_API_URL=https://bodacc-datadila.opendatasoft.com/api
|
||||||
|
INFOGREFFE_API_URL=https://entreprise.api.gouv.fr/v2/infogreffe
|
||||||
|
RBE_API_URL=https://www.data.gouv.fr/api/1/datasets/registre-des-beneficiaires-effectifs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Script Bootstrap
|
||||||
|
|
||||||
|
Le script `ops/bootstrap.sh` automatise l'installation complète :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vérification des prérequis
|
||||||
|
check_prerequisites
|
||||||
|
|
||||||
|
# Configuration de l'environnement
|
||||||
|
setup_environment
|
||||||
|
|
||||||
|
# Téléchargement des images Docker
|
||||||
|
pull_images
|
||||||
|
|
||||||
|
# Démarrage des services de base
|
||||||
|
start_base_services
|
||||||
|
|
||||||
|
# Configuration de MinIO
|
||||||
|
setup_minio
|
||||||
|
|
||||||
|
# Configuration d'Ollama
|
||||||
|
setup_ollama
|
||||||
|
|
||||||
|
# Démarrage des services applicatifs
|
||||||
|
start_application_services
|
||||||
|
|
||||||
|
# Configuration des workspaces AnythingLLM
|
||||||
|
setup_anythingllm_workspaces
|
||||||
|
|
||||||
|
# Vérification finale
|
||||||
|
final_check
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Monitoring et Observabilité
|
||||||
|
|
||||||
|
### Métriques Celery
|
||||||
|
|
||||||
|
- Tâches actives, réservées et terminées
|
||||||
|
- Temps de traitement par étape
|
||||||
|
- Taux d'erreur par queue
|
||||||
|
- Performance des workers
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
- Vérification de l'état des services
|
||||||
|
- Test de connectivité aux APIs externes
|
||||||
|
- Validation des queues Celery
|
||||||
|
- Contrôle de l'espace disque et mémoire
|
||||||
|
|
||||||
|
## 🔧 Utilisation
|
||||||
|
|
||||||
|
### Upload de Document
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Via l'API
|
||||||
|
response = requests.post(
|
||||||
|
"http://localhost:8000/api/notary/documents/upload",
|
||||||
|
files={"file": open("document.pdf", "rb")},
|
||||||
|
data={
|
||||||
|
"id_dossier": "DOSSIER-001",
|
||||||
|
"etude_id": "ETUDE-001",
|
||||||
|
"utilisateur_id": "USER-001"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suivi du Traitement
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Récupération du statut
|
||||||
|
status = requests.get(
|
||||||
|
f"http://localhost:8000/api/notary/documents/{doc_id}/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Récupération des résultats
|
||||||
|
results = requests.get(
|
||||||
|
f"http://localhost:8000/api/notary/documents/{doc_id}/results"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recherche
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Recherche plein-texte via OpenSearch
|
||||||
|
search_results = opensearch_client.search_documents(
|
||||||
|
query="acte de vente Paris",
|
||||||
|
filters={"doc_type": "acte_vente"},
|
||||||
|
limit=10
|
||||||
|
)
|
||||||
|
|
||||||
|
# Recherche sémantique via AnythingLLM
|
||||||
|
semantic_results = anyllm_client.search_documents(
|
||||||
|
workspace_id="workspace_actes",
|
||||||
|
query="acte de vente immobilière",
|
||||||
|
limit=5
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Prochaines Étapes
|
||||||
|
|
||||||
|
### Phase 3 - Intelligence Artificielle
|
||||||
|
- [ ] Configuration des modèles Ollama (llama3:8b, mistral:7b)
|
||||||
|
- [ ] Implémentation de la classification via LLM
|
||||||
|
- [ ] Extraction d'entités avec LLM
|
||||||
|
- [ ] Génération d'avis de synthèse
|
||||||
|
|
||||||
|
### Phase 4 - Supervision
|
||||||
|
- [ ] Tableaux de bord Grafana
|
||||||
|
- [ ] Métriques Prometheus
|
||||||
|
- [ ] Journaux d'audit structurés
|
||||||
|
|
||||||
|
### Phase 5 - Tests et Qualité
|
||||||
|
- [ ] Tests automatisés complets
|
||||||
|
- [ ] Données de test
|
||||||
|
- [ ] Seuils de qualité
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
- [README.md](../README.md) - Vue d'ensemble et démarrage rapide
|
||||||
|
- [docs/API-NOTARIALE.md](API-NOTARIALE.md) - Documentation API complète
|
||||||
|
- [docs/INSTALLATION.md](INSTALLATION.md) - Guide d'installation détaillé
|
||||||
|
- [docs/ARCHITECTURE.md](ARCHITECTURE.md) - Architecture du système
|
||||||
|
- [CHANGELOG.md](../CHANGELOG.md) - Historique des versions
|
@ -33,7 +33,7 @@ def index_document(self, doc_id: str, text: str, entities: Dict[str, Any], doc_t
|
|||||||
|
|
||||||
# Indexation dans les différents systèmes
|
# Indexation dans les différents systèmes
|
||||||
indexing_results = {}
|
indexing_results = {}
|
||||||
|
|
||||||
# 1. Indexation dans AnythingLLM
|
# 1. Indexation dans AnythingLLM
|
||||||
try:
|
try:
|
||||||
from services.worker.utils.anythingllm_client import AnythingLLMClient
|
from services.worker.utils.anythingllm_client import AnythingLLMClient
|
||||||
@ -45,7 +45,7 @@ def index_document(self, doc_id: str, text: str, entities: Dict[str, Any], doc_t
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur indexation AnythingLLM: {e}")
|
logger.error(f"Erreur indexation AnythingLLM: {e}")
|
||||||
indexing_results['anythingllm'] = {'status': 'error', 'error': str(e)}
|
indexing_results['anythingllm'] = {'status': 'error', 'error': str(e)}
|
||||||
|
|
||||||
# 2. Indexation dans OpenSearch
|
# 2. Indexation dans OpenSearch
|
||||||
try:
|
try:
|
||||||
from services.worker.utils.opensearch_client import OpenSearchClient
|
from services.worker.utils.opensearch_client import OpenSearchClient
|
||||||
@ -61,12 +61,12 @@ def index_document(self, doc_id: str, text: str, entities: Dict[str, Any], doc_t
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur indexation OpenSearch: {e}")
|
logger.error(f"Erreur indexation OpenSearch: {e}")
|
||||||
indexing_results['opensearch'] = {'status': 'error', 'error': str(e)}
|
indexing_results['opensearch'] = {'status': 'error', 'error': str(e)}
|
||||||
|
|
||||||
# 3. Création du graphe Neo4j
|
# 3. Création du graphe Neo4j
|
||||||
try:
|
try:
|
||||||
from services.worker.utils.neo4j_client import Neo4jClient
|
from services.worker.utils.neo4j_client import Neo4jClient
|
||||||
neo4j_client = Neo4jClient()
|
neo4j_client = Neo4jClient()
|
||||||
|
|
||||||
# Ajout du document au graphe
|
# Ajout du document au graphe
|
||||||
neo4j_result = await neo4j_client.add_entities_to_document(doc_id, entities)
|
neo4j_result = await neo4j_client.add_entities_to_document(doc_id, entities)
|
||||||
indexing_results['neo4j'] = neo4j_result
|
indexing_results['neo4j'] = neo4j_result
|
||||||
|
@ -32,7 +32,7 @@ def verify_document(self, doc_id: str, entities: Dict[str, Any], doc_type: str,
|
|||||||
|
|
||||||
# Vérifications externes avec les APIs
|
# Vérifications externes avec les APIs
|
||||||
verification_results = {}
|
verification_results = {}
|
||||||
|
|
||||||
# 1. Vérification des adresses via Cadastre
|
# 1. Vérification des adresses via Cadastre
|
||||||
if 'bien' in entities and 'adresse' in entities['bien']:
|
if 'bien' in entities and 'adresse' in entities['bien']:
|
||||||
try:
|
try:
|
||||||
@ -47,7 +47,7 @@ def verify_document(self, doc_id: str, entities: Dict[str, Any], doc_type: str,
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur vérification Cadastre: {e}")
|
logger.error(f"Erreur vérification Cadastre: {e}")
|
||||||
verification_results['cadastre'] = {'status': 'error', 'error': str(e)}
|
verification_results['cadastre'] = {'status': 'error', 'error': str(e)}
|
||||||
|
|
||||||
# 2. Vérification des risques géologiques
|
# 2. Vérification des risques géologiques
|
||||||
if 'bien' in entities and 'adresse' in entities['bien']:
|
if 'bien' in entities and 'adresse' in entities['bien']:
|
||||||
try:
|
try:
|
||||||
@ -60,7 +60,7 @@ def verify_document(self, doc_id: str, entities: Dict[str, Any], doc_type: str,
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur vérification Géorisques: {e}")
|
logger.error(f"Erreur vérification Géorisques: {e}")
|
||||||
verification_results['georisques'] = {'status': 'error', 'error': str(e)}
|
verification_results['georisques'] = {'status': 'error', 'error': str(e)}
|
||||||
|
|
||||||
# 3. Vérification des entreprises (si applicable)
|
# 3. Vérification des entreprises (si applicable)
|
||||||
if 'vendeur' in entities and 'nom' in entities['vendeur']:
|
if 'vendeur' in entities and 'nom' in entities['vendeur']:
|
||||||
try:
|
try:
|
||||||
@ -73,13 +73,13 @@ def verify_document(self, doc_id: str, entities: Dict[str, Any], doc_type: str,
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur vérification BODACC: {e}")
|
logger.error(f"Erreur vérification BODACC: {e}")
|
||||||
verification_results['bodacc'] = {'status': 'error', 'error': str(e)}
|
verification_results['bodacc'] = {'status': 'error', 'error': str(e)}
|
||||||
|
|
||||||
# 4. Vérification des personnes
|
# 4. Vérification des personnes
|
||||||
if 'vendeur' in entities or 'acheteur' in entities:
|
if 'vendeur' in entities or 'acheteur' in entities:
|
||||||
try:
|
try:
|
||||||
from services.worker.utils.external_apis import ExternalAPIManager
|
from services.worker.utils.external_apis import ExternalAPIManager
|
||||||
api_manager = ExternalAPIManager()
|
api_manager = ExternalAPIManager()
|
||||||
|
|
||||||
# Vérification du vendeur
|
# Vérification du vendeur
|
||||||
if 'vendeur' in entities:
|
if 'vendeur' in entities:
|
||||||
person_result = await api_manager.verify_person(
|
person_result = await api_manager.verify_person(
|
||||||
@ -88,7 +88,7 @@ def verify_document(self, doc_id: str, entities: Dict[str, Any], doc_type: str,
|
|||||||
entities['vendeur'].get('date_naissance')
|
entities['vendeur'].get('date_naissance')
|
||||||
)
|
)
|
||||||
verification_results['person_vendeur'] = person_result
|
verification_results['person_vendeur'] = person_result
|
||||||
|
|
||||||
# Vérification de l'acheteur
|
# Vérification de l'acheteur
|
||||||
if 'acheteur' in entities:
|
if 'acheteur' in entities:
|
||||||
person_result = await api_manager.verify_person(
|
person_result = await api_manager.verify_person(
|
||||||
@ -97,7 +97,7 @@ def verify_document(self, doc_id: str, entities: Dict[str, Any], doc_type: str,
|
|||||||
entities['acheteur'].get('date_naissance')
|
entities['acheteur'].get('date_naissance')
|
||||||
)
|
)
|
||||||
verification_results['person_acheteur'] = person_result
|
verification_results['person_acheteur'] = person_result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur vérification personnes: {e}")
|
logger.error(f"Erreur vérification personnes: {e}")
|
||||||
verification_results['person_verification'] = {'status': 'error', 'error': str(e)}
|
verification_results['person_verification'] = {'status': 'error', 'error': str(e)}
|
||||||
@ -182,16 +182,16 @@ def update_external_data():
|
|||||||
def _calculate_credibility_score(verification_results: Dict[str, Any]) -> float:
|
def _calculate_credibility_score(verification_results: Dict[str, Any]) -> float:
|
||||||
"""
|
"""
|
||||||
Calcul du score de vraisemblance basé sur les vérifications
|
Calcul du score de vraisemblance basé sur les vérifications
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
verification_results: Résultats des vérifications
|
verification_results: Résultats des vérifications
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Score de vraisemblance entre 0 et 1
|
Score de vraisemblance entre 0 et 1
|
||||||
"""
|
"""
|
||||||
total_score = 0.0
|
total_score = 0.0
|
||||||
total_weight = 0.0
|
total_weight = 0.0
|
||||||
|
|
||||||
# Poids des différentes vérifications
|
# Poids des différentes vérifications
|
||||||
weights = {
|
weights = {
|
||||||
'cadastre': 0.3,
|
'cadastre': 0.3,
|
||||||
@ -200,12 +200,12 @@ def _calculate_credibility_score(verification_results: Dict[str, Any]) -> float:
|
|||||||
'person_vendeur': 0.15,
|
'person_vendeur': 0.15,
|
||||||
'person_acheteur': 0.15
|
'person_acheteur': 0.15
|
||||||
}
|
}
|
||||||
|
|
||||||
for verification_type, result in verification_results.items():
|
for verification_type, result in verification_results.items():
|
||||||
if verification_type in weights:
|
if verification_type in weights:
|
||||||
weight = weights[verification_type]
|
weight = weights[verification_type]
|
||||||
total_weight += weight
|
total_weight += weight
|
||||||
|
|
||||||
if result.get('status') == 'verified':
|
if result.get('status') == 'verified':
|
||||||
confidence = result.get('confidence', 0.8)
|
confidence = result.get('confidence', 0.8)
|
||||||
total_score += confidence * weight
|
total_score += confidence * weight
|
||||||
@ -215,11 +215,11 @@ def _calculate_credibility_score(verification_results: Dict[str, Any]) -> float:
|
|||||||
elif result.get('status') == 'error':
|
elif result.get('status') == 'error':
|
||||||
# Erreur réduit le score
|
# Erreur réduit le score
|
||||||
total_score += 0.2 * weight
|
total_score += 0.2 * weight
|
||||||
|
|
||||||
# Normalisation du score
|
# Normalisation du score
|
||||||
if total_weight > 0:
|
if total_weight > 0:
|
||||||
final_score = total_score / total_weight
|
final_score = total_score / total_weight
|
||||||
else:
|
else:
|
||||||
final_score = 0.5 # Score par défaut si aucune vérification
|
final_score = 0.5 # Score par défaut si aucune vérification
|
||||||
|
|
||||||
return min(max(final_score, 0.0), 1.0)
|
return min(max(final_score, 0.0), 1.0)
|
||||||
|
@ -12,37 +12,37 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class AnythingLLMClient:
|
class AnythingLLMClient:
|
||||||
"""Client pour l'intégration avec AnythingLLM"""
|
"""Client pour l'intégration avec AnythingLLM"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.base_url = os.getenv('ANYLLM_BASE_URL', 'http://anythingllm:3001')
|
self.base_url = os.getenv('ANYLLM_BASE_URL', 'http://anythingllm:3001')
|
||||||
self.api_key = os.getenv('ANYLLM_API_KEY', 'change_me')
|
self.api_key = os.getenv('ANYLLM_API_KEY', 'change_me')
|
||||||
|
|
||||||
# Configuration des workspaces
|
# Configuration des workspaces
|
||||||
self.workspaces = {
|
self.workspaces = {
|
||||||
'normes': os.getenv('ANYLLM_WORKSPACE_NORMES', 'workspace_normes'),
|
'normes': os.getenv('ANYLLM_WORKSPACE_NORMES', 'workspace_normes'),
|
||||||
'trames': os.getenv('ANYLLM_WORKSPACE_TRAMES', 'workspace_trames'),
|
'trames': os.getenv('ANYLLM_WORKSPACE_TRAMES', 'workspace_trames'),
|
||||||
'actes': os.getenv('ANYLLM_WORKSPACE_ACTES', 'workspace_actes')
|
'actes': os.getenv('ANYLLM_WORKSPACE_ACTES', 'workspace_actes')
|
||||||
}
|
}
|
||||||
|
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.session.headers.update({
|
self.session.headers.update({
|
||||||
'Authorization': f'Bearer {self.api_key}',
|
'Authorization': f'Bearer {self.api_key}',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
})
|
})
|
||||||
|
|
||||||
async def create_workspace(self, name: str, description: str = None) -> Dict[str, Any]:
|
async def create_workspace(self, name: str, description: str = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Création d'un workspace AnythingLLM
|
Création d'un workspace AnythingLLM
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Nom du workspace
|
name: Nom du workspace
|
||||||
description: Description du workspace
|
description: Description du workspace
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de la création
|
Résultat de la création
|
||||||
"""
|
"""
|
||||||
logger.info(f"🏢 Création du workspace AnythingLLM: {name}")
|
logger.info(f"🏢 Création du workspace AnythingLLM: {name}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payload = {
|
payload = {
|
||||||
'name': name,
|
'name': name,
|
||||||
@ -55,13 +55,13 @@ class AnythingLLMClient:
|
|||||||
'embeddingsModel': 'text-embedding-ada-002',
|
'embeddingsModel': 'text-embedding-ada-002',
|
||||||
'vectorTag': name.lower().replace(' ', '_')
|
'vectorTag': name.lower().replace(' ', '_')
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.session.post(
|
response = self.session.post(
|
||||||
f"{self.base_url}/api/workspace/new",
|
f"{self.base_url}/api/workspace/new",
|
||||||
json=payload,
|
json=payload,
|
||||||
timeout=30
|
timeout=30
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
logger.info(f"✅ Workspace {name} créé avec succès")
|
logger.info(f"✅ Workspace {name} créé avec succès")
|
||||||
@ -78,49 +78,49 @@ class AnythingLLMClient:
|
|||||||
'error': f"Erreur API: {response.status_code}",
|
'error': f"Erreur API: {response.status_code}",
|
||||||
'response': response.text
|
'response': response.text
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la création du workspace {name}: {e}")
|
logger.error(f"Erreur lors de la création du workspace {name}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def upload_document(self, workspace_id: str, document_data: bytes, filename: str,
|
async def upload_document(self, workspace_id: str, document_data: bytes, filename: str,
|
||||||
metadata: Dict[str, Any] = None) -> Dict[str, Any]:
|
metadata: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Upload d'un document dans un workspace
|
Upload d'un document dans un workspace
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
workspace_id: ID du workspace
|
workspace_id: ID du workspace
|
||||||
document_data: Données du document
|
document_data: Données du document
|
||||||
filename: Nom du fichier
|
filename: Nom du fichier
|
||||||
metadata: Métadonnées du document
|
metadata: Métadonnées du document
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de l'upload
|
Résultat de l'upload
|
||||||
"""
|
"""
|
||||||
logger.info(f"📄 Upload du document {filename} dans le workspace {workspace_id}")
|
logger.info(f"📄 Upload du document {filename} dans le workspace {workspace_id}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Préparation des fichiers
|
# Préparation des fichiers
|
||||||
files = {
|
files = {
|
||||||
'file': (filename, document_data, 'application/octet-stream')
|
'file': (filename, document_data, 'application/octet-stream')
|
||||||
}
|
}
|
||||||
|
|
||||||
# Préparation des données
|
# Préparation des données
|
||||||
data = {
|
data = {
|
||||||
'workspaceId': workspace_id,
|
'workspaceId': workspace_id,
|
||||||
'chunkSize': 1000,
|
'chunkSize': 1000,
|
||||||
'chunkOverlap': 200
|
'chunkOverlap': 200
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata:
|
if metadata:
|
||||||
data['metadata'] = json.dumps(metadata)
|
data['metadata'] = json.dumps(metadata)
|
||||||
|
|
||||||
# Suppression de l'header Content-Type pour les multipart
|
# Suppression de l'header Content-Type pour les multipart
|
||||||
headers = {'Authorization': f'Bearer {self.api_key}'}
|
headers = {'Authorization': f'Bearer {self.api_key}'}
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.base_url}/api/workspace/{workspace_id}/upload",
|
f"{self.base_url}/api/workspace/{workspace_id}/upload",
|
||||||
files=files,
|
files=files,
|
||||||
@ -128,7 +128,7 @@ class AnythingLLMClient:
|
|||||||
headers=headers,
|
headers=headers,
|
||||||
timeout=60
|
timeout=60
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
logger.info(f"✅ Document {filename} uploadé avec succès")
|
logger.info(f"✅ Document {filename} uploadé avec succès")
|
||||||
@ -147,29 +147,29 @@ class AnythingLLMClient:
|
|||||||
'error': f"Erreur API: {response.status_code}",
|
'error': f"Erreur API: {response.status_code}",
|
||||||
'response': response.text
|
'response': response.text
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de l'upload du document {filename}: {e}")
|
logger.error(f"Erreur lors de l'upload du document {filename}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def search_documents(self, workspace_id: str, query: str,
|
async def search_documents(self, workspace_id: str, query: str,
|
||||||
limit: int = 10) -> Dict[str, Any]:
|
limit: int = 10) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Recherche dans les documents d'un workspace
|
Recherche dans les documents d'un workspace
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
workspace_id: ID du workspace
|
workspace_id: ID du workspace
|
||||||
query: Requête de recherche
|
query: Requête de recherche
|
||||||
limit: Nombre maximum de résultats
|
limit: Nombre maximum de résultats
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultats de la recherche
|
Résultats de la recherche
|
||||||
"""
|
"""
|
||||||
logger.info(f"🔍 Recherche dans le workspace {workspace_id}: {query}")
|
logger.info(f"🔍 Recherche dans le workspace {workspace_id}: {query}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payload = {
|
payload = {
|
||||||
'workspaceId': workspace_id,
|
'workspaceId': workspace_id,
|
||||||
@ -179,13 +179,13 @@ class AnythingLLMClient:
|
|||||||
'temperature': 0.7,
|
'temperature': 0.7,
|
||||||
'topK': limit
|
'topK': limit
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.session.post(
|
response = self.session.post(
|
||||||
f"{self.base_url}/api/workspace/{workspace_id}/chat",
|
f"{self.base_url}/api/workspace/{workspace_id}/chat",
|
||||||
json=payload,
|
json=payload,
|
||||||
timeout=30
|
timeout=30
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
logger.info(f"✅ Recherche terminée, {len(data.get('sources', []))} résultats")
|
logger.info(f"✅ Recherche terminée, {len(data.get('sources', []))} résultats")
|
||||||
@ -204,21 +204,21 @@ class AnythingLLMClient:
|
|||||||
'error': f"Erreur API: {response.status_code}",
|
'error': f"Erreur API: {response.status_code}",
|
||||||
'response': response.text
|
'response': response.text
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la recherche: {e}")
|
logger.error(f"Erreur lors de la recherche: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def get_workspace_info(self, workspace_id: str) -> Dict[str, Any]:
|
async def get_workspace_info(self, workspace_id: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Récupération des informations d'un workspace
|
Récupération des informations d'un workspace
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
workspace_id: ID du workspace
|
workspace_id: ID du workspace
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Informations du workspace
|
Informations du workspace
|
||||||
"""
|
"""
|
||||||
@ -227,7 +227,7 @@ class AnythingLLMClient:
|
|||||||
f"{self.base_url}/api/workspace/{workspace_id}",
|
f"{self.base_url}/api/workspace/{workspace_id}",
|
||||||
timeout=10
|
timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
return {
|
return {
|
||||||
@ -240,18 +240,18 @@ class AnythingLLMClient:
|
|||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': f"Erreur API: {response.status_code}"
|
'error': f"Erreur API: {response.status_code}"
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la récupération du workspace: {e}")
|
logger.error(f"Erreur lors de la récupération du workspace: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def list_workspaces(self) -> Dict[str, Any]:
|
async def list_workspaces(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Liste tous les workspaces disponibles
|
Liste tous les workspaces disponibles
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Liste des workspaces
|
Liste des workspaces
|
||||||
"""
|
"""
|
||||||
@ -260,7 +260,7 @@ class AnythingLLMClient:
|
|||||||
f"{self.base_url}/api/workspaces",
|
f"{self.base_url}/api/workspaces",
|
||||||
timeout=10
|
timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
return {
|
return {
|
||||||
@ -274,35 +274,35 @@ class AnythingLLMClient:
|
|||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': f"Erreur API: {response.status_code}"
|
'error': f"Erreur API: {response.status_code}"
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la liste des workspaces: {e}")
|
logger.error(f"Erreur lors de la liste des workspaces: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def index_document_for_actes(self, doc_id: str, text: str,
|
async def index_document_for_actes(self, doc_id: str, text: str,
|
||||||
entities: Dict[str, Any],
|
entities: Dict[str, Any],
|
||||||
doc_type: str) -> Dict[str, Any]:
|
doc_type: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Indexation d'un document dans le workspace des actes
|
Indexation d'un document dans le workspace des actes
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
doc_id: ID du document
|
doc_id: ID du document
|
||||||
text: Texte du document
|
text: Texte du document
|
||||||
entities: Entités extraites
|
entities: Entités extraites
|
||||||
doc_type: Type de document
|
doc_type: Type de document
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de l'indexation
|
Résultat de l'indexation
|
||||||
"""
|
"""
|
||||||
logger.info(f"📚 Indexation du document {doc_id} dans le workspace actes")
|
logger.info(f"📚 Indexation du document {doc_id} dans le workspace actes")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Préparation du contenu structuré
|
# Préparation du contenu structuré
|
||||||
structured_content = self._prepare_structured_content(doc_id, text, entities, doc_type)
|
structured_content = self._prepare_structured_content(doc_id, text, entities, doc_type)
|
||||||
|
|
||||||
# Upload du contenu structuré
|
# Upload du contenu structuré
|
||||||
workspace_id = await self._get_workspace_id('actes')
|
workspace_id = await self._get_workspace_id('actes')
|
||||||
if not workspace_id:
|
if not workspace_id:
|
||||||
@ -310,39 +310,39 @@ class AnythingLLMClient:
|
|||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': 'Workspace actes non trouvé'
|
'error': 'Workspace actes non trouvé'
|
||||||
}
|
}
|
||||||
|
|
||||||
filename = f"{doc_id}_structured.txt"
|
filename = f"{doc_id}_structured.txt"
|
||||||
document_data = structured_content.encode('utf-8')
|
document_data = structured_content.encode('utf-8')
|
||||||
|
|
||||||
result = await self.upload_document(workspace_id, document_data, filename, {
|
result = await self.upload_document(workspace_id, document_data, filename, {
|
||||||
'doc_id': doc_id,
|
'doc_id': doc_id,
|
||||||
'doc_type': doc_type,
|
'doc_type': doc_type,
|
||||||
'entities': entities,
|
'entities': entities,
|
||||||
'indexed_at': datetime.now().isoformat()
|
'indexed_at': datetime.now().isoformat()
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de l'indexation du document {doc_id}: {e}")
|
logger.error(f"Erreur lors de l'indexation du document {doc_id}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def search_similar_actes(self, doc_type: str, entities: Dict[str, Any]) -> Dict[str, Any]:
|
async def search_similar_actes(self, doc_type: str, entities: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Recherche d'actes similaires
|
Recherche d'actes similaires
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
doc_type: Type de document
|
doc_type: Type de document
|
||||||
entities: Entités extraites
|
entities: Entités extraites
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Actes similaires trouvés
|
Actes similaires trouvés
|
||||||
"""
|
"""
|
||||||
logger.info(f"🔍 Recherche d'actes similaires pour le type: {doc_type}")
|
logger.info(f"🔍 Recherche d'actes similaires pour le type: {doc_type}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
workspace_id = await self._get_workspace_id('actes')
|
workspace_id = await self._get_workspace_id('actes')
|
||||||
if not workspace_id:
|
if not workspace_id:
|
||||||
@ -350,22 +350,22 @@ class AnythingLLMClient:
|
|||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': 'Workspace actes non trouvé'
|
'error': 'Workspace actes non trouvé'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Construction de la requête de recherche
|
# Construction de la requête de recherche
|
||||||
query = self._build_similarity_query(doc_type, entities)
|
query = self._build_similarity_query(doc_type, entities)
|
||||||
|
|
||||||
result = await self.search_documents(workspace_id, query, limit=5)
|
result = await self.search_documents(workspace_id, query, limit=5)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la recherche d'actes similaires: {e}")
|
logger.error(f"Erreur lors de la recherche d'actes similaires: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
def _prepare_structured_content(self, doc_id: str, text: str,
|
def _prepare_structured_content(self, doc_id: str, text: str,
|
||||||
entities: Dict[str, Any], doc_type: str) -> str:
|
entities: Dict[str, Any], doc_type: str) -> str:
|
||||||
"""Prépare le contenu structuré pour l'indexation"""
|
"""Prépare le contenu structuré pour l'indexation"""
|
||||||
content = f"""DOCUMENT ID: {doc_id}
|
content = f"""DOCUMENT ID: {doc_id}
|
||||||
@ -382,11 +382,11 @@ TEXTE DU DOCUMENT:
|
|||||||
Ce document a été traité par le pipeline notarial v1.2.0
|
Ce document a été traité par le pipeline notarial v1.2.0
|
||||||
"""
|
"""
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def _build_similarity_query(self, doc_type: str, entities: Dict[str, Any]) -> str:
|
def _build_similarity_query(self, doc_type: str, entities: Dict[str, Any]) -> str:
|
||||||
"""Construit une requête de recherche pour trouver des actes similaires"""
|
"""Construit une requête de recherche pour trouver des actes similaires"""
|
||||||
query_parts = [f"type:{doc_type}"]
|
query_parts = [f"type:{doc_type}"]
|
||||||
|
|
||||||
# Ajout des entités importantes
|
# Ajout des entités importantes
|
||||||
if 'vendeur' in entities:
|
if 'vendeur' in entities:
|
||||||
query_parts.append(f"vendeur:{entities['vendeur'].get('nom', '')}")
|
query_parts.append(f"vendeur:{entities['vendeur'].get('nom', '')}")
|
||||||
@ -394,9 +394,9 @@ Ce document a été traité par le pipeline notarial v1.2.0
|
|||||||
query_parts.append(f"acheteur:{entities['acheteur'].get('nom', '')}")
|
query_parts.append(f"acheteur:{entities['acheteur'].get('nom', '')}")
|
||||||
if 'bien' in entities:
|
if 'bien' in entities:
|
||||||
query_parts.append(f"adresse:{entities['bien'].get('adresse', '')}")
|
query_parts.append(f"adresse:{entities['bien'].get('adresse', '')}")
|
||||||
|
|
||||||
return " ".join(query_parts)
|
return " ".join(query_parts)
|
||||||
|
|
||||||
async def _get_workspace_id(self, workspace_name: str) -> Optional[str]:
|
async def _get_workspace_id(self, workspace_name: str) -> Optional[str]:
|
||||||
"""Récupère l'ID d'un workspace par son nom"""
|
"""Récupère l'ID d'un workspace par son nom"""
|
||||||
try:
|
try:
|
||||||
|
@ -12,13 +12,13 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class ExternalAPIManager:
|
class ExternalAPIManager:
|
||||||
"""Gestionnaire des APIs externes pour la vérification des données"""
|
"""Gestionnaire des APIs externes pour la vérification des données"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.session.headers.update({
|
self.session.headers.update({
|
||||||
'User-Agent': 'Notariat-Pipeline/1.2.0'
|
'User-Agent': 'Notariat-Pipeline/1.2.0'
|
||||||
})
|
})
|
||||||
|
|
||||||
# Configuration des URLs des APIs
|
# Configuration des URLs des APIs
|
||||||
self.apis = {
|
self.apis = {
|
||||||
'cadastre': os.getenv('CADASTRE_API_URL', 'https://apicarto.ign.fr/api/cadastre'),
|
'cadastre': os.getenv('CADASTRE_API_URL', 'https://apicarto.ign.fr/api/cadastre'),
|
||||||
@ -27,52 +27,52 @@ class ExternalAPIManager:
|
|||||||
'infogreffe': os.getenv('INFOGREFFE_API_URL', 'https://entreprise.api.gouv.fr/v2/infogreffe'),
|
'infogreffe': os.getenv('INFOGREFFE_API_URL', 'https://entreprise.api.gouv.fr/v2/infogreffe'),
|
||||||
'rbe': os.getenv('RBE_API_URL', 'https://www.data.gouv.fr/api/1/datasets/registre-des-beneficiaires-effectifs')
|
'rbe': os.getenv('RBE_API_URL', 'https://www.data.gouv.fr/api/1/datasets/registre-des-beneficiaires-effectifs')
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache pour éviter les appels répétés
|
# Cache pour éviter les appels répétés
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
self.cache_ttl = 3600 # 1 heure
|
self.cache_ttl = 3600 # 1 heure
|
||||||
|
|
||||||
async def verify_address(self, address: str, postal_code: str = None, city: str = None) -> Dict[str, Any]:
|
async def verify_address(self, address: str, postal_code: str = None, city: str = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Vérification d'une adresse via l'API Cadastre
|
Vérification d'une adresse via l'API Cadastre
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
address: Adresse à vérifier
|
address: Adresse à vérifier
|
||||||
postal_code: Code postal
|
postal_code: Code postal
|
||||||
city: Ville
|
city: Ville
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de la vérification
|
Résultat de la vérification
|
||||||
"""
|
"""
|
||||||
logger.info(f"🏠 Vérification de l'adresse: {address}")
|
logger.info(f"🏠 Vérification de l'adresse: {address}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Construction de la requête
|
# Construction de la requête
|
||||||
params = {
|
params = {
|
||||||
'q': address,
|
'q': address,
|
||||||
'limit': 5
|
'limit': 5
|
||||||
}
|
}
|
||||||
|
|
||||||
if postal_code:
|
if postal_code:
|
||||||
params['code_postal'] = postal_code
|
params['code_postal'] = postal_code
|
||||||
if city:
|
if city:
|
||||||
params['commune'] = city
|
params['commune'] = city
|
||||||
|
|
||||||
# Appel à l'API Cadastre
|
# Appel à l'API Cadastre
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
f"{self.apis['cadastre']}/parcelle",
|
f"{self.apis['cadastre']}/parcelle",
|
||||||
params=params,
|
params=params,
|
||||||
timeout=10
|
timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
if data.get('features'):
|
if data.get('features'):
|
||||||
# Adresse trouvée
|
# Adresse trouvée
|
||||||
feature = data['features'][0]
|
feature = data['features'][0]
|
||||||
properties = feature.get('properties', {})
|
properties = feature.get('properties', {})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'status': 'verified',
|
'status': 'verified',
|
||||||
'confidence': 0.95,
|
'confidence': 0.95,
|
||||||
@ -100,7 +100,7 @@ class ExternalAPIManager:
|
|||||||
'error': f"Erreur API: {response.status_code}",
|
'error': f"Erreur API: {response.status_code}",
|
||||||
'source': 'cadastre_api'
|
'source': 'cadastre_api'
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la vérification de l'adresse: {e}")
|
logger.error(f"Erreur lors de la vérification de l'adresse: {e}")
|
||||||
return {
|
return {
|
||||||
@ -109,27 +109,27 @@ class ExternalAPIManager:
|
|||||||
'error': str(e),
|
'error': str(e),
|
||||||
'source': 'cadastre_api'
|
'source': 'cadastre_api'
|
||||||
}
|
}
|
||||||
|
|
||||||
async def check_geological_risks(self, address: str, coordinates: List[float] = None) -> Dict[str, Any]:
|
async def check_geological_risks(self, address: str, coordinates: List[float] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Vérification des risques géologiques via l'API Géorisques
|
Vérification des risques géologiques via l'API Géorisques
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
address: Adresse à vérifier
|
address: Adresse à vérifier
|
||||||
coordinates: Coordonnées GPS [longitude, latitude]
|
coordinates: Coordonnées GPS [longitude, latitude]
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de la vérification des risques
|
Résultat de la vérification des risques
|
||||||
"""
|
"""
|
||||||
logger.info(f"🌍 Vérification des risques géologiques: {address}")
|
logger.info(f"🌍 Vérification des risques géologiques: {address}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Si pas de coordonnées, essayer de les obtenir via géocodage
|
# Si pas de coordonnées, essayer de les obtenir via géocodage
|
||||||
if not coordinates:
|
if not coordinates:
|
||||||
coords_result = await self._geocode_address(address)
|
coords_result = await self._geocode_address(address)
|
||||||
if coords_result.get('coordinates'):
|
if coords_result.get('coordinates'):
|
||||||
coordinates = coords_result['coordinates']
|
coordinates = coords_result['coordinates']
|
||||||
|
|
||||||
if not coordinates:
|
if not coordinates:
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
@ -137,23 +137,23 @@ class ExternalAPIManager:
|
|||||||
'error': 'Coordonnées non disponibles',
|
'error': 'Coordonnées non disponibles',
|
||||||
'source': 'georisques_api'
|
'source': 'georisques_api'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Appel à l'API Géorisques
|
# Appel à l'API Géorisques
|
||||||
params = {
|
params = {
|
||||||
'lon': coordinates[0],
|
'lon': coordinates[0],
|
||||||
'lat': coordinates[1],
|
'lat': coordinates[1],
|
||||||
'distance': 1000 # 1km de rayon
|
'distance': 1000 # 1km de rayon
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
f"{self.apis['georisques']}/v1/gaspar/risques",
|
f"{self.apis['georisques']}/v1/gaspar/risques",
|
||||||
params=params,
|
params=params,
|
||||||
timeout=10
|
timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
risks = []
|
risks = []
|
||||||
if data.get('data'):
|
if data.get('data'):
|
||||||
for risk in data['data']:
|
for risk in data['data']:
|
||||||
@ -163,7 +163,7 @@ class ExternalAPIManager:
|
|||||||
'description': risk.get('description', ''),
|
'description': risk.get('description', ''),
|
||||||
'distance': risk.get('distance', 0)
|
'distance': risk.get('distance', 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'status': 'completed',
|
'status': 'completed',
|
||||||
'confidence': 0.90,
|
'confidence': 0.90,
|
||||||
@ -181,7 +181,7 @@ class ExternalAPIManager:
|
|||||||
'error': f"Erreur API: {response.status_code}",
|
'error': f"Erreur API: {response.status_code}",
|
||||||
'source': 'georisques_api'
|
'source': 'georisques_api'
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la vérification des risques géologiques: {e}")
|
logger.error(f"Erreur lors de la vérification des risques géologiques: {e}")
|
||||||
return {
|
return {
|
||||||
@ -190,45 +190,45 @@ class ExternalAPIManager:
|
|||||||
'error': str(e),
|
'error': str(e),
|
||||||
'source': 'georisques_api'
|
'source': 'georisques_api'
|
||||||
}
|
}
|
||||||
|
|
||||||
async def verify_company(self, company_name: str, siren: str = None) -> Dict[str, Any]:
|
async def verify_company(self, company_name: str, siren: str = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Vérification d'une entreprise via l'API BODACC
|
Vérification d'une entreprise via l'API BODACC
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
company_name: Nom de l'entreprise
|
company_name: Nom de l'entreprise
|
||||||
siren: Numéro SIREN (optionnel)
|
siren: Numéro SIREN (optionnel)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de la vérification de l'entreprise
|
Résultat de la vérification de l'entreprise
|
||||||
"""
|
"""
|
||||||
logger.info(f"🏢 Vérification de l'entreprise: {company_name}")
|
logger.info(f"🏢 Vérification de l'entreprise: {company_name}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Construction de la requête
|
# Construction de la requête
|
||||||
params = {
|
params = {
|
||||||
'q': company_name,
|
'q': company_name,
|
||||||
'rows': 5
|
'rows': 5
|
||||||
}
|
}
|
||||||
|
|
||||||
if siren:
|
if siren:
|
||||||
params['siren'] = siren
|
params['siren'] = siren
|
||||||
|
|
||||||
# Appel à l'API BODACC
|
# Appel à l'API BODACC
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
f"{self.apis['bodacc']}/records/1.0/search/",
|
f"{self.apis['bodacc']}/records/1.0/search/",
|
||||||
params=params,
|
params=params,
|
||||||
timeout=10
|
timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
if data.get('records'):
|
if data.get('records'):
|
||||||
# Entreprise trouvée
|
# Entreprise trouvée
|
||||||
record = data['records'][0]
|
record = data['records'][0]
|
||||||
fields = record.get('fields', {})
|
fields = record.get('fields', {})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'status': 'verified',
|
'status': 'verified',
|
||||||
'confidence': 0.90,
|
'confidence': 0.90,
|
||||||
@ -262,7 +262,7 @@ class ExternalAPIManager:
|
|||||||
'error': f"Erreur API: {response.status_code}",
|
'error': f"Erreur API: {response.status_code}",
|
||||||
'source': 'bodacc_api'
|
'source': 'bodacc_api'
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la vérification de l'entreprise: {e}")
|
logger.error(f"Erreur lors de la vérification de l'entreprise: {e}")
|
||||||
return {
|
return {
|
||||||
@ -271,28 +271,28 @@ class ExternalAPIManager:
|
|||||||
'error': str(e),
|
'error': str(e),
|
||||||
'source': 'bodacc_api'
|
'source': 'bodacc_api'
|
||||||
}
|
}
|
||||||
|
|
||||||
async def verify_person(self, first_name: str, last_name: str, birth_date: str = None) -> Dict[str, Any]:
|
async def verify_person(self, first_name: str, last_name: str, birth_date: str = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Vérification d'une personne (recherche d'informations publiques)
|
Vérification d'une personne (recherche d'informations publiques)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
first_name: Prénom
|
first_name: Prénom
|
||||||
last_name: Nom de famille
|
last_name: Nom de famille
|
||||||
birth_date: Date de naissance (format YYYY-MM-DD)
|
birth_date: Date de naissance (format YYYY-MM-DD)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de la vérification de la personne
|
Résultat de la vérification de la personne
|
||||||
"""
|
"""
|
||||||
logger.info(f"👤 Vérification de la personne: {first_name} {last_name}")
|
logger.info(f"👤 Vérification de la personne: {first_name} {last_name}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Recherche dans le RBE (Registre des Bénéficiaires Effectifs)
|
# Recherche dans le RBE (Registre des Bénéficiaires Effectifs)
|
||||||
rbe_result = await self._search_rbe(first_name, last_name)
|
rbe_result = await self._search_rbe(first_name, last_name)
|
||||||
|
|
||||||
# Recherche dans Infogreffe (si entreprise)
|
# Recherche dans Infogreffe (si entreprise)
|
||||||
infogreffe_result = await self._search_infogreffe(first_name, last_name)
|
infogreffe_result = await self._search_infogreffe(first_name, last_name)
|
||||||
|
|
||||||
# Compilation des résultats
|
# Compilation des résultats
|
||||||
results = {
|
results = {
|
||||||
'status': 'completed',
|
'status': 'completed',
|
||||||
@ -304,13 +304,13 @@ class ExternalAPIManager:
|
|||||||
'source': 'multiple_apis',
|
'source': 'multiple_apis',
|
||||||
'verified_at': datetime.now().isoformat()
|
'verified_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Calcul de la confiance globale
|
# Calcul de la confiance globale
|
||||||
if rbe_result.get('found') or infogreffe_result.get('found'):
|
if rbe_result.get('found') or infogreffe_result.get('found'):
|
||||||
results['confidence'] = 0.85
|
results['confidence'] = 0.85
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la vérification de la personne: {e}")
|
logger.error(f"Erreur lors de la vérification de la personne: {e}")
|
||||||
return {
|
return {
|
||||||
@ -319,7 +319,7 @@ class ExternalAPIManager:
|
|||||||
'error': str(e),
|
'error': str(e),
|
||||||
'source': 'person_verification'
|
'source': 'person_verification'
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _geocode_address(self, address: str) -> Dict[str, Any]:
|
async def _geocode_address(self, address: str) -> Dict[str, Any]:
|
||||||
"""Géocodage d'une adresse"""
|
"""Géocodage d'une adresse"""
|
||||||
try:
|
try:
|
||||||
@ -328,13 +328,13 @@ class ExternalAPIManager:
|
|||||||
'q': address,
|
'q': address,
|
||||||
'limit': 1
|
'limit': 1
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
f"{self.apis['cadastre']}/geocodage",
|
f"{self.apis['cadastre']}/geocodage",
|
||||||
params=params,
|
params=params,
|
||||||
timeout=10
|
timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
if data.get('features'):
|
if data.get('features'):
|
||||||
@ -344,13 +344,13 @@ class ExternalAPIManager:
|
|||||||
'coordinates': coords,
|
'coordinates': coords,
|
||||||
'formatted_address': feature.get('properties', {}).get('label', address)
|
'formatted_address': feature.get('properties', {}).get('label', address)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {'coordinates': None}
|
return {'coordinates': None}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors du géocodage: {e}")
|
logger.error(f"Erreur lors du géocodage: {e}")
|
||||||
return {'coordinates': None}
|
return {'coordinates': None}
|
||||||
|
|
||||||
async def _search_rbe(self, first_name: str, last_name: str) -> Dict[str, Any]:
|
async def _search_rbe(self, first_name: str, last_name: str) -> Dict[str, Any]:
|
||||||
"""Recherche dans le Registre des Bénéficiaires Effectifs"""
|
"""Recherche dans le Registre des Bénéficiaires Effectifs"""
|
||||||
try:
|
try:
|
||||||
@ -358,13 +358,13 @@ class ExternalAPIManager:
|
|||||||
'q': f"{first_name} {last_name}",
|
'q': f"{first_name} {last_name}",
|
||||||
'rows': 5
|
'rows': 5
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
f"{self.apis['rbe']}/search",
|
f"{self.apis['rbe']}/search",
|
||||||
params=params,
|
params=params,
|
||||||
timeout=10
|
timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
return {
|
return {
|
||||||
@ -372,13 +372,13 @@ class ExternalAPIManager:
|
|||||||
'count': len(data.get('results', [])),
|
'count': len(data.get('results', [])),
|
||||||
'results': data.get('results', [])[:3] # Limite à 3 résultats
|
'results': data.get('results', [])[:3] # Limite à 3 résultats
|
||||||
}
|
}
|
||||||
|
|
||||||
return {'found': False, 'count': 0, 'results': []}
|
return {'found': False, 'count': 0, 'results': []}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la recherche RBE: {e}")
|
logger.error(f"Erreur lors de la recherche RBE: {e}")
|
||||||
return {'found': False, 'count': 0, 'results': []}
|
return {'found': False, 'count': 0, 'results': []}
|
||||||
|
|
||||||
async def _search_infogreffe(self, first_name: str, last_name: str) -> Dict[str, Any]:
|
async def _search_infogreffe(self, first_name: str, last_name: str) -> Dict[str, Any]:
|
||||||
"""Recherche dans Infogreffe"""
|
"""Recherche dans Infogreffe"""
|
||||||
try:
|
try:
|
||||||
@ -386,13 +386,13 @@ class ExternalAPIManager:
|
|||||||
'q': f"{first_name} {last_name}",
|
'q': f"{first_name} {last_name}",
|
||||||
'per_page': 5
|
'per_page': 5
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
f"{self.apis['infogreffe']}/search",
|
f"{self.apis['infogreffe']}/search",
|
||||||
params=params,
|
params=params,
|
||||||
timeout=10
|
timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
return {
|
return {
|
||||||
@ -400,35 +400,35 @@ class ExternalAPIManager:
|
|||||||
'count': len(data.get('results', [])),
|
'count': len(data.get('results', [])),
|
||||||
'results': data.get('results', [])[:3] # Limite à 3 résultats
|
'results': data.get('results', [])[:3] # Limite à 3 résultats
|
||||||
}
|
}
|
||||||
|
|
||||||
return {'found': False, 'count': 0, 'results': []}
|
return {'found': False, 'count': 0, 'results': []}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la recherche Infogreffe: {e}")
|
logger.error(f"Erreur lors de la recherche Infogreffe: {e}")
|
||||||
return {'found': False, 'count': 0, 'results': []}
|
return {'found': False, 'count': 0, 'results': []}
|
||||||
|
|
||||||
def get_cache_key(self, api: str, params: Dict[str, Any]) -> str:
|
def get_cache_key(self, api: str, params: Dict[str, Any]) -> str:
|
||||||
"""Génère une clé de cache pour les paramètres donnés"""
|
"""Génère une clé de cache pour les paramètres donnés"""
|
||||||
import hashlib
|
import hashlib
|
||||||
key_data = f"{api}:{json.dumps(params, sort_keys=True)}"
|
key_data = f"{api}:{json.dumps(params, sort_keys=True)}"
|
||||||
return hashlib.md5(key_data.encode()).hexdigest()
|
return hashlib.md5(key_data.encode()).hexdigest()
|
||||||
|
|
||||||
def is_cache_valid(self, cache_key: str) -> bool:
|
def is_cache_valid(self, cache_key: str) -> bool:
|
||||||
"""Vérifie si le cache est encore valide"""
|
"""Vérifie si le cache est encore valide"""
|
||||||
if cache_key not in self.cache:
|
if cache_key not in self.cache:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
cache_time = self.cache[cache_key].get('timestamp', 0)
|
cache_time = self.cache[cache_key].get('timestamp', 0)
|
||||||
current_time = datetime.now().timestamp()
|
current_time = datetime.now().timestamp()
|
||||||
|
|
||||||
return (current_time - cache_time) < self.cache_ttl
|
return (current_time - cache_time) < self.cache_ttl
|
||||||
|
|
||||||
def get_from_cache(self, cache_key: str) -> Optional[Dict[str, Any]]:
|
def get_from_cache(self, cache_key: str) -> Optional[Dict[str, Any]]:
|
||||||
"""Récupère une valeur du cache"""
|
"""Récupère une valeur du cache"""
|
||||||
if self.is_cache_valid(cache_key):
|
if self.is_cache_valid(cache_key):
|
||||||
return self.cache[cache_key].get('data')
|
return self.cache[cache_key].get('data')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_cache(self, cache_key: str, data: Dict[str, Any]) -> None:
|
def set_cache(self, cache_key: str, data: Dict[str, Any]) -> None:
|
||||||
"""Met une valeur en cache"""
|
"""Met une valeur en cache"""
|
||||||
self.cache[cache_key] = {
|
self.cache[cache_key] = {
|
||||||
|
@ -12,15 +12,15 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class Neo4jClient:
|
class Neo4jClient:
|
||||||
"""Client pour l'intégration avec Neo4j"""
|
"""Client pour l'intégration avec Neo4j"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.uri = os.getenv('NEO4J_URI', 'bolt://neo4j:7687')
|
self.uri = os.getenv('NEO4J_URI', 'bolt://neo4j:7687')
|
||||||
self.username = os.getenv('NEO4J_USER', 'neo4j')
|
self.username = os.getenv('NEO4J_USER', 'neo4j')
|
||||||
self.password = os.getenv('NEO4J_PASSWORD', 'neo4j_pwd')
|
self.password = os.getenv('NEO4J_PASSWORD', 'neo4j_pwd')
|
||||||
|
|
||||||
self.driver = None
|
self.driver = None
|
||||||
self._connect()
|
self._connect()
|
||||||
|
|
||||||
def _connect(self):
|
def _connect(self):
|
||||||
"""Connexion à Neo4j"""
|
"""Connexion à Neo4j"""
|
||||||
try:
|
try:
|
||||||
@ -32,25 +32,25 @@ class Neo4jClient:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur de connexion à Neo4j: {e}")
|
logger.error(f"❌ Erreur de connexion à Neo4j: {e}")
|
||||||
self.driver = None
|
self.driver = None
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Fermeture de la connexion"""
|
"""Fermeture de la connexion"""
|
||||||
if self.driver:
|
if self.driver:
|
||||||
self.driver.close()
|
self.driver.close()
|
||||||
|
|
||||||
async def create_dossier_context(self, dossier_id: str, metadata: Dict[str, Any]) -> Dict[str, Any]:
|
async def create_dossier_context(self, dossier_id: str, metadata: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Création du contexte d'un dossier dans le graphe
|
Création du contexte d'un dossier dans le graphe
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dossier_id: ID du dossier
|
dossier_id: ID du dossier
|
||||||
metadata: Métadonnées du dossier
|
metadata: Métadonnées du dossier
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de la création
|
Résultat de la création
|
||||||
"""
|
"""
|
||||||
logger.info(f"📁 Création du contexte du dossier {dossier_id}")
|
logger.info(f"📁 Création du contexte du dossier {dossier_id}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.driver.session() as session:
|
with self.driver.session() as session:
|
||||||
# Création du nœud dossier
|
# Création du nœud dossier
|
||||||
@ -63,14 +63,14 @@ class Neo4jClient:
|
|||||||
d.status = $status,
|
d.status = $status,
|
||||||
d.metadata = $metadata
|
d.metadata = $metadata
|
||||||
RETURN d
|
RETURN d
|
||||||
""",
|
""",
|
||||||
dossier_id=dossier_id,
|
dossier_id=dossier_id,
|
||||||
etude_id=metadata.get('etude_id'),
|
etude_id=metadata.get('etude_id'),
|
||||||
utilisateur_id=metadata.get('utilisateur_id'),
|
utilisateur_id=metadata.get('utilisateur_id'),
|
||||||
status=metadata.get('status', 'active'),
|
status=metadata.get('status', 'active'),
|
||||||
metadata=json.dumps(metadata)
|
metadata=json.dumps(metadata)
|
||||||
)
|
)
|
||||||
|
|
||||||
record = result.single()
|
record = result.single()
|
||||||
if record:
|
if record:
|
||||||
logger.info(f"✅ Contexte du dossier {dossier_id} créé")
|
logger.info(f"✅ Contexte du dossier {dossier_id} créé")
|
||||||
@ -84,29 +84,29 @@ class Neo4jClient:
|
|||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': 'Impossible de créer le contexte du dossier'
|
'error': 'Impossible de créer le contexte du dossier'
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la création du contexte du dossier {dossier_id}: {e}")
|
logger.error(f"❌ Erreur lors de la création du contexte du dossier {dossier_id}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def add_document_to_dossier(self, dossier_id: str, doc_id: str,
|
async def add_document_to_dossier(self, dossier_id: str, doc_id: str,
|
||||||
doc_metadata: Dict[str, Any]) -> Dict[str, Any]:
|
doc_metadata: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Ajout d'un document à un dossier
|
Ajout d'un document à un dossier
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dossier_id: ID du dossier
|
dossier_id: ID du dossier
|
||||||
doc_id: ID du document
|
doc_id: ID du document
|
||||||
doc_metadata: Métadonnées du document
|
doc_metadata: Métadonnées du document
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de l'ajout
|
Résultat de l'ajout
|
||||||
"""
|
"""
|
||||||
logger.info(f"📄 Ajout du document {doc_id} au dossier {dossier_id}")
|
logger.info(f"📄 Ajout du document {doc_id} au dossier {dossier_id}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.driver.session() as session:
|
with self.driver.session() as session:
|
||||||
# Création du nœud document et relation avec le dossier
|
# Création du nœud document et relation avec le dossier
|
||||||
@ -129,7 +129,7 @@ class Neo4jClient:
|
|||||||
status=doc_metadata.get('status', 'uploaded'),
|
status=doc_metadata.get('status', 'uploaded'),
|
||||||
metadata=json.dumps(doc_metadata)
|
metadata=json.dumps(doc_metadata)
|
||||||
)
|
)
|
||||||
|
|
||||||
record = result.single()
|
record = result.single()
|
||||||
if record:
|
if record:
|
||||||
logger.info(f"✅ Document {doc_id} ajouté au dossier {dossier_id}")
|
logger.info(f"✅ Document {doc_id} ajouté au dossier {dossier_id}")
|
||||||
@ -144,27 +144,27 @@ class Neo4jClient:
|
|||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': 'Impossible d\'ajouter le document au dossier'
|
'error': 'Impossible d\'ajouter le document au dossier'
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de l'ajout du document {doc_id} au dossier {dossier_id}: {e}")
|
logger.error(f"❌ Erreur lors de l'ajout du document {doc_id} au dossier {dossier_id}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def add_entities_to_document(self, doc_id: str, entities: Dict[str, Any]) -> Dict[str, Any]:
|
async def add_entities_to_document(self, doc_id: str, entities: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Ajout des entités extraites à un document
|
Ajout des entités extraites à un document
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
doc_id: ID du document
|
doc_id: ID du document
|
||||||
entities: Entités extraites
|
entities: Entités extraites
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de l'ajout
|
Résultat de l'ajout
|
||||||
"""
|
"""
|
||||||
logger.info(f"🏷️ Ajout des entités au document {doc_id}")
|
logger.info(f"🏷️ Ajout des entités au document {doc_id}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.driver.session() as session:
|
with self.driver.session() as session:
|
||||||
# Traitement des entités selon leur type
|
# Traitement des entités selon leur type
|
||||||
@ -179,7 +179,7 @@ class Neo4jClient:
|
|||||||
await self._add_amount_entities(session, doc_id, entity_data)
|
await self._add_amount_entities(session, doc_id, entity_data)
|
||||||
elif entity_type == 'dates':
|
elif entity_type == 'dates':
|
||||||
await self._add_date_entities(session, doc_id, entity_data)
|
await self._add_date_entities(session, doc_id, entity_data)
|
||||||
|
|
||||||
logger.info(f"✅ Entités ajoutées au document {doc_id}")
|
logger.info(f"✅ Entités ajoutées au document {doc_id}")
|
||||||
return {
|
return {
|
||||||
'status': 'added',
|
'status': 'added',
|
||||||
@ -187,14 +187,14 @@ class Neo4jClient:
|
|||||||
'entities_count': len(entities),
|
'entities_count': len(entities),
|
||||||
'added_at': datetime.now().isoformat()
|
'added_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de l'ajout des entités au document {doc_id}: {e}")
|
logger.error(f"❌ Erreur lors de l'ajout des entités au document {doc_id}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _add_person_entities(self, session, doc_id: str, persons: List[Dict[str, Any]]):
|
async def _add_person_entities(self, session, doc_id: str, persons: List[Dict[str, Any]]):
|
||||||
"""Ajout des entités personnes"""
|
"""Ajout des entités personnes"""
|
||||||
for person in persons:
|
for person in persons:
|
||||||
@ -218,7 +218,7 @@ class Neo4jClient:
|
|||||||
nationalite=person.get('nationalite'),
|
nationalite=person.get('nationalite'),
|
||||||
adresse=person.get('adresse')
|
adresse=person.get('adresse')
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _add_address_entities(self, session, doc_id: str, addresses: List[Dict[str, Any]]):
|
async def _add_address_entities(self, session, doc_id: str, addresses: List[Dict[str, Any]]):
|
||||||
"""Ajout des entités adresses"""
|
"""Ajout des entités adresses"""
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
@ -243,7 +243,7 @@ class Neo4jClient:
|
|||||||
region=address.get('region'),
|
region=address.get('region'),
|
||||||
coordinates=json.dumps(address.get('coordinates', []))
|
coordinates=json.dumps(address.get('coordinates', []))
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _add_property_entities(self, session, doc_id: str, properties: List[Dict[str, Any]]):
|
async def _add_property_entities(self, session, doc_id: str, properties: List[Dict[str, Any]]):
|
||||||
"""Ajout des entités biens"""
|
"""Ajout des entités biens"""
|
||||||
for property_data in properties:
|
for property_data in properties:
|
||||||
@ -266,7 +266,7 @@ class Neo4jClient:
|
|||||||
type_bien=property_data.get('type_bien'),
|
type_bien=property_data.get('type_bien'),
|
||||||
reference_cadastrale=property_data.get('reference_cadastrale')
|
reference_cadastrale=property_data.get('reference_cadastrale')
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _add_amount_entities(self, session, doc_id: str, amounts: List[Dict[str, Any]]):
|
async def _add_amount_entities(self, session, doc_id: str, amounts: List[Dict[str, Any]]):
|
||||||
"""Ajout des entités montants"""
|
"""Ajout des entités montants"""
|
||||||
for amount in amounts:
|
for amount in amounts:
|
||||||
@ -286,7 +286,7 @@ class Neo4jClient:
|
|||||||
type_montant=amount.get('type_montant'),
|
type_montant=amount.get('type_montant'),
|
||||||
description=amount.get('description')
|
description=amount.get('description')
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _add_date_entities(self, session, doc_id: str, dates: List[Dict[str, Any]]):
|
async def _add_date_entities(self, session, doc_id: str, dates: List[Dict[str, Any]]):
|
||||||
"""Ajout des entités dates"""
|
"""Ajout des entités dates"""
|
||||||
for date_data in dates:
|
for date_data in dates:
|
||||||
@ -305,20 +305,20 @@ class Neo4jClient:
|
|||||||
type_date=date_data.get('type_date'),
|
type_date=date_data.get('type_date'),
|
||||||
description=date_data.get('description')
|
description=date_data.get('description')
|
||||||
)
|
)
|
||||||
|
|
||||||
async def find_related_documents(self, doc_id: str, max_depth: int = 2) -> Dict[str, Any]:
|
async def find_related_documents(self, doc_id: str, max_depth: int = 2) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Recherche de documents liés
|
Recherche de documents liés
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
doc_id: ID du document
|
doc_id: ID du document
|
||||||
max_depth: Profondeur maximale de recherche
|
max_depth: Profondeur maximale de recherche
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Documents liés trouvés
|
Documents liés trouvés
|
||||||
"""
|
"""
|
||||||
logger.info(f"🔗 Recherche de documents liés au document {doc_id}")
|
logger.info(f"🔗 Recherche de documents liés au document {doc_id}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.driver.session() as session:
|
with self.driver.session() as session:
|
||||||
result = session.run("""
|
result = session.run("""
|
||||||
@ -331,7 +331,7 @@ class Neo4jClient:
|
|||||||
doc_id=doc_id,
|
doc_id=doc_id,
|
||||||
max_depth=max_depth
|
max_depth=max_depth
|
||||||
)
|
)
|
||||||
|
|
||||||
related_docs = []
|
related_docs = []
|
||||||
for record in result:
|
for record in result:
|
||||||
related_docs.append({
|
related_docs.append({
|
||||||
@ -340,7 +340,7 @@ class Neo4jClient:
|
|||||||
'type': record['related'].get('type'),
|
'type': record['related'].get('type'),
|
||||||
'distance': record['distance']
|
'distance': record['distance']
|
||||||
})
|
})
|
||||||
|
|
||||||
logger.info(f"✅ {len(related_docs)} documents liés trouvés")
|
logger.info(f"✅ {len(related_docs)} documents liés trouvés")
|
||||||
return {
|
return {
|
||||||
'status': 'completed',
|
'status': 'completed',
|
||||||
@ -349,26 +349,26 @@ class Neo4jClient:
|
|||||||
'count': len(related_docs),
|
'count': len(related_docs),
|
||||||
'searched_at': datetime.now().isoformat()
|
'searched_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la recherche de documents liés: {e}")
|
logger.error(f"❌ Erreur lors de la recherche de documents liés: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def get_dossier_summary(self, dossier_id: str) -> Dict[str, Any]:
|
async def get_dossier_summary(self, dossier_id: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Récupération du résumé d'un dossier
|
Récupération du résumé d'un dossier
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dossier_id: ID du dossier
|
dossier_id: ID du dossier
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résumé du dossier
|
Résumé du dossier
|
||||||
"""
|
"""
|
||||||
logger.info(f"📊 Génération du résumé du dossier {dossier_id}")
|
logger.info(f"📊 Génération du résumé du dossier {dossier_id}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.driver.session() as session:
|
with self.driver.session() as session:
|
||||||
# Statistiques générales
|
# Statistiques générales
|
||||||
@ -376,16 +376,16 @@ class Neo4jClient:
|
|||||||
MATCH (d:Dossier {id: $dossier_id})
|
MATCH (d:Dossier {id: $dossier_id})
|
||||||
OPTIONAL MATCH (d)-[:CONTAINS]->(doc:Document)
|
OPTIONAL MATCH (d)-[:CONTAINS]->(doc:Document)
|
||||||
OPTIONAL MATCH (doc)-[:MENTIONS]->(entity)
|
OPTIONAL MATCH (doc)-[:MENTIONS]->(entity)
|
||||||
RETURN
|
RETURN
|
||||||
count(DISTINCT doc) as documents_count,
|
count(DISTINCT doc) as documents_count,
|
||||||
count(DISTINCT entity) as entities_count,
|
count(DISTINCT entity) as entities_count,
|
||||||
collect(DISTINCT doc.type) as document_types
|
collect(DISTINCT doc.type) as document_types
|
||||||
""",
|
""",
|
||||||
dossier_id=dossier_id
|
dossier_id=dossier_id
|
||||||
)
|
)
|
||||||
|
|
||||||
stats_record = stats_result.single()
|
stats_record = stats_result.single()
|
||||||
|
|
||||||
# Entités les plus fréquentes
|
# Entités les plus fréquentes
|
||||||
entities_result = session.run("""
|
entities_result = session.run("""
|
||||||
MATCH (d:Dossier {id: $dossier_id})-[:CONTAINS]->(doc:Document)-[:MENTIONS]->(entity)
|
MATCH (d:Dossier {id: $dossier_id})-[:CONTAINS]->(doc:Document)-[:MENTIONS]->(entity)
|
||||||
@ -395,14 +395,14 @@ class Neo4jClient:
|
|||||||
""",
|
""",
|
||||||
dossier_id=dossier_id
|
dossier_id=dossier_id
|
||||||
)
|
)
|
||||||
|
|
||||||
entity_frequencies = []
|
entity_frequencies = []
|
||||||
for record in entities_result:
|
for record in entities_result:
|
||||||
entity_frequencies.append({
|
entity_frequencies.append({
|
||||||
'type': record['entity_type'],
|
'type': record['entity_type'],
|
||||||
'frequency': record['frequency']
|
'frequency': record['frequency']
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'status': 'completed',
|
'status': 'completed',
|
||||||
'dossier_id': dossier_id,
|
'dossier_id': dossier_id,
|
||||||
@ -414,38 +414,38 @@ class Neo4jClient:
|
|||||||
},
|
},
|
||||||
'generated_at': datetime.now().isoformat()
|
'generated_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la génération du résumé du dossier {dossier_id}: {e}")
|
logger.error(f"❌ Erreur lors de la génération du résumé du dossier {dossier_id}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def create_relationships_between_entities(self, doc_id: str,
|
async def create_relationships_between_entities(self, doc_id: str,
|
||||||
relationships: List[Dict[str, Any]]) -> Dict[str, Any]:
|
relationships: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Création de relations entre entités
|
Création de relations entre entités
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
doc_id: ID du document
|
doc_id: ID du document
|
||||||
relationships: Liste des relations à créer
|
relationships: Liste des relations à créer
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de la création des relations
|
Résultat de la création des relations
|
||||||
"""
|
"""
|
||||||
logger.info(f"🔗 Création de relations pour le document {doc_id}")
|
logger.info(f"🔗 Création de relations pour le document {doc_id}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.driver.session() as session:
|
with self.driver.session() as session:
|
||||||
created_relations = 0
|
created_relations = 0
|
||||||
|
|
||||||
for rel in relationships:
|
for rel in relationships:
|
||||||
rel_type = rel.get('type')
|
rel_type = rel.get('type')
|
||||||
from_entity = rel.get('from')
|
from_entity = rel.get('from')
|
||||||
to_entity = rel.get('to')
|
to_entity = rel.get('to')
|
||||||
properties = rel.get('properties', {})
|
properties = rel.get('properties', {})
|
||||||
|
|
||||||
if rel_type and from_entity and to_entity:
|
if rel_type and from_entity and to_entity:
|
||||||
result = session.run(f"""
|
result = session.run(f"""
|
||||||
MATCH (doc:Document {{id: $doc_id}})
|
MATCH (doc:Document {{id: $doc_id}})
|
||||||
@ -462,10 +462,10 @@ class Neo4jClient:
|
|||||||
to_id=to_entity['id'],
|
to_id=to_entity['id'],
|
||||||
properties=json.dumps(properties)
|
properties=json.dumps(properties)
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.single():
|
if result.single():
|
||||||
created_relations += 1
|
created_relations += 1
|
||||||
|
|
||||||
logger.info(f"✅ {created_relations} relations créées")
|
logger.info(f"✅ {created_relations} relations créées")
|
||||||
return {
|
return {
|
||||||
'status': 'completed',
|
'status': 'completed',
|
||||||
@ -473,7 +473,7 @@ class Neo4jClient:
|
|||||||
'relations_created': created_relations,
|
'relations_created': created_relations,
|
||||||
'created_at': datetime.now().isoformat()
|
'created_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la création des relations: {e}")
|
logger.error(f"❌ Erreur lors de la création des relations: {e}")
|
||||||
return {
|
return {
|
||||||
|
@ -12,14 +12,14 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class OpenSearchClient:
|
class OpenSearchClient:
|
||||||
"""Client pour l'intégration avec OpenSearch"""
|
"""Client pour l'intégration avec OpenSearch"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.host = os.getenv('OPENSEARCH_HOST', 'opensearch')
|
self.host = os.getenv('OPENSEARCH_HOST', 'opensearch')
|
||||||
self.port = int(os.getenv('OPENSEARCH_PORT', '9200'))
|
self.port = int(os.getenv('OPENSEARCH_PORT', '9200'))
|
||||||
self.username = os.getenv('OPENSEARCH_USER', 'admin')
|
self.username = os.getenv('OPENSEARCH_USER', 'admin')
|
||||||
self.password = os.getenv('OPENSEARCH_PASSWORD', 'opensearch_pwd')
|
self.password = os.getenv('OPENSEARCH_PASSWORD', 'opensearch_pwd')
|
||||||
self.use_ssl = os.getenv('OPENSEARCH_USE_SSL', 'false').lower() == 'true'
|
self.use_ssl = os.getenv('OPENSEARCH_USE_SSL', 'false').lower() == 'true'
|
||||||
|
|
||||||
# Configuration du client OpenSearch
|
# Configuration du client OpenSearch
|
||||||
self.client = OpenSearch(
|
self.client = OpenSearch(
|
||||||
hosts=[{'host': self.host, 'port': self.port}],
|
hosts=[{'host': self.host, 'port': self.port}],
|
||||||
@ -29,12 +29,12 @@ class OpenSearchClient:
|
|||||||
connection_class=RequestsHttpConnection,
|
connection_class=RequestsHttpConnection,
|
||||||
timeout=30
|
timeout=30
|
||||||
)
|
)
|
||||||
|
|
||||||
# Index par défaut
|
# Index par défaut
|
||||||
self.default_index = os.getenv('OPENSEARCH_INDEX', 'notariat_documents')
|
self.default_index = os.getenv('OPENSEARCH_INDEX', 'notariat_documents')
|
||||||
|
|
||||||
self._ensure_index_exists()
|
self._ensure_index_exists()
|
||||||
|
|
||||||
def _ensure_index_exists(self):
|
def _ensure_index_exists(self):
|
||||||
"""Vérifie et crée l'index s'il n'existe pas"""
|
"""Vérifie et crée l'index s'il n'existe pas"""
|
||||||
try:
|
try:
|
||||||
@ -45,7 +45,7 @@ class OpenSearchClient:
|
|||||||
logger.info(f"✅ Index {self.default_index} existe déjà")
|
logger.info(f"✅ Index {self.default_index} existe déjà")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la vérification de l'index: {e}")
|
logger.error(f"❌ Erreur lors de la vérification de l'index: {e}")
|
||||||
|
|
||||||
def _create_index(self):
|
def _create_index(self):
|
||||||
"""Crée l'index avec le mapping approprié"""
|
"""Crée l'index avec le mapping approprié"""
|
||||||
mapping = {
|
mapping = {
|
||||||
@ -112,22 +112,22 @@ class OpenSearchClient:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.client.indices.create(index=self.default_index, body=mapping)
|
self.client.indices.create(index=self.default_index, body=mapping)
|
||||||
|
|
||||||
async def index_document(self, doc_id: str, document_data: Dict[str, Any]) -> Dict[str, Any]:
|
async def index_document(self, doc_id: str, document_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Indexation d'un document dans OpenSearch
|
Indexation d'un document dans OpenSearch
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
doc_id: ID du document
|
doc_id: ID du document
|
||||||
document_data: Données du document à indexer
|
document_data: Données du document à indexer
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de l'indexation
|
Résultat de l'indexation
|
||||||
"""
|
"""
|
||||||
logger.info(f"📚 Indexation du document {doc_id} dans OpenSearch")
|
logger.info(f"📚 Indexation du document {doc_id} dans OpenSearch")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Préparation du document pour l'indexation
|
# Préparation du document pour l'indexation
|
||||||
indexed_doc = {
|
indexed_doc = {
|
||||||
@ -145,14 +145,14 @@ class OpenSearchClient:
|
|||||||
"created_at": datetime.now().isoformat(),
|
"created_at": datetime.now().isoformat(),
|
||||||
"updated_at": datetime.now().isoformat()
|
"updated_at": datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Indexation du document
|
# Indexation du document
|
||||||
response = self.client.index(
|
response = self.client.index(
|
||||||
index=self.default_index,
|
index=self.default_index,
|
||||||
id=doc_id,
|
id=doc_id,
|
||||||
body=indexed_doc
|
body=indexed_doc
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"✅ Document {doc_id} indexé avec succès")
|
logger.info(f"✅ Document {doc_id} indexé avec succès")
|
||||||
return {
|
return {
|
||||||
'status': 'indexed',
|
'status': 'indexed',
|
||||||
@ -161,30 +161,30 @@ class OpenSearchClient:
|
|||||||
'version': response.get('_version'),
|
'version': response.get('_version'),
|
||||||
'indexed_at': datetime.now().isoformat()
|
'indexed_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de l'indexation du document {doc_id}: {e}")
|
logger.error(f"❌ Erreur lors de l'indexation du document {doc_id}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def search_documents(self, query: str, filters: Dict[str, Any] = None,
|
async def search_documents(self, query: str, filters: Dict[str, Any] = None,
|
||||||
limit: int = 10, offset: int = 0) -> Dict[str, Any]:
|
limit: int = 10, offset: int = 0) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Recherche de documents dans OpenSearch
|
Recherche de documents dans OpenSearch
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: Requête de recherche
|
query: Requête de recherche
|
||||||
filters: Filtres à appliquer
|
filters: Filtres à appliquer
|
||||||
limit: Nombre maximum de résultats
|
limit: Nombre maximum de résultats
|
||||||
offset: Décalage pour la pagination
|
offset: Décalage pour la pagination
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultats de la recherche
|
Résultats de la recherche
|
||||||
"""
|
"""
|
||||||
logger.info(f"🔍 Recherche dans OpenSearch: {query}")
|
logger.info(f"🔍 Recherche dans OpenSearch: {query}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Construction de la requête
|
# Construction de la requête
|
||||||
search_body = {
|
search_body = {
|
||||||
@ -217,12 +217,12 @@ class OpenSearchClient:
|
|||||||
"from": offset,
|
"from": offset,
|
||||||
"size": limit
|
"size": limit
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ajout des filtres
|
# Ajout des filtres
|
||||||
if filters:
|
if filters:
|
||||||
bool_query = search_body["query"]["bool"]
|
bool_query = search_body["query"]["bool"]
|
||||||
bool_query["filter"] = []
|
bool_query["filter"] = []
|
||||||
|
|
||||||
for field, value in filters.items():
|
for field, value in filters.items():
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
bool_query["filter"].append({
|
bool_query["filter"].append({
|
||||||
@ -232,17 +232,17 @@ class OpenSearchClient:
|
|||||||
bool_query["filter"].append({
|
bool_query["filter"].append({
|
||||||
"term": {field: value}
|
"term": {field: value}
|
||||||
})
|
})
|
||||||
|
|
||||||
# Exécution de la recherche
|
# Exécution de la recherche
|
||||||
response = self.client.search(
|
response = self.client.search(
|
||||||
index=self.default_index,
|
index=self.default_index,
|
||||||
body=search_body
|
body=search_body
|
||||||
)
|
)
|
||||||
|
|
||||||
# Traitement des résultats
|
# Traitement des résultats
|
||||||
hits = response.get('hits', {})
|
hits = response.get('hits', {})
|
||||||
total = hits.get('total', {}).get('value', 0)
|
total = hits.get('total', {}).get('value', 0)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for hit in hits.get('hits', []):
|
for hit in hits.get('hits', []):
|
||||||
result = {
|
result = {
|
||||||
@ -254,7 +254,7 @@ class OpenSearchClient:
|
|||||||
'created_at': hit['_source'].get('created_at')
|
'created_at': hit['_source'].get('created_at')
|
||||||
}
|
}
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
logger.info(f"✅ Recherche terminée: {len(results)} résultats sur {total}")
|
logger.info(f"✅ Recherche terminée: {len(results)} résultats sur {total}")
|
||||||
return {
|
return {
|
||||||
'status': 'completed',
|
'status': 'completed',
|
||||||
@ -264,32 +264,32 @@ class OpenSearchClient:
|
|||||||
'took': response.get('took'),
|
'took': response.get('took'),
|
||||||
'searched_at': datetime.now().isoformat()
|
'searched_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la recherche: {e}")
|
logger.error(f"❌ Erreur lors de la recherche: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def search_by_entities(self, entities: Dict[str, Any],
|
async def search_by_entities(self, entities: Dict[str, Any],
|
||||||
limit: int = 10) -> Dict[str, Any]:
|
limit: int = 10) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Recherche de documents par entités
|
Recherche de documents par entités
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entities: Entités à rechercher
|
entities: Entités à rechercher
|
||||||
limit: Nombre maximum de résultats
|
limit: Nombre maximum de résultats
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultats de la recherche
|
Résultats de la recherche
|
||||||
"""
|
"""
|
||||||
logger.info(f"🏷️ Recherche par entités dans OpenSearch")
|
logger.info(f"🏷️ Recherche par entités dans OpenSearch")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Construction de la requête pour les entités
|
# Construction de la requête pour les entités
|
||||||
must_queries = []
|
must_queries = []
|
||||||
|
|
||||||
for entity_type, entity_values in entities.items():
|
for entity_type, entity_values in entities.items():
|
||||||
if isinstance(entity_values, list):
|
if isinstance(entity_values, list):
|
||||||
for value in entity_values:
|
for value in entity_values:
|
||||||
@ -320,7 +320,7 @@ class OpenSearchClient:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
search_body = {
|
search_body = {
|
||||||
"query": {
|
"query": {
|
||||||
"bool": {
|
"bool": {
|
||||||
@ -333,17 +333,17 @@ class OpenSearchClient:
|
|||||||
],
|
],
|
||||||
"size": limit
|
"size": limit
|
||||||
}
|
}
|
||||||
|
|
||||||
# Exécution de la recherche
|
# Exécution de la recherche
|
||||||
response = self.client.search(
|
response = self.client.search(
|
||||||
index=self.default_index,
|
index=self.default_index,
|
||||||
body=search_body
|
body=search_body
|
||||||
)
|
)
|
||||||
|
|
||||||
# Traitement des résultats
|
# Traitement des résultats
|
||||||
hits = response.get('hits', {})
|
hits = response.get('hits', {})
|
||||||
total = hits.get('total', {}).get('value', 0)
|
total = hits.get('total', {}).get('value', 0)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for hit in hits.get('hits', []):
|
for hit in hits.get('hits', []):
|
||||||
result = {
|
result = {
|
||||||
@ -355,7 +355,7 @@ class OpenSearchClient:
|
|||||||
'created_at': hit['_source'].get('created_at')
|
'created_at': hit['_source'].get('created_at')
|
||||||
}
|
}
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
logger.info(f"✅ Recherche par entités terminée: {len(results)} résultats")
|
logger.info(f"✅ Recherche par entités terminée: {len(results)} résultats")
|
||||||
return {
|
return {
|
||||||
'status': 'completed',
|
'status': 'completed',
|
||||||
@ -364,21 +364,21 @@ class OpenSearchClient:
|
|||||||
'results': results,
|
'results': results,
|
||||||
'searched_at': datetime.now().isoformat()
|
'searched_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la recherche par entités: {e}")
|
logger.error(f"❌ Erreur lors de la recherche par entités: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def get_document(self, doc_id: str) -> Dict[str, Any]:
|
async def get_document(self, doc_id: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Récupération d'un document par son ID
|
Récupération d'un document par son ID
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
doc_id: ID du document
|
doc_id: ID du document
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Document récupéré
|
Document récupéré
|
||||||
"""
|
"""
|
||||||
@ -387,7 +387,7 @@ class OpenSearchClient:
|
|||||||
index=self.default_index,
|
index=self.default_index,
|
||||||
id=doc_id
|
id=doc_id
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.get('found'):
|
if response.get('found'):
|
||||||
return {
|
return {
|
||||||
'status': 'found',
|
'status': 'found',
|
||||||
@ -400,31 +400,31 @@ class OpenSearchClient:
|
|||||||
'status': 'not_found',
|
'status': 'not_found',
|
||||||
'doc_id': doc_id
|
'doc_id': doc_id
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la récupération du document {doc_id}: {e}")
|
logger.error(f"❌ Erreur lors de la récupération du document {doc_id}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def update_document(self, doc_id: str, updates: Dict[str, Any]) -> Dict[str, Any]:
|
async def update_document(self, doc_id: str, updates: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Mise à jour d'un document
|
Mise à jour d'un document
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
doc_id: ID du document
|
doc_id: ID du document
|
||||||
updates: Mises à jour à appliquer
|
updates: Mises à jour à appliquer
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de la mise à jour
|
Résultat de la mise à jour
|
||||||
"""
|
"""
|
||||||
logger.info(f"🔄 Mise à jour du document {doc_id}")
|
logger.info(f"🔄 Mise à jour du document {doc_id}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Ajout de la date de mise à jour
|
# Ajout de la date de mise à jour
|
||||||
updates['updated_at'] = datetime.now().isoformat()
|
updates['updated_at'] = datetime.now().isoformat()
|
||||||
|
|
||||||
response = self.client.update(
|
response = self.client.update(
|
||||||
index=self.default_index,
|
index=self.default_index,
|
||||||
id=doc_id,
|
id=doc_id,
|
||||||
@ -432,7 +432,7 @@ class OpenSearchClient:
|
|||||||
"doc": updates
|
"doc": updates
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"✅ Document {doc_id} mis à jour")
|
logger.info(f"✅ Document {doc_id} mis à jour")
|
||||||
return {
|
return {
|
||||||
'status': 'updated',
|
'status': 'updated',
|
||||||
@ -440,57 +440,57 @@ class OpenSearchClient:
|
|||||||
'version': response.get('_version'),
|
'version': response.get('_version'),
|
||||||
'updated_at': datetime.now().isoformat()
|
'updated_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la mise à jour du document {doc_id}: {e}")
|
logger.error(f"❌ Erreur lors de la mise à jour du document {doc_id}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def delete_document(self, doc_id: str) -> Dict[str, Any]:
|
async def delete_document(self, doc_id: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Suppression d'un document
|
Suppression d'un document
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
doc_id: ID du document
|
doc_id: ID du document
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Résultat de la suppression
|
Résultat de la suppression
|
||||||
"""
|
"""
|
||||||
logger.info(f"🗑️ Suppression du document {doc_id}")
|
logger.info(f"🗑️ Suppression du document {doc_id}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self.client.delete(
|
response = self.client.delete(
|
||||||
index=self.default_index,
|
index=self.default_index,
|
||||||
id=doc_id
|
id=doc_id
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"✅ Document {doc_id} supprimé")
|
logger.info(f"✅ Document {doc_id} supprimé")
|
||||||
return {
|
return {
|
||||||
'status': 'deleted',
|
'status': 'deleted',
|
||||||
'doc_id': doc_id,
|
'doc_id': doc_id,
|
||||||
'deleted_at': datetime.now().isoformat()
|
'deleted_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la suppression du document {doc_id}: {e}")
|
logger.error(f"❌ Erreur lors de la suppression du document {doc_id}: {e}")
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def get_index_stats(self) -> Dict[str, Any]:
|
async def get_index_stats(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Récupération des statistiques de l'index
|
Récupération des statistiques de l'index
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Statistiques de l'index
|
Statistiques de l'index
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
stats = self.client.indices.stats(index=self.default_index)
|
stats = self.client.indices.stats(index=self.default_index)
|
||||||
index_stats = stats['indices'][self.default_index]
|
index_stats = stats['indices'][self.default_index]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'index': self.default_index,
|
'index': self.default_index,
|
||||||
@ -502,7 +502,7 @@ class OpenSearchClient:
|
|||||||
},
|
},
|
||||||
'retrieved_at': datetime.now().isoformat()
|
'retrieved_at': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lors de la récupération des statistiques: {e}")
|
logger.error(f"❌ Erreur lors de la récupération des statistiques: {e}")
|
||||||
return {
|
return {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user