Skip to content

Commit 8a9c992

Browse files
author
Alex J Lennon
committed
πŸ”§ Add comprehensive deduplication to AI news analysis
βœ… Enhanced AI prompts with explicit deduplication instructions βœ… Added programmatic deduplication with keyword overlap detection (40% threshold) βœ… Updated both OpenAI and Anthropic prompts for consistency βœ… Improved fallback categorization with deduplication (50% threshold) βœ… Enhanced synthesis prompts to avoid repetitive content 🎯 FIXES: Multiple instances of same China stories and other duplicates πŸ€– AI Analysis: Now explicitly instructed to select best version of duplicate stories πŸ” Safety Net: Programmatic overlap detection prevents AI misses πŸ“° Result: Unique, distinct stories in each theme category Example: Multiple 'China economy', 'China trade', 'China GDP' stories β†’ One comprehensive China story Reported-by: Molloy
1 parent ba23d77 commit 8a9c992

1 file changed

Lines changed: 67 additions & 18 deletions

File tree

β€Žgithub_ai_news_digest.pyβ€Ž

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ async def ai_analyze_stories(self, all_stories: List[NewsStory]) -> Dict[str, Li
174174

175175
ai_prompt = f"""
176176
Analyze these UK news headlines and categorize them into themes.
177-
Identify the most significant stories being covered by multiple sources.
177+
CRITICALLY IMPORTANT: Identify duplicate or similar stories about the same event and select only the BEST/MOST COMPREHENSIVE version of each story.
178178
179179
Headlines:
180180
{chr(10).join(story_titles)}
@@ -186,18 +186,25 @@ async def ai_analyze_stories(self, all_stories: List[NewsStory]) -> Dict[str, Li
186186
"technology": [{{"index": 3, "significance": 6, "reasoning": "Tech developments"}}]
187187
}}
188188
189-
Rules:
190-
1. Return ONLY the JSON object, no other text
191-
2. Use only these themes: politics, economy, health, international, climate, technology, crime
192-
3. Rate significance 1-10 based on coverage breadth
193-
4. Focus on stories with cross-source coverage
189+
DEDUPLICATION RULES (CRITICAL):
190+
1. If multiple headlines cover the SAME story/event (e.g., multiple China stories, same political announcement), select ONLY the most comprehensive one
191+
2. Look for similar keywords, names, locations, events - these indicate duplicate coverage
192+
3. For example: If you see "China economy" and "China trade" and "China GDP" - these might be the same story, pick the best one
193+
4. Prioritize headlines with more specific details over generic ones
194+
5. Each theme should have UNIQUE, DISTINCT stories - no duplicates allowed
195+
196+
OTHER RULES:
197+
6. Return ONLY the JSON object, no other text
198+
7. Use only these themes: politics, economy, health, international, climate, technology, crime
199+
8. Rate significance 1-10 based on coverage breadth and uniqueness
200+
9. Focus on stories with cross-source coverage but avoid duplicates
194201
"""
195202

196203
if self.ai_provider == 'openai':
197204
response = self.openai_client.chat.completions.create(
198205
model="gpt-4",
199206
messages=[
200-
{"role": "system", "content": "You are an expert news analyst categorizing UK headlines for accessibility services. Focus on accuracy and significance."},
207+
{"role": "system", "content": "You are an expert news analyst categorizing UK headlines for accessibility services. CRITICAL: Eliminate duplicate stories about the same events. Focus on accuracy, significance, and uniqueness. Never include multiple stories about the same event or topic."},
201208
{"role": "user", "content": ai_prompt}
202209
],
203210
temperature=0.3,
@@ -211,28 +218,49 @@ async def ai_analyze_stories(self, all_stories: List[NewsStory]) -> Dict[str, Li
211218
max_tokens=1500,
212219
temperature=0.3,
213220
messages=[
214-
{"role": "user", "content": f"You are an expert news analyst. {ai_prompt}"}
221+
{"role": "user", "content": f"You are an expert news analyst. CRITICAL: Eliminate duplicate stories about the same events. Focus on uniqueness and avoid redundancy. {ai_prompt}"}
215222
]
216223
)
217224
ai_analysis = json.loads(response.content[0].text)
218225

219-
# Apply AI analysis to stories
226+
# Apply AI analysis to stories and add programmatic deduplication
220227
themes = {}
221228
for theme, story_analyses in ai_analysis.items():
222229
theme_stories = []
230+
seen_keywords = set() # Track keywords to prevent similar stories
231+
223232
for analysis in story_analyses:
224233
story_idx = analysis['index'] - 1 # Convert to 0-based
225234
if 0 <= story_idx < len(all_stories):
226235
story = all_stories[story_idx]
227-
story.theme = theme
228-
story.significance_score = analysis['significance']
229-
theme_stories.append(story)
236+
237+
# Extract key terms for deduplication check
238+
story_keywords = set(word.lower() for word in story.title.split()
239+
if len(word) > 3 and word.isalpha())
240+
241+
# Check for significant overlap with existing stories
242+
overlap_threshold = 0.4 # 40% keyword overlap indicates duplicate
243+
is_duplicate = False
244+
245+
for existing_keywords in seen_keywords:
246+
if existing_keywords and story_keywords:
247+
overlap = len(story_keywords & existing_keywords) / len(story_keywords | existing_keywords)
248+
if overlap > overlap_threshold:
249+
is_duplicate = True
250+
print(f" πŸ”„ Skipping potential duplicate: '{story.title[:50]}...' (overlap: {overlap:.2f})")
251+
break
252+
253+
if not is_duplicate:
254+
story.theme = theme
255+
story.significance_score = analysis['significance']
256+
theme_stories.append(story)
257+
seen_keywords.add(frozenset(story_keywords))
230258

231259
if theme_stories:
232260
# Sort by significance score
233261
theme_stories.sort(key=lambda x: x.significance_score or 0, reverse=True)
234262
themes[theme] = theme_stories
235-
print(f" 🎯 {theme.capitalize()}: {len(theme_stories)} stories (AI analyzed)")
263+
print(f" 🎯 {theme.capitalize()}: {len(theme_stories)} stories (AI analyzed, duplicates removed)")
236264

237265
return themes
238266

@@ -243,7 +271,7 @@ async def ai_analyze_stories(self, all_stories: List[NewsStory]) -> Dict[str, Li
243271

244272
def fallback_categorization(self, all_stories: List[NewsStory]) -> Dict[str, List[NewsStory]]:
245273
"""
246-
Fallback to keyword-based categorization if AI fails
274+
Fallback to keyword-based categorization if AI fails, with deduplication
247275
"""
248276
theme_keywords = {
249277
'politics': ['government', 'minister', 'parliament', 'election', 'policy', 'mp', 'labour', 'conservative'],
@@ -258,10 +286,29 @@ def fallback_categorization(self, all_stories: List[NewsStory]) -> Dict[str, Lis
258286
themes = {}
259287
for theme, keywords in theme_keywords.items():
260288
theme_stories = []
289+
seen_keywords = set() # Track keywords to prevent similar stories
290+
261291
for story in all_stories:
262292
if any(keyword in story.title.lower() for keyword in keywords):
263-
story.theme = theme
264-
theme_stories.append(story)
293+
# Extract key terms for deduplication check
294+
story_keywords = set(word.lower() for word in story.title.split()
295+
if len(word) > 3 and word.isalpha())
296+
297+
# Check for significant overlap with existing stories
298+
overlap_threshold = 0.5 # 50% overlap for fallback mode (more strict)
299+
is_duplicate = False
300+
301+
for existing_keywords in seen_keywords:
302+
if existing_keywords and story_keywords:
303+
overlap = len(story_keywords & existing_keywords) / len(story_keywords | existing_keywords)
304+
if overlap > overlap_threshold:
305+
is_duplicate = True
306+
break
307+
308+
if not is_duplicate:
309+
story.theme = theme
310+
theme_stories.append(story)
311+
seen_keywords.add(frozenset(story_keywords))
265312

266313
if len(theme_stories) >= 2:
267314
themes[theme] = theme_stories
@@ -299,14 +346,16 @@ async def ai_synthesize_content(self, theme: str, stories: List[NewsStory]) -> s
299346
- Do NOT mention specific news sources or outlets
300347
- Do NOT mention how many sources are covering this
301348
- Focus on what's happening, not who's reporting it
349+
- AVOID repetitive content - synthesize into ONE coherent narrative
350+
- If multiple stories are about the same event, combine them into one summary
302351
- Start with: "In {theme} news today..."
303352
"""
304353

305354
if self.ai_provider == 'openai':
306355
response = self.openai_client.chat.completions.create(
307356
model="gpt-4",
308357
messages=[
309-
{"role": "system", "content": "You are creating accessible news content for visually impaired users. Write clearly and conversationally for audio consumption. Never copy original text - always synthesize."},
358+
{"role": "system", "content": "You are creating accessible news content for visually impaired users. Write clearly and conversationally for audio consumption. Never copy original text - always synthesize. Avoid repetitive content - combine similar stories into one coherent narrative."},
310359
{"role": "user", "content": ai_prompt}
311360
],
312361
temperature=0.4,
@@ -320,7 +369,7 @@ async def ai_synthesize_content(self, theme: str, stories: List[NewsStory]) -> s
320369
max_tokens=300,
321370
temperature=0.4,
322371
messages=[
323-
{"role": "user", "content": f"You are creating accessible news content. {ai_prompt}"}
372+
{"role": "user", "content": f"You are creating accessible news content. Avoid repetitive content - synthesize similar stories into one narrative. {ai_prompt}"}
324373
]
325374
)
326375
return response.content[0].text.strip()

0 commit comments

Comments
Β (0)