From a978f19088d696770acce50c6d72f4eb22bf84fc Mon Sep 17 00:00:00 2001 From: caty21 Date: Sat, 27 Jun 2026 19:40:48 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20Sem.=20pr=C3=A9c.=20HEAD=E2=86=92GET,=20?= =?UTF-8?q?prevMeetings=20cache,=20remove=20footer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - investinglive: tryUrl HEAD→GET + res.body?.cancel() (WordPress bloque HEAD) fenêtre recherche article préc. élargie 21→28 jours - rateprobability: CBRatePath + prevMeetings/prevWeekDate, loadPrevWeekCachedBody() fetchAllCBPaths enrichit prevMeetings depuis snapshot GitHub Actions - fetch-rate-data.mjs: rotation current→previousWeek (5–9 jours) pour Rate Curve - CurrencyCard: rateCurveData préfère prevMeetings (exact/meeting) > ilDelta (approx) légende hasPrevCurve pilote affichage ligne bleue et label date - page.tsx: suppression footer Sources/LLM, remplacement par pb-12 Co-Authored-By: Claude Sonnet 4.6 --- .github/scripts/fetch-rate-data.mjs | 31 +++++++++++++++++++++++++++-- app/page.tsx | 11 ++-------- components/CurrencyCard.tsx | 25 +++++++++++++---------- lib/investinglive.ts | 11 ++++++---- lib/rateprobability.ts | 29 +++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 25 deletions(-) diff --git a/.github/scripts/fetch-rate-data.mjs b/.github/scripts/fetch-rate-data.mjs index f200cef..4935514 100644 --- a/.github/scripts/fetch-rate-data.mjs +++ b/.github/scripts/fetch-rate-data.mjs @@ -1,6 +1,6 @@ // Runs in GitHub Actions (not Vercel) — fetches from rateprobability.com // GitHub Actions IPs are not blocked by the site. -import { writeFileSync, mkdirSync } from "fs"; +import { writeFileSync, mkdirSync, readFileSync } from "fs"; const CB_KEYS = [ ["USD", "fed"], @@ -45,10 +45,37 @@ for (const [ccy, slug] of CB_KEYS) { await new Promise(r => setTimeout(r, 300)); // 300ms entre requêtes } +// Rotation : si les données existantes datent de 5–9 jours → les sauvegarder comme previousWeek +let previousWeek = null; +let previousWeekFetchedAt = null; +try { + const existing = JSON.parse(readFileSync("data/rate-probabilities.json", "utf8")); + const ageMs = Date.now() - new Date(existing.fetchedAt).getTime(); + const day = 86400000; + if (ageMs >= 5 * day && ageMs <= 9 * day) { + // Les données courantes datent d'une semaine → les promouvoir en previousWeek + previousWeek = existing.data; + previousWeekFetchedAt = existing.fetchedAt; + console.log(`\nRotated existing data (${(ageMs / day).toFixed(1)}d old) to previousWeek`); + } else if (existing.previousWeek && existing.previousWeekFetchedAt) { + // Conserver le previousWeek existant s'il est encore dans la fenêtre utile (< 11 jours) + const prevAge = Date.now() - new Date(existing.previousWeekFetchedAt).getTime(); + if (prevAge < 11 * day) { + previousWeek = existing.previousWeek; + previousWeekFetchedAt = existing.previousWeekFetchedAt; + } + } +} catch { /* pas de fichier existant */ } + mkdirSync("data", { recursive: true }); writeFileSync( "data/rate-probabilities.json", - JSON.stringify({ data: results, fetchedAt: new Date().toISOString() }, null, 2) + JSON.stringify({ + data: results, + fetchedAt: new Date().toISOString(), + ...(previousWeek ? { previousWeek, previousWeekFetchedAt } : {}), + }, null, 2) ); console.log(`\nSaved ${Object.keys(results).length} CBs: ${Object.keys(results).join(", ")}`); +if (previousWeek) console.log(`previousWeek preserved (${Object.keys(previousWeek).join(", ")})`); diff --git a/app/page.tsx b/app/page.tsx index b7cbbb5..c847c18 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -634,15 +634,8 @@ export default function Dashboard() {
Cliquer sur chaque signal pour voir l'analyse détaillée
- {/* Footer */} -
-

- Sources: FRED · ECB · BoE · BoC · CFTC · Frankfurter · Myfxbook · ForexFactory -

-

- LLM: Groq (Llama 3.1) · Données à titre informatif uniquement — pas de conseil financier -

-
+ {/* Padding bas pour éviter que le contenu s'arrête brutalement au scroll */} +
); } diff --git a/components/CurrencyCard.tsx b/components/CurrencyCard.tsx index 470db08..6b68ad0 100644 --- a/components/CurrencyCard.tsx +++ b/components/CurrencyCard.tsx @@ -341,7 +341,7 @@ function SignalBar({ strength, direction }: { strength: number; direction: Signa function OISEnhancedBlock({ ratePath }: { ratePath: CBRatePath }) { const [chartTab, setChartTab] = useState<"curve" | "implied" | "scenarios">("curve"); - const { currentRate, meetings, yearEndImplied, ilDelta, ilCurrent } = ratePath; + const { currentRate, meetings, yearEndImplied, ilDelta, ilCurrent, prevMeetings, prevWeekDate } = ratePath; if (!meetings.length) return null; const m0 = meetings[0]; @@ -368,11 +368,16 @@ function OISEnhancedBlock({ ratePath }: { ratePath: CBRatePath }) { // Chart data (limit to 10 meetings) const chartMeetings = meetings.slice(0, 10); - const rateCurveData = chartMeetings.map(m => ({ - label: m.label, - current: +m.impliedRate.toFixed(3), - ...(ilDelta ? { weekAgo: +(m.impliedRate + ilDelta.bpsDelta / 100).toFixed(3) } : {}), - })); + // prevMeetings (snapshot RP) = per-meeting exact; ilDelta (IL article) = décalage uniforme approximatif + const rateCurveData = chartMeetings.map(m => { + const prevM = prevMeetings?.find(p => p.dateIso === m.dateIso); + const weekAgo = prevM + ? +prevM.impliedRate.toFixed(3) + : ilDelta ? +(m.impliedRate + ilDelta.bpsDelta / 100).toFixed(3) : undefined; + return { label: m.label, current: +m.impliedRate.toFixed(3), ...(weekAgo !== undefined ? { weekAgo } : {}) }; + }); + const hasPrevCurve = !!(prevMeetings?.length || ilDelta); + const prevCurveLabel = prevWeekDate ?? ilDelta?.prevDate ?? null; const impliedPtsData = chartMeetings.map(m => ({ label: m.label, @@ -507,9 +512,9 @@ function OISEnhancedBlock({ ratePath }: { ratePath: CBRatePath }) { Actuel - - - {ilDelta ? `Sem. préc. (${ilDelta.prevDate})` : "Sem. préc. (indispo)"} + + + {hasPrevCurve && prevCurveLabel ? `Sem. préc. (${prevCurveLabel})` : hasPrevCurve ? "Sem. préc." : "Sem. préc. (indispo)"} @@ -526,7 +531,7 @@ function OISEnhancedBlock({ ratePath }: { ratePath: CBRatePath }) { formatter={(v: number, name: string) => [`${v.toFixed(3)}%`, name === "current" ? "Actuel" : "Sem. préc."]} /> - {ilDelta && ( + {hasPrevCurve && ( )} diff --git a/lib/investinglive.ts b/lib/investinglive.ts index f7b7591..0102e60 100644 --- a/lib/investinglive.ts +++ b/lib/investinglive.ts @@ -52,12 +52,15 @@ async function tryUrl(daysAgo: number): Promise { const dateStr = `${yyyymmdd.slice(0,4)}-${yyyymmdd.slice(4,6)}-${yyyymmdd.slice(6,8)}`; const url = `https://investinglive.com/news/how-have-interest-rate-expectations-changed-after-this-weeks-event-${yyyymmdd}/`; try { + // GET au lieu de HEAD — certains serveurs WordPress refusent HEAD (405/404 même si la page existe) const res = await fetch(url, { - method: "HEAD", + method: "GET", headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36" }, cache: "no-store", }); - return res.ok ? { url, dateStr, daysAgo } : null; + if (!res.ok) return null; + await res.body?.cancel(); // libère la connexion sans télécharger le body + return { url, dateStr, daysAgo }; } catch { return null; } } @@ -73,8 +76,8 @@ async function findArticleRefs(): Promise<{ current: ArticleRef | null; previous if (!current) return { current: null, previous: null }; let previous: ArticleRef | null = null; - // Start the day after the current article and look up to 21 days further back - for (let d = current.daysAgo + 1; d <= current.daysAgo + 21; d++) { + // Start the day after the current article and look up to 28 days further back + for (let d = current.daysAgo + 1; d <= current.daysAgo + 28; d++) { const found = await tryUrl(d); if (found) { previous = found; break; } } diff --git a/lib/rateprobability.ts b/lib/rateprobability.ts index 267d6b9..1c41ebc 100644 --- a/lib/rateprobability.ts +++ b/lib/rateprobability.ts @@ -44,6 +44,8 @@ export interface CBRatePath { yearEndImplied: number | null; // taux impliqué à la dernière réunion connue (SOFR) ilCurrent?: ILCurrent; // valeurs absolues de l'article IL courant ilDelta?: ILWeeklyDelta; // delta vs article IL semaine précédente + prevMeetings?: RateProbMeeting[]; // réunions semaine précédente (snapshot RP) + prevWeekDate?: string; // date du snapshot semaine précédente } export type RateProbData = Partial>; @@ -244,6 +246,21 @@ function loadCachedRPBody(ccy: string, slug: string): Record | } catch { return null; } } +function loadPrevWeekCachedBody(ccy: string): { body: Record; date: string } | null { + try { + const filePath = join(process.cwd(), "data", "rate-probabilities.json"); + const raw = readFileSync(filePath, "utf8"); + const parsed = JSON.parse(raw) as { previousWeek?: Record; previousWeekFetchedAt?: string }; + if (!parsed.previousWeek || !parsed.previousWeekFetchedAt) return null; + const entry = parsed.previousWeek[ccy] as Record | undefined; + if (!entry) return null; + const ageMs = Date.now() - new Date(parsed.previousWeekFetchedAt).getTime(); + // Le snapshot semaine précédente doit dater de 4 à 10 jours + if (ageMs < 3 * 86400000 || ageMs > 11 * 86400000) return null; + return { body: entry, date: parsed.previousWeekFetchedAt.slice(0, 10) }; + } catch { return null; } +} + // Reparse un body brut de rateprobability.com (même format que fetchCBPath) function parseCBBody(ccy: Currency, body: Record): CBRatePath | null { const today = body["today"] as Record | undefined; @@ -301,6 +318,18 @@ export async function fetchAllCBPaths(): Promise { } } + // Enrichissement prevMeetings depuis snapshot semaine précédente (GitHub Actions) + for (const [ccy] of CB_KEYS) { + const path = data[ccy as Currency]; + if (!path) continue; + const prev = loadPrevWeekCachedBody(ccy); + if (!prev) continue; + const prevPath = parseCBBody(ccy as Currency, prev.body); + if (prevPath?.meetings.length) { + data[ccy as Currency] = { ...path, prevMeetings: prevPath.meetings, prevWeekDate: prev.date }; + } + } + // CHF/SNB : rateprobability ne couvre pas la SNB → InvestingLive est la seule source if (!data["CHF"] && ilData["CHF"]) { const snbPath = buildSNBPath(ilData, 0.00); // taux actuel SNB = 0%