feat: causal lab
This commit is contained in:
@@ -372,6 +372,18 @@ export default function MacroSeriesPage() {
|
||||
|
||||
const EVO_COLORS = ['#f59e0b', '#38bdf8', '#a78bfa', '#34d399', '#fb923c', '#f472b6']
|
||||
|
||||
// Compute focused event date for the Log tab (used by both chart and table)
|
||||
const logFocusDate = (() => {
|
||||
if (!forecastEvolution.allDates.length) return null
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
const twelveMonthsAgo = new Date(); twelveMonthsAgo.setFullYear(twelveMonthsAgo.getFullYear() - 1)
|
||||
const cutoff = twelveMonthsAgo.toISOString().slice(0, 10)
|
||||
const upcoming = forecastEvolution.allDates.filter(d => d >= today).sort()
|
||||
const recentPast = forecastEvolution.allDates.filter(d => d >= cutoff && d < today).sort().reverse()
|
||||
const ordered = [...upcoming, ...recentPast]
|
||||
return selectedEventDate ?? ordered[0] ?? null
|
||||
})()
|
||||
|
||||
return (
|
||||
<div className="flex h-full overflow-hidden" style={{ minHeight: 0 }}>
|
||||
{/* Sidebar */}
|
||||
@@ -531,7 +543,12 @@ export default function MacroSeriesPage() {
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-medium text-slate-300">
|
||||
Journal des changements — {selectedId}
|
||||
<span className="text-slate-500 text-xs ml-2">({logEntries.length} entrées)</span>
|
||||
{logFocusDate && <span className="text-slate-400 text-xs ml-2">· release {logFocusDate}</span>}
|
||||
<span className="text-slate-500 text-xs ml-2">
|
||||
({logFocusDate
|
||||
? logEntries.filter(e => e.event_date === logFocusDate).length
|
||||
: logEntries.length} entrées)
|
||||
</span>
|
||||
</span>
|
||||
{logEntries.length === 0 && (
|
||||
<button onClick={handleBackfill} disabled={backfilling}
|
||||
@@ -543,18 +560,20 @@ export default function MacroSeriesPage() {
|
||||
{/* Forecast evolution — focus one release at a time */}
|
||||
{forecastEvolution.allDates.length > 0 && (() => {
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
const upcoming = forecastEvolution.allDates.filter(d => d >= today).sort()
|
||||
const past = forecastEvolution.allDates.filter(d => d < today).sort().reverse()
|
||||
const ordered = [...upcoming, ...past]
|
||||
const twelveMonthsAgo = new Date(); twelveMonthsAgo.setFullYear(twelveMonthsAgo.getFullYear() - 1)
|
||||
const cutoff = twelveMonthsAgo.toISOString().slice(0, 10)
|
||||
const upcoming = forecastEvolution.allDates.filter(d => d >= today).sort()
|
||||
const recentPast = forecastEvolution.allDates.filter(d => d >= cutoff && d < today).sort().reverse()
|
||||
const ordered = [...upcoming, ...recentPast]
|
||||
const focusDate = selectedEventDate ?? ordered[0] ?? null
|
||||
if (!focusDate) return null
|
||||
|
||||
const allPts = forecastEvolution.byDate[focusDate] ?? []
|
||||
// Keep only the 4 weeks window before event_date
|
||||
const cutoff = new Date(focusDate)
|
||||
cutoff.setDate(cutoff.getDate() - 28)
|
||||
const cutoffStr = cutoff.toISOString().slice(0, 16)
|
||||
const pts = allPts.filter(p => p.time >= cutoffStr)
|
||||
const windowStart = new Date(focusDate)
|
||||
windowStart.setDate(windowStart.getDate() - 28)
|
||||
const windowStr = windowStart.toISOString().slice(0, 16)
|
||||
const pts = allPts.filter(p => p.time >= windowStr)
|
||||
const chartPts = pts.map(p => ({ time: p.time.slice(0, 10), forecast: p.forecast, actual: p.actual }))
|
||||
const hasActual = pts.some(p => p.actual != null)
|
||||
const actualVal = pts.find(p => p.actual != null)?.actual ?? null
|
||||
@@ -647,7 +666,7 @@ export default function MacroSeriesPage() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{[...logEntries].reverse().map((e, i) => {
|
||||
{[...logEntries].filter(e => !logFocusDate || e.event_date === logFocusDate).reverse().map((e, i) => {
|
||||
const isActual = e.actual_value != null
|
||||
const isForecastChange = e.changed === 'forecast' || e.changed === 'both'
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user