v3: Self-learning loop — nightly /api/learn, keyword-based scoring boost, performance feedback into topic selection
This commit is contained in:
@@ -508,6 +508,62 @@ def api_stats():
|
||||
})
|
||||
|
||||
|
||||
@app.route("/api/learn", methods=["POST"])
|
||||
def api_learn():
|
||||
"""Nightly learning: analyze performance and update internal models."""
|
||||
db = get_db()
|
||||
|
||||
# Aggregate keyword performance
|
||||
rows = db.execute("""
|
||||
SELECT a.keywords, COUNT(p.id) as views
|
||||
FROM articles a
|
||||
LEFT JOIN pageviews p ON a.id = p.article_id
|
||||
WHERE a.status = 'published'
|
||||
GROUP BY a.id
|
||||
ORDER BY views DESC
|
||||
LIMIT 20
|
||||
""").fetchall()
|
||||
|
||||
keyword_views = {}
|
||||
for row in rows:
|
||||
try:
|
||||
kws = json.loads(row["keywords"]) if isinstance(row["keywords"], str) else (row["keywords"] or [])
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
kws = []
|
||||
for kw in kws:
|
||||
keyword_views[kw] = keyword_views.get(kw, 0) + (row["views"] or 0)
|
||||
|
||||
top_keywords = sorted(keyword_views.items(), key=lambda x: x[1], reverse=True)[:15]
|
||||
|
||||
# Best performing word count range
|
||||
wc_row = db.execute("""
|
||||
SELECT AVG(a.word_count) as avg_wc, AVG(a.reading_time) as avg_rt
|
||||
FROM articles a
|
||||
LEFT JOIN pageviews p ON a.id = p.article_id
|
||||
WHERE a.status = 'published'
|
||||
GROUP BY a.id
|
||||
HAVING COUNT(p.id) > 0
|
||||
ORDER BY COUNT(p.id) DESC
|
||||
LIMIT 10
|
||||
""").fetchone()
|
||||
|
||||
# Store learning
|
||||
db.execute("""
|
||||
INSERT OR REPLACE INTO learning (metric, value, recorded_at)
|
||||
VALUES ('top_keywords', ?, datetime('now'))
|
||||
""", (json.dumps(top_keywords),))
|
||||
|
||||
db.commit()
|
||||
|
||||
return jsonify({
|
||||
"status": "learned",
|
||||
"top_keywords": [{"keyword": kw, "views": v} for kw, v in top_keywords[:10]],
|
||||
"optimal_word_count": round(wc_row["avg_wc"]) if wc_row and wc_row["avg_wc"] else None,
|
||||
"optimal_reading_time": round(wc_row["avg_rt"]) if wc_row and wc_row["avg_rt"] else None,
|
||||
"total_articles_analyzed": db.execute("SELECT COUNT(*) FROM articles WHERE status='published'").fetchone()[0],
|
||||
})
|
||||
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
db = get_db()
|
||||
|
||||
Reference in New Issue
Block a user