fix: actualités — tri chronologique cassé + 3 flux RSS morts remplacés

Tri (lib/newsfeed.ts + components/NewsTab.tsx) :
Un bonus "+3h pour les catégories prioritaires" (Guerre/Chef d'État/Crise…)
était appliqué avant le tri par date. En temps normal ça remonte discrètement
une poignée d'articles importants ; mais en période de crise soutenue (conflit
Moyen-Orient actuel), la quasi-totalité des articles matche une de ces
catégories — le bonus s'applique presque partout et casse l'ordre
chronologique au lieu de l'affiner, produisant des blocs par source de plus en
plus vieux au lieu d'un flux unique correctement entrelacé. Vérifié en direct :
54 articles avant fix avec des dizaines d'inversions chronologiques, 0 après.
Le badge visuel " Prioritaire" reste pour signaler l'importance sans trahir
la fraîcheur réelle.

Flux RSS (lib/newsfeed.ts RSS_FEEDS) — 3 des 6 sources ne contribuaient plus
rien, vérifié en direct :
  - ForexLive : /feed/news est une coquille vide (0 <item>) depuis leur
    migration Nuxt.js vers investinglive.com — déjà couvert par le scraper
    dédié fetchInvestingLiveNews(), aucune perte réelle en le retirant.
  - MarketWatch : l'ancien miroir dowjones.io ne sert que des articles vieux
    de 1+ an (filtrés par le cutoff 8 jours) → remplacé par l'URL RSS actuelle
    (marketwatch.com/rss/topstories, testée fraîche).
  - Reuters : feeds.reuters.com n'existe plus (échec DNS, fermé par Reuters il
    y a plusieurs années) → remplacé par CNBC Markets (testé frais).

Bloomberg (403 anti-bot) et FinancialJuice (site restructuré) restent à zéro,
déjà connu et accepté (échecs silencieux gérés par try/catch).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
caty21
2026-07-08 18:49:16 +02:00
co-authored by Claude Sonnet 5
parent 062892c340
commit 7c9b6ac89d
4 changed files with 35 additions and 25 deletions
+10 -8
View File
@@ -55,7 +55,7 @@ const HIGH_PRIO = new Set([
const SOURCE_COLORS: Record<string, string> = { const SOURCE_COLORS: Record<string, string> = {
"InvestingLive": "bg-amber-500/20 text-amber-400", "InvestingLive": "bg-amber-500/20 text-amber-400",
"Reuters": "bg-orange-500/20 text-orange-400", "CNBC": "bg-orange-500/20 text-orange-400",
"Bloomberg Economics": "bg-sky-500/20 text-sky-400", "Bloomberg Economics": "bg-sky-500/20 text-sky-400",
"Bloomberg CB": "bg-sky-500/20 text-sky-400", "Bloomberg CB": "bg-sky-500/20 text-sky-400",
"Bloomberg FX": "bg-sky-500/20 text-sky-400", "Bloomberg FX": "bg-sky-500/20 text-sky-400",
@@ -72,14 +72,16 @@ function formatRelativeTime(isoDate: string): string {
} }
// ── Tri ─────────────────────────────────────────────────────────────────────── // ── Tri ───────────────────────────────────────────────────────────────────────
// Score plus élevé = apparaît en premier. // Strictement chronologique (score = timestamp). Un bonus "+3h prioritaire /
// Récence primaire → haute priorité (+3h) → catégorisé (+1h) → bruit sans catégorie (0) // +1h catégorisé" existait ici, mais en période de crise soutenue la quasi-
// totalité des articles matche une catégorie prioritaire (Guerre/Géopolitique/
// Chef d'État) : le bonus s'applique alors presque partout et casse l'ordre
// chronologique au lieu de l'affiner (des articles plus vieux mais
// "prioritaires" passent devant des articles réellement plus récents). Le
// badge visuel "⚡ Prioritaire" (NewsCard) signale déjà l'importance sans
// trahir la fraîcheur réelle du flux.
function scoreItem(item: NewsItem): number { function scoreItem(item: NewsItem): number {
const t = new Date(item.publishedAt).getTime(); return new Date(item.publishedAt).getTime();
const isPrio = item.categories.some(c => HIGH_PRIO.has(c));
const hasCat = item.categories.length > 0 || item.impacts.length > 0;
return t + (isPrio ? 3 * 3_600_000 : hasCat ? 1 * 3_600_000 : 0);
} }
// ── Composant principal ─────────────────────────────────────────────────────── // ── Composant principal ───────────────────────────────────────────────────────
+1 -1
View File
@@ -1 +1 @@
{"ts":1783520638250,"data":{"current":{},"prev":{},"prevDate":null}} {"ts":1783528251592,"data":{"current":{"NZD":{"currency":"NZD","nextMeetingProbPct":82,"nextMeetingIsHike":true,"nextMeetingIsNoChange":false,"bpsYearEnd":62,"publishedDate":"2026-07-03"},"USD":{"currency":"USD","nextMeetingProbPct":17,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":30,"publishedDate":"2026-07-03"},"EUR":{"currency":"EUR","nextMeetingProbPct":28,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":24,"publishedDate":"2026-07-03"},"JPY":{"currency":"JPY","nextMeetingProbPct":1,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":20,"publishedDate":"2026-07-03"},"GBP":{"currency":"GBP","nextMeetingProbPct":7,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":18,"publishedDate":"2026-07-03"},"AUD":{"currency":"AUD","nextMeetingProbPct":15,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":10,"publishedDate":"2026-07-03"},"CAD":{"currency":"CAD","nextMeetingProbPct":9,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":8,"publishedDate":"2026-07-03"},"CHF":{"currency":"CHF","nextMeetingProbPct":4,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":5,"publishedDate":"2026-07-03"}},"prev":{"NZD":{"currency":"NZD","nextMeetingProbPct":62,"nextMeetingIsHike":true,"nextMeetingIsNoChange":false,"bpsYearEnd":55,"publishedDate":"2026-06-26"},"USD":{"currency":"USD","nextMeetingProbPct":30,"nextMeetingIsHike":true,"nextMeetingIsNoChange":false,"bpsYearEnd":31,"publishedDate":"2026-06-26"},"EUR":{"currency":"EUR","nextMeetingProbPct":31,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":27,"publishedDate":"2026-06-26"},"GBP":{"currency":"GBP","nextMeetingProbPct":14,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":20,"publishedDate":"2026-06-26"},"JPY":{"currency":"JPY","nextMeetingProbPct":3,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":19,"publishedDate":"2026-06-26"},"CAD":{"currency":"CAD","nextMeetingProbPct":4,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":18,"publishedDate":"2026-06-26"},"AUD":{"currency":"AUD","nextMeetingProbPct":13,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":8,"publishedDate":"2026-06-26"},"CHF":{"currency":"CHF","nextMeetingProbPct":6,"nextMeetingIsHike":false,"nextMeetingIsNoChange":true,"bpsYearEnd":5,"publishedDate":"2026-06-26"}},"prevDate":"2026-06-26"}}
+23 -15
View File
@@ -675,13 +675,20 @@ async function fetchInvestingLiveNews(): Promise<NewsItem[]> {
// ForexLive : commentaires macro en temps réel // ForexLive : commentaires macro en temps réel
// MarketWatch : marchés généraux // MarketWatch : marchés généraux
// Vérifié en direct (2026-07-08) :
// - ForexLive retiré : /feed/news est une coquille vide (0 <item>) depuis leur
// migration Nuxt.js vers investinglive.com — déjà couvert par le scraper
// dédié fetchInvestingLiveNews() (API réelle), donc pas de perte réelle.
// - MarketWatch : l'ancien miroir dowjones.io sert des articles vieux de 1+ an
// (tous filtrés par le cutoff 8 jours) → remplacé par l'URL RSS actuelle.
// - Reuters : feeds.reuters.com n'existe plus (échec DNS, fermé côté Reuters
// il y a plusieurs années) → remplacé par CNBC Markets (flux frais, testé).
const RSS_FEEDS: { url: string; source: string }[] = [ const RSS_FEEDS: { url: string; source: string }[] = [
{ url: "https://www.fxstreet.com/rss/news", source: "FXStreet" }, { url: "https://www.fxstreet.com/rss/news", source: "FXStreet" },
{ url: "https://www.forexlive.com/feed/news", source: "ForexLive" }, { url: "https://www.marketwatch.com/rss/topstories", source: "MarketWatch" },
{ url: "https://feeds.content.dowjones.io/public/rss/mw_realtimeheadlines", source: "MarketWatch" }, { url: "https://finance.yahoo.com/rss/topfinstories", source: "Yahoo Finance" },
{ url: "https://finance.yahoo.com/rss/topfinstories", source: "Yahoo Finance" }, { url: "https://www.cnbc.com/id/100003114/device/rss/rss.html", source: "CNBC" },
{ url: "https://feeds.reuters.com/reuters/businessNews", source: "Reuters" }, { url: "https://www.investing.com/rss/news.rss", source: "Investing.com" },
{ url: "https://www.investing.com/rss/news.rss", source: "Investing.com" },
]; ];
async function fetchRssFeeds(): Promise<NewsItem[]> { async function fetchRssFeeds(): Promise<NewsItem[]> {
@@ -895,15 +902,16 @@ export async function fetchAllNews(): Promise<NewsItem[]> {
return true; return true;
}); });
const PRIORITY_SET = new Set(["Discours BC", "Décision Taux", "Crise", "Guerre", "Chef d'État", "Probabilités Taux"]); // Tri strictement chronologique (décroissant). Un bonus "+3h pour les
const isPrio = (item: NewsItem) => item.categories.some(c => PRIORITY_SET.has(c)); // catégories prioritaires" existait ici auparavant, mais en période de
const PRIO_BONUS_MS = 3 * 60 * 60 * 1000; // +3h boost pour les prioritaires // crise soutenue (ex. conflit Moyen-Orient) la quasi-totalité des articles
// matche "Guerre"/"Géopolitique"/"Chef d'État" — le bonus s'appliquait donc
deduped.sort((a, b) => { // presque partout et cassait l'ordre chronologique au lieu de l'affiner
const ta = new Date(a.publishedAt).getTime() + (isPrio(a) ? PRIO_BONUS_MS : 0); // (des articles plus anciens mais "prioritaires" passaient devant des
const tb = new Date(b.publishedAt).getTime() + (isPrio(b) ? PRIO_BONUS_MS : 0); // articles réellement plus récents). Le badge visuel "⚡ Prioritaire"
return tb - ta; // (NewsTab.tsx) reste le bon endroit pour signaler l'importance sans
}); // trahir la fraîcheur réelle du flux.
deduped.sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime());
return deduped; return deduped;
} }
+1 -1
View File
File diff suppressed because one or more lines are too long