From 20b58fbbcce8332932c0c9048b526e0f933272d3 Mon Sep 17 00:00:00 2001 From: Michael Bunsen Date: Fri, 15 May 2026 16:34:07 -0700 Subject: [PATCH 1/3] feat(ui): live stats panel in occurrence list sidebar Adds an OccurrenceStats panel above the filter sections on the occurrence list page. Consumes the /occurrences/stats/model-agreement/ endpoint, threading the same active filter array the list view sends so the numbers always reflect the current result set. Shows two metrics: verified occurrences % and human-model agreement rate % (rank-level / under-order agreement). Co-Authored-By: Claude --- ui/src/pages/occurrences/occurrence-stats.tsx | 73 +++++++++++++++++++ ui/src/pages/occurrences/occurrences.tsx | 2 + 2 files changed, 75 insertions(+) create mode 100644 ui/src/pages/occurrences/occurrence-stats.tsx diff --git a/ui/src/pages/occurrences/occurrence-stats.tsx b/ui/src/pages/occurrences/occurrence-stats.tsx new file mode 100644 index 000000000..7b12cbb8e --- /dev/null +++ b/ui/src/pages/occurrences/occurrence-stats.tsx @@ -0,0 +1,73 @@ +import { useModelAgreement } from 'data-services/hooks/occurrences/stats/useModelAgreement' +import { Box } from 'nova-ui-kit' + +interface OccurrenceStatsProps { + projectId?: string + filters: { field: string; value?: string; error?: string }[] +} + +const StatBar = ({ label, value }: { label: string; value: number }) => { + const pct = Math.round(Math.min(Math.max(value, 0), 1) * 100) + + return ( +
+ + {label} + +
+
+
+
+ {pct}% +
+
+ ) +} + +// Live verified / agreement stats for the occurrence list. Threads the same +// filter array the list view sends so the numbers always match the result set. +export const OccurrenceStats = ({ + projectId, + filters, +}: OccurrenceStatsProps) => { + const activeFilters = filters.reduce>( + (acc, { field, value, error }) => { + if (value?.length && !error) { + acc[field] = value + } + return acc + }, + {} + ) + + const { data, isLoading, error } = useModelAgreement(projectId, activeFilters) + + if (error || (!isLoading && !data)) { + return null + } + + return ( + + Stats +
+ {isLoading || !data ? ( + <> +
+
+ + ) : ( + <> + + + + )} +
+ + ) +} diff --git a/ui/src/pages/occurrences/occurrences.tsx b/ui/src/pages/occurrences/occurrences.tsx index 9a1071a3c..77da98ea6 100644 --- a/ui/src/pages/occurrences/occurrences.tsx +++ b/ui/src/pages/occurrences/occurrences.tsx @@ -35,6 +35,7 @@ import { useSelectedView } from 'utils/useSelectedView' import { useSort } from 'utils/useSort' import { columns } from './occurrence-columns' import { OccurrenceGallery } from './occurrence-gallery' +import { OccurrenceStats } from './occurrence-stats' import { OccurrenceNavigation } from './occurrence-navigation' import { OccurrencesActions } from './occurrences-actions' @@ -94,6 +95,7 @@ export const Occurrences = () => { <>
+ From d0669ee5f157edfd134e204e6ad8e3e483cb60b9 Mon Sep 17 00:00:00 2001 From: Michael Bunsen Date: Wed, 20 May 2026 17:52:45 -0700 Subject: [PATCH 2/3] feat(ui): switch stats panel to agreed_any_rank_pct (BE rename) One-line field rename in the occurrence stats panel to match the backend's dropped ORDER threshold. Hook type rename + multi-value filter support landed on the base branch (4a92c0bc on #1307). Co-Authored-By: Claude --- ui/src/pages/occurrences/occurrence-stats.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/pages/occurrences/occurrence-stats.tsx b/ui/src/pages/occurrences/occurrence-stats.tsx index 7b12cbb8e..c12c22430 100644 --- a/ui/src/pages/occurrences/occurrence-stats.tsx +++ b/ui/src/pages/occurrences/occurrence-stats.tsx @@ -63,7 +63,7 @@ export const OccurrenceStats = ({ )} From 5e5252dc0077ba44fd681f8fa188e675179b2b27 Mon Sep 17 00:00:00 2001 From: Michael Bunsen Date: Wed, 20 May 2026 18:29:08 -0700 Subject: [PATCH 3/3] feat(ui): show raw verified count next to percentage `StatBar` takes an optional `count` rendered as "0% (121)". Wired into the Verified occurrences bar so a small-but-nonzero verified set that rounds to 0% still surfaces the underlying count. Co-Authored-By: Claude --- ui/src/pages/occurrences/occurrence-stats.tsx | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/ui/src/pages/occurrences/occurrence-stats.tsx b/ui/src/pages/occurrences/occurrence-stats.tsx index c12c22430..b688c01e3 100644 --- a/ui/src/pages/occurrences/occurrence-stats.tsx +++ b/ui/src/pages/occurrences/occurrence-stats.tsx @@ -6,7 +6,17 @@ interface OccurrenceStatsProps { filters: { field: string; value?: string; error?: string }[] } -const StatBar = ({ label, value }: { label: string; value: number }) => { +const StatBar = ({ + label, + value, + count, +}: { + label: string + value: number + // Optional raw count shown alongside the percentage, e.g. "0% (23)". Useful + // when the percentage rounds to 0 but the underlying count is non-zero. + count?: number +}) => { const pct = Math.round(Math.min(Math.max(value, 0), 1) * 100) return ( @@ -21,7 +31,15 @@ const StatBar = ({ label, value }: { label: string; value: number }) => { style={{ width: `${pct}%` }} />
- {pct}% + + {pct}% + {count !== undefined ? ( + + {' '} + ({count.toLocaleString()}) + + ) : null} +
) @@ -60,7 +78,11 @@ export const OccurrenceStats = ({ ) : ( <> - +