136 lines
4.2 KiB
Python
136 lines
4.2 KiB
Python
"""
|
|
Personalization API routes for Munich News Daily.
|
|
Provides endpoints to test and preview personalized content.
|
|
"""
|
|
|
|
from flask import Blueprint, request, jsonify
|
|
from datetime import datetime, timedelta
|
|
from database import articles_collection
|
|
from services.personalization_service import (
|
|
rank_articles_for_user,
|
|
select_personalized_articles,
|
|
get_personalization_explanation,
|
|
get_personalization_stats
|
|
)
|
|
|
|
personalization_bp = Blueprint('personalization', __name__)
|
|
|
|
|
|
@personalization_bp.route('/api/personalize/preview/<email>', methods=['GET'])
|
|
def preview_personalized_newsletter(email):
|
|
"""
|
|
Preview personalized newsletter for a user.
|
|
|
|
Query parameters:
|
|
max_articles: Maximum articles to return (default: 10)
|
|
hours_lookback: Hours of articles to consider (default: 24)
|
|
|
|
Returns:
|
|
JSON with personalized article selection and statistics
|
|
"""
|
|
try:
|
|
max_articles = request.args.get('max_articles', 10, type=int)
|
|
hours_lookback = request.args.get('hours_lookback', 24, type=int)
|
|
|
|
# Get recent articles
|
|
cutoff_date = datetime.utcnow() - timedelta(hours=hours_lookback)
|
|
articles = list(articles_collection.find({
|
|
'created_at': {'$gte': cutoff_date},
|
|
'summary': {'$exists': True, '$ne': None}
|
|
}).sort('created_at', -1))
|
|
|
|
|
|
# Select personalized articles
|
|
personalized = select_personalized_articles(
|
|
articles,
|
|
email,
|
|
max_articles=max_articles
|
|
)
|
|
|
|
# Get statistics
|
|
stats = get_personalization_stats(personalized, email)
|
|
|
|
# Format response
|
|
articles_response = []
|
|
for article in personalized:
|
|
articles_response.append({
|
|
'title': article.get('title', ''),
|
|
'title_en': article.get('title_en'),
|
|
'summary': article.get('summary', ''),
|
|
'link': article.get('link', ''),
|
|
'category': article.get('category', 'general'),
|
|
'keywords': article.get('keywords', []),
|
|
'personalization_score': article.get('personalization_score', 0.0),
|
|
'published_at': article.get('published_at', '')
|
|
})
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'email': email,
|
|
'articles': articles_response,
|
|
'statistics': stats
|
|
}), 200
|
|
|
|
except Exception as e:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': str(e)
|
|
}), 500
|
|
|
|
|
|
@personalization_bp.route('/api/personalize/explain', methods=['POST'])
|
|
def explain_recommendation():
|
|
"""
|
|
Explain why an article was recommended to a user.
|
|
|
|
Request body:
|
|
{
|
|
"email": "user@example.com",
|
|
"article_id": "article-id-here"
|
|
}
|
|
|
|
Returns:
|
|
JSON with explanation of recommendation
|
|
"""
|
|
try:
|
|
data = request.get_json()
|
|
|
|
if not data or 'email' not in data or 'article_id' not in data:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'email and article_id required'
|
|
}), 400
|
|
|
|
email = data['email']
|
|
article_id = data['article_id']
|
|
|
|
# Get article
|
|
from bson import ObjectId
|
|
article = articles_collection.find_one({'_id': ObjectId(article_id)})
|
|
|
|
if not article:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Article not found'
|
|
}), 404
|
|
|
|
# Get user interests
|
|
from services.interest_profiling_service import get_user_interests
|
|
user_interests = get_user_interests(email)
|
|
|
|
# Generate explanation
|
|
explanation = get_personalization_explanation(article, user_interests)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'email': email,
|
|
'article_title': article.get('title', ''),
|
|
'explanation': explanation
|
|
}), 200
|
|
|
|
except Exception as e:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': str(e)
|
|
}), 500
|