From ccc9c0080eba782a0c951dac029dae528ab30cf5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 15:09:50 +0000 Subject: [PATCH] fix: Fix AI service call and domain URL building - Use AIService.analyze_image() class method instead of module function - Handle AIExtractionResponse tuple return type properly - Add _clean_domain() to strip protocol/www from stored domains - Fixes "https://https://" URL duplication issue --- app/services/direct_search_service.py | 28 ++++++++++++++++++++- app/services/search_service.py | 35 +++++++++++++++------------ 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/app/services/direct_search_service.py b/app/services/direct_search_service.py index 0af587a..044b64c 100644 --- a/app/services/direct_search_service.py +++ b/app/services/direct_search_service.py @@ -118,7 +118,10 @@ async def _search_site_browserless( timeout: float, ) -> list[SearchResult]: """Recherche sur un site via Browserless (Playwright)""" - domain = site.get("domain", "").lower() + raw_domain = site.get("domain", "").lower() + # Nettoyer le domaine (enlever protocole, www, slash final) + domain = _clean_domain(raw_domain) + search_url = site.get("search_url") or _get_default_search_url(domain) product_selector = site.get("product_link_selector") or _get_default_product_selector(domain) wait_selector = _get_default_wait_selector(domain) @@ -358,6 +361,29 @@ def _extract_domain(url: str) -> str: return "" +def _clean_domain(domain: str) -> str: + """Nettoie un domaine (enlève protocole, www, slash final)""" + if not domain: + return "" + + domain = domain.strip().lower() + + # Enlever le protocole + if domain.startswith("https://"): + domain = domain[8:] + elif domain.startswith("http://"): + domain = domain[7:] + + # Enlever www. + if domain.startswith("www."): + domain = domain[4:] + + # Enlever le slash final + domain = domain.rstrip("/") + + return domain + + def _is_non_product_url(url: str) -> bool: """Vérifie si l'URL n'est pas une page produit""" non_product_patterns = [ diff --git a/app/services/search_service.py b/app/services/search_service.py index 0833ffb..6a68e86 100644 --- a/app/services/search_service.py +++ b/app/services/search_service.py @@ -12,7 +12,8 @@ from sqlalchemy.orm import Session from app.models import SearchSite from app.schemas import SearchProgress, SearchResultItem -from app.services import ai_service, direct_search_service, light_scraper_service +from app.services import direct_search_service, light_scraper_service +from app.services.ai_service import AIService from app.services.scraper_service import ScraperService from app.services.settings_service import SettingsService @@ -227,23 +228,25 @@ async def _scrape_with_browserless( ) # Extraction IA - ai_result = await ai_service.analyze_image( - screenshot_path=screenshot_path, - webpage_text=page_text, + ai_result = await AIService.analyze_image( + image_path=screenshot_path, + page_text=page_text, ) - if ai_result and ai_result.get("price"): - return SearchResultItem( - url=url, - title=fallback_title, - price=ai_result.get("price"), - currency=ai_result.get("currency", "EUR"), - in_stock=ai_result.get("in_stock"), - image_url=None, # Pas d'image depuis le screenshot - site_name=site_name, - site_domain=domain, - confidence=ai_result.get("price_confidence", 0.5), - ) + if ai_result: + extraction, metadata = ai_result + if extraction and extraction.price is not None: + return SearchResultItem( + url=url, + title=fallback_title, + price=extraction.price, + currency=extraction.currency or "EUR", + in_stock=extraction.in_stock, + image_url=None, + site_name=site_name, + site_domain=domain, + confidence=extraction.price_confidence or 0.5, + ) return SearchResultItem( url=url,