fix: chart.js legend error + smart cache for date-range queries

- Fix forecast chart confidence band fill config (fill:'+1' → explicit target)
  to resolve 't.legend.handleEvent' TypeError in Chart.js 4.4.1
- Add getOrFetchRange() to cache.js: auto TTL based on data age
  (end date >10 days old → 24h cache, recent → 5min cache)
- Apply smart cache to 6 heavy endpoints: bi, bi/revenue, bi/strategic,
  providers, providers/failed, providers/trend

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-02-16 22:48:53 -05:00
parent 844f931076
commit 6bfd21a111
3 changed files with 44 additions and 11 deletions

View File

@@ -2142,15 +2142,19 @@ async function loadForecast() {
data: {
labels: allLabels,
datasets: [
{ label: 'Historical', data: histFull, borderColor: theme.blue, backgroundColor: 'transparent', borderWidth: 2, pointRadius: 0, tension: 0.3 },
{ label: 'Forecast', data: predValues, borderColor: theme.green, backgroundColor: 'transparent', borderWidth: 2, borderDash: [6,3], pointRadius: 0, tension: 0.3 },
{ label: 'Upper 95%', data: upperValues, borderColor: 'transparent', backgroundColor: theme.green + '15', fill: '+1', pointRadius: 0 },
{ label: 'Lower 95%', data: lowerValues, borderColor: 'transparent', backgroundColor: 'transparent', fill: false, pointRadius: 0 }
{ label: 'Confidence 95%', data: upperValues, borderColor: 'transparent', backgroundColor: theme.green + '15', fill: { target: 3, above: theme.green + '15' }, pointRadius: 0, tension: 0.3 },
{ label: 'Historical', data: histFull, borderColor: theme.blue, backgroundColor: 'transparent', borderWidth: 2, pointRadius: 0, tension: 0.3, fill: false },
{ label: 'Forecast', data: predValues, borderColor: theme.green, backgroundColor: 'transparent', borderWidth: 2, borderDash: [6,3], pointRadius: 0, tension: 0.3, fill: false },
{ data: lowerValues, borderColor: 'transparent', backgroundColor: 'transparent', pointRadius: 0, tension: 0.3, fill: false }
]
},
options: {
responsive: true, maintainAspectRatio: false,
plugins: { legend: { position: 'top', labels: { color: theme.text, usePointStyle: true, pointStyle: 'line', font: { size: 11 } } } },
plugins: {
legend: { position: 'top', labels: { color: theme.text, usePointStyle: true, pointStyle: 'line', font: { size: 11 }, filter: function(item) { return item.text !== undefined; } } },
tooltip: { filter: function(item) { return item.dataset.label !== undefined; } },
filler: { propagate: false }
},
scales: {
x: { ticks: { color: theme.text, maxTicksLimit: 15, font: { size: 10 } }, grid: { color: theme.grid } },
y: { ticks: { color: theme.text, callback: function(v){return '$' + (v>=1000 ? Math.round(v/1000)+'K' : v);} }, grid: { color: theme.grid } }

View File

@@ -132,6 +132,32 @@ async function getOrFetch(key, fetchFn, ttl = DEFAULT_TTL) {
return value;
}
/**
* Smart TTL para queries com date range:
* - end date > 10 dias atrás → dados imutáveis → TTL 24h
* - end date recente → dados mudam → TTL curto (5 min)
*/
const IMMUTABLE_TTL = 24 * 60 * 60 * 1000; // 24h
const FRESH_TTL = 5 * 60 * 1000; // 5 min
const STALE_DAYS = 10;
function getDateRangeTTL(endDate) {
const end = new Date(endDate + 'T23:59:59');
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - STALE_DAYS);
return end < cutoff ? IMMUTABLE_TTL : FRESH_TTL;
}
/**
* Cache-aware fetch para queries com date range.
* Key = prefix + start + end. TTL automático baseado na idade dos dados.
*/
async function getOrFetchRange(prefix, start, end, fetchFn) {
const key = `${prefix}:${start}:${end}`;
const ttl = getDateRangeTTL(end);
return getOrFetch(key, fetchFn, ttl);
}
/**
* Stats do cache
*/
@@ -160,5 +186,7 @@ module.exports = {
clear,
registerAutoRefresh,
getOrFetch,
getOrFetchRange,
getDateRangeTTL,
stats
};