mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 03:13:12 +00:00
fix: ignore malformed bar analytics date keys (#1556)
* fix: ignore malformed bar analytics date keys * style: apply prettier formatting to dynamic imports
This commit is contained in:
1 parent
b811cacaef
commit
e37a87c3a2
2 files changed
+41
-9
No files matched your search
@@ -259,9 +259,20 @@ function surfaceLabel(source: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Local-midnight Date from a YYYY-MM-DD day key (calendar-day anchored). */
|
/** Local-midnight Date from a YYYY-MM-DD day key (calendar-day anchored). */
|
||||||
function dateFromDayKey(key: string): Date {
|
function dateFromDayKey(key: string): Date | null {
|
||||||
const [y, m, d] = key.split('-').map((n) => parseInt(n, 10));
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
|
||||||
return new Date(y, (m || 1) - 1, d || 1);
|
if (!match) return null;
|
||||||
|
|
||||||
|
const y = Number(match[1]);
|
||||||
|
const m = Number(match[2]);
|
||||||
|
const d = Number(match[3]);
|
||||||
|
const date = new Date(y, m - 1, d);
|
||||||
|
|
||||||
|
if (date.getFullYear() !== y || date.getMonth() !== m - 1 || date.getDate() !== d) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -329,7 +340,9 @@ export function computeBarAnalyticsFromDaily(
|
|||||||
// Pass 1 — daily: cost, per-model spend, per-surface spend, sparkline cost.
|
// Pass 1 — daily: cost, per-model spend, per-surface spend, sparkline cost.
|
||||||
for (const d of daily) {
|
for (const d of daily) {
|
||||||
if (!d || !d.date) continue;
|
if (!d || !d.date) continue;
|
||||||
const delta = dayDelta(now, dateFromDayKey(d.date));
|
const activityDate = dateFromDayKey(d.date);
|
||||||
|
if (!activityDate) continue;
|
||||||
|
const delta = dayDelta(now, activityDate);
|
||||||
if (delta < 0) continue; // ignore future-dated noise
|
if (delta < 0) continue; // ignore future-dated noise
|
||||||
const cost = Number.isFinite(d.totalCost) ? d.totalCost : Number.isFinite(d.cost) ? d.cost : 0;
|
const cost = Number.isFinite(d.totalCost) ? d.totalCost : Number.isFinite(d.cost) ? d.cost : 0;
|
||||||
const source = d.source || '';
|
const source = d.source || '';
|
||||||
@@ -362,7 +375,9 @@ export function computeBarAnalyticsFromDaily(
|
|||||||
for (const h of hourly) {
|
for (const h of hourly) {
|
||||||
if (!h || !h.hour) continue;
|
if (!h || !h.hour) continue;
|
||||||
const dayKey = h.hour.slice(0, 10);
|
const dayKey = h.hour.slice(0, 10);
|
||||||
const delta = dayDelta(now, dateFromDayKey(dayKey));
|
const activityDate = dateFromDayKey(dayKey);
|
||||||
|
if (!activityDate) continue;
|
||||||
|
const delta = dayDelta(now, activityDate);
|
||||||
if (delta < 0) continue;
|
if (delta < 0) continue;
|
||||||
const requests = Number.isFinite(h.requestCount) ? (h.requestCount as number) : 0;
|
const requests = Number.isFinite(h.requestCount) ? (h.requestCount as number) : 0;
|
||||||
if (requests <= 0) continue;
|
if (requests <= 0) continue;
|
||||||
@@ -396,10 +411,9 @@ export function computeBarAnalyticsFromDaily(
|
|||||||
.filter((s) => s.cost > 0 || s.requests > 0)
|
.filter((s) => s.cost > 0 || s.requests > 0)
|
||||||
.sort((a, b) => b.cost - a.cost);
|
.sort((a, b) => b.cost - a.cost);
|
||||||
|
|
||||||
const lastActivityAt = lastActivityKey ? dateFromDayKey(lastActivityKey).toISOString() : null;
|
const lastActivityDate = lastActivityKey ? dateFromDayKey(lastActivityKey) : null;
|
||||||
const daysSinceLastActivity = lastActivityKey
|
const lastActivityAt = lastActivityDate ? lastActivityDate.toISOString() : null;
|
||||||
? dayDelta(now, dateFromDayKey(lastActivityKey))
|
const daysSinceLastActivity = lastActivityDate ? dayDelta(now, lastActivityDate) : null;
|
||||||
: null;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
today,
|
today,
|
||||||
|
|||||||
@@ -228,6 +228,24 @@ describe('computeBarAnalyticsFromDaily — monthToDate', () => {
|
|||||||
expect(a.last30d.cost).toBe(20); // rolling 30d still populated
|
expect(a.last30d.cost).toBe(20); // rolling 30d still populated
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('ignores malformed aggregate date keys instead of throwing', () => {
|
||||||
|
const a = computeBarAnalyticsFromDaily(
|
||||||
|
[daily({ date: 'May 1 2026', totalCost: 10 }), daily({ date: '2026-06-08', totalCost: 4 })],
|
||||||
|
[
|
||||||
|
hourly({ hour: 'May 1 2026 00:00', requestCount: 5 }),
|
||||||
|
hourly({ hour: '2026-06-08 09:00', requestCount: 3 }),
|
||||||
|
],
|
||||||
|
NOW
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(a.today.cost).toBe(4);
|
||||||
|
expect(a.today.requests).toBe(3);
|
||||||
|
expect(a.allTime.cost).toBe(4);
|
||||||
|
expect(a.allTime.requests).toBe(3);
|
||||||
|
expect(a.lastActivityAt).toBe(new Date(2026, 5, 8).toISOString());
|
||||||
|
expect(a.daysSinceLastActivity).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns zeroed monthToDate for empty daily and hourly input', () => {
|
it('returns zeroed monthToDate for empty daily and hourly input', () => {
|
||||||
const a = computeBarAnalyticsFromDaily([], [], NOW);
|
const a = computeBarAnalyticsFromDaily([], [], NOW);
|
||||||
expect(a.monthToDate).toEqual({ cost: 0, requests: 0 });
|
expect(a.monthToDate).toEqual({ cost: 0, requests: 0 });
|
||||||
|
|||||||
Reference in new issue
Block a user