Slyv.News (1.0.0)
Installation
dotnet nuget add source --name logikonline --username your_username --password your_token dotnet add package --source logikonline --version 1.0.0 Slyv.NewsAbout this package
News aggregation and trend analysis library with RSS feed integration, sentiment tracking, intelligent categorization, and content filtering. Supports multi-source news aggregation, topic extraction, trend detection, and personalized news recommendations. Includes temporal trend analysis for tracking news evolution over time.
Slyv.News
Standalone news aggregation and analysis library for the Slyv platform.
Overview
Slyv.News is a platform-agnostic library that provides news fetching, sentiment analysis, trend tracking, and goal-aligned content filtering. It can be used in:
- ✅ Mobile apps (single user, local SQLite) via .NET MAUI
- ✅ Web APIs (multi-user, SQL Server)
- ✅ Console apps (testing, demos)
Architecture
Following the same pattern as Slyv.Analysis and Slyv.Social, this library:
- No dependency on Slyv.Core - Completely standalone
- Repository pattern - Abstract data access via
INewsRepository - Single-user context - No multi-tenancy complexity
- Flexible storage - Implement
INewsRepositoryfor your data store
Dependencies
Slyv.News
└── Slyv.Analysis (for sentiment analysis, temporal analysis, topic modeling)
Project Structure
Slyv.News/
├── Data/
│ ├── NewsEntities.cs # News entities (NewsArticle, NewsCategory, NewsSource, etc.)
│ └── TrendEntities.cs # Trend tracking entities
├── Models/
│ └── NewsModels.cs # DTOs and API response models
├── Common/
│ └── Enums.cs # News-related enums
├── Services/
│ ├── NewsService.cs # Core news fetching and filtering
│ └── NewsTrendAnalysisService.cs # Trend analysis and insights
└── Abstractions/
└── INewsRepository.cs # Data access abstraction
Usage
Mobile App (MAUI with SQLite)
// Slyv.Mobile/MauiProgram.cs
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
// Register SQLite DbContext for mobile
builder.Services.AddDbContext<MobileDbContext>(options =>
options.UseSqlite($"Data Source={dbPath}"));
// Register repository implementation for mobile
builder.Services.AddSingleton<INewsRepository, SqliteNewsRepository>();
// Register news services
builder.Services.AddHttpClient<INewsService, NewsService>();
builder.Services.AddScoped<INewsTrendAnalysisService, NewsTrendAnalysisService>();
return builder.Build();
}
// Implement SqliteNewsRepository in your mobile app
public class SqliteNewsRepository : INewsRepository
{
private readonly MobileDbContext _context;
public SqliteNewsRepository(MobileDbContext context)
{
_context = context;
}
// Implement all INewsRepository methods for SQLite
}
Web API (ASP.NET Core with SQL Server)
// Slyv.Core/Data/EfCoreNewsRepository.cs
public class EfCoreNewsRepository : INewsRepository
{
private readonly SlyvDbContext _context;
public EfCoreNewsRepository(SlyvDbContext context)
{
_context = context;
}
// Implement all INewsRepository methods for EF Core/SQL Server
}
// Slyv.Api/Program.cs
builder.Services.AddDbContext<SlyvDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddScoped<INewsRepository, EfCoreNewsRepository>();
builder.Services.AddHttpClient<INewsService, NewsService>();
builder.Services.AddScoped<INewsTrendAnalysisService, NewsTrendAnalysisService>();
Features
News Fetching
- MediaStack API integration
- Multi-category support
- Pagination for large datasets
- Duplicate detection via content hashing
- Source and category management
Sentiment Analysis
- Article sentiment scoring
- Emotion detection
- Temporal trend analysis
- Pattern detection
Goal-Aligned Filtering
- Filter articles by user goals
- Relevance scoring
- Personalized recommendations
- Audit trail for filtering decisions
Trend Analysis
- Daily/weekly/monthly snapshots
- Category trend tracking
- User progress monitoring
- Anomaly detection
- Predictive insights
Entities
Core Entities
NewsArticle- News article with sentiment and metadataNewsCategory- Article categories with statisticsNewsSource- News sources with credibility scoresNewsTag- Article tags for topic tracking
User-Specific Entities
UserNewsPreference- User news preferences and filtersNewsArticleInteraction- User interactions with articlesUserNewsRelevance- Goal alignment scores per article
Trend Tracking
NewsTrendSnapshot- Global news trendsCategoryTrendSnapshot- Category-specific trendsUserProgressSnapshot- User reading progressSentimentTrendAnalysis- Sentiment over timeTrendAlert- Alerts for significant changes
Code Examples
News Fetching
Basic News Fetching
using Slyv.News.Services;
using Slyv.News.Models;
public class NewsController
{
private readonly INewsService _newsService;
public async Task<List<NewsArticle>> GetLatestNews()
{
// Fetch latest 100 news articles across all categories
var (articles, newCount, duplicateCount) = await _newsService.FetchAllNewsAsync(limit: 100);
Console.WriteLine($"Fetched {newCount} new articles");
Console.WriteLine($"Skipped {duplicateCount} duplicates");
foreach (var article in articles)
{
Console.WriteLine($"{article.Title}");
Console.WriteLine($" Source: {article.Source?.Name}");
Console.WriteLine($" Sentiment: {article.SentimentScore:F2}");
Console.WriteLine($" Published: {article.PublishedAt:yyyy-MM-dd}");
}
return articles;
}
}
Fetch News by Category
// Fetch news with user preferences
var preferences = new NewsPreferencesDto
{
Categories = new List<string> { "technology", "science", "business" },
Countries = new List<string> { "us", "gb" },
Languages = new List<string> { "en" },
ExcludeSources = new List<string> { "tabloid-news" }
};
var (articles, newCount, duplicateCount) = await _newsService.FetchNewsAsync(
preferences,
limit: 50
);
Console.WriteLine($"Found {articles.Count} articles matching your preferences");
Extended News Fetching (Pagination)
// Fetch large volume of news (500+ articles) with automatic pagination
var (articles, newCount, duplicateCount) = await _newsService.FetchExtendedNewsAsync(totalLimit: 500);
Console.WriteLine($"Fetched {newCount} new articles out of {totalLimit} requested");
Console.WriteLine($"Categories covered: {articles.Select(a => a.Category).Distinct().Count()}");
Goal-Aligned News Filtering
Get News Aligned with User Goals
using Slyv.News.Services;
public class PersonalizedNewsService
{
private readonly INewsService _newsService;
public async Task<GoalAlignedNewsFeedDto> GetPersonalizedFeed(Guid userId)
{
// Get news feed filtered by user's active goals
var feed = await _newsService.GetGoalAlignedFeedAsync(userId, includeAudit: true);
Console.WriteLine($"Total articles: {feed.TotalArticles}");
Console.WriteLine($"Filtered articles: {feed.FilteredArticles.Count}");
Console.WriteLine($"Average alignment: {feed.AverageAlignment:P0}");
foreach (var article in feed.FilteredArticles)
{
Console.WriteLine($"\n{article.Article.Title}");
Console.WriteLine($" Alignment score: {article.AlignmentScore:P0}");
Console.WriteLine($" Matching goals:");
foreach (var goalMatch in article.MatchingGoals ?? [])
{
Console.WriteLine($" - {goalMatch.GoalTitle}: {goalMatch.Score:P0}");
}
}
// Review audit trail
if (feed.AuditTrail != null)
{
Console.WriteLine($"\nFiltering decisions:");
Console.WriteLine($" Applied thresholds: {feed.AuditTrail.AppliedThresholds}");
Console.WriteLine($" Rejection reasons: {string.Join(", ", feed.AuditTrail.RejectionReasons)}");
}
return feed;
}
}
Calculate Article Relevance for User
// Calculate how relevant an article is to a user's goals
var articleId = Guid.Parse("...");
var userId = Guid.Parse("...");
var relevance = await _newsService.CalculateUserRelevanceAsync(userId, articleId);
Console.WriteLine($"Article relevance:");
Console.WriteLine($" Overall score: {relevance.RelevanceScore:P0}");
Console.WriteLine($" Sentiment alignment: {relevance.SentimentAlignment:F2}");
Console.WriteLine($" Category match: {relevance.CategoryMatch}");
Console.WriteLine($" Goal alignments: {relevance.GoalAlignments?.Count ?? 0}");
News Analysis
Analyze Article Sentiment
// Analyze a news article for sentiment and emotion
var article = await _newsService.GetArticleByIdAsync(articleId);
if (article != null)
{
var analyzed = await _newsService.AnalyzeArticleAsync(article);
Console.WriteLine($"Title: {analyzed.Title}");
Console.WriteLine($"Sentiment: {analyzed.SentimentLabel} ({analyzed.SentimentScore:F2})");
Console.WriteLine($"Emotion: {analyzed.EmotionTone}");
Console.WriteLine($"Confidence: {analyzed.SentimentConfidence:P0}");
}
Trend Analysis
Get News Trends
using Slyv.News.Services;
public class TrendAnalysisService
{
private readonly INewsTrendAnalysisService _trendService;
public async Task AnalyzeTrends(Guid userId)
{
// Analyze trends for the user
await _trendService.AnalyzeTrendsAsync(userId);
// Get daily trend snapshot
var dailyTrend = await _trendService.GetDailyTrendSnapshotAsync(DateTime.Today, userId);
if (dailyTrend != null)
{
Console.WriteLine($"Daily News Trends for {dailyTrend.SnapshotDate:yyyy-MM-dd}:");
Console.WriteLine($" Articles analyzed: {dailyTrend.ArticlesAnalyzed}");
Console.WriteLine($" Average sentiment: {dailyTrend.AverageSentiment:F2}");
Console.WriteLine($" Top categories: {string.Join(", ", dailyTrend.TopCategories ?? [])}");
Console.WriteLine($" Trending topics: {string.Join(", ", dailyTrend.TrendingTopics ?? [])}");
}
// Get sentiment trend over time
var sentimentTrends = await _trendService.GetSentimentTrendAsync(
userId,
startDate: DateTime.Today.AddDays(-30),
endDate: DateTime.Today
);
Console.WriteLine($"\n30-Day Sentiment Trend:");
foreach (var trend in sentimentTrends)
{
Console.WriteLine($" {trend.AnalysisDate:yyyy-MM-dd}: {trend.AverageSentiment:F2}");
Console.WriteLine($" Positive: {trend.PositivePercentage:P0}");
Console.WriteLine($" Negative: {trend.NegativePercentage:P0}");
}
}
public async Task MonitorTrendAlerts(Guid userId)
{
// Get active trend alerts
var alerts = await _trendService.GetActiveTrendAlertsAsync(userId);
Console.WriteLine($"Active Trend Alerts ({alerts.Count}):");
foreach (var alert in alerts)
{
Console.WriteLine($" {alert.AlertType}: {alert.Message}");
Console.WriteLine($" Severity: {alert.Severity}");
Console.WriteLine($" Detected: {alert.DetectedAt:g}");
}
}
}
User Preferences
Update News Preferences
// Update user's news preferences
var preferences = new NewsPreferencesDto
{
Categories = new List<string> { "technology", "science", "health" },
Countries = new List<string> { "us" },
Languages = new List<string> { "en" },
MinSentimentScore = 0.3, // Prefer positive news
MaxArticlesPerDay = 50,
ExcludeSources = new List<string> { "low-quality-source" }
};
var success = await _newsService.UpdateUserPreferencesAsync(userId, preferences);
if (success)
{
Console.WriteLine("News preferences updated successfully");
}
Repository Implementation
EF Core News Repository
using Microsoft.EntityFrameworkCore;
using Slyv.News.Abstractions;
using Slyv.News.Data;
public class EfCoreNewsRepository : INewsRepository
{
private readonly SlyvDbContext _context;
public EfCoreNewsRepository(SlyvDbContext context)
{
_context = context;
}
public async Task<NewsArticle?> GetArticleByIdAsync(Guid id, CancellationToken ct = default)
{
return await _context.NewsArticles
.Include(a => a.Source)
.Include(a => a.Categories)
.Include(a => a.Tags)
.AsNoTracking()
.FirstOrDefaultAsync(a => a.Id == id, ct);
}
public async Task<List<NewsArticle>> GetArticlesByCategoryAsync(
int categoryId,
int limit = 50,
CancellationToken ct = default)
{
return await _context.NewsArticles
.Include(a => a.Source)
.Where(a => a.Categories.Any(c => c.CategoryId == categoryId))
.OrderByDescending(a => a.PublishedAt)
.Take(limit)
.AsNoTracking()
.ToListAsync(ct);
}
public async Task<NewsArticle> SaveArticleAsync(NewsArticle article, CancellationToken ct = default)
{
_context.NewsArticles.Add(article);
await _context.SaveChangesAsync(ct);
return article;
}
public async Task<NewsArticle?> GetArticleByUrlAsync(string url, CancellationToken ct = default)
{
return await _context.NewsArticles
.FirstOrDefaultAsync(a => a.Url == url, ct);
}
public async Task<List<NewsArticle>> GetRecentArticlesAsync(
DateTime since,
int limit = 100,
CancellationToken ct = default)
{
return await _context.NewsArticles
.Include(a => a.Source)
.Where(a => a.PublishedAt >= since)
.OrderByDescending(a => a.PublishedAt)
.Take(limit)
.AsNoTracking()
.ToListAsync(ct);
}
public async Task<int> GetSourceCountAsync(CancellationToken ct = default)
{
return await _context.NewsSources.CountAsync(ct);
}
public async Task<List<NewsSource>> GetAllSourcesAsync(CancellationToken ct = default)
{
return await _context.NewsSources
.AsNoTracking()
.ToListAsync(ct);
}
public async Task<int> SaveChangesAsync(CancellationToken ct = default)
{
return await _context.SaveChangesAsync(ct);
}
}
Configuration
appsettings.json
{
"MediaStack": {
"ApiKey": "your-mediastack-api-key",
"BaseUrl": "http://api.mediastack.com/v1/"
},
"NewsSettings": {
"DefaultLimit": 50,
"MaxArticlesPerRequest": 100,
"CacheDuration": "01:00:00",
"MinSentimentThreshold": 0.0,
"EnableGoalFiltering": true
}
}
Service Registration
// Program.cs
builder.Services.AddHttpClient<INewsService, NewsService>();
builder.Services.AddScoped<INewsTrendAnalysisService, NewsTrendAnalysisService>();
builder.Services.AddScoped<INewsRepository, EfCoreNewsRepository>();
// Register Slyv.Analysis dependencies (required)
builder.Services.AddSingleton<AdvancedSentimentAnalyzer>();
builder.Services.AddSingleton<TopicModeling>();
// Register Slyv.Goals dependencies (for goal-aligned filtering)
builder.Services.AddScoped<IGoalRepository, EfCoreGoalRepository>();
builder.Services.AddScoped<IGoalAlignmentService, GoalAlignmentService>();
Best Practices
1. Handle Duplicate Detection
// ✅ Good - NewsService automatically detects duplicates
var (articles, newCount, duplicateCount) = await _newsService.FetchAllNewsAsync(100);
Console.WriteLine($"New: {newCount}, Duplicates: {duplicateCount}");
// Articles are deduplicated by content hash
2. Use Goal-Aligned Filtering
// ✅ Good - Filter by user goals for personalized feed
var feed = await _newsService.GetGoalAlignedFeedAsync(userId);
// ❌ Bad - Showing all news without filtering
var allArticles = await _newsService.FetchAllNewsAsync(1000);
3. Monitor Trends Over Time
// ✅ Good - Track trends to identify patterns
await _trendService.AnalyzeTrendsAsync(userId);
var trends = await _trendService.GetSentimentTrendAsync(userId, startDate, endDate);
// Detect anomalies and send alerts
var alerts = await _trendService.GetActiveTrendAlertsAsync(userId);
4. Cache News Articles
// ✅ Good - Query recent articles from database
var recentArticles = await _newsRepository.GetRecentArticlesAsync(
DateTime.Today.AddHours(-6),
limit: 50
);
// ❌ Bad - Fetching from API every time
// This costs API credits and is slower
Troubleshooting
Issue: No news articles returned
Solution: Check MediaStack API key configuration:
{
"MediaStack": {
"ApiKey": "your-api-key-here"
}
}
Issue: Duplicate articles appearing
Solution: NewsService automatically deduplicates by content hash. Check repository implementation saves content hash:
article.ContentHash = ComputeContentHash(article.Title + article.Description);
Issue: Goal-aligned filtering not working
Solution: Ensure GoalRepository and GoalAlignmentService are registered:
builder.Services.AddScoped<IGoalRepository, EfCoreGoalRepository>();
builder.Services.AddScoped<IGoalAlignmentService, GoalAlignmentService>();
License
Part of the Slyv project. See main repository for license information.
Built with ❤️ by the Slyv Team
Stay informed with personalized, goal-aligned news
Initial release of Slyv.News with news aggregation and trend analysis capabilities.
Dependencies
| ID | Version | Target Framework |
|---|---|---|
| Slyv.Analysis | 1.0.0 | net9.0 |
| Slyv.Goals | 1.0.0 | net9.0 |
| Microsoft.Extensions.Configuration.Abstractions | 9.0.10 | net9.0 |
| Microsoft.Extensions.DependencyInjection | 9.0.10 | net9.0 |
| Microsoft.Extensions.DependencyInjection.Abstractions | 9.0.10 | net9.0 |
| Microsoft.Extensions.Http | 9.0.10 | net9.0 |
| Microsoft.Extensions.Logging.Abstractions | 9.0.10 | net9.0 |
| Microsoft.Extensions.Options | 9.0.10 | net9.0 |
| System.Text.Json | 9.0.10 | net9.0 |