""" 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() }