4NK_IA_back/services/worker/utils/external_apis.py
Nicolas Cantu 8c089127af 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
2025-09-10 18:45:50 +02:00

438 lines
16 KiB
Python

"""
Intégrations avec les APIs externes pour la vérification des données
"""
import os
import logging
import requests
from typing import Dict, Any, Optional, List
import json
from datetime import datetime
logger = logging.getLogger(__name__)
class ExternalAPIManager:
"""Gestionnaire des APIs externes pour la vérification des données"""
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Notariat-Pipeline/1.2.0'
})
# Configuration des URLs des APIs
self.apis = {
'cadastre': os.getenv('CADASTRE_API_URL', 'https://apicarto.ign.fr/api/cadastre'),
'georisques': os.getenv('GEORISQUES_API_URL', 'https://www.georisques.gouv.fr/api'),
'bodacc': os.getenv('BODACC_API_URL', 'https://bodacc-datadila.opendatasoft.com/api'),
'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')
}
# Cache pour éviter les appels répétés
self.cache = {}
self.cache_ttl = 3600 # 1 heure
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
Args:
address: Adresse à vérifier
postal_code: Code postal
city: Ville
Returns:
Résultat de la vérification
"""
logger.info(f"🏠 Vérification de l'adresse: {address}")
try:
# Construction de la requête
params = {
'q': address,
'limit': 5
}
if postal_code:
params['code_postal'] = postal_code
if city:
params['commune'] = city
# Appel à l'API Cadastre
response = self.session.get(
f"{self.apis['cadastre']}/parcelle",
params=params,
timeout=10
)
if response.status_code == 200:
data = response.json()
if data.get('features'):
# Adresse trouvée
feature = data['features'][0]
properties = feature.get('properties', {})
return {
'status': 'verified',
'confidence': 0.95,
'verified_address': properties.get('adresse', address),
'cadastral_reference': properties.get('numero', ''),
'surface': properties.get('contenance', 0),
'coordinates': feature.get('geometry', {}).get('coordinates', []),
'source': 'cadastre_api',
'verified_at': datetime.now().isoformat()
}
else:
# Adresse non trouvée
return {
'status': 'not_found',
'confidence': 0.0,
'message': 'Adresse non trouvée dans le cadastre',
'source': 'cadastre_api',
'verified_at': datetime.now().isoformat()
}
else:
logger.warning(f"Erreur API Cadastre: {response.status_code}")
return {
'status': 'error',
'confidence': 0.0,
'error': f"Erreur API: {response.status_code}",
'source': 'cadastre_api'
}
except Exception as e:
logger.error(f"Erreur lors de la vérification de l'adresse: {e}")
return {
'status': 'error',
'confidence': 0.0,
'error': str(e),
'source': 'cadastre_api'
}
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
Args:
address: Adresse à vérifier
coordinates: Coordonnées GPS [longitude, latitude]
Returns:
Résultat de la vérification des risques
"""
logger.info(f"🌍 Vérification des risques géologiques: {address}")
try:
# Si pas de coordonnées, essayer de les obtenir via géocodage
if not coordinates:
coords_result = await self._geocode_address(address)
if coords_result.get('coordinates'):
coordinates = coords_result['coordinates']
if not coordinates:
return {
'status': 'error',
'confidence': 0.0,
'error': 'Coordonnées non disponibles',
'source': 'georisques_api'
}
# Appel à l'API Géorisques
params = {
'lon': coordinates[0],
'lat': coordinates[1],
'distance': 1000 # 1km de rayon
}
response = self.session.get(
f"{self.apis['georisques']}/v1/gaspar/risques",
params=params,
timeout=10
)
if response.status_code == 200:
data = response.json()
risks = []
if data.get('data'):
for risk in data['data']:
risks.append({
'type': risk.get('type_risque', ''),
'level': risk.get('niveau_risque', ''),
'description': risk.get('description', ''),
'distance': risk.get('distance', 0)
})
return {
'status': 'completed',
'confidence': 0.90,
'risks_found': len(risks),
'risks': risks,
'coordinates': coordinates,
'source': 'georisques_api',
'checked_at': datetime.now().isoformat()
}
else:
logger.warning(f"Erreur API Géorisques: {response.status_code}")
return {
'status': 'error',
'confidence': 0.0,
'error': f"Erreur API: {response.status_code}",
'source': 'georisques_api'
}
except Exception as e:
logger.error(f"Erreur lors de la vérification des risques géologiques: {e}")
return {
'status': 'error',
'confidence': 0.0,
'error': str(e),
'source': 'georisques_api'
}
async def verify_company(self, company_name: str, siren: str = None) -> Dict[str, Any]:
"""
Vérification d'une entreprise via l'API BODACC
Args:
company_name: Nom de l'entreprise
siren: Numéro SIREN (optionnel)
Returns:
Résultat de la vérification de l'entreprise
"""
logger.info(f"🏢 Vérification de l'entreprise: {company_name}")
try:
# Construction de la requête
params = {
'q': company_name,
'rows': 5
}
if siren:
params['siren'] = siren
# Appel à l'API BODACC
response = self.session.get(
f"{self.apis['bodacc']}/records/1.0/search/",
params=params,
timeout=10
)
if response.status_code == 200:
data = response.json()
if data.get('records'):
# Entreprise trouvée
record = data['records'][0]
fields = record.get('fields', {})
return {
'status': 'verified',
'confidence': 0.90,
'company_name': fields.get('nom_raison_sociale', company_name),
'siren': fields.get('siren', siren),
'siret': fields.get('siret', ''),
'address': fields.get('adresse', ''),
'postal_code': fields.get('code_postal', ''),
'city': fields.get('ville', ''),
'activity': fields.get('activite_principale', ''),
'legal_form': fields.get('forme_juridique', ''),
'creation_date': fields.get('date_creation', ''),
'status': fields.get('etat_administratif', ''),
'source': 'bodacc_api',
'verified_at': datetime.now().isoformat()
}
else:
# Entreprise non trouvée
return {
'status': 'not_found',
'confidence': 0.0,
'message': 'Entreprise non trouvée dans le BODACC',
'source': 'bodacc_api',
'verified_at': datetime.now().isoformat()
}
else:
logger.warning(f"Erreur API BODACC: {response.status_code}")
return {
'status': 'error',
'confidence': 0.0,
'error': f"Erreur API: {response.status_code}",
'source': 'bodacc_api'
}
except Exception as e:
logger.error(f"Erreur lors de la vérification de l'entreprise: {e}")
return {
'status': 'error',
'confidence': 0.0,
'error': str(e),
'source': 'bodacc_api'
}
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)
Args:
first_name: Prénom
last_name: Nom de famille
birth_date: Date de naissance (format YYYY-MM-DD)
Returns:
Résultat de la vérification de la personne
"""
logger.info(f"👤 Vérification de la personne: {first_name} {last_name}")
try:
# Recherche dans le RBE (Registre des Bénéficiaires Effectifs)
rbe_result = await self._search_rbe(first_name, last_name)
# Recherche dans Infogreffe (si entreprise)
infogreffe_result = await self._search_infogreffe(first_name, last_name)
# Compilation des résultats
results = {
'status': 'completed',
'confidence': 0.70,
'person_name': f"{first_name} {last_name}",
'birth_date': birth_date,
'rbe_results': rbe_result,
'infogreffe_results': infogreffe_result,
'source': 'multiple_apis',
'verified_at': datetime.now().isoformat()
}
# Calcul de la confiance globale
if rbe_result.get('found') or infogreffe_result.get('found'):
results['confidence'] = 0.85
return results
except Exception as e:
logger.error(f"Erreur lors de la vérification de la personne: {e}")
return {
'status': 'error',
'confidence': 0.0,
'error': str(e),
'source': 'person_verification'
}
async def _geocode_address(self, address: str) -> Dict[str, Any]:
"""Géocodage d'une adresse"""
try:
# Utilisation de l'API de géocodage de l'IGN
params = {
'q': address,
'limit': 1
}
response = self.session.get(
f"{self.apis['cadastre']}/geocodage",
params=params,
timeout=10
)
if response.status_code == 200:
data = response.json()
if data.get('features'):
feature = data['features'][0]
coords = feature.get('geometry', {}).get('coordinates', [])
return {
'coordinates': coords,
'formatted_address': feature.get('properties', {}).get('label', address)
}
return {'coordinates': None}
except Exception as e:
logger.error(f"Erreur lors du géocodage: {e}")
return {'coordinates': None}
async def _search_rbe(self, first_name: str, last_name: str) -> Dict[str, Any]:
"""Recherche dans le Registre des Bénéficiaires Effectifs"""
try:
params = {
'q': f"{first_name} {last_name}",
'rows': 5
}
response = self.session.get(
f"{self.apis['rbe']}/search",
params=params,
timeout=10
)
if response.status_code == 200:
data = response.json()
return {
'found': len(data.get('results', [])) > 0,
'count': len(data.get('results', [])),
'results': data.get('results', [])[:3] # Limite à 3 résultats
}
return {'found': False, 'count': 0, 'results': []}
except Exception as e:
logger.error(f"Erreur lors de la recherche RBE: {e}")
return {'found': False, 'count': 0, 'results': []}
async def _search_infogreffe(self, first_name: str, last_name: str) -> Dict[str, Any]:
"""Recherche dans Infogreffe"""
try:
params = {
'q': f"{first_name} {last_name}",
'per_page': 5
}
response = self.session.get(
f"{self.apis['infogreffe']}/search",
params=params,
timeout=10
)
if response.status_code == 200:
data = response.json()
return {
'found': len(data.get('results', [])) > 0,
'count': len(data.get('results', [])),
'results': data.get('results', [])[:3] # Limite à 3 résultats
}
return {'found': False, 'count': 0, 'results': []}
except Exception as e:
logger.error(f"Erreur lors de la recherche Infogreffe: {e}")
return {'found': False, 'count': 0, 'results': []}
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"""
import hashlib
key_data = f"{api}:{json.dumps(params, sort_keys=True)}"
return hashlib.md5(key_data.encode()).hexdigest()
def is_cache_valid(self, cache_key: str) -> bool:
"""Vérifie si le cache est encore valide"""
if cache_key not in self.cache:
return False
cache_time = self.cache[cache_key].get('timestamp', 0)
current_time = datetime.now().timestamp()
return (current_time - cache_time) < self.cache_ttl
def get_from_cache(self, cache_key: str) -> Optional[Dict[str, Any]]:
"""Récupère une valeur du cache"""
if self.is_cache_valid(cache_key):
return self.cache[cache_key].get('data')
return None
def set_cache(self, cache_key: str, data: Dict[str, Any]) -> None:
"""Met une valeur en cache"""
self.cache[cache_key] = {
'data': data,
'timestamp': datetime.now().timestamp()
}