fix: aggregate anomaly data per day instead of taking latest — fixes missing returned bars when multiple syncs per day

This commit is contained in:
root
2026-05-01 19:28:29 +00:00
parent bfa52c7f8f
commit 5a9df2103f
3 changed files with 124 additions and 18 deletions

View File

@@ -226,34 +226,44 @@ export default function IvantiCountsChart() {
);
// Build archive activity data aligned to the same date axis as the main chart.
// Aggregate anomaly rows by date (take the last sync per day, matching the
// counts history pattern), then merge onto the chartData date set.
// Aggregate anomaly rows by date — sum archived/returned counts and merge
// classifications across all syncs that day, then align to the chartData dates.
const archiveData = useMemo(() => {
if (!anomalies.length || !chartData.length) return [];
// Group anomalies by date, keep the latest per day
// Aggregate all anomaly rows per date (sum counts, merge classifications)
const byDate = {};
for (const a of anomalies) {
const rawDate = extractDate(a.sync_timestamp);
const dateKey = fmtDate(rawDate);
// anomaly/history returns newest first, so first seen per date is the latest
if (!byDate[dateKey]) {
byDate[dateKey] = a;
byDate[dateKey] = {
archived: 0,
returned: 0,
classification: {},
return_classification: {},
is_significant: false,
};
}
const entry = byDate[dateKey];
entry.archived += (a.newly_archived_count || 0);
entry.returned += (a.returned_count || 0);
if (a.is_significant) entry.is_significant = true;
// Merge classification counts
for (const [key, val] of Object.entries(a.classification || {})) {
entry.classification[key] = (entry.classification[key] || 0) + (val || 0);
}
for (const [key, val] of Object.entries(a.return_classification || {})) {
entry.return_classification[key] = (entry.return_classification[key] || 0) + (val || 0);
}
}
// Map onto the chart date axis so both charts share the same X positions
return chartData.map(point => {
const anomaly = byDate[point.date];
if (anomaly) {
return {
date: point.date,
archived: anomaly.newly_archived_count || 0,
returned: anomaly.returned_count || 0,
classification: anomaly.classification || {},
return_classification: anomaly.return_classification || {},
is_significant: anomaly.is_significant,
};
const agg = byDate[point.date];
if (agg) {
return { date: point.date, ...agg };
}
return { date: point.date, archived: 0, returned: 0, classification: {}, return_classification: {}, is_significant: false };
});
@@ -377,13 +387,13 @@ export default function IvantiCountsChart() {
}}>
Archive Activity
</div>
<ResponsiveContainer width="100%" height={64}>
<ResponsiveContainer width="100%" height={80}>
<BarChart data={archiveData} margin={{ top: 2, right: 12, bottom: 0, left: -12 }}>
<CartesianGrid {...GRID_STYLE} />
<XAxis dataKey="date" tick={false} axisLine={false} />
<YAxis tick={AXIS_STYLE} allowDecimals={false} width={30} />
<Tooltip content={<ArchiveTooltip />} />
<Bar dataKey="archived" name="Archived" stackId="a" maxBarSize={12}>
<Bar dataKey="archived" name="Archived" stackId="a" maxBarSize={14}>
{archiveData.map((entry, idx) => (
<Cell
key={`arch-${idx}`}
@@ -391,7 +401,14 @@ export default function IvantiCountsChart() {
/>
))}
</Bar>
<Bar dataKey="returned" name="Returned" stackId="a" fill={TEAL} maxBarSize={12} />
<Bar dataKey="returned" name="Returned" stackId="a" maxBarSize={14}>
{archiveData.map((entry, idx) => (
<Cell
key={`ret-${idx}`}
fill={TEAL}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>