Skip to content

Commit d8bc84b

Browse files
mihowclaude
andcommitted
feat(ui): add useModelAgreement hook for occurrence stats
Typed React Query wrapper for /occurrences/stats/model-agreement/. Owned by this UI PR (#1308); the backend PR (#1307) is now backend-only. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e086c9f commit d8bc84b

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { API_ROUTES, API_URL } from 'data-services/constants'
2+
import { useAuthorizedQuery } from '../../auth/useAuthorizedQuery'
3+
4+
interface ModelAgreementResponse {
5+
project_id: number
6+
total_occurrences: number
7+
verified_count: number
8+
verified_pct: number
9+
verified_with_prediction_count: number
10+
no_prediction_count: number
11+
agreed_exact_count: number
12+
agreed_exact_pct: number
13+
agreed_any_rank_count: number
14+
agreed_any_rank_pct: number
15+
// Only populated when the caller passes ?agreement_coarsest_rank=<RANK>.
16+
agreement_coarsest_rank: string | null
17+
agreed_coarser_rank_count: number | null
18+
agreed_coarser_rank_pct: number | null
19+
}
20+
21+
type FilterPrimitive = string | number | boolean
22+
type FilterValue = FilterPrimitive | FilterPrimitive[] | null | undefined
23+
24+
// Accepts an arbitrary filter map so the occurrence list page's filter state
25+
// can be threaded through unchanged (deployment, event, taxon, score
26+
// thresholds, apply_defaults, etc). Arrays are appended as repeated query
27+
// params so multi-select filters (e.g. `algorithm`, `not_algorithm`, which
28+
// the backend reads via `request.query_params.getlist(...)`) survive.
29+
export const useModelAgreement = (
30+
projectId?: string,
31+
filters?: Record<string, FilterValue>
32+
) => {
33+
const url = `${API_URL}/${API_ROUTES.OCCURRENCES}/stats/model-agreement/`
34+
35+
const params = new URLSearchParams()
36+
if (projectId) params.set('project_id', projectId)
37+
if (filters) {
38+
Object.entries(filters).forEach(([key, value]) => {
39+
if (value === undefined || value === null || value === '') return
40+
if (Array.isArray(value)) {
41+
value.forEach((item) => {
42+
if (item !== undefined && item !== null && item !== '') {
43+
params.append(key, String(item))
44+
}
45+
})
46+
return
47+
}
48+
params.set(key, String(value))
49+
})
50+
}
51+
const queryString = params.toString()
52+
53+
const { data, isLoading, isFetching, error } =
54+
useAuthorizedQuery<ModelAgreementResponse>({
55+
queryKey: [
56+
API_ROUTES.OCCURRENCES,
57+
'stats',
58+
'model-agreement',
59+
projectId,
60+
queryString,
61+
],
62+
url: `${url}?${queryString}`,
63+
})
64+
65+
return {
66+
data,
67+
isLoading,
68+
isFetching,
69+
error,
70+
}
71+
}

0 commit comments

Comments
 (0)