[{"data":1,"prerenderedAt":2676},["ShallowReactive",2],{"blog-personalized-news-feeds":3},{"id":4,"title":5,"author":6,"body":7,"date":2664,"description":2665,"draft":2666,"extension":2667,"image":2668,"meta":2669,"navigation":729,"path":2670,"seo":2671,"sitemap":2672,"stem":2673,"tags":2674,"__hash__":2675},"blog\u002Fblog\u002Fpersonalized-news-feeds.md","Building Personalized News Feeds with Machine Learning","AllNewsAPI Team",{"type":8,"value":9,"toc":2644},"minimark",[10,14,19,22,41,45,50,53,783,787,790,1365,1369,1372,1624,1628,1632,1900,1904,2297,2301,2305,2308,2431,2435,2438,2462,2466,2469,2541,2545,2597,2601,2604,2613,2617,2640],[11,12,13],"p",{},"Generic news feeds are a thing of the past. Modern users expect personalized content tailored to their interests. This guide shows you how to build intelligent news recommendation systems using machine learning.",[15,16,18],"h2",{"id":17},"why-personalization-matters","Why Personalization Matters",[11,20,21],{},"Personalized news feeds lead to:",[23,24,25,29,32,35,38],"ul",{},[26,27,28],"li",{},"3x higher engagement rates",[26,30,31],{},"2x longer session times",[26,33,34],{},"50% increase in return visits",[26,36,37],{},"Better user satisfaction",[26,39,40],{},"Reduced information overload",[15,42,44],{"id":43},"personalization-approaches","Personalization Approaches",[46,47,49],"h3",{"id":48},"_1-content-based-filtering","1. Content-Based Filtering",[11,51,52],{},"Recommend articles similar to what users have read:",[54,55,60],"pre",{"className":56,"code":57,"language":58,"meta":59,"style":59},"language-javascript shiki shiki-themes github-light github-dark","class ContentBasedRecommender {\n  constructor() {\n    this.userProfiles = new Map();\n  }\n  \n  \u002F\u002F Extract features from article\n  extractFeatures(article) {\n    return {\n      category: article.category || 'general',\n      source: article.source.name,\n      keywords: this.extractKeywords(article.title + ' ' + article.description),\n      publishedAt: new Date(article.publishedAt)\n    };\n  }\n  \n  extractKeywords(text) {\n    \u002F\u002F Simple keyword extraction (use NLP library for production)\n    const words = text.toLowerCase()\n      .replace(\u002F[^\\w\\s]\u002Fg, '')\n      .split(\u002F\\s+\u002F)\n      .filter(word => word.length > 4);\n    \n    \u002F\u002F Count word frequency\n    const frequency = {};\n    words.forEach(word => {\n      frequency[word] = (frequency[word] || 0) + 1;\n    });\n    \n    \u002F\u002F Return top keywords\n    return Object.entries(frequency)\n      .sort((a, b) => b[1] - a[1])\n      .slice(0, 10)\n      .map(([word]) => word);\n  }\n  \n  \u002F\u002F Update user profile based on interactions\n  updateUserProfile(userId, article, interaction) {\n    if (!this.userProfiles.has(userId)) {\n      this.userProfiles.set(userId, {\n        categories: {},\n        sources: {},\n        keywords: {},\n        readCount: 0\n      });\n    }\n    \n    const profile = this.userProfiles.get(userId);\n    const features = this.extractFeatures(article);\n    \n    \u002F\u002F Weight based on interaction type\n    const weight = {\n      'read': 1,\n      'like': 2,\n      'share': 3,\n      'save': 2\n    }[interaction] || 1;\n    \n    \u002F\u002F Update category preferences\n    profile.categories[features.category] = \n      (profile.categories[features.category] || 0) + weight;\n    \n    \u002F\u002F Update source preferences\n    profile.sources[features.source] = \n      (profile.sources[features.source] || 0) + weight;\n    \n    \u002F\u002F Update keyword preferences\n    features.keywords.forEach(keyword => {\n      profile.keywords[keyword] = \n        (profile.keywords[keyword] || 0) + weight;\n    });\n    \n    profile.readCount++;\n  }\n  \n  \u002F\u002F Calculate similarity score between article and user profile\n  calculateScore(userId, article) {\n    const profile = this.userProfiles.get(userId);\n    if (!profile || profile.readCount === 0) return 0;\n    \n    const features = this.extractFeatures(article);\n    let score = 0;\n    \n    \u002F\u002F Category score (40% weight)\n    const categoryScore = profile.categories[features.category] || 0;\n    score += (categoryScore \u002F profile.readCount) * 0.4;\n    \n    \u002F\u002F Source score (20% weight)\n    const sourceScore = profile.sources[features.source] || 0;\n    score += (sourceScore \u002F profile.readCount) * 0.2;\n    \n    \u002F\u002F Keyword score (40% weight)\n    const keywordScores = features.keywords.map(\n      keyword => profile.keywords[keyword] || 0\n    );\n    const avgKeywordScore = keywordScores.reduce((a, b) => a + b, 0) \u002F \n      (keywordScores.length || 1);\n    score += (avgKeywordScore \u002F profile.readCount) * 0.4;\n    \n    return score;\n  }\n  \n  \u002F\u002F Get personalized recommendations\n  recommend(userId, articles, limit = 10) {\n    const scoredArticles = articles.map(article => ({\n      article,\n      score: this.calculateScore(userId, article)\n    }));\n    \n    return scoredArticles\n      .sort((a, b) => b.score - a.score)\n      .slice(0, limit)\n      .map(item => item.article);\n  }\n}\n\n\u002F\u002F Usage\nconst recommender = new ContentBasedRecommender();\n\n\u002F\u002F Track user interactions\nrecommender.updateUserProfile('user123', article, 'read');\nrecommender.updateUserProfile('user123', article, 'like');\n\n\u002F\u002F Get recommendations\nconst recommendations = recommender.recommend('user123', allArticles, 10);\n","javascript","",[61,62,63,71,77,83,89,95,101,107,113,119,125,131,137,143,148,153,159,165,171,177,183,189,195,201,207,213,219,225,230,236,242,248,254,260,265,270,276,282,288,294,300,306,312,318,324,330,335,341,347,352,358,364,370,376,382,388,394,399,405,411,417,422,428,434,440,445,451,457,463,469,474,479,485,490,495,501,507,512,518,523,528,534,539,545,551,557,562,568,574,580,585,591,597,603,609,615,621,627,632,638,643,648,654,660,666,672,678,684,689,695,701,707,713,718,724,731,737,743,748,754,760,766,771,777],"code",{"__ignoreMap":59},[64,65,68],"span",{"class":66,"line":67},"line",1,[64,69,70],{},"class ContentBasedRecommender {\n",[64,72,74],{"class":66,"line":73},2,[64,75,76],{},"  constructor() {\n",[64,78,80],{"class":66,"line":79},3,[64,81,82],{},"    this.userProfiles = new Map();\n",[64,84,86],{"class":66,"line":85},4,[64,87,88],{},"  }\n",[64,90,92],{"class":66,"line":91},5,[64,93,94],{},"  \n",[64,96,98],{"class":66,"line":97},6,[64,99,100],{},"  \u002F\u002F Extract features from article\n",[64,102,104],{"class":66,"line":103},7,[64,105,106],{},"  extractFeatures(article) {\n",[64,108,110],{"class":66,"line":109},8,[64,111,112],{},"    return {\n",[64,114,116],{"class":66,"line":115},9,[64,117,118],{},"      category: article.category || 'general',\n",[64,120,122],{"class":66,"line":121},10,[64,123,124],{},"      source: article.source.name,\n",[64,126,128],{"class":66,"line":127},11,[64,129,130],{},"      keywords: this.extractKeywords(article.title + ' ' + article.description),\n",[64,132,134],{"class":66,"line":133},12,[64,135,136],{},"      publishedAt: new Date(article.publishedAt)\n",[64,138,140],{"class":66,"line":139},13,[64,141,142],{},"    };\n",[64,144,146],{"class":66,"line":145},14,[64,147,88],{},[64,149,151],{"class":66,"line":150},15,[64,152,94],{},[64,154,156],{"class":66,"line":155},16,[64,157,158],{},"  extractKeywords(text) {\n",[64,160,162],{"class":66,"line":161},17,[64,163,164],{},"    \u002F\u002F Simple keyword extraction (use NLP library for production)\n",[64,166,168],{"class":66,"line":167},18,[64,169,170],{},"    const words = text.toLowerCase()\n",[64,172,174],{"class":66,"line":173},19,[64,175,176],{},"      .replace(\u002F[^\\w\\s]\u002Fg, '')\n",[64,178,180],{"class":66,"line":179},20,[64,181,182],{},"      .split(\u002F\\s+\u002F)\n",[64,184,186],{"class":66,"line":185},21,[64,187,188],{},"      .filter(word => word.length > 4);\n",[64,190,192],{"class":66,"line":191},22,[64,193,194],{},"    \n",[64,196,198],{"class":66,"line":197},23,[64,199,200],{},"    \u002F\u002F Count word frequency\n",[64,202,204],{"class":66,"line":203},24,[64,205,206],{},"    const frequency = {};\n",[64,208,210],{"class":66,"line":209},25,[64,211,212],{},"    words.forEach(word => {\n",[64,214,216],{"class":66,"line":215},26,[64,217,218],{},"      frequency[word] = (frequency[word] || 0) + 1;\n",[64,220,222],{"class":66,"line":221},27,[64,223,224],{},"    });\n",[64,226,228],{"class":66,"line":227},28,[64,229,194],{},[64,231,233],{"class":66,"line":232},29,[64,234,235],{},"    \u002F\u002F Return top keywords\n",[64,237,239],{"class":66,"line":238},30,[64,240,241],{},"    return Object.entries(frequency)\n",[64,243,245],{"class":66,"line":244},31,[64,246,247],{},"      .sort((a, b) => b[1] - a[1])\n",[64,249,251],{"class":66,"line":250},32,[64,252,253],{},"      .slice(0, 10)\n",[64,255,257],{"class":66,"line":256},33,[64,258,259],{},"      .map(([word]) => word);\n",[64,261,263],{"class":66,"line":262},34,[64,264,88],{},[64,266,268],{"class":66,"line":267},35,[64,269,94],{},[64,271,273],{"class":66,"line":272},36,[64,274,275],{},"  \u002F\u002F Update user profile based on interactions\n",[64,277,279],{"class":66,"line":278},37,[64,280,281],{},"  updateUserProfile(userId, article, interaction) {\n",[64,283,285],{"class":66,"line":284},38,[64,286,287],{},"    if (!this.userProfiles.has(userId)) {\n",[64,289,291],{"class":66,"line":290},39,[64,292,293],{},"      this.userProfiles.set(userId, {\n",[64,295,297],{"class":66,"line":296},40,[64,298,299],{},"        categories: {},\n",[64,301,303],{"class":66,"line":302},41,[64,304,305],{},"        sources: {},\n",[64,307,309],{"class":66,"line":308},42,[64,310,311],{},"        keywords: {},\n",[64,313,315],{"class":66,"line":314},43,[64,316,317],{},"        readCount: 0\n",[64,319,321],{"class":66,"line":320},44,[64,322,323],{},"      });\n",[64,325,327],{"class":66,"line":326},45,[64,328,329],{},"    }\n",[64,331,333],{"class":66,"line":332},46,[64,334,194],{},[64,336,338],{"class":66,"line":337},47,[64,339,340],{},"    const profile = this.userProfiles.get(userId);\n",[64,342,344],{"class":66,"line":343},48,[64,345,346],{},"    const features = this.extractFeatures(article);\n",[64,348,350],{"class":66,"line":349},49,[64,351,194],{},[64,353,355],{"class":66,"line":354},50,[64,356,357],{},"    \u002F\u002F Weight based on interaction type\n",[64,359,361],{"class":66,"line":360},51,[64,362,363],{},"    const weight = {\n",[64,365,367],{"class":66,"line":366},52,[64,368,369],{},"      'read': 1,\n",[64,371,373],{"class":66,"line":372},53,[64,374,375],{},"      'like': 2,\n",[64,377,379],{"class":66,"line":378},54,[64,380,381],{},"      'share': 3,\n",[64,383,385],{"class":66,"line":384},55,[64,386,387],{},"      'save': 2\n",[64,389,391],{"class":66,"line":390},56,[64,392,393],{},"    }[interaction] || 1;\n",[64,395,397],{"class":66,"line":396},57,[64,398,194],{},[64,400,402],{"class":66,"line":401},58,[64,403,404],{},"    \u002F\u002F Update category preferences\n",[64,406,408],{"class":66,"line":407},59,[64,409,410],{},"    profile.categories[features.category] = \n",[64,412,414],{"class":66,"line":413},60,[64,415,416],{},"      (profile.categories[features.category] || 0) + weight;\n",[64,418,420],{"class":66,"line":419},61,[64,421,194],{},[64,423,425],{"class":66,"line":424},62,[64,426,427],{},"    \u002F\u002F Update source preferences\n",[64,429,431],{"class":66,"line":430},63,[64,432,433],{},"    profile.sources[features.source] = \n",[64,435,437],{"class":66,"line":436},64,[64,438,439],{},"      (profile.sources[features.source] || 0) + weight;\n",[64,441,443],{"class":66,"line":442},65,[64,444,194],{},[64,446,448],{"class":66,"line":447},66,[64,449,450],{},"    \u002F\u002F Update keyword preferences\n",[64,452,454],{"class":66,"line":453},67,[64,455,456],{},"    features.keywords.forEach(keyword => {\n",[64,458,460],{"class":66,"line":459},68,[64,461,462],{},"      profile.keywords[keyword] = \n",[64,464,466],{"class":66,"line":465},69,[64,467,468],{},"        (profile.keywords[keyword] || 0) + weight;\n",[64,470,472],{"class":66,"line":471},70,[64,473,224],{},[64,475,477],{"class":66,"line":476},71,[64,478,194],{},[64,480,482],{"class":66,"line":481},72,[64,483,484],{},"    profile.readCount++;\n",[64,486,488],{"class":66,"line":487},73,[64,489,88],{},[64,491,493],{"class":66,"line":492},74,[64,494,94],{},[64,496,498],{"class":66,"line":497},75,[64,499,500],{},"  \u002F\u002F Calculate similarity score between article and user profile\n",[64,502,504],{"class":66,"line":503},76,[64,505,506],{},"  calculateScore(userId, article) {\n",[64,508,510],{"class":66,"line":509},77,[64,511,340],{},[64,513,515],{"class":66,"line":514},78,[64,516,517],{},"    if (!profile || profile.readCount === 0) return 0;\n",[64,519,521],{"class":66,"line":520},79,[64,522,194],{},[64,524,526],{"class":66,"line":525},80,[64,527,346],{},[64,529,531],{"class":66,"line":530},81,[64,532,533],{},"    let score = 0;\n",[64,535,537],{"class":66,"line":536},82,[64,538,194],{},[64,540,542],{"class":66,"line":541},83,[64,543,544],{},"    \u002F\u002F Category score (40% weight)\n",[64,546,548],{"class":66,"line":547},84,[64,549,550],{},"    const categoryScore = profile.categories[features.category] || 0;\n",[64,552,554],{"class":66,"line":553},85,[64,555,556],{},"    score += (categoryScore \u002F profile.readCount) * 0.4;\n",[64,558,560],{"class":66,"line":559},86,[64,561,194],{},[64,563,565],{"class":66,"line":564},87,[64,566,567],{},"    \u002F\u002F Source score (20% weight)\n",[64,569,571],{"class":66,"line":570},88,[64,572,573],{},"    const sourceScore = profile.sources[features.source] || 0;\n",[64,575,577],{"class":66,"line":576},89,[64,578,579],{},"    score += (sourceScore \u002F profile.readCount) * 0.2;\n",[64,581,583],{"class":66,"line":582},90,[64,584,194],{},[64,586,588],{"class":66,"line":587},91,[64,589,590],{},"    \u002F\u002F Keyword score (40% weight)\n",[64,592,594],{"class":66,"line":593},92,[64,595,596],{},"    const keywordScores = features.keywords.map(\n",[64,598,600],{"class":66,"line":599},93,[64,601,602],{},"      keyword => profile.keywords[keyword] || 0\n",[64,604,606],{"class":66,"line":605},94,[64,607,608],{},"    );\n",[64,610,612],{"class":66,"line":611},95,[64,613,614],{},"    const avgKeywordScore = keywordScores.reduce((a, b) => a + b, 0) \u002F \n",[64,616,618],{"class":66,"line":617},96,[64,619,620],{},"      (keywordScores.length || 1);\n",[64,622,624],{"class":66,"line":623},97,[64,625,626],{},"    score += (avgKeywordScore \u002F profile.readCount) * 0.4;\n",[64,628,630],{"class":66,"line":629},98,[64,631,194],{},[64,633,635],{"class":66,"line":634},99,[64,636,637],{},"    return score;\n",[64,639,641],{"class":66,"line":640},100,[64,642,88],{},[64,644,646],{"class":66,"line":645},101,[64,647,94],{},[64,649,651],{"class":66,"line":650},102,[64,652,653],{},"  \u002F\u002F Get personalized recommendations\n",[64,655,657],{"class":66,"line":656},103,[64,658,659],{},"  recommend(userId, articles, limit = 10) {\n",[64,661,663],{"class":66,"line":662},104,[64,664,665],{},"    const scoredArticles = articles.map(article => ({\n",[64,667,669],{"class":66,"line":668},105,[64,670,671],{},"      article,\n",[64,673,675],{"class":66,"line":674},106,[64,676,677],{},"      score: this.calculateScore(userId, article)\n",[64,679,681],{"class":66,"line":680},107,[64,682,683],{},"    }));\n",[64,685,687],{"class":66,"line":686},108,[64,688,194],{},[64,690,692],{"class":66,"line":691},109,[64,693,694],{},"    return scoredArticles\n",[64,696,698],{"class":66,"line":697},110,[64,699,700],{},"      .sort((a, b) => b.score - a.score)\n",[64,702,704],{"class":66,"line":703},111,[64,705,706],{},"      .slice(0, limit)\n",[64,708,710],{"class":66,"line":709},112,[64,711,712],{},"      .map(item => item.article);\n",[64,714,716],{"class":66,"line":715},113,[64,717,88],{},[64,719,721],{"class":66,"line":720},114,[64,722,723],{},"}\n",[64,725,727],{"class":66,"line":726},115,[64,728,730],{"emptyLinePlaceholder":729},true,"\n",[64,732,734],{"class":66,"line":733},116,[64,735,736],{},"\u002F\u002F Usage\n",[64,738,740],{"class":66,"line":739},117,[64,741,742],{},"const recommender = new ContentBasedRecommender();\n",[64,744,746],{"class":66,"line":745},118,[64,747,730],{"emptyLinePlaceholder":729},[64,749,751],{"class":66,"line":750},119,[64,752,753],{},"\u002F\u002F Track user interactions\n",[64,755,757],{"class":66,"line":756},120,[64,758,759],{},"recommender.updateUserProfile('user123', article, 'read');\n",[64,761,763],{"class":66,"line":762},121,[64,764,765],{},"recommender.updateUserProfile('user123', article, 'like');\n",[64,767,769],{"class":66,"line":768},122,[64,770,730],{"emptyLinePlaceholder":729},[64,772,774],{"class":66,"line":773},123,[64,775,776],{},"\u002F\u002F Get recommendations\n",[64,778,780],{"class":66,"line":779},124,[64,781,782],{},"const recommendations = recommender.recommend('user123', allArticles, 10);\n",[46,784,786],{"id":785},"_2-collaborative-filtering","2. Collaborative Filtering",[11,788,789],{},"Recommend based on similar users' preferences:",[54,791,793],{"className":56,"code":792,"language":58,"meta":59,"style":59},"class CollaborativeFilteringRecommender {\n  constructor() {\n    this.userArticleMatrix = new Map(); \u002F\u002F userId -> Set of article URLs\n    this.articleUserMatrix = new Map(); \u002F\u002F article URL -> Set of userIds\n  }\n  \n  \u002F\u002F Record user interaction with article\n  recordInteraction(userId, articleUrl) {\n    \u002F\u002F Update user-article matrix\n    if (!this.userArticleMatrix.has(userId)) {\n      this.userArticleMatrix.set(userId, new Set());\n    }\n    this.userArticleMatrix.get(userId).add(articleUrl);\n    \n    \u002F\u002F Update article-user matrix\n    if (!this.articleUserMatrix.has(articleUrl)) {\n      this.articleUserMatrix.set(articleUrl, new Set());\n    }\n    this.articleUserMatrix.get(articleUrl).add(userId);\n  }\n  \n  \u002F\u002F Calculate similarity between two users (Jaccard similarity)\n  calculateUserSimilarity(userId1, userId2) {\n    const articles1 = this.userArticleMatrix.get(userId1) || new Set();\n    const articles2 = this.userArticleMatrix.get(userId2) || new Set();\n    \n    if (articles1.size === 0 || articles2.size === 0) return 0;\n    \n    \u002F\u002F Intersection\n    const intersection = new Set(\n      [...articles1].filter(x => articles2.has(x))\n    );\n    \n    \u002F\u002F Union\n    const union = new Set([...articles1, ...articles2]);\n    \n    return intersection.size \u002F union.size;\n  }\n  \n  \u002F\u002F Find similar users\n  findSimilarUsers(userId, limit = 10) {\n    const similarities = [];\n    \n    for (const [otherUserId] of this.userArticleMatrix) {\n      if (otherUserId === userId) continue;\n      \n      const similarity = this.calculateUserSimilarity(userId, otherUserId);\n      if (similarity > 0) {\n        similarities.push({ userId: otherUserId, similarity });\n      }\n    }\n    \n    return similarities\n      .sort((a, b) => b.similarity - a.similarity)\n      .slice(0, limit);\n  }\n  \n  \u002F\u002F Get recommendations based on similar users\n  recommend(userId, allArticles, limit = 10) {\n    const userArticles = this.userArticleMatrix.get(userId) || new Set();\n    const similarUsers = this.findSimilarUsers(userId, 20);\n    \n    if (similarUsers.length === 0) {\n      \u002F\u002F No similar users, return popular articles\n      return this.getPopularArticles(allArticles, limit);\n    }\n    \n    \u002F\u002F Score articles based on similar users\n    const articleScores = new Map();\n    \n    similarUsers.forEach(({ userId: similarUserId, similarity }) => {\n      const articles = this.userArticleMatrix.get(similarUserId) || new Set();\n      \n      articles.forEach(articleUrl => {\n        \u002F\u002F Skip articles user has already seen\n        if (userArticles.has(articleUrl)) return;\n        \n        const currentScore = articleScores.get(articleUrl) || 0;\n        articleScores.set(articleUrl, currentScore + similarity);\n      });\n    });\n    \n    \u002F\u002F Get top scored articles\n    const recommendations = Array.from(articleScores.entries())\n      .sort((a, b) => b[1] - a[1])\n      .slice(0, limit)\n      .map(([url]) => url);\n    \n    \u002F\u002F Map URLs back to article objects\n    return allArticles.filter(article => \n      recommendations.includes(article.url)\n    );\n  }\n  \n  getPopularArticles(articles, limit) {\n    \u002F\u002F Return most read articles\n    const popularity = new Map();\n    \n    for (const [articleUrl, users] of this.articleUserMatrix) {\n      popularity.set(articleUrl, users.size);\n    }\n    \n    return articles\n      .sort((a, b) => {\n        const popA = popularity.get(a.url) || 0;\n        const popB = popularity.get(b.url) || 0;\n        return popB - popA;\n      })\n      .slice(0, limit);\n  }\n}\n\n\u002F\u002F Usage\nconst cfRecommender = new CollaborativeFilteringRecommender();\n\n\u002F\u002F Record interactions\ncfRecommender.recordInteraction('user1', 'article-url-1');\ncfRecommender.recordInteraction('user1', 'article-url-2');\ncfRecommender.recordInteraction('user2', 'article-url-1');\ncfRecommender.recordInteraction('user2', 'article-url-3');\n\n\u002F\u002F Get recommendations\nconst recommendations = cfRecommender.recommend('user1', allArticles, 10);\n",[61,794,795,800,804,809,814,818,822,827,832,837,842,847,851,856,860,865,870,875,879,884,888,892,897,902,907,912,916,921,925,930,935,940,944,948,953,958,962,967,971,975,980,985,990,994,999,1004,1009,1014,1019,1024,1029,1033,1037,1042,1047,1052,1056,1060,1065,1070,1075,1080,1084,1089,1094,1099,1103,1107,1112,1117,1121,1126,1131,1135,1140,1145,1150,1155,1160,1165,1169,1173,1177,1182,1187,1191,1195,1200,1204,1209,1214,1219,1223,1227,1231,1236,1241,1246,1250,1255,1260,1264,1268,1273,1278,1283,1288,1293,1298,1302,1306,1310,1314,1318,1323,1327,1332,1337,1342,1347,1352,1356,1360],{"__ignoreMap":59},[64,796,797],{"class":66,"line":67},[64,798,799],{},"class CollaborativeFilteringRecommender {\n",[64,801,802],{"class":66,"line":73},[64,803,76],{},[64,805,806],{"class":66,"line":79},[64,807,808],{},"    this.userArticleMatrix = new Map(); \u002F\u002F userId -> Set of article URLs\n",[64,810,811],{"class":66,"line":85},[64,812,813],{},"    this.articleUserMatrix = new Map(); \u002F\u002F article URL -> Set of userIds\n",[64,815,816],{"class":66,"line":91},[64,817,88],{},[64,819,820],{"class":66,"line":97},[64,821,94],{},[64,823,824],{"class":66,"line":103},[64,825,826],{},"  \u002F\u002F Record user interaction with article\n",[64,828,829],{"class":66,"line":109},[64,830,831],{},"  recordInteraction(userId, articleUrl) {\n",[64,833,834],{"class":66,"line":115},[64,835,836],{},"    \u002F\u002F Update user-article matrix\n",[64,838,839],{"class":66,"line":121},[64,840,841],{},"    if (!this.userArticleMatrix.has(userId)) {\n",[64,843,844],{"class":66,"line":127},[64,845,846],{},"      this.userArticleMatrix.set(userId, new Set());\n",[64,848,849],{"class":66,"line":133},[64,850,329],{},[64,852,853],{"class":66,"line":139},[64,854,855],{},"    this.userArticleMatrix.get(userId).add(articleUrl);\n",[64,857,858],{"class":66,"line":145},[64,859,194],{},[64,861,862],{"class":66,"line":150},[64,863,864],{},"    \u002F\u002F Update article-user matrix\n",[64,866,867],{"class":66,"line":155},[64,868,869],{},"    if (!this.articleUserMatrix.has(articleUrl)) {\n",[64,871,872],{"class":66,"line":161},[64,873,874],{},"      this.articleUserMatrix.set(articleUrl, new Set());\n",[64,876,877],{"class":66,"line":167},[64,878,329],{},[64,880,881],{"class":66,"line":173},[64,882,883],{},"    this.articleUserMatrix.get(articleUrl).add(userId);\n",[64,885,886],{"class":66,"line":179},[64,887,88],{},[64,889,890],{"class":66,"line":185},[64,891,94],{},[64,893,894],{"class":66,"line":191},[64,895,896],{},"  \u002F\u002F Calculate similarity between two users (Jaccard similarity)\n",[64,898,899],{"class":66,"line":197},[64,900,901],{},"  calculateUserSimilarity(userId1, userId2) {\n",[64,903,904],{"class":66,"line":203},[64,905,906],{},"    const articles1 = this.userArticleMatrix.get(userId1) || new Set();\n",[64,908,909],{"class":66,"line":209},[64,910,911],{},"    const articles2 = this.userArticleMatrix.get(userId2) || new Set();\n",[64,913,914],{"class":66,"line":215},[64,915,194],{},[64,917,918],{"class":66,"line":221},[64,919,920],{},"    if (articles1.size === 0 || articles2.size === 0) return 0;\n",[64,922,923],{"class":66,"line":227},[64,924,194],{},[64,926,927],{"class":66,"line":232},[64,928,929],{},"    \u002F\u002F Intersection\n",[64,931,932],{"class":66,"line":238},[64,933,934],{},"    const intersection = new Set(\n",[64,936,937],{"class":66,"line":244},[64,938,939],{},"      [...articles1].filter(x => articles2.has(x))\n",[64,941,942],{"class":66,"line":250},[64,943,608],{},[64,945,946],{"class":66,"line":256},[64,947,194],{},[64,949,950],{"class":66,"line":262},[64,951,952],{},"    \u002F\u002F Union\n",[64,954,955],{"class":66,"line":267},[64,956,957],{},"    const union = new Set([...articles1, ...articles2]);\n",[64,959,960],{"class":66,"line":272},[64,961,194],{},[64,963,964],{"class":66,"line":278},[64,965,966],{},"    return intersection.size \u002F union.size;\n",[64,968,969],{"class":66,"line":284},[64,970,88],{},[64,972,973],{"class":66,"line":290},[64,974,94],{},[64,976,977],{"class":66,"line":296},[64,978,979],{},"  \u002F\u002F Find similar users\n",[64,981,982],{"class":66,"line":302},[64,983,984],{},"  findSimilarUsers(userId, limit = 10) {\n",[64,986,987],{"class":66,"line":308},[64,988,989],{},"    const similarities = [];\n",[64,991,992],{"class":66,"line":314},[64,993,194],{},[64,995,996],{"class":66,"line":320},[64,997,998],{},"    for (const [otherUserId] of this.userArticleMatrix) {\n",[64,1000,1001],{"class":66,"line":326},[64,1002,1003],{},"      if (otherUserId === userId) continue;\n",[64,1005,1006],{"class":66,"line":332},[64,1007,1008],{},"      \n",[64,1010,1011],{"class":66,"line":337},[64,1012,1013],{},"      const similarity = this.calculateUserSimilarity(userId, otherUserId);\n",[64,1015,1016],{"class":66,"line":343},[64,1017,1018],{},"      if (similarity > 0) {\n",[64,1020,1021],{"class":66,"line":349},[64,1022,1023],{},"        similarities.push({ userId: otherUserId, similarity });\n",[64,1025,1026],{"class":66,"line":354},[64,1027,1028],{},"      }\n",[64,1030,1031],{"class":66,"line":360},[64,1032,329],{},[64,1034,1035],{"class":66,"line":366},[64,1036,194],{},[64,1038,1039],{"class":66,"line":372},[64,1040,1041],{},"    return similarities\n",[64,1043,1044],{"class":66,"line":378},[64,1045,1046],{},"      .sort((a, b) => b.similarity - a.similarity)\n",[64,1048,1049],{"class":66,"line":384},[64,1050,1051],{},"      .slice(0, limit);\n",[64,1053,1054],{"class":66,"line":390},[64,1055,88],{},[64,1057,1058],{"class":66,"line":396},[64,1059,94],{},[64,1061,1062],{"class":66,"line":401},[64,1063,1064],{},"  \u002F\u002F Get recommendations based on similar users\n",[64,1066,1067],{"class":66,"line":407},[64,1068,1069],{},"  recommend(userId, allArticles, limit = 10) {\n",[64,1071,1072],{"class":66,"line":413},[64,1073,1074],{},"    const userArticles = this.userArticleMatrix.get(userId) || new Set();\n",[64,1076,1077],{"class":66,"line":419},[64,1078,1079],{},"    const similarUsers = this.findSimilarUsers(userId, 20);\n",[64,1081,1082],{"class":66,"line":424},[64,1083,194],{},[64,1085,1086],{"class":66,"line":430},[64,1087,1088],{},"    if (similarUsers.length === 0) {\n",[64,1090,1091],{"class":66,"line":436},[64,1092,1093],{},"      \u002F\u002F No similar users, return popular articles\n",[64,1095,1096],{"class":66,"line":442},[64,1097,1098],{},"      return this.getPopularArticles(allArticles, limit);\n",[64,1100,1101],{"class":66,"line":447},[64,1102,329],{},[64,1104,1105],{"class":66,"line":453},[64,1106,194],{},[64,1108,1109],{"class":66,"line":459},[64,1110,1111],{},"    \u002F\u002F Score articles based on similar users\n",[64,1113,1114],{"class":66,"line":465},[64,1115,1116],{},"    const articleScores = new Map();\n",[64,1118,1119],{"class":66,"line":471},[64,1120,194],{},[64,1122,1123],{"class":66,"line":476},[64,1124,1125],{},"    similarUsers.forEach(({ userId: similarUserId, similarity }) => {\n",[64,1127,1128],{"class":66,"line":481},[64,1129,1130],{},"      const articles = this.userArticleMatrix.get(similarUserId) || new Set();\n",[64,1132,1133],{"class":66,"line":487},[64,1134,1008],{},[64,1136,1137],{"class":66,"line":492},[64,1138,1139],{},"      articles.forEach(articleUrl => {\n",[64,1141,1142],{"class":66,"line":497},[64,1143,1144],{},"        \u002F\u002F Skip articles user has already seen\n",[64,1146,1147],{"class":66,"line":503},[64,1148,1149],{},"        if (userArticles.has(articleUrl)) return;\n",[64,1151,1152],{"class":66,"line":509},[64,1153,1154],{},"        \n",[64,1156,1157],{"class":66,"line":514},[64,1158,1159],{},"        const currentScore = articleScores.get(articleUrl) || 0;\n",[64,1161,1162],{"class":66,"line":520},[64,1163,1164],{},"        articleScores.set(articleUrl, currentScore + similarity);\n",[64,1166,1167],{"class":66,"line":525},[64,1168,323],{},[64,1170,1171],{"class":66,"line":530},[64,1172,224],{},[64,1174,1175],{"class":66,"line":536},[64,1176,194],{},[64,1178,1179],{"class":66,"line":541},[64,1180,1181],{},"    \u002F\u002F Get top scored articles\n",[64,1183,1184],{"class":66,"line":547},[64,1185,1186],{},"    const recommendations = Array.from(articleScores.entries())\n",[64,1188,1189],{"class":66,"line":553},[64,1190,247],{},[64,1192,1193],{"class":66,"line":559},[64,1194,706],{},[64,1196,1197],{"class":66,"line":564},[64,1198,1199],{},"      .map(([url]) => url);\n",[64,1201,1202],{"class":66,"line":570},[64,1203,194],{},[64,1205,1206],{"class":66,"line":576},[64,1207,1208],{},"    \u002F\u002F Map URLs back to article objects\n",[64,1210,1211],{"class":66,"line":582},[64,1212,1213],{},"    return allArticles.filter(article => \n",[64,1215,1216],{"class":66,"line":587},[64,1217,1218],{},"      recommendations.includes(article.url)\n",[64,1220,1221],{"class":66,"line":593},[64,1222,608],{},[64,1224,1225],{"class":66,"line":599},[64,1226,88],{},[64,1228,1229],{"class":66,"line":605},[64,1230,94],{},[64,1232,1233],{"class":66,"line":611},[64,1234,1235],{},"  getPopularArticles(articles, limit) {\n",[64,1237,1238],{"class":66,"line":617},[64,1239,1240],{},"    \u002F\u002F Return most read articles\n",[64,1242,1243],{"class":66,"line":623},[64,1244,1245],{},"    const popularity = new Map();\n",[64,1247,1248],{"class":66,"line":629},[64,1249,194],{},[64,1251,1252],{"class":66,"line":634},[64,1253,1254],{},"    for (const [articleUrl, users] of this.articleUserMatrix) {\n",[64,1256,1257],{"class":66,"line":640},[64,1258,1259],{},"      popularity.set(articleUrl, users.size);\n",[64,1261,1262],{"class":66,"line":645},[64,1263,329],{},[64,1265,1266],{"class":66,"line":650},[64,1267,194],{},[64,1269,1270],{"class":66,"line":656},[64,1271,1272],{},"    return articles\n",[64,1274,1275],{"class":66,"line":662},[64,1276,1277],{},"      .sort((a, b) => {\n",[64,1279,1280],{"class":66,"line":668},[64,1281,1282],{},"        const popA = popularity.get(a.url) || 0;\n",[64,1284,1285],{"class":66,"line":674},[64,1286,1287],{},"        const popB = popularity.get(b.url) || 0;\n",[64,1289,1290],{"class":66,"line":680},[64,1291,1292],{},"        return popB - popA;\n",[64,1294,1295],{"class":66,"line":686},[64,1296,1297],{},"      })\n",[64,1299,1300],{"class":66,"line":691},[64,1301,1051],{},[64,1303,1304],{"class":66,"line":697},[64,1305,88],{},[64,1307,1308],{"class":66,"line":703},[64,1309,723],{},[64,1311,1312],{"class":66,"line":709},[64,1313,730],{"emptyLinePlaceholder":729},[64,1315,1316],{"class":66,"line":715},[64,1317,736],{},[64,1319,1320],{"class":66,"line":720},[64,1321,1322],{},"const cfRecommender = new CollaborativeFilteringRecommender();\n",[64,1324,1325],{"class":66,"line":726},[64,1326,730],{"emptyLinePlaceholder":729},[64,1328,1329],{"class":66,"line":733},[64,1330,1331],{},"\u002F\u002F Record interactions\n",[64,1333,1334],{"class":66,"line":739},[64,1335,1336],{},"cfRecommender.recordInteraction('user1', 'article-url-1');\n",[64,1338,1339],{"class":66,"line":745},[64,1340,1341],{},"cfRecommender.recordInteraction('user1', 'article-url-2');\n",[64,1343,1344],{"class":66,"line":750},[64,1345,1346],{},"cfRecommender.recordInteraction('user2', 'article-url-1');\n",[64,1348,1349],{"class":66,"line":756},[64,1350,1351],{},"cfRecommender.recordInteraction('user2', 'article-url-3');\n",[64,1353,1354],{"class":66,"line":762},[64,1355,730],{"emptyLinePlaceholder":729},[64,1357,1358],{"class":66,"line":768},[64,1359,776],{},[64,1361,1362],{"class":66,"line":773},[64,1363,1364],{},"const recommendations = cfRecommender.recommend('user1', allArticles, 10);\n",[46,1366,1368],{"id":1367},"_3-hybrid-approach","3. Hybrid Approach",[11,1370,1371],{},"Combine multiple recommendation strategies:",[54,1373,1375],{"className":56,"code":1374,"language":58,"meta":59,"style":59},"class HybridRecommender {\n  constructor() {\n    this.contentBased = new ContentBasedRecommender();\n    this.collaborative = new CollaborativeFilteringRecommender();\n  }\n  \n  recordInteraction(userId, article, interactionType) {\n    \u002F\u002F Update both recommenders\n    this.contentBased.updateUserProfile(userId, article, interactionType);\n    this.collaborative.recordInteraction(userId, article.url);\n  }\n  \n  recommend(userId, articles, limit = 10) {\n    \u002F\u002F Get recommendations from both systems\n    const contentRecs = this.contentBased.recommend(userId, articles, limit * 2);\n    const collabRecs = this.collaborative.recommend(userId, articles, limit * 2);\n    \n    \u002F\u002F Combine and deduplicate\n    const combined = new Map();\n    \n    \u002F\u002F Add content-based recommendations (weight: 0.6)\n    contentRecs.forEach((article, index) => {\n      const score = (contentRecs.length - index) * 0.6;\n      combined.set(article.url, { article, score });\n    });\n    \n    \u002F\u002F Add collaborative recommendations (weight: 0.4)\n    collabRecs.forEach((article, index) => {\n      const score = (collabRecs.length - index) * 0.4;\n      const existing = combined.get(article.url);\n      \n      if (existing) {\n        existing.score += score;\n      } else {\n        combined.set(article.url, { article, score });\n      }\n    });\n    \n    \u002F\u002F Sort by combined score and return top N\n    return Array.from(combined.values())\n      .sort((a, b) => b.score - a.score)\n      .slice(0, limit)\n      .map(item => item.article);\n  }\n}\n\n\u002F\u002F Usage\nconst recommender = new HybridRecommender();\n\n\u002F\u002F Track interactions\nrecommender.recordInteraction('user123', article, 'read');\n\n\u002F\u002F Get personalized feed\nconst personalizedFeed = recommender.recommend('user123', allArticles, 20);\n",[61,1376,1377,1382,1386,1391,1396,1400,1404,1409,1414,1419,1424,1428,1432,1436,1441,1446,1451,1455,1460,1465,1469,1474,1479,1484,1489,1493,1497,1502,1507,1512,1517,1521,1526,1531,1536,1541,1545,1549,1553,1558,1563,1567,1571,1575,1579,1583,1587,1591,1596,1600,1605,1610,1614,1619],{"__ignoreMap":59},[64,1378,1379],{"class":66,"line":67},[64,1380,1381],{},"class HybridRecommender {\n",[64,1383,1384],{"class":66,"line":73},[64,1385,76],{},[64,1387,1388],{"class":66,"line":79},[64,1389,1390],{},"    this.contentBased = new ContentBasedRecommender();\n",[64,1392,1393],{"class":66,"line":85},[64,1394,1395],{},"    this.collaborative = new CollaborativeFilteringRecommender();\n",[64,1397,1398],{"class":66,"line":91},[64,1399,88],{},[64,1401,1402],{"class":66,"line":97},[64,1403,94],{},[64,1405,1406],{"class":66,"line":103},[64,1407,1408],{},"  recordInteraction(userId, article, interactionType) {\n",[64,1410,1411],{"class":66,"line":109},[64,1412,1413],{},"    \u002F\u002F Update both recommenders\n",[64,1415,1416],{"class":66,"line":115},[64,1417,1418],{},"    this.contentBased.updateUserProfile(userId, article, interactionType);\n",[64,1420,1421],{"class":66,"line":121},[64,1422,1423],{},"    this.collaborative.recordInteraction(userId, article.url);\n",[64,1425,1426],{"class":66,"line":127},[64,1427,88],{},[64,1429,1430],{"class":66,"line":133},[64,1431,94],{},[64,1433,1434],{"class":66,"line":139},[64,1435,659],{},[64,1437,1438],{"class":66,"line":145},[64,1439,1440],{},"    \u002F\u002F Get recommendations from both systems\n",[64,1442,1443],{"class":66,"line":150},[64,1444,1445],{},"    const contentRecs = this.contentBased.recommend(userId, articles, limit * 2);\n",[64,1447,1448],{"class":66,"line":155},[64,1449,1450],{},"    const collabRecs = this.collaborative.recommend(userId, articles, limit * 2);\n",[64,1452,1453],{"class":66,"line":161},[64,1454,194],{},[64,1456,1457],{"class":66,"line":167},[64,1458,1459],{},"    \u002F\u002F Combine and deduplicate\n",[64,1461,1462],{"class":66,"line":173},[64,1463,1464],{},"    const combined = new Map();\n",[64,1466,1467],{"class":66,"line":179},[64,1468,194],{},[64,1470,1471],{"class":66,"line":185},[64,1472,1473],{},"    \u002F\u002F Add content-based recommendations (weight: 0.6)\n",[64,1475,1476],{"class":66,"line":191},[64,1477,1478],{},"    contentRecs.forEach((article, index) => {\n",[64,1480,1481],{"class":66,"line":197},[64,1482,1483],{},"      const score = (contentRecs.length - index) * 0.6;\n",[64,1485,1486],{"class":66,"line":203},[64,1487,1488],{},"      combined.set(article.url, { article, score });\n",[64,1490,1491],{"class":66,"line":209},[64,1492,224],{},[64,1494,1495],{"class":66,"line":215},[64,1496,194],{},[64,1498,1499],{"class":66,"line":221},[64,1500,1501],{},"    \u002F\u002F Add collaborative recommendations (weight: 0.4)\n",[64,1503,1504],{"class":66,"line":227},[64,1505,1506],{},"    collabRecs.forEach((article, index) => {\n",[64,1508,1509],{"class":66,"line":232},[64,1510,1511],{},"      const score = (collabRecs.length - index) * 0.4;\n",[64,1513,1514],{"class":66,"line":238},[64,1515,1516],{},"      const existing = combined.get(article.url);\n",[64,1518,1519],{"class":66,"line":244},[64,1520,1008],{},[64,1522,1523],{"class":66,"line":250},[64,1524,1525],{},"      if (existing) {\n",[64,1527,1528],{"class":66,"line":256},[64,1529,1530],{},"        existing.score += score;\n",[64,1532,1533],{"class":66,"line":262},[64,1534,1535],{},"      } else {\n",[64,1537,1538],{"class":66,"line":267},[64,1539,1540],{},"        combined.set(article.url, { article, score });\n",[64,1542,1543],{"class":66,"line":272},[64,1544,1028],{},[64,1546,1547],{"class":66,"line":278},[64,1548,224],{},[64,1550,1551],{"class":66,"line":284},[64,1552,194],{},[64,1554,1555],{"class":66,"line":290},[64,1556,1557],{},"    \u002F\u002F Sort by combined score and return top N\n",[64,1559,1560],{"class":66,"line":296},[64,1561,1562],{},"    return Array.from(combined.values())\n",[64,1564,1565],{"class":66,"line":302},[64,1566,700],{},[64,1568,1569],{"class":66,"line":308},[64,1570,706],{},[64,1572,1573],{"class":66,"line":314},[64,1574,712],{},[64,1576,1577],{"class":66,"line":320},[64,1578,88],{},[64,1580,1581],{"class":66,"line":326},[64,1582,723],{},[64,1584,1585],{"class":66,"line":332},[64,1586,730],{"emptyLinePlaceholder":729},[64,1588,1589],{"class":66,"line":337},[64,1590,736],{},[64,1592,1593],{"class":66,"line":343},[64,1594,1595],{},"const recommender = new HybridRecommender();\n",[64,1597,1598],{"class":66,"line":349},[64,1599,730],{"emptyLinePlaceholder":729},[64,1601,1602],{"class":66,"line":354},[64,1603,1604],{},"\u002F\u002F Track interactions\n",[64,1606,1607],{"class":66,"line":360},[64,1608,1609],{},"recommender.recordInteraction('user123', article, 'read');\n",[64,1611,1612],{"class":66,"line":366},[64,1613,730],{"emptyLinePlaceholder":729},[64,1615,1616],{"class":66,"line":372},[64,1617,1618],{},"\u002F\u002F Get personalized feed\n",[64,1620,1621],{"class":66,"line":378},[64,1622,1623],{},"const personalizedFeed = recommender.recommend('user123', allArticles, 20);\n",[15,1625,1627],{"id":1626},"building-a-complete-personalization-system","Building a Complete Personalization System",[46,1629,1631],{"id":1630},"backend-service","Backend Service",[54,1633,1635],{"className":56,"code":1634,"language":58,"meta":59,"style":59},"\u002F\u002F personalization-service.js\nconst express = require('express');\nconst app = express();\nconst HybridRecommender = require('.\u002Frecommender');\nconst newsApi = require('.\u002Fnews-api');\n\nconst recommender = new HybridRecommender();\n\n\u002F\u002F Track article view\napp.post('\u002Fapi\u002Ftrack\u002Fview', async (req, res) => {\n  const { userId, articleUrl } = req.body;\n  \n  try {\n    const article = await getArticleByUrl(articleUrl);\n    recommender.recordInteraction(userId, article, 'read');\n    res.json({ success: true });\n  } catch (error) {\n    res.status(500).json({ error: error.message });\n  }\n});\n\n\u002F\u002F Track article interaction\napp.post('\u002Fapi\u002Ftrack\u002Finteraction', async (req, res) => {\n  const { userId, articleUrl, type } = req.body;\n  \n  try {\n    const article = await getArticleByUrl(articleUrl);\n    recommender.recordInteraction(userId, article, type);\n    res.json({ success: true });\n  } catch (error) {\n    res.status(500).json({ error: error.message });\n  }\n});\n\n\u002F\u002F Get personalized feed\napp.get('\u002Fapi\u002Ffeed\u002Fpersonalized', async (req, res) => {\n  const { userId, limit = 20 } = req.query;\n  \n  try {\n    \u002F\u002F Fetch latest articles\n    const articles = await newsApi.getHeadlines('us');\n    \n    \u002F\u002F Get personalized recommendations\n    const recommendations = recommender.recommend(\n      userId, \n      articles.articles, \n      parseInt(limit)\n    );\n    \n    res.json({ articles: recommendations });\n  } catch (error) {\n    res.status(500).json({ error: error.message });\n  }\n});\n\napp.listen(3000, () => {\n  console.log('Personalization service running on port 3000');\n});\n",[61,1636,1637,1642,1647,1652,1657,1662,1666,1670,1674,1679,1684,1689,1693,1698,1703,1708,1713,1718,1723,1727,1732,1736,1741,1746,1751,1755,1759,1763,1768,1772,1776,1780,1784,1788,1792,1796,1801,1806,1810,1814,1819,1824,1828,1833,1838,1843,1848,1853,1857,1861,1866,1870,1874,1878,1882,1886,1891,1896],{"__ignoreMap":59},[64,1638,1639],{"class":66,"line":67},[64,1640,1641],{},"\u002F\u002F personalization-service.js\n",[64,1643,1644],{"class":66,"line":73},[64,1645,1646],{},"const express = require('express');\n",[64,1648,1649],{"class":66,"line":79},[64,1650,1651],{},"const app = express();\n",[64,1653,1654],{"class":66,"line":85},[64,1655,1656],{},"const HybridRecommender = require('.\u002Frecommender');\n",[64,1658,1659],{"class":66,"line":91},[64,1660,1661],{},"const newsApi = require('.\u002Fnews-api');\n",[64,1663,1664],{"class":66,"line":97},[64,1665,730],{"emptyLinePlaceholder":729},[64,1667,1668],{"class":66,"line":103},[64,1669,1595],{},[64,1671,1672],{"class":66,"line":109},[64,1673,730],{"emptyLinePlaceholder":729},[64,1675,1676],{"class":66,"line":115},[64,1677,1678],{},"\u002F\u002F Track article view\n",[64,1680,1681],{"class":66,"line":121},[64,1682,1683],{},"app.post('\u002Fapi\u002Ftrack\u002Fview', async (req, res) => {\n",[64,1685,1686],{"class":66,"line":127},[64,1687,1688],{},"  const { userId, articleUrl } = req.body;\n",[64,1690,1691],{"class":66,"line":133},[64,1692,94],{},[64,1694,1695],{"class":66,"line":139},[64,1696,1697],{},"  try {\n",[64,1699,1700],{"class":66,"line":145},[64,1701,1702],{},"    const article = await getArticleByUrl(articleUrl);\n",[64,1704,1705],{"class":66,"line":150},[64,1706,1707],{},"    recommender.recordInteraction(userId, article, 'read');\n",[64,1709,1710],{"class":66,"line":155},[64,1711,1712],{},"    res.json({ success: true });\n",[64,1714,1715],{"class":66,"line":161},[64,1716,1717],{},"  } catch (error) {\n",[64,1719,1720],{"class":66,"line":167},[64,1721,1722],{},"    res.status(500).json({ error: error.message });\n",[64,1724,1725],{"class":66,"line":173},[64,1726,88],{},[64,1728,1729],{"class":66,"line":179},[64,1730,1731],{},"});\n",[64,1733,1734],{"class":66,"line":185},[64,1735,730],{"emptyLinePlaceholder":729},[64,1737,1738],{"class":66,"line":191},[64,1739,1740],{},"\u002F\u002F Track article interaction\n",[64,1742,1743],{"class":66,"line":197},[64,1744,1745],{},"app.post('\u002Fapi\u002Ftrack\u002Finteraction', async (req, res) => {\n",[64,1747,1748],{"class":66,"line":203},[64,1749,1750],{},"  const { userId, articleUrl, type } = req.body;\n",[64,1752,1753],{"class":66,"line":209},[64,1754,94],{},[64,1756,1757],{"class":66,"line":215},[64,1758,1697],{},[64,1760,1761],{"class":66,"line":221},[64,1762,1702],{},[64,1764,1765],{"class":66,"line":227},[64,1766,1767],{},"    recommender.recordInteraction(userId, article, type);\n",[64,1769,1770],{"class":66,"line":232},[64,1771,1712],{},[64,1773,1774],{"class":66,"line":238},[64,1775,1717],{},[64,1777,1778],{"class":66,"line":244},[64,1779,1722],{},[64,1781,1782],{"class":66,"line":250},[64,1783,88],{},[64,1785,1786],{"class":66,"line":256},[64,1787,1731],{},[64,1789,1790],{"class":66,"line":262},[64,1791,730],{"emptyLinePlaceholder":729},[64,1793,1794],{"class":66,"line":267},[64,1795,1618],{},[64,1797,1798],{"class":66,"line":272},[64,1799,1800],{},"app.get('\u002Fapi\u002Ffeed\u002Fpersonalized', async (req, res) => {\n",[64,1802,1803],{"class":66,"line":278},[64,1804,1805],{},"  const { userId, limit = 20 } = req.query;\n",[64,1807,1808],{"class":66,"line":284},[64,1809,94],{},[64,1811,1812],{"class":66,"line":290},[64,1813,1697],{},[64,1815,1816],{"class":66,"line":296},[64,1817,1818],{},"    \u002F\u002F Fetch latest articles\n",[64,1820,1821],{"class":66,"line":302},[64,1822,1823],{},"    const articles = await newsApi.getHeadlines('us');\n",[64,1825,1826],{"class":66,"line":308},[64,1827,194],{},[64,1829,1830],{"class":66,"line":314},[64,1831,1832],{},"    \u002F\u002F Get personalized recommendations\n",[64,1834,1835],{"class":66,"line":320},[64,1836,1837],{},"    const recommendations = recommender.recommend(\n",[64,1839,1840],{"class":66,"line":326},[64,1841,1842],{},"      userId, \n",[64,1844,1845],{"class":66,"line":332},[64,1846,1847],{},"      articles.articles, \n",[64,1849,1850],{"class":66,"line":337},[64,1851,1852],{},"      parseInt(limit)\n",[64,1854,1855],{"class":66,"line":343},[64,1856,608],{},[64,1858,1859],{"class":66,"line":349},[64,1860,194],{},[64,1862,1863],{"class":66,"line":354},[64,1864,1865],{},"    res.json({ articles: recommendations });\n",[64,1867,1868],{"class":66,"line":360},[64,1869,1717],{},[64,1871,1872],{"class":66,"line":366},[64,1873,1722],{},[64,1875,1876],{"class":66,"line":372},[64,1877,88],{},[64,1879,1880],{"class":66,"line":378},[64,1881,1731],{},[64,1883,1884],{"class":66,"line":384},[64,1885,730],{"emptyLinePlaceholder":729},[64,1887,1888],{"class":66,"line":390},[64,1889,1890],{},"app.listen(3000, () => {\n",[64,1892,1893],{"class":66,"line":396},[64,1894,1895],{},"  console.log('Personalization service running on port 3000');\n",[64,1897,1898],{"class":66,"line":401},[64,1899,1731],{},[46,1901,1903],{"id":1902},"frontend-integration","Frontend Integration",[54,1905,1907],{"className":56,"code":1906,"language":58,"meta":59,"style":59},"\u002F\u002F PersonalizedFeed.jsx\nimport React, { useState, useEffect } from 'react';\nimport ArticleCard from '.\u002FArticleCard';\n\nfunction PersonalizedFeed({ userId }) {\n  const [articles, setArticles] = useState([]);\n  const [loading, setLoading] = useState(true);\n  \n  useEffect(() => {\n    fetchPersonalizedFeed();\n  }, [userId]);\n  \n  async function fetchPersonalizedFeed() {\n    try {\n      const response = await fetch(\n        `\u002Fapi\u002Ffeed\u002Fpersonalized?userId=${userId}&limit=20`\n      );\n      const data = await response.json();\n      setArticles(data.articles);\n    } catch (error) {\n      console.error('Error fetching feed:', error);\n    } finally {\n      setLoading(false);\n    }\n  }\n  \n  async function trackView(article) {\n    await fetch('\u002Fapi\u002Ftrack\u002Fview', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify({\n        userId,\n        articleUrl: article.url\n      })\n    });\n  }\n  \n  async function trackInteraction(article, type) {\n    await fetch('\u002Fapi\u002Ftrack\u002Finteraction', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify({\n        userId,\n        articleUrl: article.url,\n        type\n      })\n    });\n  }\n  \n  function handleArticleClick(article) {\n    trackView(article);\n    window.open(article.url, '_blank');\n  }\n  \n  function handleLike(article) {\n    trackInteraction(article, 'like');\n  }\n  \n  function handleShare(article) {\n    trackInteraction(article, 'share');\n  }\n  \n  if (loading) return \u003Cdiv>Loading your personalized feed...\u003C\u002Fdiv>;\n  \n  return (\n    \u003Cdiv className=\"personalized-feed\">\n      \u003Ch2>Your Personalized News Feed\u003C\u002Fh2>\n      \u003Cdiv className=\"articles\">\n        {articles.map(article => (\n          \u003CArticleCard\n            key={article.url}\n            article={article}\n            onClick={() => handleArticleClick(article)}\n            onLike={() => handleLike(article)}\n            onShare={() => handleShare(article)}\n          \u002F>\n        ))}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  );\n}\n\nexport default PersonalizedFeed;\n",[61,1908,1909,1914,1919,1924,1928,1933,1938,1943,1947,1952,1957,1962,1966,1971,1976,1981,1986,1991,1996,2001,2006,2011,2016,2021,2025,2029,2033,2038,2043,2048,2053,2058,2063,2068,2072,2076,2080,2084,2089,2094,2098,2102,2106,2110,2115,2120,2124,2128,2132,2136,2141,2146,2151,2155,2159,2164,2169,2173,2177,2182,2187,2191,2195,2200,2204,2209,2214,2219,2224,2229,2234,2239,2244,2249,2254,2259,2264,2269,2274,2279,2284,2288,2292],{"__ignoreMap":59},[64,1910,1911],{"class":66,"line":67},[64,1912,1913],{},"\u002F\u002F PersonalizedFeed.jsx\n",[64,1915,1916],{"class":66,"line":73},[64,1917,1918],{},"import React, { useState, useEffect } from 'react';\n",[64,1920,1921],{"class":66,"line":79},[64,1922,1923],{},"import ArticleCard from '.\u002FArticleCard';\n",[64,1925,1926],{"class":66,"line":85},[64,1927,730],{"emptyLinePlaceholder":729},[64,1929,1930],{"class":66,"line":91},[64,1931,1932],{},"function PersonalizedFeed({ userId }) {\n",[64,1934,1935],{"class":66,"line":97},[64,1936,1937],{},"  const [articles, setArticles] = useState([]);\n",[64,1939,1940],{"class":66,"line":103},[64,1941,1942],{},"  const [loading, setLoading] = useState(true);\n",[64,1944,1945],{"class":66,"line":109},[64,1946,94],{},[64,1948,1949],{"class":66,"line":115},[64,1950,1951],{},"  useEffect(() => {\n",[64,1953,1954],{"class":66,"line":121},[64,1955,1956],{},"    fetchPersonalizedFeed();\n",[64,1958,1959],{"class":66,"line":127},[64,1960,1961],{},"  }, [userId]);\n",[64,1963,1964],{"class":66,"line":133},[64,1965,94],{},[64,1967,1968],{"class":66,"line":139},[64,1969,1970],{},"  async function fetchPersonalizedFeed() {\n",[64,1972,1973],{"class":66,"line":145},[64,1974,1975],{},"    try {\n",[64,1977,1978],{"class":66,"line":150},[64,1979,1980],{},"      const response = await fetch(\n",[64,1982,1983],{"class":66,"line":155},[64,1984,1985],{},"        `\u002Fapi\u002Ffeed\u002Fpersonalized?userId=${userId}&limit=20`\n",[64,1987,1988],{"class":66,"line":161},[64,1989,1990],{},"      );\n",[64,1992,1993],{"class":66,"line":167},[64,1994,1995],{},"      const data = await response.json();\n",[64,1997,1998],{"class":66,"line":173},[64,1999,2000],{},"      setArticles(data.articles);\n",[64,2002,2003],{"class":66,"line":179},[64,2004,2005],{},"    } catch (error) {\n",[64,2007,2008],{"class":66,"line":185},[64,2009,2010],{},"      console.error('Error fetching feed:', error);\n",[64,2012,2013],{"class":66,"line":191},[64,2014,2015],{},"    } finally {\n",[64,2017,2018],{"class":66,"line":197},[64,2019,2020],{},"      setLoading(false);\n",[64,2022,2023],{"class":66,"line":203},[64,2024,329],{},[64,2026,2027],{"class":66,"line":209},[64,2028,88],{},[64,2030,2031],{"class":66,"line":215},[64,2032,94],{},[64,2034,2035],{"class":66,"line":221},[64,2036,2037],{},"  async function trackView(article) {\n",[64,2039,2040],{"class":66,"line":227},[64,2041,2042],{},"    await fetch('\u002Fapi\u002Ftrack\u002Fview', {\n",[64,2044,2045],{"class":66,"line":232},[64,2046,2047],{},"      method: 'POST',\n",[64,2049,2050],{"class":66,"line":238},[64,2051,2052],{},"      headers: { 'Content-Type': 'application\u002Fjson' },\n",[64,2054,2055],{"class":66,"line":244},[64,2056,2057],{},"      body: JSON.stringify({\n",[64,2059,2060],{"class":66,"line":250},[64,2061,2062],{},"        userId,\n",[64,2064,2065],{"class":66,"line":256},[64,2066,2067],{},"        articleUrl: article.url\n",[64,2069,2070],{"class":66,"line":262},[64,2071,1297],{},[64,2073,2074],{"class":66,"line":267},[64,2075,224],{},[64,2077,2078],{"class":66,"line":272},[64,2079,88],{},[64,2081,2082],{"class":66,"line":278},[64,2083,94],{},[64,2085,2086],{"class":66,"line":284},[64,2087,2088],{},"  async function trackInteraction(article, type) {\n",[64,2090,2091],{"class":66,"line":290},[64,2092,2093],{},"    await fetch('\u002Fapi\u002Ftrack\u002Finteraction', {\n",[64,2095,2096],{"class":66,"line":296},[64,2097,2047],{},[64,2099,2100],{"class":66,"line":302},[64,2101,2052],{},[64,2103,2104],{"class":66,"line":308},[64,2105,2057],{},[64,2107,2108],{"class":66,"line":314},[64,2109,2062],{},[64,2111,2112],{"class":66,"line":320},[64,2113,2114],{},"        articleUrl: article.url,\n",[64,2116,2117],{"class":66,"line":326},[64,2118,2119],{},"        type\n",[64,2121,2122],{"class":66,"line":332},[64,2123,1297],{},[64,2125,2126],{"class":66,"line":337},[64,2127,224],{},[64,2129,2130],{"class":66,"line":343},[64,2131,88],{},[64,2133,2134],{"class":66,"line":349},[64,2135,94],{},[64,2137,2138],{"class":66,"line":354},[64,2139,2140],{},"  function handleArticleClick(article) {\n",[64,2142,2143],{"class":66,"line":360},[64,2144,2145],{},"    trackView(article);\n",[64,2147,2148],{"class":66,"line":366},[64,2149,2150],{},"    window.open(article.url, '_blank');\n",[64,2152,2153],{"class":66,"line":372},[64,2154,88],{},[64,2156,2157],{"class":66,"line":378},[64,2158,94],{},[64,2160,2161],{"class":66,"line":384},[64,2162,2163],{},"  function handleLike(article) {\n",[64,2165,2166],{"class":66,"line":390},[64,2167,2168],{},"    trackInteraction(article, 'like');\n",[64,2170,2171],{"class":66,"line":396},[64,2172,88],{},[64,2174,2175],{"class":66,"line":401},[64,2176,94],{},[64,2178,2179],{"class":66,"line":407},[64,2180,2181],{},"  function handleShare(article) {\n",[64,2183,2184],{"class":66,"line":413},[64,2185,2186],{},"    trackInteraction(article, 'share');\n",[64,2188,2189],{"class":66,"line":419},[64,2190,88],{},[64,2192,2193],{"class":66,"line":424},[64,2194,94],{},[64,2196,2197],{"class":66,"line":430},[64,2198,2199],{},"  if (loading) return \u003Cdiv>Loading your personalized feed...\u003C\u002Fdiv>;\n",[64,2201,2202],{"class":66,"line":436},[64,2203,94],{},[64,2205,2206],{"class":66,"line":442},[64,2207,2208],{},"  return (\n",[64,2210,2211],{"class":66,"line":447},[64,2212,2213],{},"    \u003Cdiv className=\"personalized-feed\">\n",[64,2215,2216],{"class":66,"line":453},[64,2217,2218],{},"      \u003Ch2>Your Personalized News Feed\u003C\u002Fh2>\n",[64,2220,2221],{"class":66,"line":459},[64,2222,2223],{},"      \u003Cdiv className=\"articles\">\n",[64,2225,2226],{"class":66,"line":465},[64,2227,2228],{},"        {articles.map(article => (\n",[64,2230,2231],{"class":66,"line":471},[64,2232,2233],{},"          \u003CArticleCard\n",[64,2235,2236],{"class":66,"line":476},[64,2237,2238],{},"            key={article.url}\n",[64,2240,2241],{"class":66,"line":481},[64,2242,2243],{},"            article={article}\n",[64,2245,2246],{"class":66,"line":487},[64,2247,2248],{},"            onClick={() => handleArticleClick(article)}\n",[64,2250,2251],{"class":66,"line":492},[64,2252,2253],{},"            onLike={() => handleLike(article)}\n",[64,2255,2256],{"class":66,"line":497},[64,2257,2258],{},"            onShare={() => handleShare(article)}\n",[64,2260,2261],{"class":66,"line":503},[64,2262,2263],{},"          \u002F>\n",[64,2265,2266],{"class":66,"line":509},[64,2267,2268],{},"        ))}\n",[64,2270,2271],{"class":66,"line":514},[64,2272,2273],{},"      \u003C\u002Fdiv>\n",[64,2275,2276],{"class":66,"line":520},[64,2277,2278],{},"    \u003C\u002Fdiv>\n",[64,2280,2281],{"class":66,"line":525},[64,2282,2283],{},"  );\n",[64,2285,2286],{"class":66,"line":530},[64,2287,723],{},[64,2289,2290],{"class":66,"line":536},[64,2291,730],{"emptyLinePlaceholder":729},[64,2293,2294],{"class":66,"line":541},[64,2295,2296],{},"export default PersonalizedFeed;\n",[15,2298,2300],{"id":2299},"advanced-features","Advanced Features",[46,2302,2304],{"id":2303},"_1-diversity-in-recommendations","1. Diversity in Recommendations",[11,2306,2307],{},"Avoid filter bubbles by ensuring diversity:",[54,2309,2311],{"className":56,"code":2310,"language":58,"meta":59,"style":59},"function diversifyRecommendations(articles, limit) {\n  const diverse = [];\n  const usedCategories = new Set();\n  const usedSources = new Set();\n  \n  for (const article of articles) {\n    if (diverse.length >= limit) break;\n    \n    \u002F\u002F Ensure category diversity\n    if (usedCategories.size \u003C 3 || !usedCategories.has(article.category)) {\n      diverse.push(article);\n      usedCategories.add(article.category);\n      usedSources.add(article.source.name);\n    }\n  }\n  \n  \u002F\u002F Fill remaining slots\n  for (const article of articles) {\n    if (diverse.length >= limit) break;\n    if (!diverse.includes(article)) {\n      diverse.push(article);\n    }\n  }\n  \n  return diverse;\n}\n",[61,2312,2313,2318,2323,2328,2333,2337,2342,2347,2351,2356,2361,2366,2371,2376,2380,2384,2388,2393,2397,2401,2406,2410,2414,2418,2422,2427],{"__ignoreMap":59},[64,2314,2315],{"class":66,"line":67},[64,2316,2317],{},"function diversifyRecommendations(articles, limit) {\n",[64,2319,2320],{"class":66,"line":73},[64,2321,2322],{},"  const diverse = [];\n",[64,2324,2325],{"class":66,"line":79},[64,2326,2327],{},"  const usedCategories = new Set();\n",[64,2329,2330],{"class":66,"line":85},[64,2331,2332],{},"  const usedSources = new Set();\n",[64,2334,2335],{"class":66,"line":91},[64,2336,94],{},[64,2338,2339],{"class":66,"line":97},[64,2340,2341],{},"  for (const article of articles) {\n",[64,2343,2344],{"class":66,"line":103},[64,2345,2346],{},"    if (diverse.length >= limit) break;\n",[64,2348,2349],{"class":66,"line":109},[64,2350,194],{},[64,2352,2353],{"class":66,"line":115},[64,2354,2355],{},"    \u002F\u002F Ensure category diversity\n",[64,2357,2358],{"class":66,"line":121},[64,2359,2360],{},"    if (usedCategories.size \u003C 3 || !usedCategories.has(article.category)) {\n",[64,2362,2363],{"class":66,"line":127},[64,2364,2365],{},"      diverse.push(article);\n",[64,2367,2368],{"class":66,"line":133},[64,2369,2370],{},"      usedCategories.add(article.category);\n",[64,2372,2373],{"class":66,"line":139},[64,2374,2375],{},"      usedSources.add(article.source.name);\n",[64,2377,2378],{"class":66,"line":145},[64,2379,329],{},[64,2381,2382],{"class":66,"line":150},[64,2383,88],{},[64,2385,2386],{"class":66,"line":155},[64,2387,94],{},[64,2389,2390],{"class":66,"line":161},[64,2391,2392],{},"  \u002F\u002F Fill remaining slots\n",[64,2394,2395],{"class":66,"line":167},[64,2396,2341],{},[64,2398,2399],{"class":66,"line":173},[64,2400,2346],{},[64,2402,2403],{"class":66,"line":179},[64,2404,2405],{},"    if (!diverse.includes(article)) {\n",[64,2407,2408],{"class":66,"line":185},[64,2409,2365],{},[64,2411,2412],{"class":66,"line":191},[64,2413,329],{},[64,2415,2416],{"class":66,"line":197},[64,2417,88],{},[64,2419,2420],{"class":66,"line":203},[64,2421,94],{},[64,2423,2424],{"class":66,"line":209},[64,2425,2426],{},"  return diverse;\n",[64,2428,2429],{"class":66,"line":215},[64,2430,723],{},[46,2432,2434],{"id":2433},"_2-time-decay","2. Time Decay",[11,2436,2437],{},"Give more weight to recent interactions:",[54,2439,2441],{"className":56,"code":2440,"language":58,"meta":59,"style":59},"function applyTimeDecay(score, timestamp, decayRate = 0.1) {\n  const daysSince = (Date.now() - timestamp) \u002F (1000 * 60 * 60 * 24);\n  return score * Math.exp(-decayRate * daysSince);\n}\n",[61,2442,2443,2448,2453,2458],{"__ignoreMap":59},[64,2444,2445],{"class":66,"line":67},[64,2446,2447],{},"function applyTimeDecay(score, timestamp, decayRate = 0.1) {\n",[64,2449,2450],{"class":66,"line":73},[64,2451,2452],{},"  const daysSince = (Date.now() - timestamp) \u002F (1000 * 60 * 60 * 24);\n",[64,2454,2455],{"class":66,"line":79},[64,2456,2457],{},"  return score * Math.exp(-decayRate * daysSince);\n",[64,2459,2460],{"class":66,"line":85},[64,2461,723],{},[46,2463,2465],{"id":2464},"_3-cold-start-problem","3. Cold Start Problem",[11,2467,2468],{},"Handle new users with no history:",[54,2470,2472],{"className":56,"code":2471,"language":58,"meta":59,"style":59},"function handleColdStart(userId, articles) {\n  \u002F\u002F Show popular articles from diverse categories\n  const categories = ['technology', 'business', 'sports', 'entertainment'];\n  const recommendations = [];\n  \n  categories.forEach(category => {\n    const categoryArticles = articles\n      .filter(a => a.category === category)\n      .slice(0, 3);\n    recommendations.push(...categoryArticles);\n  });\n  \n  return recommendations;\n}\n",[61,2473,2474,2479,2484,2489,2494,2498,2503,2508,2513,2518,2523,2528,2532,2537],{"__ignoreMap":59},[64,2475,2476],{"class":66,"line":67},[64,2477,2478],{},"function handleColdStart(userId, articles) {\n",[64,2480,2481],{"class":66,"line":73},[64,2482,2483],{},"  \u002F\u002F Show popular articles from diverse categories\n",[64,2485,2486],{"class":66,"line":79},[64,2487,2488],{},"  const categories = ['technology', 'business', 'sports', 'entertainment'];\n",[64,2490,2491],{"class":66,"line":85},[64,2492,2493],{},"  const recommendations = [];\n",[64,2495,2496],{"class":66,"line":91},[64,2497,94],{},[64,2499,2500],{"class":66,"line":97},[64,2501,2502],{},"  categories.forEach(category => {\n",[64,2504,2505],{"class":66,"line":103},[64,2506,2507],{},"    const categoryArticles = articles\n",[64,2509,2510],{"class":66,"line":109},[64,2511,2512],{},"      .filter(a => a.category === category)\n",[64,2514,2515],{"class":66,"line":115},[64,2516,2517],{},"      .slice(0, 3);\n",[64,2519,2520],{"class":66,"line":121},[64,2521,2522],{},"    recommendations.push(...categoryArticles);\n",[64,2524,2525],{"class":66,"line":127},[64,2526,2527],{},"  });\n",[64,2529,2530],{"class":66,"line":133},[64,2531,94],{},[64,2533,2534],{"class":66,"line":139},[64,2535,2536],{},"  return recommendations;\n",[64,2538,2539],{"class":66,"line":145},[64,2540,723],{},[15,2542,2544],{"id":2543},"best-practices","Best Practices",[2546,2547,2548,2555,2561,2567,2573,2579,2585,2591],"ol",{},[26,2549,2550,2554],{},[2551,2552,2553],"strong",{},"Start simple"," - Begin with content-based filtering",[26,2556,2557,2560],{},[2551,2558,2559],{},"Track everything"," - More data = better recommendations",[26,2562,2563,2566],{},[2551,2564,2565],{},"Ensure diversity"," - Avoid filter bubbles",[26,2568,2569,2572],{},[2551,2570,2571],{},"Handle cold start"," - Have fallbacks for new users",[26,2574,2575,2578],{},[2551,2576,2577],{},"A\u002FB test"," - Measure impact on engagement",[26,2580,2581,2584],{},[2551,2582,2583],{},"Respect privacy"," - Be transparent about data usage",[26,2586,2587,2590],{},[2551,2588,2589],{},"Allow feedback"," - Let users refine recommendations",[26,2592,2593,2596],{},[2551,2594,2595],{},"Monitor performance"," - Track recommendation quality",[15,2598,2600],{"id":2599},"conclusion","Conclusion",[11,2602,2603],{},"Personalized news feeds significantly improve user engagement and satisfaction. Start with simple content-based filtering and progressively add collaborative filtering and hybrid approaches as you gather more data.",[11,2605,2606,2607,2612],{},"Ready to build personalized experiences? ",[2608,2609,2611],"a",{"href":2610},"\u002Fsignup","Get started with AllNewsAPI"," today!",[15,2614,2616],{"id":2615},"resources","Resources",[23,2618,2619,2627,2633],{},[26,2620,2621],{},[2608,2622,2626],{"href":2623,"rel":2624},"https:\u002F\u002Fwww.springer.com\u002Fgp\u002Fbook\u002F9780387858203",[2625],"nofollow","Recommendation Systems Handbook",[26,2628,2629],{},[2608,2630,2632],{"href":2631},"\u002Fdocs","AllNewsAPI Documentation",[26,2634,2635],{},[2608,2636,2639],{"href":2637,"rel":2638},"https:\u002F\u002Fdevelopers.google.com\u002Fmachine-learning\u002Frecommendation",[2625],"Collaborative Filtering Tutorial",[2641,2642,2643],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":59,"searchDepth":73,"depth":73,"links":2645},[2646,2647,2652,2656,2661,2662,2663],{"id":17,"depth":73,"text":18},{"id":43,"depth":73,"text":44,"children":2648},[2649,2650,2651],{"id":48,"depth":79,"text":49},{"id":785,"depth":79,"text":786},{"id":1367,"depth":79,"text":1368},{"id":1626,"depth":73,"text":1627,"children":2653},[2654,2655],{"id":1630,"depth":79,"text":1631},{"id":1902,"depth":79,"text":1903},{"id":2299,"depth":73,"text":2300,"children":2657},[2658,2659,2660],{"id":2303,"depth":79,"text":2304},{"id":2433,"depth":79,"text":2434},{"id":2464,"depth":79,"text":2465},{"id":2543,"depth":73,"text":2544},{"id":2599,"depth":73,"text":2600},{"id":2615,"depth":73,"text":2616},"2024-11-18","Learn how to create personalized news recommendations using collaborative filtering and content-based algorithms",false,"md","\u002Fblog\u002Fbuilding-personalized-news-feeds-with-machine-learning.webp",{},"\u002Fblog\u002Fpersonalized-news-feeds",{"title":5,"description":2665},{"loc":2670},"blog\u002Fpersonalized-news-feeds","machine-learning","UDqij_fvbWXynJM3YQ9Z33q5PbG-TgdOTpC0tvbQpw4",1783882207699]