1- from typing import List , Optional
2- import aiohttp
3- from datetime import UTC , datetime
41import logging
2+ from datetime import UTC , datetime
3+ from typing import List , Optional
54from uuid import UUID , uuid4
6- from app .core .config import settings
5+
6+ import aiohttp
77from sqlalchemy .exc import IntegrityError
88
9+ from app .core .config import settings
910from app .core .exceptions import ValidationError
11+ from app .core .utils .url import normalize_domain_name
1012from app .models .database .models import SourceModel
11- from app .services .interfaces .web_search_service import WebSearchServiceInterface
1213from app .repositories .implementations .source_repository import SourceRepository
1314from app .services .domain_service import DomainService
14- from app .core . utils . url import normalize_domain_name
15+ from app .services . interfaces . web_search_service import WebSearchServiceInterface
1516
1617logger = logging .getLogger (__name__ )
1718
19+ # hard filter
20+ BLOCKED_SOURCE_DOMAINS = {
21+ "reddit.com" ,
22+ "instagram.com" ,
23+ "youtube.com" ,
24+ "youtu.be" ,
25+ "bsky.app" ,
26+ "bsky.social" ,
27+ "tiktok.com" ,
28+ "facebook.com" ,
29+ "x.com" ,
30+ "twitter.com" ,
31+ "threads.net" ,
32+ }
33+
1834
1935class GoogleWebSearchService (WebSearchServiceInterface ):
2036 def __init__ (self , domain_service : DomainService , source_repository : SourceRepository ):
@@ -24,24 +40,50 @@ def __init__(self, domain_service: DomainService, source_repository: SourceRepos
2440 self .domain_service = domain_service
2541 self .source_repository = source_repository
2642
43+ def _is_blocked_domain (self , domain_name : str ) -> bool :
44+ normalized_domain = normalize_domain_name (domain_name )
45+ return any (
46+ normalized_domain == blocked_domain or normalized_domain .endswith (f".{ blocked_domain } " )
47+ for blocked_domain in BLOCKED_SOURCE_DOMAINS
48+ )
49+
50+ def _has_acceptable_credibility (self , credibility_score : Optional [float ]) -> bool :
51+ # This removes both unknown credibility and explicit 0 credibility.
52+ return credibility_score is not None and credibility_score > 0
53+
54+ def _is_allowed_source (self , source : SourceModel ) -> bool :
55+ domain_name = (
56+ source .domain .domain_name
57+ if hasattr (source , "domain" ) and source .domain
58+ else normalize_domain_name (source .url )
59+ )
60+ return not self ._is_blocked_domain (domain_name ) and self ._has_acceptable_credibility (source .credibility_score )
61+
62+ def _filter_allowed_sources (self , sources : List [SourceModel ]) -> List [SourceModel ]:
63+ return [source for source in sources if self ._is_allowed_source (source )]
64+
2765 async def search_and_create_sources (
2866 self , claim_text : str , search_id : UUID , num_results : int = 5 , language : str = "english"
2967 ) -> List [SourceModel ]:
3068 """Search for sources and create or update records."""
3169 try :
70+ logger .warning ("NEW SOURCE FILTER CODE IS RUNNING" )
3271 params = {
3372 "key" : self .api_key ,
3473 "cx" : self .search_engine_id ,
3574 "q" : claim_text ,
36- "num" : min (num_results , 10 ),
75+ # "num": min(num_results, 10),
76+ # Fetch max results because source policy filtering happens after search, not in the query.
77+ "num" : 10 ,
3778 "fields" : "items(title,link,snippet)" ,
3879 }
3980 if language == "english" :
4081 params = {
4182 "key" : self .api_key ,
4283 "cx" : self .search_engine_id ,
4384 "q" : claim_text ,
44- "num" : min (num_results , 10 ),
85+ # "num": min(num_results, 10),
86+ "num" : 10 ,
4587 "fields" : "items(title,link,snippet)" ,
4688 "lr" : "lang_en" ,
4789 }
@@ -50,7 +92,8 @@ async def search_and_create_sources(
5092 "key" : self .api_key ,
5193 "cx" : self .search_engine_id ,
5294 "q" : claim_text ,
53- "num" : min (num_results , 10 ),
95+ # "num": min(num_results, 10),
96+ "num" : 10 ,
5497 "fields" : "items(title,link,snippet)" ,
5598 "lr" : "lang_fr" ,
5699 }
@@ -70,16 +113,38 @@ async def search_and_create_sources(
70113
71114 for item in data ["items" ]:
72115 try :
116+ # domain_name = normalize_domain_name(item["link"])
117+ # domain, is_new = await self.domain_service.get_or_create_domain(domain_name)
118+
119+ # if is_new:
120+ # logger.info(f"Created new domain record for: {domain_name}")
121+
122+ # source = await self._create_new_source(item, search_id, domain.id, domain.credibility_score)
123+ # if source:
124+ # sources.append(source)
125+ # logger.debug(f"Created new source for URL: {item['link']}")
73126 domain_name = normalize_domain_name (item ["link" ])
127+ if self ._is_blocked_domain (domain_name ):
128+ logger .info (f"Skipping blocked source domain: { domain_name } " )
129+ continue
74130 domain , is_new = await self .domain_service .get_or_create_domain (domain_name )
75-
76131 if is_new :
77132 logger .info (f"Created new domain record for: { domain_name } " )
133+ if not self ._has_acceptable_credibility (domain .credibility_score ):
134+ logger .info (
135+ f"Skipping source with low/unknown credibility: "
136+ f"{ domain_name } ({ domain .credibility_score } )"
137+ )
138+ continue
78139
79- source = await self ._create_new_source (item , search_id , domain .id , domain .credibility_score )
140+ source = await self ._create_new_source (
141+ item ,
142+ search_id ,
143+ domain .id ,
144+ domain .credibility_score ,
145+ )
80146 if source :
81147 sources .append (source )
82- logger .debug (f"Created new source for URL: { item ['link' ]} " )
83148
84149 except Exception as e :
85150 logger .error (f"Error processing search result: { str (e )} " , exc_info = True )
@@ -128,6 +193,7 @@ async def _create_new_source(
128193
129194 def format_sources_for_prompt (self , sources : List [SourceModel ], language : str = "english" ) -> str :
130195 """Format sources into a string for the LLM prompt."""
196+ sources = self ._filter_allowed_sources (sources )
131197 if language == "english" :
132198 if not sources :
133199 return "No reliable sources found."
@@ -178,10 +244,12 @@ def format_sources_for_prompt(self, sources: List[SourceModel], language: str =
178244
179245 def calculate_overall_credibility (self , sources : List [SourceModel ]) -> float :
180246 """Calculate overall credibility score for a set of sources."""
247+ sources = self ._filter_allowed_sources (sources )
181248 if not sources :
182249 return 0.0
183250
184251 # Filter out sources with null credibility scores
252+ # valid_scores = [source.credibility_score for source in sources if source.credibility_score is not None]
185253 valid_scores = [source .credibility_score for source in sources if source .credibility_score is not None ]
186254
187255 if not valid_scores :
0 commit comments