Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions ui/src/pages/occurrences/occurrence-stats.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="space-y-2">
<span className="body-overline font-bold text-muted-foreground">
{label}
</span>
<div className="flex items-center gap-3">
<div className="h-2 flex-1 rounded-full bg-muted">
<div
className="h-2 rounded-full bg-primary transition-all"
style={{ width: `${pct}%` }}
/>
</div>
<span className="body-base tabular-nums">{pct}%</span>
</div>
</div>
)
}

// 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<Record<string, string>>(
(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 (
<Box className="w-full h-min shrink-0 p-2 rounded-lg md:w-72 md:p-4 md:rounded-xl no-print">
<span className="body-overline font-bold">Stats</span>
<div className="mt-4 space-y-6">
{isLoading || !data ? (
<>
<div className="h-12 animate-pulse rounded-md bg-muted" />
<div className="h-12 animate-pulse rounded-md bg-muted" />
</>
) : (
<>
<StatBar label="Verified occurrences" value={data.verified_pct} />
<StatBar
label="Human-model agreement rate"
value={data.agreed_under_order_pct}
/>
</>
)}
</div>
</Box>
)
}
2 changes: 2 additions & 0 deletions ui/src/pages/occurrences/occurrences.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -94,6 +95,7 @@ export const Occurrences = () => {
<>
<div className="flex flex-col gap-6 md:flex-row">
<div className="space-y-6">
<OccurrenceStats projectId={projectId} filters={filters} />
<FilterSection defaultOpen>
<FilterControl field="detections__source_image" readonly />
<FilterControl field="event" readonly />
Expand Down