feat: banques centrales (vote+dot plot+previsions), calendrier elargi, fix InvestingLive, M3 auto

Central bank governance (nouvel onglet Banques centrales) :
- lib/centralBankGovernance.ts + app/api/central-bank-sources : scraping live du
  vote de la derniere reunion, dot plot Fed (SEP), et desormais les previsions
  macro (PIB + inflation) publiees par chaque BC elle-meme (Fed SEP, Eurosystem
  staff projections, BoJ Outlook Report PDF, SNB conditional forecast, BoC MPR,
  RBA SMP). GBP/NZD laisses honnetement vides quand aucune source chiffree
  fiable n'est accessible (RBNZ bloque par Cloudflare).
- components/CentralBankSourcesTab.tsx : nouvel onglet avec cards par banque.

Taux directeurs + Money Supply M3 :
- data/rate_decisions.json corrige (JPY, EUR, NZD etc. etaient perimes d'1-2
  decisions) et desormais auto-maintenu : .github/workflows/update-rate-decisions.yml
  (horaire) detecte les changements de taux via Trading Economics et fait
  glisser current -> prev sans perte de donnee.
- data/money-supply-m3.json (nouveau) + .github/workflows/fetch-money-supply.yml
  (hebdo) : M3 par devise (proxy M2 pour l'USD, la Fed ne publiant plus M3
  depuis 2006), affiche dans CurrencyCard.

Calendrier economique elargi :
- lib/calendar-countries.ts, lib/calendar-taxonomy.ts, lib/fxstreetCalendar.ts :
  couverture pays elargie + classification des evenements + source FXStreet.

Fix InvestingLive :
- lib/investinglive.ts : l'ancienne API WordPress (wp-json) renvoyait 404 depuis
  leur migration Nuxt.js -> reecrit vers api.investinglive.com/api/homepage/articles,
  + fix crash silencieux (Tldr pas toujours un tableau).

Divers :
- .vercel/ ajoute au .gitignore (ne doit jamais etre commite, cf. son propre README).
- scripts/ (lancement PWA, push env Vercel), captures d'ecran, cache InvestingLive.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
caty21
2026-07-08 16:47:59 +02:00
co-authored by Claude Sonnet 5
parent 82a732fbdf
commit 0657b50c07
38 changed files with 3257 additions and 443 deletions
+24 -18
View File
@@ -620,42 +620,48 @@ async function fetchRssFeed(url: string, source: string): Promise<NewsItem[]> {
} catch { return []; }
}
// ── Source 1 : InvestingLive via WordPress REST API ──────────────────────────
// Beaucoup plus fiable que le scraping HTML — retourne du JSON structuré
// ── Source 1 : InvestingLive via leur API interne (Nuxt/api.investinglive.com)
// L'ancien endpoint WordPress (wp-json) renvoie 404 depuis la migration du site
// vers Nuxt.js (rebrand ForexLive → investingLive) — remplacé par l'API JSON
// qui alimente leur propre page d'accueil (aucune clé requise, publique).
async function fetchInvestingLiveNews(): Promise<NewsItem[]> {
try {
// WordPress REST API : liste des posts récents, filtrée sur catégorie forex si dispo
const res = await fetch(
"https://investinglive.com/wp-json/wp/v2/posts?per_page=20&orderby=date&order=desc&_fields=id,title,link,date,excerpt,categories",
"https://api.investinglive.com/api/homepage/articles",
{
next: { revalidate: 1800 },
next: { revalidate: 900 },
headers: { ...TE_HEADERS, "Accept": "application/json" },
}
);
if (!res.ok) return [];
const posts = await res.json() as Array<{
id: number;
title: { rendered: string };
link: string;
date: string;
excerpt: { rendered: string };
}>;
const data = await res.json() as {
LatestArticles?: Array<{
Id: string;
Title: string;
Slug: string;
Tldr?: string[] | string | null; // schéma incohérent selon le type d'article
PublishedOn: string;
Category?: { Name: string; Slug: string };
}>;
};
const posts = data.LatestArticles;
if (!Array.isArray(posts)) return [];
return posts.slice(0, 20).map(post => {
const title = post.title?.rendered?.replace(/<[^>]+>/g, "").replace(/&#8217;/g, "'").replace(/&#8220;/g, '"').replace(/&#8221;/g, '"').trim() ?? "";
const summary = post.excerpt?.rendered?.replace(/<[^>]+>/g, "").trim().slice(0, 250);
const title = post.Title?.trim() ?? "";
const summary = (Array.isArray(post.Tldr) ? post.Tldr.join(" ") : post.Tldr ?? "").slice(0, 250);
const catSlug = post.Category?.Slug ?? "news";
const url = `https://investinglive.com/${catSlug}/${post.Slug}/`;
const combined = `${title} ${summary ?? ""}`;
const { impacts, categories } = applyRules(combined);
return {
id: `il-${post.id}`,
id: `il-${post.Id}`,
title,
url: post.link,
url,
source: "InvestingLive",
publishedAt: parseDate(post.date),
publishedAt: parseDate(post.PublishedOn),
summary,
impacts,
categories,