Compare commits

..
8 Commits
Author SHA1 Message Date
retoor aa071db7ae chore: move hidden sentiment div below article excerpt in newspaper template
The diff relocates the hidden sentiment display element from above the article-meta section to a position after the excerpt text within the article-content div, preserving its hidden state while adjusting DOM order for cleaner template structure.
2025-10-06 07:38:43 +00:00
retoor 719b31c465 chore: replace hidden input with hidden div for sentiment display in newspaper template 2025-10-06 07:37:27 +00:00
retoor 4efb11dc6a feat: add sentiment analysis module using VADER with classify and score functions
Implement a new sentiment.py module that provides VADER-based sentiment analysis. The module includes an `analyze_sentiment_vader` function that classifies text as Positive, Negative, or Neutral based on compound score thresholds (>=0.05, <=-0.05), and a convenience `analyze` function wrapping the analyzer instance. Returns compound score and detailed polarity scores alongside classification.
2025-10-06 07:35:13 +00:00
retoor 422e4bf0c9 feat: integrate vaderSentiment analysis into article ingestion and search endpoints 2025-10-06 07:35:02 +00:00
retoor 6416b18ca6 fix: assign first_newspaper variable before loop to fix newspaper context in newspaper_latest endpoint 2025-10-06 06:05:47 +00:00
retoor 367b5043cb feat: aggregate articles across newspapers and limit to 30 with whitespace cleanup 2025-10-06 06:04:14 +00:00
retoor 3e804dcea9 feat: integrate ChromaDB for persistent article indexing and deduplication in sync tasks 2025-10-06 05:48:53 +00:00
retoor 93e1c282aa chore: scaffold initial project structure with FastAPI app, SQLite DB, and Jinja2 templates 2025-10-02 19:17:36 +00:00
4 changed files with 48 additions and 2 deletions
+2
View File
@@ -7,3 +7,5 @@ aiohttp==3.9.1
feedparser==6.0.10
websockets==12.0
trafilatura==1.6.2
vaderSentiment
+8 -2
View File
@@ -4,6 +4,7 @@ from fastapi.templating import Jinja2Templates
import dataset
import json
import aiohttp
import sentiment
import feedparser
import asyncio
from datetime import datetime
@@ -356,15 +357,17 @@ async def websocket_sync(websocket: WebSocket):
'last_synchronized': datetime.now().isoformat()
}
existing = articles_table.find_one(guid=article_data['guid'])
if not existing:
new_articles.append(article_data)
articles_count += 1
article_data['sentiment'] = json.dumps(sentiment.analyze(entry.get('description', '') or entry.get('summary', '')))
articles_table.upsert(article_data, ['guid'])
# Index the article to ChromaDB
doc_content = f"{article_data.get('title', '')}\n{article_data.get('description', '')}"
metadata = {key: str(value) for key, value in article_data.items() if key != 'content'} # Exclude large content from metadata
chroma_collection.upsert(
documents=[doc_content],
@@ -490,8 +493,9 @@ async def search_articles(
for i, doc_id in enumerate(results['ids'][0]):
res = results['metadatas'][0][i]
res['distance'] = results['distances'][0][i]
res['sentiment'] = sentiment.analyze(res.get('description', '') or res.get('content', '') or res.get('title', ''))
formatted_results.append(res)
return JSONResponse(content={"results": formatted_results})
else:
@@ -565,6 +569,8 @@ async def newspaper_latest(request: Request):
for article in articles:
for key, value in article.items():
article[key] = str(value).strip().replace(' ', '')
article['sentiment'] = sentiment.analyze(article.get('description', '') or article.get('content', '') or res.get('title', ''))
return templates.TemplateResponse("newspaper_view.html", {
"request": request,
"newspaper": first_newspaper,
+35
View File
@@ -0,0 +1,35 @@
import json
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
def analyze_sentiment_vader(text, analyzer):
"""
Analyzes text using VADER and returns a dictionary with the results.
Args:
text (str): The text content to analyze.
analyzer (SentimentIntensityAnalyzer): An instantiated VADER analyzer.
Returns:
dict: A dictionary containing the sentiment classification, compound score,
and detailed scores (positive, neutral, negative).
"""
scores = analyzer.polarity_scores(text)
compound_score = scores['compound']
if compound_score >= 0.05:
sentiment = 'Positive'
elif compound_score <= -0.05:
sentiment = 'Negative'
else:
sentiment = 'Neutral'
return {
'sentiment': sentiment,
'score': compound_score,
'details': scores
}
vader_analyzer = SentimentIntensityAnalyzer()
def analyze(content):
return analyze_sentiment_vader(content, vader_analyzer)
+3
View File
@@ -164,6 +164,7 @@
<h2 class="article-title">
<a href="{{ article.link }}" target="_blank">{{ article.title }}</a>
</h2>
<div class="article-meta">
<span class="article-source">{{ article.feed_name }}</span>
{% if article.author %}
@@ -178,6 +179,8 @@
{% set clean_text = full_content|striptags %}
{% set display_text = clean_text[:500] if article.content else clean_text[:300] %}
{{ display_text }}{% if clean_text|length > (500 if article.content else 300) %}...{% endif %}
<div class="article-sentiment" style="display: none">Sentiment: {{ article.sentiment }}</div>
</div>
{% set words = full_content.split() %}