diff --git a/frontend/src/components/ExperimentCalendar.jsx b/frontend/src/components/ExperimentCalendar.jsx index 2d53ae7..63deb5b 100644 --- a/frontend/src/components/ExperimentCalendar.jsx +++ b/frontend/src/components/ExperimentCalendar.jsx @@ -53,25 +53,22 @@ function DayPanel({ date, animals, allStatuses, template, experimentId, selected const fieldLabel = fieldOptions.find((f) => f.value === selectedField)?.label; - // Bar chart: this day's field value per animal (numeric only, filtered to animals present that day) + // Bar chart: animals with analysis_summary on this day → Success/Failure/Total from that summary const barData = useMemo(() => { - if (!selectedField) return []; - const isBuiltin = BUILTIN_KEYS_SET.has(selectedField); return animalsWithData .map((animal) => { const status = statusByAnimal[animal.id]; - if (!status) return null; - const raw = isBuiltin ? status[selectedField] : status.custom_fields?.[selectedField]; - if (raw === null || raw === undefined || String(raw).trim() === '') return null; - const num = parseFloat(raw); - if (isNaN(num)) return null; + const summary = status?.analysis_summary; + if (!summary || summary.total === 0) return null; return { name: animal.animal_name || animal.animal_id_string || animal.id.slice(0, 8), - value: num, + total: summary.total, + success: summary.counts?.Success ?? 0, + failure: summary.counts?.Failure ?? 0, }; }) .filter(Boolean); - }, [animalsWithData, statusByAnimal, selectedField]); + }, [animalsWithData, statusByAnimal]); return (
@@ -120,16 +117,17 @@ function DayPanel({ date, animals, allStatuses, template, experimentId, selected {/* Bar chart: overall per-subject completeness */} {barData.length > 0 && (
-

- {fieldLabel ?? 'Field'} — this session -

+

Session metrics

- [v, fieldLabel ?? 'Value']} /> - + + + + +
diff --git a/frontend/tests/ExperimentCalendar.test.jsx b/frontend/tests/ExperimentCalendar.test.jsx index 7aefa27..589cfcf 100644 --- a/frontend/tests/ExperimentCalendar.test.jsx +++ b/frontend/tests/ExperimentCalendar.test.jsx @@ -38,7 +38,7 @@ function todayStr() { return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; } -function makeStatus(animalId, date, customFields = {}, vitals = null) { +function makeStatus(animalId, date, customFields = {}, vitals = null, analysisSummary = null) { return { id: `s-${animalId}-${date}`, animal_id: animalId, @@ -48,6 +48,7 @@ function makeStatus(animalId, date, customFields = {}, vitals = null) { treatment: null, notes: null, custom_fields: customFields, + analysis_summary: analysisSummary, }; } @@ -248,10 +249,11 @@ describe('day click modal', () => { // ── Bar chart (inside day modal) ────────────────────────────────────────────── describe('bar chart inside day modal', () => { - it('renders bar chart with that day\'s numeric field values', async () => { + it('renders session metrics bar chart when analysis_summary has data', async () => { const dateStr = todayStr(); + const summary = { total: 18, counts: { Success: 5, Failure: 13 }, success_rate: 0.28 }; const statuses = [ - makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }), + makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }, null, summary), ]; render( , @@ -262,31 +264,16 @@ describe('bar chart inside day modal', () => { }); }); - it('omits animals with empty or non-numeric field value from bar chart', async () => { + it('hides bar chart when no animal has analysis_summary with total > 0', async () => { const dateStr = todayStr(); - // Rat A has numeric weight; Rat B has empty weight — Rat B excluded from chart but shown in day list const statuses = [ makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '325' }), - makeStatus('a2000000-0000-0000-0000-000000000002', dateStr, {}), ]; render( , ); fireEvent.click(screen.getByTestId(`day-${dateStr}`)); await waitFor(() => screen.getByRole('dialog')); - expect(screen.getByTestId('bar-chart')).toBeInTheDocument(); - }); - - it('hides bar chart when no animals have a numeric value for the selected field that day', async () => { - const dateStr = todayStr(); - // vitals is text — no bar chart - const statuses = [makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, {}, 'HR 72')]; - render( - , - ); - fireEvent.change(screen.getByTestId('field-select'), { target: { value: 'vitals' } }); - fireEvent.click(screen.getByTestId(`day-${dateStr}`)); - await waitFor(() => screen.getByRole('dialog')); expect(screen.queryByTestId('bar-chart')).not.toBeInTheDocument(); }); });