// screenMATCH React Query hooks over the tool's namespace client. Keys are prefixed with the tool name // so the three tools never collide in the shared query cache. import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { screenApi } from './api' export function useScreenRequirements() { return useQuery({ queryKey: ['screenmatch', 'requirements'], queryFn: screenApi.requirements }) } export function useScreenRequirement(id: string | null) { return useQuery({ queryKey: ['screenmatch', 'requirement', id], queryFn: () => screenApi.requirement(id as string), enabled: !!id, }) } export function useScreenCandidate(id: string | null) { return useQuery({ queryKey: ['screenmatch', 'candidate', id], queryFn: () => screenApi.candidate(id as string), enabled: !!id, }) } export function useScreenRanked(requirementId: string | null) { return useQuery({ queryKey: ['screenmatch', 'ranked', requirementId], queryFn: () => screenApi.rankCandidates(requirementId as string), enabled: !!requirementId, }) } export function useScreenOpportunities(candidateId: string | null) { return useQuery({ queryKey: ['screenmatch', 'opportunities', candidateId], queryFn: () => screenApi.opportunities(candidateId as string), enabled: !!candidateId, }) } // An outcome is a scoring INPUT, not a passive log line: it feeds the AIOE weight profile and the // counters the admin surface reports. Nothing was invalidated here, so the Outcomes recorded stat the // operator just incremented kept showing the pre click number until a hard reload, which reads as the // click having been swallowed. Deliberately narrow: the cached screen view is NOT invalidated, because // refetching it would re POST the scoring endpoint that the same counter is fed from, so recording one // decision would inflate the number it is meant to report honestly. function useInvalidate() { const qc = useQueryClient() return () => { void qc.invalidateQueries({ queryKey: ['aioe', 'status'] }) } } export function useRecordOutcome() { const inv = useInvalidate() return useMutation({ mutationFn: (o: import('../../lib/types').OutcomeInput) => screenApi.recordOutcome(o), onSuccess: () => inv(), }) } export function useAliases() { return useQuery({ queryKey: ['screenmatch', 'aliases'], queryFn: screenApi.aliases }) } export function useAioeStatus() { return useQuery({ queryKey: ['aioe', 'status'], queryFn: screenApi.aioeStatus, retry: false }) } // The qualification of one candidate against one requirement. It is a POST only because the pair travels // in a body; it reads a score and changes nothing the caller owns, so it is modelled as a QUERY. // // As a mutation it had to be fired from an effect on mount, and StrictMode runs effects twice in // development, so every navigation issued POST /screen twice against the endpoint whose calls the // Continuous Learning tile reports as outcomes recorded. A mutation also has no cache entry, so returning // to a candidate already scored a second ago paid for the full evaluation again. Keying on the pair means // the transition to another candidate reads a different cache slot, which is also what stops a stale // score rendering under the new candidate's name (the reset() the old effect had to do by hand). export function useScreenScreen(candidateId: string | null, requirementId: string | null) { return useQuery({ queryKey: ['screenmatch', 'screen', candidateId, requirementId], queryFn: () => screenApi.screen(candidateId as string, requirementId as string), enabled: !!candidateId && !!requirementId, }) }