@burdenoff/microfe-atheletebridge 2026.805.1 → 2026.827.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"useAthleteBridgeApi.js","names":[],"sources":["../../../src/atheletebridge/hooks/useAthleteBridgeApi.ts"],"sourcesContent":["/**\n * AthleteBridge API hooks\n * @module atheletebridge/hooks\n *\n * Thin react-query wrappers over the real `wspace-atheletebridge-svc` federated\n * GraphQL contract, reached through the workspace gateway with the workspace\n * dual-token. Every query/mutation here maps 1:1 to an `ath*` root field in the\n * service's `.hive-schema.graphql`. NO MOCKS — all data is live.\n */\n\nimport { useCallback, useMemo } from 'react';\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { graphqlFetch, clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useAthleteBridge } from '../context';\nimport type {\n AthAgentRun,\n AthAgentRunConnection,\n AthAgentRunOverride,\n AthAiAssessment,\n AthAiModelInfo,\n AthAvailability,\n AthAvailabilityConnection,\n AthCompleteSessionInput,\n AthCreateFixtureInput,\n AthCreateProgramInput,\n AthCreateTeamInput,\n AthAddMemberInput,\n AthAddDrillInput,\n AthBookSessionInput,\n AthDrill,\n AthFixture,\n AthFixtureConnection,\n AthFixtureStatus,\n AthGenerateProgramInput,\n AthGenerateProgramResult,\n AthLogWorkoutInput,\n AthMember,\n AthMemberConnection,\n AthMemberStatus,\n AthPerformanceMetric,\n AthPerformanceMetricConnection,\n AthPlanAdjustment,\n AthProgram,\n AthProgramConnection,\n AthProgramLevel,\n AthProgramStatus,\n AthRecordMetricInput,\n AthRecordResultInput,\n AthSession,\n AthSessionConnection,\n AthSessionStatus,\n AthSetAvailabilityInput,\n AthSport,\n AthStanding,\n AthStandingConnection,\n AthTeam,\n AthTeamConnection,\n AthTeamStatus,\n AthUpdateProgramInput,\n AthUpdateTeamInput,\n AthWorkoutLog,\n AthWorkoutLogConnection,\n AthWorkspaceAiConfig,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Typed errors\n// ============================================================================\n\n/** `extensions.code` for an upstream AI provider that is down or unconfigured. */\nexport const AI_PROVIDER_UNAVAILABLE = 'AI_PROVIDER_UNAVAILABLE';\n\n/**\n * A GraphQL error surfaced with its `extensions` intact.\n *\n * The svc throws stable, typed errors (`NOT_FOUND`, `CONFLICT`,\n * `FAILED_PRECONDITION`, `AI_PROVIDER_UNAVAILABLE`, `BAD_USER_INPUT`) and, for\n * AI failures, an `extensions.agentRunId` pointing at the FAILED ledger row\n * that holds every attempt's raw upstream message. Flattening all of that into\n * `new Error(message)` is what made an unconfigured provider indistinguishable\n * from a crashed service, so the code and the run id are carried through to the\n * UI instead.\n */\nexport class AthApiError extends Error {\n /** `extensions.code`, or null when the server did not send one. */\n readonly code: string | null;\n /** `extensions.agentRunId` — the ledger row to inspect, when present. */\n readonly agentRunId: string | null;\n\n constructor(message: string, extensions?: Record<string, unknown>) {\n super(message);\n this.name = 'AthApiError';\n const code = extensions?.code;\n const agentRunId = extensions?.agentRunId;\n this.code = typeof code === 'string' && code ? code : null;\n this.agentRunId = typeof agentRunId === 'string' && agentRunId ? agentRunId : null;\n }\n}\n\n/** Whether an unknown thrown value is an AI-provider outage we can explain. */\nexport const isAiProviderUnavailable = (error: unknown): error is AthApiError =>\n error instanceof AthApiError && error.code === AI_PROVIDER_UNAVAILABLE;\n\n// ============================================================================\n// Low-level query wrapper\n// ============================================================================\n\nconst athQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n const first = response.errors[0];\n throw new AthApiError(first?.message ?? 'GraphQL request failed', first?.extensions);\n }\n\n if (!response.data) {\n throw new AthApiError('GraphQL response did not include data');\n }\n\n // This MFE bypasses Apollo, so fe-libs' short-lived graphqlFetch read cache\n // would otherwise serve pre-write data to the very next refetch issued by a\n // mutation's onSuccess invalidation. Clear it after any successful mutation so\n // every downstream read is fresh (fleet-wide stale-read-after-write fix).\n if (query.trimStart().startsWith('mutation')) {\n clearGraphqlFetchResponseCache();\n }\n\n return response.data;\n};\n\n// Global-gateway read wrapper — the workspace-scoped MFE reaches the tenant user\n// directory (for real user pickers) through the global gateway.\nconst athGlobalQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'global',\n query,\n variables,\n suppressGlobalErrorEvent: true,\n });\n\n if (response.errors?.length) {\n const first = response.errors[0];\n throw new AthApiError(first?.message ?? 'GraphQL request failed', first?.extensions);\n }\n\n if (!response.data) {\n throw new AthApiError('GraphQL response did not include data');\n }\n\n return response.data;\n};\n\n// ============================================================================\n// GraphQL fragments (inline strings — exact fields from the svc schema)\n// ============================================================================\n\nconst TEAM_FIELDS = `\n id workspaceId name sport status description ageGroup season homeVenue\n headCoachId tags createdAt updatedAt\n`;\n\nconst MEMBER_FIELDS = `\n id workspaceId teamId userId displayName role status position jerseyNumber\n height weight dominantSide notes joinedAt createdAt updatedAt\n`;\n\nconst FIXTURE_FIELDS = `\n id workspaceId type status competition round venue homeTeamId awayTeamId\n homeScore awayScore scheduledAt kickoffAt notes createdAt updatedAt\n homeTeam { id name sport }\n awayTeam { id name sport }\n`;\n\nconst STANDING_FIELDS = `\n id workspaceId competition teamId played won drawn lost goalsFor goalsAgainst\n points rank createdAt updatedAt\n team { id name sport }\n`;\n\nconst DRILL_FIELDS = `\n id workspaceId programId name description weekNumber orderIndex sets reps\n durationSec restSec createdAt updatedAt\n video { id }\n`;\n\nconst PROGRAM_FIELDS = `\n id workspaceId coachId name sport level status summary weeks isTemplate tags\n createdAt updatedAt\n`;\n\nconst SESSION_FIELDS = `\n id workspaceId coachId athleteId programId title type status location\n meetingUrl scheduledAt durationMin completedAt feedback rating\n createdAt updatedAt\n`;\n\nconst WORKOUT_LOG_FIELDS = `\n id workspaceId athleteId programId title status performedAt durationMin rpe\n totalLoad metrics notes createdAt updatedAt\n`;\n\nconst METRIC_FIELDS = `\n id workspaceId athleteId type label value unit source recordedAt\n createdAt updatedAt\n`;\n\n// ============================================================================\n// Teams\n// ============================================================================\n\nexport interface TeamFilters {\n sport?: AthSport;\n status?: AthTeamStatus;\n pagination?: PaginationInput;\n}\n\nexport const useTeams = (filters: TeamFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'teams', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListTeams: AthTeamConnection }>(\n `query AthListTeams($sport: AthSport, $status: AthTeamStatus, $pagination: PaginationInput) {\n athListTeams(sport: $sport, status: $status, pagination: $pagination) {\n items { ${TEAM_FIELDS} members { items { id } } }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n sport: filters.sport,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListTeams;\n },\n });\n};\n\nexport const useTeam = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'team', id],\n queryFn: async () => {\n const result = await athQuery<{ athTeam: AthTeam | null }>(\n `query AthTeam($id: ID!) {\n athTeam(id: $id) {\n ${TEAM_FIELDS}\n members { items { ${MEMBER_FIELDS} } }\n fixtures { items { ${FIXTURE_FIELDS} } }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athTeam;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useTeamMembers = (teamId: string | undefined, status?: AthMemberStatus) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'members', teamId, status],\n queryFn: async () => {\n const result = await athQuery<{ athListMembers: AthMemberConnection }>(\n `query AthListMembers($teamId: ID!, $status: AthMemberStatus) {\n athListMembers(teamId: $teamId, status: $status) { items { ${MEMBER_FIELDS} } }\n }`,\n { teamId, status },\n apiGatewayUrl\n );\n return result.athListMembers.items;\n },\n enabled: Boolean(teamId),\n });\n};\n\nexport const useCreateTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateTeamInput) => {\n const result = await athQuery<{ athCreateTeam: AthTeam }>(\n `mutation AthCreateTeam($input: AthCreateTeamInput!) {\n athCreateTeam(input: $input) { ${TEAM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athCreateTeam;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n },\n });\n};\n\nexport const useUpdateTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthUpdateTeamInput }) => {\n const result = await athQuery<{ athUpdateTeam: AthTeam }>(\n `mutation AthUpdateTeam($id: ID!, $input: AthUpdateTeamInput!) {\n athUpdateTeam(id: $id, input: $input) { ${TEAM_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athUpdateTeam;\n },\n onSuccess: (team) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', team.id] });\n },\n });\n};\n\nexport const useArchiveTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athArchiveTeam: boolean }>(\n `mutation AthArchiveTeam($id: ID!) { athArchiveTeam(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athArchiveTeam;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n },\n });\n};\n\nexport const useAddMember = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthAddMemberInput) => {\n const result = await athQuery<{ athAddMember: AthMember }>(\n `mutation AthAddMember($input: AthAddMemberInput!) {\n athAddMember(input: $input) { ${MEMBER_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athAddMember;\n },\n onSuccess: (member) => {\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'members', member.teamId],\n });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', member.teamId] });\n },\n });\n};\n\nexport const useRemoveMember = (teamId?: string) => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athRemoveMember: boolean }>(\n `mutation AthRemoveMember($id: ID!) { athRemoveMember(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athRemoveMember;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'members', teamId] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', teamId] });\n },\n });\n};\n\n// ============================================================================\n// Fixtures & standings\n// ============================================================================\n\nexport interface FixtureFilters {\n status?: AthFixtureStatus;\n teamId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useFixtures = (filters: FixtureFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'fixtures', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListFixtures: AthFixtureConnection }>(\n `query AthListFixtures($status: AthFixtureStatus, $teamId: ID, $pagination: PaginationInput) {\n athListFixtures(status: $status, teamId: $teamId, pagination: $pagination) {\n items { ${FIXTURE_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n teamId: filters.teamId,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListFixtures;\n },\n });\n};\n\nexport const useFixture = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'fixture', id],\n queryFn: async () => {\n const result = await athQuery<{ athFixture: AthFixture | null }>(\n `query AthFixture($id: ID!) {\n athFixture(id: $id) { ${FIXTURE_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athFixture;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useStandings = (competition: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'standings', competition],\n queryFn: async () => {\n const result = await athQuery<{ athStandings: AthStandingConnection }>(\n `query AthStandings($competition: String!) {\n athStandings(competition: $competition) { items { ${STANDING_FIELDS} } }\n }`,\n { competition },\n apiGatewayUrl\n );\n return result.athStandings.items;\n },\n enabled: Boolean(competition),\n });\n};\n\nexport const useScheduleFixture = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateFixtureInput) => {\n const result = await athQuery<{ athScheduleFixture: AthFixture }>(\n `mutation AthScheduleFixture($input: AthCreateFixtureInput!) {\n athScheduleFixture(input: $input) { ${FIXTURE_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athScheduleFixture;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixtures'] });\n },\n });\n};\n\nexport const useRecordFixtureResult = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthRecordResultInput }) => {\n const result = await athQuery<{ athRecordFixtureResult: AthFixture }>(\n `mutation AthRecordFixtureResult($id: ID!, $input: AthRecordResultInput!) {\n athRecordFixtureResult(id: $id, input: $input) { ${FIXTURE_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athRecordFixtureResult;\n },\n onSuccess: (fixture) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixtures'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixture', fixture.id] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'standings'] });\n },\n });\n};\n\n// ============================================================================\n// Programs & drills\n// ============================================================================\n\nexport interface ProgramFilters {\n sport?: AthSport;\n level?: AthProgramLevel;\n status?: AthProgramStatus;\n pagination?: PaginationInput;\n}\n\nexport const usePrograms = (filters: ProgramFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'programs', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListPrograms: AthProgramConnection }>(\n `query AthListPrograms($sport: AthSport, $level: AthProgramLevel, $status: AthProgramStatus, $pagination: PaginationInput) {\n athListPrograms(sport: $sport, level: $level, status: $status, pagination: $pagination) {\n items { ${PROGRAM_FIELDS} drills { items { id } } }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n sport: filters.sport,\n level: filters.level,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListPrograms;\n },\n });\n};\n\nexport const useProgram = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'program', id],\n queryFn: async () => {\n const result = await athQuery<{ athProgram: AthProgram | null }>(\n `query AthProgram($id: ID!) {\n athProgram(id: $id) {\n ${PROGRAM_FIELDS}\n drills { items { ${DRILL_FIELDS} } }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athProgram;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useCreateProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateProgramInput) => {\n const result = await athQuery<{ athCreateProgram: AthProgram }>(\n `mutation AthCreateProgram($input: AthCreateProgramInput!) {\n athCreateProgram(input: $input) { ${PROGRAM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athCreateProgram;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n },\n });\n};\n\nexport const useUpdateProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthUpdateProgramInput }) => {\n const result = await athQuery<{ athUpdateProgram: AthProgram }>(\n `mutation AthUpdateProgram($id: ID!, $input: AthUpdateProgramInput!) {\n athUpdateProgram(id: $id, input: $input) { ${PROGRAM_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athUpdateProgram;\n },\n onSuccess: (program) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', program.id] });\n },\n });\n};\n\nexport const useTransitionProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, status }: { id: string; status: AthProgramStatus }) => {\n const result = await athQuery<{ athTransitionProgram: AthProgram }>(\n `mutation AthTransitionProgram($id: ID!, $status: AthProgramStatus!) {\n athTransitionProgram(id: $id, status: $status) { ${PROGRAM_FIELDS} }\n }`,\n { id, status },\n apiGatewayUrl\n );\n return result.athTransitionProgram;\n },\n onSuccess: (program) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', program.id] });\n },\n });\n};\n\nexport const useArchiveProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athArchiveProgram: boolean }>(\n `mutation AthArchiveProgram($id: ID!) { athArchiveProgram(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athArchiveProgram;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n },\n });\n};\n\nexport const useAddDrill = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthAddDrillInput) => {\n const result = await athQuery<{ athAddDrill: AthDrill }>(\n `mutation AthAddDrill($input: AthAddDrillInput!) {\n athAddDrill(input: $input) { ${DRILL_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athAddDrill;\n },\n onSuccess: (drill) => {\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'program', drill.programId],\n });\n },\n });\n};\n\nexport const useRemoveDrill = (programId?: string) => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athRemoveDrill: boolean }>(\n `mutation AthRemoveDrill($id: ID!) { athRemoveDrill(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athRemoveDrill;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', programId] });\n },\n });\n};\n\n// ============================================================================\n// Coaching sessions\n// ============================================================================\n\nexport interface SessionFilters {\n athleteId?: string;\n coachId?: string;\n status?: AthSessionStatus;\n pagination?: PaginationInput;\n}\n\nexport const useSessions = (filters: SessionFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'sessions', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListSessions: AthSessionConnection }>(\n `query AthListSessions($athleteId: ID, $coachId: ID, $status: AthSessionStatus, $pagination: PaginationInput) {\n athListSessions(athleteId: $athleteId, coachId: $coachId, status: $status, pagination: $pagination) {\n items { ${SESSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n athleteId: filters.athleteId,\n coachId: filters.coachId,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListSessions;\n },\n });\n};\n\nexport const useSession = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'session', id],\n queryFn: async () => {\n const result = await athQuery<{ athSession: AthSession | null }>(\n `query AthSession($id: ID!) {\n athSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athSession;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useBookSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthBookSessionInput) => {\n const result = await athQuery<{ athBookSession: AthSession }>(\n `mutation AthBookSession($input: AthBookSessionInput!) {\n athBookSession(input: $input) { ${SESSION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athBookSession;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n },\n });\n};\n\nexport const useConfirmSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athConfirmSession: AthSession }>(\n `mutation AthConfirmSession($id: ID!) {\n athConfirmSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athConfirmSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\nexport const useCompleteSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input?: AthCompleteSessionInput }) => {\n const result = await athQuery<{ athCompleteSession: AthSession }>(\n `mutation AthCompleteSession($id: ID!, $input: AthCompleteSessionInput) {\n athCompleteSession(id: $id, input: $input) { ${SESSION_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athCompleteSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\nexport const useCancelSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athCancelSession: AthSession }>(\n `mutation AthCancelSession($id: ID!) {\n athCancelSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athCancelSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\n// ============================================================================\n// Workout logs & performance metrics\n// ============================================================================\n\nexport interface WorkoutLogFilters {\n athleteId?: string;\n programId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useWorkoutLogs = (filters: WorkoutLogFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'workout-logs', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListWorkoutLogs: AthWorkoutLogConnection }>(\n `query AthListWorkoutLogs($athleteId: ID, $programId: ID, $pagination: PaginationInput) {\n athListWorkoutLogs(athleteId: $athleteId, programId: $programId, pagination: $pagination) {\n items { ${WORKOUT_LOG_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n athleteId: filters.athleteId,\n programId: filters.programId,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListWorkoutLogs;\n },\n });\n};\n\nexport const usePerformanceMetrics = (athleteId: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'metrics', athleteId],\n queryFn: async () => {\n const result = await athQuery<{ athPerformanceMetrics: AthPerformanceMetricConnection }>(\n `query AthPerformanceMetrics($athleteId: ID!) {\n athPerformanceMetrics(athleteId: $athleteId) { items { ${METRIC_FIELDS} } }\n }`,\n { athleteId },\n apiGatewayUrl\n );\n return result.athPerformanceMetrics.items;\n },\n enabled: Boolean(athleteId),\n });\n};\n\nexport const useLogWorkout = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthLogWorkoutInput) => {\n const result = await athQuery<{ athLogWorkout: AthWorkoutLog }>(\n `mutation AthLogWorkout($input: AthLogWorkoutInput!) {\n athLogWorkout(input: $input) { ${WORKOUT_LOG_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athLogWorkout;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'workout-logs'] });\n },\n });\n};\n\nexport const useRecordMetric = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthRecordMetricInput) => {\n const result = await athQuery<{ athRecordMetric: AthPerformanceMetric }>(\n `mutation AthRecordMetric($input: AthRecordMetricInput!) {\n athRecordMetric(input: $input) { ${METRIC_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athRecordMetric;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'metrics'] });\n },\n });\n};\n\n// ============================================================================\n// Availability / RSVP (BOFF-4134)\n// ============================================================================\n\nconst AVAILABILITY_FIELDS = `\n id workspaceId memberId fixtureId sessionId response respondedAt note\n createdAt updatedAt\n member { id displayName role }\n`;\n\nexport interface AvailabilityFilters {\n fixtureId?: string;\n sessionId?: string;\n}\n\n/** Lists availability responses for a fixture OR a session. */\nexport const useAvailability = (filters: AvailabilityFilters) => {\n const { apiGatewayUrl } = useAthleteBridge();\n const { fixtureId, sessionId } = filters;\n return useQuery({\n queryKey: ['atheletebridge', 'availability', fixtureId ?? null, sessionId ?? null],\n queryFn: async () => {\n const result = await athQuery<{ athListAvailability: AthAvailabilityConnection }>(\n `query AthListAvailability($fixtureId: ID, $sessionId: ID) {\n athListAvailability(fixtureId: $fixtureId, sessionId: $sessionId) {\n items { ${AVAILABILITY_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { fixtureId, sessionId },\n apiGatewayUrl\n );\n return result.athListAvailability.items;\n },\n enabled: Boolean(fixtureId || sessionId),\n });\n};\n\nexport const useSetAvailability = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthSetAvailabilityInput) => {\n const result = await athQuery<{ athSetAvailability: AthAvailability }>(\n `mutation AthSetAvailability($input: AthSetAvailabilityInput!) {\n athSetAvailability(input: $input) { ${AVAILABILITY_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athSetAvailability;\n },\n onSuccess: (row) => {\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'availability', row.fixtureId ?? null, row.sessionId ?? null],\n });\n },\n });\n};\n\n/**\n * Nudges roster members who have not yet responded. Publishes a domain event\n * for a future notification consumer to deliver — it does not itself send\n * push/email (no delivery channel is wired up yet). Returns the number of\n * members nudged.\n */\nexport const useRemindAvailability = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: { fixtureId?: string; sessionId?: string }) => {\n const result = await athQuery<{ athRemindAvailability: number }>(\n `mutation AthRemindAvailability($fixtureId: ID, $sessionId: ID) {\n athRemindAvailability(fixtureId: $fixtureId, sessionId: $sessionId)\n }`,\n input,\n apiGatewayUrl\n );\n return result.athRemindAvailability;\n },\n });\n};\n\n// ============================================================================\n// Workspace files — drill demo videos live in wspace-files-svc, reached through\n// the same workspace gateway. Blobs never touch the atheletebridge subgraph.\n// ============================================================================\n\nconst fileExtensionOf = (fileName: string): string => {\n const dot = fileName.lastIndexOf('.');\n return dot >= 0 ? fileName.slice(dot + 1).toLowerCase() : 'bin';\n};\n\n/**\n * Upload a file to wspace-files-svc via the standard three-step flow\n * (initiateUpload → presigned PUT → completeUpload) and resolve the new file id,\n * ready to pass as `AthAddDrillInput.videoFileId`.\n */\nexport const useUploadFile = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useCallback(\n async (file: File): Promise<string> => {\n const initiated = await athQuery<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation AthInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) { uploadUrl session { id } }\n }`,\n {\n input: {\n fileName: file.name,\n extension: fileExtensionOf(file.name),\n fileSize: file.size,\n mimeType: file.type || 'application/octet-stream',\n visibility: 'WORKSPACE',\n },\n },\n apiGatewayUrl\n );\n\n const uploadUrl = initiated.initiateUpload.uploadUrl;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('Upload could not be initialised.');\n }\n\n const put = await fetch(uploadUrl, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\n });\n if (!put.ok) {\n throw new Error(`Storage rejected the upload (HTTP ${put.status}).`);\n }\n\n const completed = await athQuery<{ completeUpload: { fileId: string } }>(\n `mutation AthCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) { fileId }\n }`,\n { input: { sessionId } },\n apiGatewayUrl\n );\n return completed.completeUpload.fileId;\n },\n [apiGatewayUrl]\n );\n};\n\nexport interface WorkspaceFileRef {\n id: string;\n name: string;\n downloadUrl: string | null;\n}\n\n/**\n * Resolve a wspace-files File (a drill's demo video) by id, on demand. Kept as a\n * separate lazy read so a missing/expired blob can never break the program page.\n */\nexport const useFile = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'file', id],\n queryFn: async () => {\n const result = await athQuery<{ getFile: WorkspaceFileRef | null }>(\n `query AthGetFile($id: ID!) {\n getFile(id: $id) { id name downloadUrl }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.getFile;\n },\n enabled: Boolean(id),\n staleTime: 60_000,\n });\n};\n\n// ============================================================================\n// User directory (global gateway) — resolve real users for team member add so a\n// coach never has to paste a raw UUID.\n// ============================================================================\n\nexport interface DirectoryUser {\n id: string;\n name: string | null;\n firstName: string | null;\n lastName: string | null;\n email: string;\n username: string;\n}\n\n/** Best display name for a directory user (name → first+last → username → email). */\nexport const directoryUserName = (user: DirectoryUser): string => {\n if (user.name?.trim()) return user.name.trim();\n const full = [user.firstName, user.lastName].filter(Boolean).join(' ').trim();\n if (full) return full;\n if (user.username?.trim()) return user.username.trim();\n return user.email;\n};\n\n/**\n * Search the tenant user directory by name/email. Enabled once the term is at\n * least two characters; failures are surfaced to the caller (which offers a\n * manual-id fallback) rather than as a global error toast.\n */\nexport const useUserSearch = (search: string) => {\n const term = search.trim();\n return useQuery({\n queryKey: ['atheletebridge', 'user-search', term],\n queryFn: async () => {\n const result = await athGlobalQuery<{\n users: { users: DirectoryUser[]; hasMore: boolean };\n }>(\n `query AthUserSearch($filter: UserFilterInput, $pagination: PaginationInput) {\n users(filter: $filter, pagination: $pagination) {\n users { id name firstName lastName email username }\n hasMore\n }\n }`,\n { filter: { search: term }, pagination: { limit: 8, offset: 0 } }\n );\n return result.users.users;\n },\n enabled: term.length >= 2,\n staleTime: 30_000,\n retry: false,\n });\n};\n\n// ============================================================================\n// User id resolution (global gateway) — resolve a set of user ids to real\n// names/emails for identity chips (coach/athlete), mirroring the UserSearchSelect\n// directory pattern. Batched into one aliased request, cached, and RBAC-safe:\n// on any error it degrades to an empty map so callers fall back to id-seeded\n// monograms rather than surfacing an error toast.\n// ============================================================================\n\nexport interface ResolvedUser {\n id: string;\n name: string;\n email: string | null;\n}\n\n/**\n * Resolve the given user ids to `{ id, name, email }` via the global `user(id)`\n * query (one aliased round-trip). Unknown / forbidden ids are simply absent from\n * the returned map.\n */\nexport const useUsersByIds = (ids: ReadonlyArray<string | null | undefined>) => {\n const unique = useMemo(\n () =>\n Array.from(\n new Set(ids.filter((value): value is string => Boolean(value && value.trim())))\n ).sort(),\n [ids]\n );\n\n return useQuery({\n queryKey: ['atheletebridge', 'users-by-id', unique],\n queryFn: async (): Promise<Record<string, ResolvedUser>> => {\n if (unique.length === 0) {\n return {};\n }\n const varDefs = unique.map((_, i) => `$id${i}: ID!`).join(', ');\n const selections = unique\n .map((_, i) => `u${i}: user(id: $id${i}) { id name firstName lastName email username }`)\n .join('\\n');\n const variables: Record<string, string> = {};\n unique.forEach((id, i) => {\n variables[`id${i}`] = id;\n });\n\n // Read tolerantly: don't throw on partial/RBAC errors — keep whatever\n // aliases resolved so a single forbidden id can't blank the whole feed.\n const response = await graphqlFetch<Record<string, DirectoryUser | null>>({\n gateway: 'global',\n query: `query AthUsersByIds(${varDefs}) {\\n${selections}\\n}`,\n variables,\n suppressGlobalErrorEvent: true,\n });\n\n const data = response.data ?? {};\n const map: Record<string, ResolvedUser> = {};\n for (const value of Object.values(data)) {\n if (value && value.id) {\n map[value.id] = {\n id: value.id,\n name: directoryUserName(value),\n email: value.email ?? null,\n };\n }\n }\n return map;\n },\n enabled: unique.length > 0,\n staleTime: 5 * 60_000,\n retry: false,\n });\n};\n\nexport interface UserDirectory {\n byId: Record<string, ResolvedUser>;\n isLoading: boolean;\n isError: boolean;\n /** Resolved display name for an id, or undefined while unknown. */\n nameOf: (id: string | null | undefined) => string | undefined;\n /** Resolved email for an id, or undefined while unknown. */\n emailOf: (id: string | null | undefined) => string | null | undefined;\n}\n\n/**\n * Convenience wrapper over {@link useUsersByIds} returning name/email lookup\n * helpers, so identity components can resolve ids without touching the query.\n */\nexport const useUserDirectory = (ids: ReadonlyArray<string | null | undefined>): UserDirectory => {\n const query = useUsersByIds(ids);\n const byId = query.data ?? {};\n return {\n byId,\n isLoading: query.isLoading,\n isError: query.isError,\n nameOf: (id) => (id ? byId[id]?.name : undefined),\n emailOf: (id) => (id ? byId[id]?.email : undefined),\n };\n};\n\n// ============================================================================\n// Recent activity — a unified, time-ordered feed derived PURELY from records\n// already loaded on the page (teams/fixtures/sessions/programs). No extra\n// network, no fabricated events: every item maps to a real timestamp on a real\n// row.\n// ============================================================================\n\nexport type ActivityType =\n 'team-created' | 'member-added' | 'fixture-updated' | 'program-published' | 'session-completed';\n\nexport interface ActivityItem {\n id: string;\n type: ActivityType;\n title: string;\n subtitle?: string;\n /** ISO timestamp the event occurred at. */\n at: string;\n entityId: string;\n entityKind: 'team' | 'fixture' | 'session' | 'program';\n}\n\nexport interface RecentActivityInput {\n teams?: AthTeam[];\n fixtures?: AthFixture[];\n sessions?: AthSession[];\n programs?: AthProgram[];\n /** Max items to return (newest first). Default 8. */\n limit?: number;\n}\n\n/** Derive a typed, time-ordered activity feed from already-loaded records. */\nexport const useRecentActivity = ({\n teams = [],\n fixtures = [],\n sessions = [],\n programs = [],\n limit = 8,\n}: RecentActivityInput): ActivityItem[] =>\n useMemo(() => {\n const items: ActivityItem[] = [];\n\n for (const team of teams) {\n items.push({\n id: `team-${team.id}`,\n type: 'team-created',\n title: `Team ${team.name} created`,\n at: team.createdAt,\n entityId: team.id,\n entityKind: 'team',\n });\n for (const member of team.members?.items ?? []) {\n items.push({\n id: `member-${member.id}`,\n type: 'member-added',\n title: `${member.displayName} added to ${team.name}`,\n at: member.joinedAt ?? member.createdAt,\n entityId: team.id,\n entityKind: 'team',\n });\n }\n }\n\n for (const program of programs) {\n if (program.status === 'PUBLISHED') {\n items.push({\n id: `program-${program.id}`,\n type: 'program-published',\n title: `${program.name} published`,\n at: program.updatedAt,\n entityId: program.id,\n entityKind: 'program',\n });\n }\n }\n\n for (const fixture of fixtures) {\n const home = fixture.homeTeam?.name ?? 'Home';\n const away = fixture.awayTeam?.name ?? (fixture.awayTeamId ? 'Away' : 'TBD');\n const hasScore = fixture.homeScore !== null && fixture.awayScore !== null;\n items.push({\n id: `fixture-${fixture.id}`,\n type: 'fixture-updated',\n title: hasScore\n ? `${home} ${fixture.homeScore}–${fixture.awayScore} ${away}`\n : `Fixture ${home} vs ${away} updated`,\n subtitle: fixture.competition ?? undefined,\n at: fixture.updatedAt,\n entityId: fixture.id,\n entityKind: 'fixture',\n });\n }\n\n for (const session of sessions) {\n if (session.status === 'COMPLETED') {\n items.push({\n id: `session-${session.id}`,\n type: 'session-completed',\n title: `Session \"${session.title}\" completed`,\n subtitle: session.rating !== null ? `Rated ${session.rating}/5` : undefined,\n at: session.completedAt ?? session.updatedAt,\n entityId: session.id,\n entityKind: 'session',\n });\n }\n }\n\n return items\n .filter((item) => Boolean(item.at) && !Number.isNaN(new Date(item.at).getTime()))\n .sort((a, b) => new Date(b.at).getTime() - new Date(a.at).getTime())\n .slice(0, limit);\n }, [teams, fixtures, sessions, programs, limit]);\n\n// ============================================================================\n// Athlete profile — compose a team member with that athlete's real training\n// data (performance metrics + workout logs + coaching sessions), keyed off the\n// member's userId (the athleteId used across the training tables).\n// ============================================================================\n\nexport interface AthleteProfile {\n /** The resolved team member, or null while loading / not found. */\n member: AthMember | null;\n /** The user id used across sessions / metrics / workouts. */\n athleteId: string | undefined;\n metrics: AthPerformanceMetric[];\n workouts: AthWorkoutLog[];\n sessions: AthSession[];\n isLoading: boolean;\n isError: boolean;\n}\n\n/** Compose a member + their metrics, workout logs and sessions into a profile. */\nexport const useAthleteProfile = (\n teamId: string | undefined,\n memberId: string | undefined\n): AthleteProfile => {\n const membersQuery = useTeamMembers(teamId);\n const member = useMemo(\n () => (membersQuery.data ?? []).find((m) => m.id === memberId) ?? null,\n [membersQuery.data, memberId]\n );\n const athleteId = member?.userId;\n\n const metricsQuery = usePerformanceMetrics(athleteId);\n const workoutsQuery = useWorkoutLogs({ athleteId, pagination: { limit: 100 } });\n const sessionsQuery = useSessions({ athleteId, pagination: { limit: 100 } });\n\n return {\n member,\n athleteId,\n metrics: metricsQuery.data ?? [],\n workouts: workoutsQuery.data?.items ?? [],\n sessions: sessionsQuery.data?.items ?? [],\n isLoading:\n membersQuery.isLoading ||\n (Boolean(athleteId) &&\n (metricsQuery.isLoading || workoutsQuery.isLoading || sessionsQuery.isLoading)),\n isError:\n membersQuery.isError ||\n metricsQuery.isError ||\n workoutsQuery.isError ||\n sessionsQuery.isError,\n };\n};\n\n// ============================================================================\n// Coach analytics — honest aggregates over real sessions / workouts / fixtures\n// / standings. No fabricated wellness or injury data.\n// ============================================================================\n\nexport interface SessionStatusCount {\n status: AthSessionStatus;\n count: number;\n}\n\nexport interface RatingByCoach {\n coachId: string;\n avgRating: number;\n count: number;\n}\n\nexport interface AnalyticsTrendPoint {\n label: string;\n value: number;\n}\n\nexport interface CoachAnalytics {\n totalSessions: number;\n ratedCount: number;\n /** Mean of session.rating over rated sessions, or null when none rated. */\n avgRating: number | null;\n completedCount: number;\n completedPct: number;\n sessionsByStatus: SessionStatusCount[];\n ratingByCoach: RatingByCoach[];\n coachIds: string[];\n /** Acute (last 7d) vs chronic (avg weekly over 28d) workout load + ratio. */\n trainingLoad: { acute: number; chronic: number; ratio: number | null };\n /** Weekly total workout load, oldest → newest (from real totalLoad). */\n loadTrend: AnalyticsTrendPoint[];\n /** Weekly session count, oldest → newest. */\n sessionTrend: AnalyticsTrendPoint[];\n /** Standings snapshot for the resolved competition. */\n standings: AthStanding[];\n competition: string | null;\n isLoading: boolean;\n isError: boolean;\n}\n\nexport interface CoachAnalyticsOptions {\n /** Competition for the standings snapshot; else the most-used one in fixtures. */\n competition?: string;\n /** Rows to sample from each list query. Default 200. */\n sampleLimit?: number;\n}\n\n/** Bucket rows into `weeks` weekly totals ending at \"now\" (oldest → newest). */\nfunction weeklyTrend<T>(\n rows: readonly T[],\n getDate: (row: T) => string,\n getValue: (row: T) => number,\n weeks = 8\n): AnalyticsTrendPoint[] {\n const MS_WEEK = 7 * 86_400_000;\n const now = Date.now();\n const buckets = Array.from({ length: weeks }, () => 0);\n for (const row of rows) {\n const time = new Date(getDate(row)).getTime();\n if (Number.isNaN(time)) {\n continue;\n }\n const age = now - time;\n if (age < 0) {\n continue;\n }\n const weeksAgo = Math.floor(age / MS_WEEK);\n if (weeksAgo >= 0 && weeksAgo < weeks) {\n buckets[weeks - 1 - weeksAgo] += getValue(row);\n }\n }\n return buckets.map((value, i) => {\n const ago = weeks - 1 - i;\n return { label: ago === 0 ? 'Now' : `${ago}w`, value };\n });\n}\n\n/** Aggregate real coaching data for the analytics dashboard. */\nexport const useCoachAnalytics = (options: CoachAnalyticsOptions = {}): CoachAnalytics => {\n const { competition: competitionOpt, sampleLimit = 200 } = options;\n\n const sessionsQuery = useSessions({ pagination: { limit: sampleLimit } });\n const workoutsQuery = useWorkoutLogs({ pagination: { limit: sampleLimit } });\n const fixturesQuery = useFixtures({ pagination: { limit: sampleLimit } });\n\n const sessions = useMemo(() => sessionsQuery.data?.items ?? [], [sessionsQuery.data]);\n const workouts = useMemo(() => workoutsQuery.data?.items ?? [], [workoutsQuery.data]);\n const fixtures = useMemo(() => fixturesQuery.data?.items ?? [], [fixturesQuery.data]);\n\n // Resolve the competition for the standings snapshot.\n const competition = useMemo(() => {\n if (competitionOpt) {\n return competitionOpt;\n }\n const counts = new Map<string, number>();\n for (const fixture of fixtures) {\n if (fixture.competition) {\n counts.set(fixture.competition, (counts.get(fixture.competition) ?? 0) + 1);\n }\n }\n let top: string | null = null;\n let best = 0;\n for (const [comp, n] of counts) {\n if (n > best) {\n best = n;\n top = comp;\n }\n }\n return top;\n }, [competitionOpt, fixtures]);\n\n const standingsQuery = useStandings(competition ?? undefined);\n\n return useMemo<CoachAnalytics>(() => {\n const totalSessions = sessions.length;\n const rated = sessions.filter((s) => s.rating !== null);\n const ratedCount = rated.length;\n const avgRating =\n ratedCount > 0 ? rated.reduce((acc, s) => acc + (s.rating ?? 0), 0) / ratedCount : null;\n const completedCount = sessions.filter((s) => s.status === 'COMPLETED').length;\n const completedPct = totalSessions > 0 ? (completedCount / totalSessions) * 100 : 0;\n\n const statusOrder: AthSessionStatus[] = [\n 'REQUESTED',\n 'CONFIRMED',\n 'COMPLETED',\n 'CANCELLED',\n 'NO_SHOW',\n ];\n const statusCounts = new Map<AthSessionStatus, number>();\n for (const s of sessions) {\n statusCounts.set(s.status, (statusCounts.get(s.status) ?? 0) + 1);\n }\n const sessionsByStatus = statusOrder\n .map((status) => ({ status, count: statusCounts.get(status) ?? 0 }))\n .filter((entry) => entry.count > 0);\n\n const byCoach = new Map<string, { sum: number; count: number }>();\n for (const s of rated) {\n const entry = byCoach.get(s.coachId) ?? { sum: 0, count: 0 };\n entry.sum += s.rating ?? 0;\n entry.count += 1;\n byCoach.set(s.coachId, entry);\n }\n const ratingByCoach = Array.from(byCoach.entries())\n .map(([coachId, { sum, count }]) => ({ coachId, avgRating: sum / count, count }))\n .sort((a, b) => b.avgRating - a.avgRating);\n\n const coachIds = Array.from(new Set(sessions.map((s) => s.coachId)));\n\n // Training load (ACWR) from real workout totalLoad.\n const now = Date.now();\n const DAY = 86_400_000;\n let acute = 0;\n let chronic28 = 0;\n for (const w of workouts) {\n const load = w.totalLoad ?? 0;\n const age = now - new Date(w.performedAt).getTime();\n if (Number.isNaN(age)) {\n continue;\n }\n if (age <= 7 * DAY) {\n acute += load;\n }\n if (age <= 28 * DAY) {\n chronic28 += load;\n }\n }\n const chronic = chronic28 / 4;\n const ratio = chronic > 0 ? acute / chronic : null;\n\n const loadTrend = weeklyTrend(\n workouts,\n (w) => w.performedAt,\n (w) => w.totalLoad ?? 0\n );\n const sessionTrend = weeklyTrend(\n sessions,\n (s) => s.scheduledAt,\n () => 1\n );\n\n return {\n totalSessions,\n ratedCount,\n avgRating,\n completedCount,\n completedPct,\n sessionsByStatus,\n ratingByCoach,\n coachIds,\n trainingLoad: { acute, chronic, ratio },\n loadTrend,\n sessionTrend,\n standings: standingsQuery.data ?? [],\n competition,\n isLoading:\n sessionsQuery.isLoading ||\n workoutsQuery.isLoading ||\n fixturesQuery.isLoading ||\n (Boolean(competition) && standingsQuery.isLoading),\n isError:\n sessionsQuery.isError ||\n workoutsQuery.isError ||\n fixturesQuery.isError ||\n standingsQuery.isError,\n };\n }, [\n sessions,\n workouts,\n competition,\n standingsQuery.data,\n standingsQuery.isLoading,\n standingsQuery.isError,\n sessionsQuery.isLoading,\n sessionsQuery.isError,\n workoutsQuery.isLoading,\n workoutsQuery.isError,\n fixturesQuery.isLoading,\n fixturesQuery.isError,\n ]);\n};\n\n// ============================================================================\n// AI operations (BOFF-3574) — model selection, the append-only decision ledger\n// and the coach's review actions. Every field below is live in the composed\n// production supergraph; nothing here is mocked.\n// ============================================================================\n\nconst AGENT_RUN_FIELDS = `\n id workspaceId actorId actorType provider model trigger status error\n promptSummary inputRefs decision tokensIn tokensOut latencyMs costUsd\n createdAt\n`;\n\nconst AI_CONFIG_FIELDS = `\n workspaceId provider model autoPilotEnabled autoPilotLastRunAt updatedAt\n`;\n\n/** The selectable (provider, model) pairs, with the platform default marked. */\nexport const useAiModelCatalog = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'ai-model-catalog'],\n queryFn: async () => {\n const result = await athQuery<{ athAiModelCatalog: AthAiModelInfo[] }>(\n `query AthAiModelCatalog {\n athAiModelCatalog { provider model label isDefault capabilities }\n }`,\n {},\n apiGatewayUrl\n );\n return result.athAiModelCatalog;\n },\n // A static catalog — refetching it on every focus is pure noise.\n staleTime: 30 * 60_000,\n });\n};\n\n/**\n * The workspace's saved AI config, or null when it has never overridden the\n * platform default chain (the query is nullable by design — a null here means\n * \"using the platform default\", not \"failed\").\n */\nexport const useWorkspaceAiConfig = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'ai-config'],\n queryFn: async () => {\n const result = await athQuery<{ athWorkspaceAiConfig: AthWorkspaceAiConfig | null }>(\n `query AthWorkspaceAiConfig {\n athWorkspaceAiConfig { ${AI_CONFIG_FIELDS} }\n }`,\n {},\n apiGatewayUrl\n );\n return result.athWorkspaceAiConfig;\n },\n });\n};\n\nexport const useSetWorkspaceAiModel = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ provider, model }: { provider: string; model: string }) => {\n const result = await athQuery<{ athSetWorkspaceAiModel: AthWorkspaceAiConfig }>(\n `mutation AthSetWorkspaceAiModel($provider: String!, $model: String!) {\n athSetWorkspaceAiModel(provider: $provider, model: $model) { ${AI_CONFIG_FIELDS} }\n }`,\n { provider, model },\n apiGatewayUrl\n );\n return result.athSetWorkspaceAiModel;\n },\n onSuccess: (config) => {\n queryClient.setQueryData(['atheletebridge', 'ai-config'], config);\n },\n });\n};\n\n/**\n * Opt this workspace into (or out of) the autonomous coaching loop. The loop is\n * ALSO gated per deployment by `ATHELETEBRIDGE_AGENT_LOOP_ENABLED`, so enabling\n * it here does not on its own start anything — and even when it does run it\n * only ever records a PROPOSED adjustment for a coach to resolve.\n */\nexport const useSetWorkspaceAutoPilot = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (enabled: boolean) => {\n const result = await athQuery<{ athSetWorkspaceAutoPilot: AthWorkspaceAiConfig }>(\n `mutation AthSetWorkspaceAutoPilot($enabled: Boolean!) {\n athSetWorkspaceAutoPilot(enabled: $enabled) { ${AI_CONFIG_FIELDS} }\n }`,\n { enabled },\n apiGatewayUrl\n );\n return result.athSetWorkspaceAutoPilot;\n },\n onSuccess: (config) => {\n queryClient.setQueryData(['atheletebridge', 'ai-config'], config);\n },\n });\n};\n\nexport interface AgentRunFilters {\n /** Rows per page. */\n limit?: number;\n /** Rows to skip. Offset-based: the composed PaginationInput has no cursor. */\n offset?: number;\n}\n\n/** The workspace's AI decision ledger, most recent first. */\nexport const useAgentRuns = (filters: AgentRunFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n const { limit = 10, offset = 0 } = filters;\n return useQuery({\n queryKey: ['atheletebridge', 'agent-runs', limit, offset],\n queryFn: async () => {\n const result = await athQuery<{ athAgentRuns: AthAgentRunConnection }>(\n `query AthAgentRuns($pagination: PaginationInput) {\n athAgentRuns(pagination: $pagination) {\n items { ${AGENT_RUN_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { pagination: { limit, offset } },\n apiGatewayUrl\n );\n return result.athAgentRuns;\n },\n });\n};\n\n/**\n * A single ledger row by id. Used to resolve the `extensions.agentRunId` an\n * `AI_PROVIDER_UNAVAILABLE` error carries, so the user sees the recorded\n * failure instead of a generic toast.\n */\nexport const useAgentRun = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'agent-run', id],\n queryFn: async () => {\n const result = await athQuery<{ athAgentRun: AthAgentRun | null }>(\n `query AthAgentRun($id: ID!) {\n athAgentRun(id: $id) { ${AGENT_RUN_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athAgentRun;\n },\n enabled: Boolean(id),\n retry: false,\n });\n};\n\n/**\n * A program with its AI provenance (`context.lastAiAdjustment`). Kept separate\n * from {@link useProgram} so the `context` selection — which only the AI\n * Operations page needs — stays off every other program read.\n */\nexport const useProgramAiState = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'program-ai-state', id],\n queryFn: async () => {\n const result = await athQuery<{\n athProgram: (Pick<AthProgram, 'id' | 'name' | 'status'> & { context: unknown }) | null;\n }>(\n `query AthProgramAiState($id: ID!) {\n athProgram(id: $id) { id name status context }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athProgram;\n },\n enabled: Boolean(id),\n retry: false,\n });\n};\n\n/**\n * Generate a DRAFT program from a live model call. Throws {@link AthApiError}\n * with code `AI_PROVIDER_UNAVAILABLE` (and the FAILED ledger row's id) when no\n * provider in the chain could answer.\n */\nexport const useGenerateProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthGenerateProgramInput) => {\n const result = await athQuery<{ athGenerateProgram: AthGenerateProgramResult }>(\n `mutation AthGenerateProgram($input: AthGenerateProgramInput!) {\n athGenerateProgram(input: $input) {\n agentRunId\n drillCount\n sessionCount\n program { ${PROGRAM_FIELDS} }\n }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athGenerateProgram;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'agent-runs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n },\n });\n};\n\n/**\n * Ask the AI to read an athlete's recent load/metric trend and PROPOSE an\n * adjustment onto a program. It changes no prescribed training load by itself —\n * a coach resolves it with {@link useOverrideAgentRun}.\n */\nexport const useAdjustPlanFromPerformance = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({\n athleteId,\n programId,\n requestedProvider,\n }: {\n athleteId: string;\n programId: string;\n requestedProvider?: string;\n }) => {\n const result = await athQuery<{ athAdjustPlanFromPerformance: AthPlanAdjustment }>(\n `mutation AthAdjustPlanFromPerformance($athleteId: ID!, $programId: ID!, $requestedProvider: String) {\n athAdjustPlanFromPerformance(athleteId: $athleteId, programId: $programId, requestedProvider: $requestedProvider) {\n agentRunId athleteId programId assessment rationale confidence\n recommendedWeeklyLoadChangePct flags appliedToProgram createdAt\n }\n }`,\n { athleteId, programId, requestedProvider },\n apiGatewayUrl\n );\n return result.athAdjustPlanFromPerformance;\n },\n onSuccess: (adjustment) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'agent-runs'] });\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'program-ai-state', adjustment.programId],\n });\n },\n });\n};\n\n/**\n * A coach's review of a prior AI decision. Recorded as a NEW, append-only\n * ledger row — never an edit of the original — which resolves the program's\n * `lastAiAdjustment` to ACCEPTED (same assessment) or OVERRIDDEN (a different\n * one).\n */\nexport const useOverrideAgentRun = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({\n runId,\n programId,\n assessment,\n note,\n }: {\n runId: string;\n programId: string;\n assessment: AthAiAssessment;\n note: string;\n }) => {\n const result = await athQuery<{ athOverrideAgentRun: AthAgentRunOverride }>(\n `mutation AthOverrideAgentRun($runId: ID!, $programId: ID!, $assessment: AthAiAssessment!, $note: String!) {\n athOverrideAgentRun(runId: $runId, programId: $programId, assessment: $assessment, note: $note) {\n id workspaceId programId supersedesRunId assessment note\n appliedToProgram createdAt\n }\n }`,\n { runId, programId, assessment, note },\n apiGatewayUrl\n );\n return result.athOverrideAgentRun;\n },\n onSuccess: (override) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'agent-runs'] });\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'program-ai-state', override.programId],\n });\n },\n });\n};\n\n// ============================================================================\n// Health probe\n// ============================================================================\n\nexport const useHealth = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'health'],\n queryFn: async () => {\n const result = await athQuery<{ athHealth: string }>(\n `query AthHealth { athHealth }`,\n {},\n apiGatewayUrl\n );\n return result.athHealth;\n },\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useAthleteBridgeApi.js","names":[],"sources":["../../../src/atheletebridge/hooks/useAthleteBridgeApi.ts"],"sourcesContent":["/**\n * AthleteBridge API hooks\n * @module atheletebridge/hooks\n *\n * Thin react-query wrappers over the real `wspace-atheletebridge-svc` federated\n * GraphQL contract, reached through the workspace gateway with the workspace\n * dual-token. Every query/mutation here maps 1:1 to an `ath*` root field in the\n * service's `.hive-schema.graphql`. NO MOCKS — all data is live.\n */\n\nimport { useCallback, useMemo } from 'react';\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { graphqlFetch, clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useAthleteBridge } from '../context';\nimport type {\n AthAgentRun,\n AthAgentRunConnection,\n AthAgentRunOverride,\n AthAiAssessment,\n AthAiModelInfo,\n AthAvailability,\n AthAvailabilityConnection,\n AthCompleteSessionInput,\n AthCreateFixtureInput,\n AthCreateProgramInput,\n AthCreateTeamInput,\n AthAddMemberInput,\n AthAddDrillInput,\n AthBookSessionInput,\n AthDrill,\n AthFixture,\n AthFixtureConnection,\n AthFixtureStatus,\n AthGenerateProgramInput,\n AthGenerateProgramResult,\n AthLogWorkoutInput,\n AthMember,\n AthMemberConnection,\n AthMemberStatus,\n AthPerformanceMetric,\n AthPerformanceMetricConnection,\n AthPlanAdjustment,\n AthProgram,\n AthProgramConnection,\n AthProgramLevel,\n AthProgramStatus,\n AthRecordMetricInput,\n AthRecordResultInput,\n AthSession,\n AthSessionConnection,\n AthSessionStatus,\n AthSetAvailabilityInput,\n AthSport,\n AthStanding,\n AthStandingConnection,\n AthTeam,\n AthTeamConnection,\n AthTeamStatus,\n AthUpdateProgramInput,\n AthUpdateTeamInput,\n AthWorkoutLog,\n AthWorkoutLogConnection,\n AthWorkspaceAiConfig,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Typed errors\n// ============================================================================\n\n/** `extensions.code` for an upstream AI provider that is down or unconfigured. */\nexport const AI_PROVIDER_UNAVAILABLE = 'AI_PROVIDER_UNAVAILABLE';\n\n/**\n * A GraphQL error surfaced with its `extensions` intact.\n *\n * The svc throws stable, typed errors (`NOT_FOUND`, `CONFLICT`,\n * `FAILED_PRECONDITION`, `AI_PROVIDER_UNAVAILABLE`, `BAD_USER_INPUT`) and, for\n * AI failures, an `extensions.agentRunId` pointing at the FAILED ledger row\n * that holds every attempt's raw upstream message. Flattening all of that into\n * `new Error(message)` is what made an unconfigured provider indistinguishable\n * from a crashed service, so the code and the run id are carried through to the\n * UI instead.\n */\nexport class AthApiError extends Error {\n /** `extensions.code`, or null when the server did not send one. */\n readonly code: string | null;\n /** `extensions.agentRunId` — the ledger row to inspect, when present. */\n readonly agentRunId: string | null;\n\n constructor(message: string, extensions?: Record<string, unknown>) {\n super(message);\n this.name = 'AthApiError';\n const code = extensions?.code;\n const agentRunId = extensions?.agentRunId;\n this.code = typeof code === 'string' && code ? code : null;\n this.agentRunId = typeof agentRunId === 'string' && agentRunId ? agentRunId : null;\n }\n}\n\n/** Whether an unknown thrown value is an AI-provider outage we can explain. */\nexport const isAiProviderUnavailable = (error: unknown): error is AthApiError =>\n error instanceof AthApiError && error.code === AI_PROVIDER_UNAVAILABLE;\n\n// ============================================================================\n// Low-level query wrapper\n// ============================================================================\n\nconst athQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n const first = response.errors[0];\n throw new AthApiError(first?.message ?? 'GraphQL request failed', first?.extensions);\n }\n\n if (!response.data) {\n throw new AthApiError('GraphQL response did not include data');\n }\n\n // This MFE bypasses Apollo, so fe-libs' short-lived graphqlFetch read cache\n // would otherwise serve pre-write data to the very next refetch issued by a\n // mutation's onSuccess invalidation. Clear it after any successful mutation so\n // every downstream read is fresh (fleet-wide stale-read-after-write fix).\n if (query.trimStart().startsWith('mutation')) {\n clearGraphqlFetchResponseCache();\n }\n\n return response.data;\n};\n\n// Global-gateway read wrapper — the workspace-scoped MFE reaches the tenant user\n// directory (for real user pickers) through the global gateway.\nconst athGlobalQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'global',\n query,\n variables,\n suppressGlobalErrorEvent: true,\n });\n\n if (response.errors?.length) {\n const first = response.errors[0];\n throw new AthApiError(first?.message ?? 'GraphQL request failed', first?.extensions);\n }\n\n if (!response.data) {\n throw new AthApiError('GraphQL response did not include data');\n }\n\n return response.data;\n};\n\n// ============================================================================\n// GraphQL fragments (inline strings — exact fields from the svc schema)\n// ============================================================================\n\nconst TEAM_FIELDS = `\n id workspaceId name sport status description ageGroup season homeVenue\n headCoachId tags createdAt updatedAt\n`;\n\nconst MEMBER_FIELDS = `\n id workspaceId teamId userId displayName role status position jerseyNumber\n height weight dominantSide notes joinedAt createdAt updatedAt\n`;\n\nconst FIXTURE_FIELDS = `\n id workspaceId type status competition round venue homeTeamId awayTeamId\n homeScore awayScore scheduledAt kickoffAt notes createdAt updatedAt\n homeTeam { id name sport }\n awayTeam { id name sport }\n`;\n\nconst STANDING_FIELDS = `\n id workspaceId competition teamId played won drawn lost goalsFor goalsAgainst\n points rank createdAt updatedAt\n team { id name sport }\n`;\n\nconst DRILL_FIELDS = `\n id workspaceId programId name description weekNumber orderIndex sets reps\n durationSec restSec createdAt updatedAt\n video { id }\n`;\n\nconst PROGRAM_FIELDS = `\n id workspaceId coachId name sport level status summary weeks isTemplate tags\n createdAt updatedAt\n`;\n\nconst SESSION_FIELDS = `\n id workspaceId coachId athleteId programId title type status location\n meetingUrl scheduledAt durationMin completedAt feedback rating\n createdAt updatedAt\n`;\n\nconst WORKOUT_LOG_FIELDS = `\n id workspaceId athleteId programId title status performedAt durationMin rpe\n totalLoad metrics notes createdAt updatedAt\n`;\n\nconst METRIC_FIELDS = `\n id workspaceId athleteId type label value unit source recordedAt\n createdAt updatedAt\n`;\n\n// ============================================================================\n// Teams\n// ============================================================================\n\nexport interface TeamFilters {\n sport?: AthSport;\n status?: AthTeamStatus;\n pagination?: PaginationInput;\n}\n\nexport const useTeams = (filters: TeamFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'teams', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListTeams: AthTeamConnection }>(\n `query AthListTeams($sport: AthSport, $status: AthTeamStatus, $pagination: PaginationInput) {\n athListTeams(sport: $sport, status: $status, pagination: $pagination) {\n items { ${TEAM_FIELDS} members { items { id } } }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n sport: filters.sport,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListTeams;\n },\n });\n};\n\nexport const useTeam = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'team', id],\n queryFn: async () => {\n const result = await athQuery<{ athTeam: AthTeam | null }>(\n `query AthTeam($id: ID!) {\n athTeam(id: $id) {\n ${TEAM_FIELDS}\n members { items { ${MEMBER_FIELDS} } }\n fixtures { items { ${FIXTURE_FIELDS} } }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athTeam;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useTeamMembers = (teamId: string | undefined, status?: AthMemberStatus) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'members', teamId, status],\n queryFn: async () => {\n const result = await athQuery<{ athListMembers: AthMemberConnection }>(\n `query AthListMembers($teamId: ID!, $status: AthMemberStatus) {\n athListMembers(teamId: $teamId, status: $status) { items { ${MEMBER_FIELDS} } }\n }`,\n { teamId, status },\n apiGatewayUrl\n );\n return result.athListMembers.items;\n },\n enabled: Boolean(teamId),\n });\n};\n\nexport const useCreateTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateTeamInput) => {\n const result = await athQuery<{ athCreateTeam: AthTeam }>(\n `mutation AthCreateTeam($input: AthCreateTeamInput!) {\n athCreateTeam(input: $input) { ${TEAM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athCreateTeam;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n },\n });\n};\n\nexport const useUpdateTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthUpdateTeamInput }) => {\n const result = await athQuery<{ athUpdateTeam: AthTeam }>(\n `mutation AthUpdateTeam($id: ID!, $input: AthUpdateTeamInput!) {\n athUpdateTeam(id: $id, input: $input) { ${TEAM_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athUpdateTeam;\n },\n onSuccess: (team) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', team.id] });\n },\n });\n};\n\nexport const useArchiveTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athArchiveTeam: boolean }>(\n `mutation AthArchiveTeam($id: ID!) { athArchiveTeam(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athArchiveTeam;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n },\n });\n};\n\nexport const useAddMember = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthAddMemberInput) => {\n const result = await athQuery<{ athAddMember: AthMember }>(\n `mutation AthAddMember($input: AthAddMemberInput!) {\n athAddMember(input: $input) { ${MEMBER_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athAddMember;\n },\n onSuccess: (member) => {\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'members', member.teamId],\n });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', member.teamId] });\n },\n });\n};\n\nexport const useRemoveMember = (teamId?: string) => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athRemoveMember: boolean }>(\n `mutation AthRemoveMember($id: ID!) { athRemoveMember(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athRemoveMember;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'members', teamId] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', teamId] });\n },\n });\n};\n\n// ============================================================================\n// Fixtures & standings\n// ============================================================================\n\nexport interface FixtureFilters {\n status?: AthFixtureStatus;\n teamId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useFixtures = (filters: FixtureFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'fixtures', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListFixtures: AthFixtureConnection }>(\n `query AthListFixtures($status: AthFixtureStatus, $teamId: ID, $pagination: PaginationInput) {\n athListFixtures(status: $status, teamId: $teamId, pagination: $pagination) {\n items { ${FIXTURE_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n teamId: filters.teamId,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListFixtures;\n },\n });\n};\n\nexport const useFixture = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'fixture', id],\n queryFn: async () => {\n const result = await athQuery<{ athFixture: AthFixture | null }>(\n `query AthFixture($id: ID!) {\n athFixture(id: $id) { ${FIXTURE_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athFixture;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useStandings = (competition: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'standings', competition],\n queryFn: async () => {\n const result = await athQuery<{ athStandings: AthStandingConnection }>(\n `query AthStandings($competition: String!) {\n athStandings(competition: $competition) { items { ${STANDING_FIELDS} } }\n }`,\n { competition },\n apiGatewayUrl\n );\n return result.athStandings.items;\n },\n enabled: Boolean(competition),\n });\n};\n\nexport const useScheduleFixture = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateFixtureInput) => {\n const result = await athQuery<{ athScheduleFixture: AthFixture }>(\n `mutation AthScheduleFixture($input: AthCreateFixtureInput!) {\n athScheduleFixture(input: $input) { ${FIXTURE_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athScheduleFixture;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixtures'] });\n },\n });\n};\n\nexport const useRecordFixtureResult = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthRecordResultInput }) => {\n const result = await athQuery<{ athRecordFixtureResult: AthFixture }>(\n `mutation AthRecordFixtureResult($id: ID!, $input: AthRecordResultInput!) {\n athRecordFixtureResult(id: $id, input: $input) { ${FIXTURE_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athRecordFixtureResult;\n },\n onSuccess: (fixture) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixtures'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixture', fixture.id] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'standings'] });\n },\n });\n};\n\n// ============================================================================\n// Programs & drills\n// ============================================================================\n\nexport interface ProgramFilters {\n sport?: AthSport;\n level?: AthProgramLevel;\n status?: AthProgramStatus;\n pagination?: PaginationInput;\n}\n\nexport const usePrograms = (filters: ProgramFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'programs', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListPrograms: AthProgramConnection }>(\n `query AthListPrograms($sport: AthSport, $level: AthProgramLevel, $status: AthProgramStatus, $pagination: PaginationInput) {\n athListPrograms(sport: $sport, level: $level, status: $status, pagination: $pagination) {\n items { ${PROGRAM_FIELDS} drills { items { id } } }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n sport: filters.sport,\n level: filters.level,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListPrograms;\n },\n });\n};\n\nexport const useProgram = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'program', id],\n queryFn: async () => {\n const result = await athQuery<{ athProgram: AthProgram | null }>(\n `query AthProgram($id: ID!) {\n athProgram(id: $id) {\n ${PROGRAM_FIELDS}\n drills { items { ${DRILL_FIELDS} } }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athProgram;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useCreateProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateProgramInput) => {\n const result = await athQuery<{ athCreateProgram: AthProgram }>(\n `mutation AthCreateProgram($input: AthCreateProgramInput!) {\n athCreateProgram(input: $input) { ${PROGRAM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athCreateProgram;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n },\n });\n};\n\nexport const useUpdateProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthUpdateProgramInput }) => {\n const result = await athQuery<{ athUpdateProgram: AthProgram }>(\n `mutation AthUpdateProgram($id: ID!, $input: AthUpdateProgramInput!) {\n athUpdateProgram(id: $id, input: $input) { ${PROGRAM_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athUpdateProgram;\n },\n onSuccess: (program) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', program.id] });\n },\n });\n};\n\nexport const useTransitionProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, status }: { id: string; status: AthProgramStatus }) => {\n const result = await athQuery<{ athTransitionProgram: AthProgram }>(\n `mutation AthTransitionProgram($id: ID!, $status: AthProgramStatus!) {\n athTransitionProgram(id: $id, status: $status) { ${PROGRAM_FIELDS} }\n }`,\n { id, status },\n apiGatewayUrl\n );\n return result.athTransitionProgram;\n },\n onSuccess: (program) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', program.id] });\n },\n });\n};\n\nexport const useArchiveProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athArchiveProgram: boolean }>(\n `mutation AthArchiveProgram($id: ID!) { athArchiveProgram(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athArchiveProgram;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n },\n });\n};\n\nexport const useAddDrill = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthAddDrillInput) => {\n const result = await athQuery<{ athAddDrill: AthDrill }>(\n `mutation AthAddDrill($input: AthAddDrillInput!) {\n athAddDrill(input: $input) { ${DRILL_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athAddDrill;\n },\n onSuccess: (drill) => {\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'program', drill.programId],\n });\n },\n });\n};\n\nexport const useRemoveDrill = (programId?: string) => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athRemoveDrill: boolean }>(\n `mutation AthRemoveDrill($id: ID!) { athRemoveDrill(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athRemoveDrill;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', programId] });\n },\n });\n};\n\n// ============================================================================\n// Coaching sessions\n// ============================================================================\n\nexport interface SessionFilters {\n athleteId?: string;\n coachId?: string;\n status?: AthSessionStatus;\n pagination?: PaginationInput;\n}\n\nexport const useSessions = (filters: SessionFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'sessions', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListSessions: AthSessionConnection }>(\n `query AthListSessions($athleteId: ID, $coachId: ID, $status: AthSessionStatus, $pagination: PaginationInput) {\n athListSessions(athleteId: $athleteId, coachId: $coachId, status: $status, pagination: $pagination) {\n items { ${SESSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n athleteId: filters.athleteId,\n coachId: filters.coachId,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListSessions;\n },\n });\n};\n\nexport const useSession = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'session', id],\n queryFn: async () => {\n const result = await athQuery<{ athSession: AthSession | null }>(\n `query AthSession($id: ID!) {\n athSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athSession;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useBookSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthBookSessionInput) => {\n const result = await athQuery<{ athBookSession: AthSession }>(\n `mutation AthBookSession($input: AthBookSessionInput!) {\n athBookSession(input: $input) { ${SESSION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athBookSession;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n },\n });\n};\n\nexport const useConfirmSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athConfirmSession: AthSession }>(\n `mutation AthConfirmSession($id: ID!) {\n athConfirmSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athConfirmSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\nexport const useCompleteSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input?: AthCompleteSessionInput }) => {\n const result = await athQuery<{ athCompleteSession: AthSession }>(\n `mutation AthCompleteSession($id: ID!, $input: AthCompleteSessionInput) {\n athCompleteSession(id: $id, input: $input) { ${SESSION_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athCompleteSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\nexport const useCancelSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athCancelSession: AthSession }>(\n `mutation AthCancelSession($id: ID!) {\n athCancelSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athCancelSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\n// ============================================================================\n// Workout logs & performance metrics\n// ============================================================================\n\nexport interface WorkoutLogFilters {\n athleteId?: string;\n programId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useWorkoutLogs = (filters: WorkoutLogFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'workout-logs', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListWorkoutLogs: AthWorkoutLogConnection }>(\n `query AthListWorkoutLogs($athleteId: ID, $programId: ID, $pagination: PaginationInput) {\n athListWorkoutLogs(athleteId: $athleteId, programId: $programId, pagination: $pagination) {\n items { ${WORKOUT_LOG_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n athleteId: filters.athleteId,\n programId: filters.programId,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListWorkoutLogs;\n },\n });\n};\n\n/**\n * An athlete's performance metrics, newest `recordedAt` first (the svc orders\n * `recordedAt: desc`). `pagination` is optional — omit it to take the server\n * default page; pass a limit when the caller needs a deeper history window than\n * the default (the svc clamps `limit` to its own ceiling, so a caller can ask\n * for more rows but never for unbounded ones).\n */\nexport const usePerformanceMetrics = (\n athleteId: string | undefined,\n pagination?: PaginationInput\n) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'metrics', athleteId, pagination ?? null],\n queryFn: async () => {\n const result = await athQuery<{ athPerformanceMetrics: AthPerformanceMetricConnection }>(\n `query AthPerformanceMetrics($athleteId: ID!, $pagination: PaginationInput) {\n athPerformanceMetrics(athleteId: $athleteId, pagination: $pagination) {\n items { ${METRIC_FIELDS} }\n }\n }`,\n { athleteId, pagination },\n apiGatewayUrl\n );\n return result.athPerformanceMetrics.items;\n },\n enabled: Boolean(athleteId),\n });\n};\n\nexport const useLogWorkout = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthLogWorkoutInput) => {\n const result = await athQuery<{ athLogWorkout: AthWorkoutLog }>(\n `mutation AthLogWorkout($input: AthLogWorkoutInput!) {\n athLogWorkout(input: $input) { ${WORKOUT_LOG_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athLogWorkout;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'workout-logs'] });\n },\n });\n};\n\nexport const useRecordMetric = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthRecordMetricInput) => {\n const result = await athQuery<{ athRecordMetric: AthPerformanceMetric }>(\n `mutation AthRecordMetric($input: AthRecordMetricInput!) {\n athRecordMetric(input: $input) { ${METRIC_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athRecordMetric;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'metrics'] });\n },\n });\n};\n\n// ============================================================================\n// Availability / RSVP (BOFF-4134)\n// ============================================================================\n\nconst AVAILABILITY_FIELDS = `\n id workspaceId memberId fixtureId sessionId response respondedAt note\n createdAt updatedAt\n member { id displayName role }\n`;\n\nexport interface AvailabilityFilters {\n fixtureId?: string;\n sessionId?: string;\n}\n\n/** Lists availability responses for a fixture OR a session. */\nexport const useAvailability = (filters: AvailabilityFilters) => {\n const { apiGatewayUrl } = useAthleteBridge();\n const { fixtureId, sessionId } = filters;\n return useQuery({\n queryKey: ['atheletebridge', 'availability', fixtureId ?? null, sessionId ?? null],\n queryFn: async () => {\n const result = await athQuery<{ athListAvailability: AthAvailabilityConnection }>(\n `query AthListAvailability($fixtureId: ID, $sessionId: ID) {\n athListAvailability(fixtureId: $fixtureId, sessionId: $sessionId) {\n items { ${AVAILABILITY_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { fixtureId, sessionId },\n apiGatewayUrl\n );\n return result.athListAvailability.items;\n },\n enabled: Boolean(fixtureId || sessionId),\n });\n};\n\nexport const useSetAvailability = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthSetAvailabilityInput) => {\n const result = await athQuery<{ athSetAvailability: AthAvailability }>(\n `mutation AthSetAvailability($input: AthSetAvailabilityInput!) {\n athSetAvailability(input: $input) { ${AVAILABILITY_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athSetAvailability;\n },\n onSuccess: (row) => {\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'availability', row.fixtureId ?? null, row.sessionId ?? null],\n });\n },\n });\n};\n\n/**\n * Nudges roster members who have not yet responded. Publishes a domain event\n * for a future notification consumer to deliver — it does not itself send\n * push/email (no delivery channel is wired up yet). Returns the number of\n * members nudged.\n */\nexport const useRemindAvailability = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: { fixtureId?: string; sessionId?: string }) => {\n const result = await athQuery<{ athRemindAvailability: number }>(\n `mutation AthRemindAvailability($fixtureId: ID, $sessionId: ID) {\n athRemindAvailability(fixtureId: $fixtureId, sessionId: $sessionId)\n }`,\n input,\n apiGatewayUrl\n );\n return result.athRemindAvailability;\n },\n });\n};\n\n// ============================================================================\n// Workspace files — drill demo videos live in wspace-files-svc, reached through\n// the same workspace gateway. Blobs never touch the atheletebridge subgraph.\n// ============================================================================\n\nconst fileExtensionOf = (fileName: string): string => {\n const dot = fileName.lastIndexOf('.');\n return dot >= 0 ? fileName.slice(dot + 1).toLowerCase() : 'bin';\n};\n\n/**\n * Upload a file to wspace-files-svc via the standard three-step flow\n * (initiateUpload → presigned PUT → completeUpload) and resolve the new file id,\n * ready to pass as `AthAddDrillInput.videoFileId`.\n */\nexport const useUploadFile = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useCallback(\n async (file: File): Promise<string> => {\n const initiated = await athQuery<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation AthInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) { uploadUrl session { id } }\n }`,\n {\n input: {\n fileName: file.name,\n extension: fileExtensionOf(file.name),\n fileSize: file.size,\n mimeType: file.type || 'application/octet-stream',\n visibility: 'WORKSPACE',\n },\n },\n apiGatewayUrl\n );\n\n const uploadUrl = initiated.initiateUpload.uploadUrl;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('Upload could not be initialised.');\n }\n\n const put = await fetch(uploadUrl, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\n });\n if (!put.ok) {\n throw new Error(`Storage rejected the upload (HTTP ${put.status}).`);\n }\n\n const completed = await athQuery<{ completeUpload: { fileId: string } }>(\n `mutation AthCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) { fileId }\n }`,\n { input: { sessionId } },\n apiGatewayUrl\n );\n return completed.completeUpload.fileId;\n },\n [apiGatewayUrl]\n );\n};\n\nexport interface WorkspaceFileRef {\n id: string;\n name: string;\n downloadUrl: string | null;\n}\n\n/**\n * Resolve a wspace-files File (a drill's demo video) by id, on demand. Kept as a\n * separate lazy read so a missing/expired blob can never break the program page.\n */\nexport const useFile = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'file', id],\n queryFn: async () => {\n const result = await athQuery<{ getFile: WorkspaceFileRef | null }>(\n `query AthGetFile($id: ID!) {\n getFile(id: $id) { id name downloadUrl }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.getFile;\n },\n enabled: Boolean(id),\n staleTime: 60_000,\n });\n};\n\n// ============================================================================\n// User directory (global gateway) — resolve real users for team member add so a\n// coach never has to paste a raw UUID.\n// ============================================================================\n\nexport interface DirectoryUser {\n id: string;\n name: string | null;\n firstName: string | null;\n lastName: string | null;\n email: string;\n username: string;\n}\n\n/** Best display name for a directory user (name → first+last → username → email). */\nexport const directoryUserName = (user: DirectoryUser): string => {\n if (user.name?.trim()) return user.name.trim();\n const full = [user.firstName, user.lastName].filter(Boolean).join(' ').trim();\n if (full) return full;\n if (user.username?.trim()) return user.username.trim();\n return user.email;\n};\n\n/**\n * Search the tenant user directory by name/email. Enabled once the term is at\n * least two characters; failures are surfaced to the caller (which offers a\n * manual-id fallback) rather than as a global error toast.\n */\nexport const useUserSearch = (search: string) => {\n const term = search.trim();\n return useQuery({\n queryKey: ['atheletebridge', 'user-search', term],\n queryFn: async () => {\n const result = await athGlobalQuery<{\n users: { users: DirectoryUser[]; hasMore: boolean };\n }>(\n `query AthUserSearch($filter: UserFilterInput, $pagination: PaginationInput) {\n users(filter: $filter, pagination: $pagination) {\n users { id name firstName lastName email username }\n hasMore\n }\n }`,\n { filter: { search: term }, pagination: { limit: 8, offset: 0 } }\n );\n return result.users.users;\n },\n enabled: term.length >= 2,\n staleTime: 30_000,\n retry: false,\n });\n};\n\n// ============================================================================\n// User id resolution (global gateway) — resolve a set of user ids to real\n// names/emails for identity chips (coach/athlete), mirroring the UserSearchSelect\n// directory pattern. Batched into one aliased request, cached, and RBAC-safe:\n// on any error it degrades to an empty map so callers fall back to id-seeded\n// monograms rather than surfacing an error toast.\n// ============================================================================\n\nexport interface ResolvedUser {\n id: string;\n name: string;\n email: string | null;\n}\n\n/**\n * Resolve the given user ids to `{ id, name, email }` via the global `user(id)`\n * query (one aliased round-trip). Unknown / forbidden ids are simply absent from\n * the returned map.\n */\nexport const useUsersByIds = (ids: ReadonlyArray<string | null | undefined>) => {\n const unique = useMemo(\n () =>\n Array.from(\n new Set(ids.filter((value): value is string => Boolean(value && value.trim())))\n ).sort(),\n [ids]\n );\n\n return useQuery({\n queryKey: ['atheletebridge', 'users-by-id', unique],\n queryFn: async (): Promise<Record<string, ResolvedUser>> => {\n if (unique.length === 0) {\n return {};\n }\n const varDefs = unique.map((_, i) => `$id${i}: ID!`).join(', ');\n const selections = unique\n .map((_, i) => `u${i}: user(id: $id${i}) { id name firstName lastName email username }`)\n .join('\\n');\n const variables: Record<string, string> = {};\n unique.forEach((id, i) => {\n variables[`id${i}`] = id;\n });\n\n // Read tolerantly: don't throw on partial/RBAC errors — keep whatever\n // aliases resolved so a single forbidden id can't blank the whole feed.\n const response = await graphqlFetch<Record<string, DirectoryUser | null>>({\n gateway: 'global',\n query: `query AthUsersByIds(${varDefs}) {\\n${selections}\\n}`,\n variables,\n suppressGlobalErrorEvent: true,\n });\n\n const data = response.data ?? {};\n const map: Record<string, ResolvedUser> = {};\n for (const value of Object.values(data)) {\n if (value && value.id) {\n map[value.id] = {\n id: value.id,\n name: directoryUserName(value),\n email: value.email ?? null,\n };\n }\n }\n return map;\n },\n enabled: unique.length > 0,\n staleTime: 5 * 60_000,\n retry: false,\n });\n};\n\nexport interface UserDirectory {\n byId: Record<string, ResolvedUser>;\n isLoading: boolean;\n isError: boolean;\n /** Resolved display name for an id, or undefined while unknown. */\n nameOf: (id: string | null | undefined) => string | undefined;\n /** Resolved email for an id, or undefined while unknown. */\n emailOf: (id: string | null | undefined) => string | null | undefined;\n}\n\n/**\n * Convenience wrapper over {@link useUsersByIds} returning name/email lookup\n * helpers, so identity components can resolve ids without touching the query.\n */\nexport const useUserDirectory = (ids: ReadonlyArray<string | null | undefined>): UserDirectory => {\n const query = useUsersByIds(ids);\n const byId = query.data ?? {};\n return {\n byId,\n isLoading: query.isLoading,\n isError: query.isError,\n nameOf: (id) => (id ? byId[id]?.name : undefined),\n emailOf: (id) => (id ? byId[id]?.email : undefined),\n };\n};\n\n// ============================================================================\n// Recent activity — a unified, time-ordered feed derived PURELY from records\n// already loaded on the page (teams/fixtures/sessions/programs). No extra\n// network, no fabricated events: every item maps to a real timestamp on a real\n// row.\n// ============================================================================\n\nexport type ActivityType =\n 'team-created' | 'member-added' | 'fixture-updated' | 'program-published' | 'session-completed';\n\nexport interface ActivityItem {\n id: string;\n type: ActivityType;\n title: string;\n subtitle?: string;\n /** ISO timestamp the event occurred at. */\n at: string;\n entityId: string;\n entityKind: 'team' | 'fixture' | 'session' | 'program';\n}\n\nexport interface RecentActivityInput {\n teams?: AthTeam[];\n fixtures?: AthFixture[];\n sessions?: AthSession[];\n programs?: AthProgram[];\n /** Max items to return (newest first). Default 8. */\n limit?: number;\n}\n\n/** Derive a typed, time-ordered activity feed from already-loaded records. */\nexport const useRecentActivity = ({\n teams = [],\n fixtures = [],\n sessions = [],\n programs = [],\n limit = 8,\n}: RecentActivityInput): ActivityItem[] =>\n useMemo(() => {\n const items: ActivityItem[] = [];\n\n for (const team of teams) {\n items.push({\n id: `team-${team.id}`,\n type: 'team-created',\n title: `Team ${team.name} created`,\n at: team.createdAt,\n entityId: team.id,\n entityKind: 'team',\n });\n for (const member of team.members?.items ?? []) {\n items.push({\n id: `member-${member.id}`,\n type: 'member-added',\n title: `${member.displayName} added to ${team.name}`,\n at: member.joinedAt ?? member.createdAt,\n entityId: team.id,\n entityKind: 'team',\n });\n }\n }\n\n for (const program of programs) {\n if (program.status === 'PUBLISHED') {\n items.push({\n id: `program-${program.id}`,\n type: 'program-published',\n title: `${program.name} published`,\n at: program.updatedAt,\n entityId: program.id,\n entityKind: 'program',\n });\n }\n }\n\n for (const fixture of fixtures) {\n const home = fixture.homeTeam?.name ?? 'Home';\n const away = fixture.awayTeam?.name ?? (fixture.awayTeamId ? 'Away' : 'TBD');\n const hasScore = fixture.homeScore !== null && fixture.awayScore !== null;\n items.push({\n id: `fixture-${fixture.id}`,\n type: 'fixture-updated',\n title: hasScore\n ? `${home} ${fixture.homeScore}–${fixture.awayScore} ${away}`\n : `Fixture ${home} vs ${away} updated`,\n subtitle: fixture.competition ?? undefined,\n at: fixture.updatedAt,\n entityId: fixture.id,\n entityKind: 'fixture',\n });\n }\n\n for (const session of sessions) {\n if (session.status === 'COMPLETED') {\n items.push({\n id: `session-${session.id}`,\n type: 'session-completed',\n title: `Session \"${session.title}\" completed`,\n subtitle: session.rating !== null ? `Rated ${session.rating}/5` : undefined,\n at: session.completedAt ?? session.updatedAt,\n entityId: session.id,\n entityKind: 'session',\n });\n }\n }\n\n return items\n .filter((item) => Boolean(item.at) && !Number.isNaN(new Date(item.at).getTime()))\n .sort((a, b) => new Date(b.at).getTime() - new Date(a.at).getTime())\n .slice(0, limit);\n }, [teams, fixtures, sessions, programs, limit]);\n\n// ============================================================================\n// Athlete profile — compose a team member with that athlete's real training\n// data (performance metrics + workout logs + coaching sessions), keyed off the\n// member's userId (the athleteId used across the training tables).\n// ============================================================================\n\nexport interface AthleteProfile {\n /** The resolved team member, or null while loading / not found. */\n member: AthMember | null;\n /** The user id used across sessions / metrics / workouts. */\n athleteId: string | undefined;\n metrics: AthPerformanceMetric[];\n workouts: AthWorkoutLog[];\n sessions: AthSession[];\n isLoading: boolean;\n isError: boolean;\n}\n\n/** Compose a member + their metrics, workout logs and sessions into a profile. */\nexport const useAthleteProfile = (\n teamId: string | undefined,\n memberId: string | undefined\n): AthleteProfile => {\n const membersQuery = useTeamMembers(teamId);\n const member = useMemo(\n () => (membersQuery.data ?? []).find((m) => m.id === memberId) ?? null,\n [membersQuery.data, memberId]\n );\n const athleteId = member?.userId;\n\n const metricsQuery = usePerformanceMetrics(athleteId);\n const workoutsQuery = useWorkoutLogs({ athleteId, pagination: { limit: 100 } });\n const sessionsQuery = useSessions({ athleteId, pagination: { limit: 100 } });\n\n return {\n member,\n athleteId,\n metrics: metricsQuery.data ?? [],\n workouts: workoutsQuery.data?.items ?? [],\n sessions: sessionsQuery.data?.items ?? [],\n isLoading:\n membersQuery.isLoading ||\n (Boolean(athleteId) &&\n (metricsQuery.isLoading || workoutsQuery.isLoading || sessionsQuery.isLoading)),\n isError:\n membersQuery.isError ||\n metricsQuery.isError ||\n workoutsQuery.isError ||\n sessionsQuery.isError,\n };\n};\n\n// ============================================================================\n// Coach analytics — honest aggregates over real sessions / workouts / fixtures\n// / standings. No fabricated wellness or injury data.\n// ============================================================================\n\nexport interface SessionStatusCount {\n status: AthSessionStatus;\n count: number;\n}\n\nexport interface RatingByCoach {\n coachId: string;\n avgRating: number;\n count: number;\n}\n\nexport interface AnalyticsTrendPoint {\n label: string;\n value: number;\n}\n\n/**\n * One athlete's acute:chronic workload, computed from that athlete's own real\n * `AthWorkoutLog.totalLoad` rows in the sampled page.\n *\n * `acute` is the summed load of the last 7 days and `chronic` the mean weekly\n * load over the last 28 days (28-day sum ÷ 4), matching the workspace-wide\n * `trainingLoad` maths. `loadedCount` is how many of those 28-day workouts\n * actually carried a `totalLoad`: when it is 0 the athlete trained but nobody\n * recorded a load, and `acute` / `chronic` are 0 because nothing was measured —\n * NOT because the athlete did nothing. Callers must render that case as\n * \"no load recorded\" rather than as a real zero.\n */\nexport interface AthleteTrainingLoad {\n athleteId: string;\n acute: number;\n chronic: number;\n /** acute ÷ chronic, or null when there is no chronic load to divide by. */\n ratio: number | null;\n /** Workouts by this athlete inside the 28-day window. */\n workoutCount: number;\n /** How many of those workouts carried a real `totalLoad`. */\n loadedCount: number;\n}\n\nexport interface CoachAnalytics {\n totalSessions: number;\n ratedCount: number;\n /** Mean of session.rating over rated sessions, or null when none rated. */\n avgRating: number | null;\n completedCount: number;\n completedPct: number;\n sessionsByStatus: SessionStatusCount[];\n ratingByCoach: RatingByCoach[];\n coachIds: string[];\n /** Acute (last 7d) vs chronic (avg weekly over 28d) workout load + ratio. */\n trainingLoad: { acute: number; chronic: number; ratio: number | null };\n /** Per-athlete acute/chronic load over the same windows, heaviest acute first. */\n loadByAthlete: AthleteTrainingLoad[];\n /** Distinct athlete ids seen in the sampled workout logs (for name lookup). */\n athleteIds: string[];\n /**\n * True when the sampled workout page stopped short of 28 days ago — the svc\n * clamps `limit`, so a busy workspace returns only the newest N workouts and\n * every load figure below (workspace-wide AND per athlete) under-counts.\n */\n loadSampleTruncated: boolean;\n /** Weekly total workout load, oldest → newest (from real totalLoad). */\n loadTrend: AnalyticsTrendPoint[];\n /** Weekly session count, oldest → newest. */\n sessionTrend: AnalyticsTrendPoint[];\n /** Standings snapshot for the resolved competition. */\n standings: AthStanding[];\n competition: string | null;\n isLoading: boolean;\n isError: boolean;\n}\n\nexport interface CoachAnalyticsOptions {\n /** Competition for the standings snapshot; else the most-used one in fixtures. */\n competition?: string;\n /** Rows to sample from each list query. Default 200. */\n sampleLimit?: number;\n}\n\n/** Bucket rows into `weeks` weekly totals ending at \"now\" (oldest → newest). */\nfunction weeklyTrend<T>(\n rows: readonly T[],\n getDate: (row: T) => string,\n getValue: (row: T) => number,\n weeks = 8\n): AnalyticsTrendPoint[] {\n const MS_WEEK = 7 * 86_400_000;\n const now = Date.now();\n const buckets = Array.from({ length: weeks }, () => 0);\n for (const row of rows) {\n const time = new Date(getDate(row)).getTime();\n if (Number.isNaN(time)) {\n continue;\n }\n const age = now - time;\n if (age < 0) {\n continue;\n }\n const weeksAgo = Math.floor(age / MS_WEEK);\n if (weeksAgo >= 0 && weeksAgo < weeks) {\n buckets[weeks - 1 - weeksAgo] += getValue(row);\n }\n }\n return buckets.map((value, i) => {\n const ago = weeks - 1 - i;\n return { label: ago === 0 ? 'Now' : `${ago}w`, value };\n });\n}\n\n/** Aggregate real coaching data for the analytics dashboard. */\nexport const useCoachAnalytics = (options: CoachAnalyticsOptions = {}): CoachAnalytics => {\n const { competition: competitionOpt, sampleLimit = 200 } = options;\n\n const sessionsQuery = useSessions({ pagination: { limit: sampleLimit } });\n const workoutsQuery = useWorkoutLogs({ pagination: { limit: sampleLimit } });\n const fixturesQuery = useFixtures({ pagination: { limit: sampleLimit } });\n\n const sessions = useMemo(() => sessionsQuery.data?.items ?? [], [sessionsQuery.data]);\n const workouts = useMemo(() => workoutsQuery.data?.items ?? [], [workoutsQuery.data]);\n const fixtures = useMemo(() => fixturesQuery.data?.items ?? [], [fixturesQuery.data]);\n\n // Resolve the competition for the standings snapshot.\n const competition = useMemo(() => {\n if (competitionOpt) {\n return competitionOpt;\n }\n const counts = new Map<string, number>();\n for (const fixture of fixtures) {\n if (fixture.competition) {\n counts.set(fixture.competition, (counts.get(fixture.competition) ?? 0) + 1);\n }\n }\n let top: string | null = null;\n let best = 0;\n for (const [comp, n] of counts) {\n if (n > best) {\n best = n;\n top = comp;\n }\n }\n return top;\n }, [competitionOpt, fixtures]);\n\n const standingsQuery = useStandings(competition ?? undefined);\n\n return useMemo<CoachAnalytics>(() => {\n const totalSessions = sessions.length;\n const rated = sessions.filter((s) => s.rating !== null);\n const ratedCount = rated.length;\n const avgRating =\n ratedCount > 0 ? rated.reduce((acc, s) => acc + (s.rating ?? 0), 0) / ratedCount : null;\n const completedCount = sessions.filter((s) => s.status === 'COMPLETED').length;\n const completedPct = totalSessions > 0 ? (completedCount / totalSessions) * 100 : 0;\n\n const statusOrder: AthSessionStatus[] = [\n 'REQUESTED',\n 'CONFIRMED',\n 'COMPLETED',\n 'CANCELLED',\n 'NO_SHOW',\n ];\n const statusCounts = new Map<AthSessionStatus, number>();\n for (const s of sessions) {\n statusCounts.set(s.status, (statusCounts.get(s.status) ?? 0) + 1);\n }\n const sessionsByStatus = statusOrder\n .map((status) => ({ status, count: statusCounts.get(status) ?? 0 }))\n .filter((entry) => entry.count > 0);\n\n const byCoach = new Map<string, { sum: number; count: number }>();\n for (const s of rated) {\n const entry = byCoach.get(s.coachId) ?? { sum: 0, count: 0 };\n entry.sum += s.rating ?? 0;\n entry.count += 1;\n byCoach.set(s.coachId, entry);\n }\n const ratingByCoach = Array.from(byCoach.entries())\n .map(([coachId, { sum, count }]) => ({ coachId, avgRating: sum / count, count }))\n .sort((a, b) => b.avgRating - a.avgRating);\n\n const coachIds = Array.from(new Set(sessions.map((s) => s.coachId)));\n\n // Training load (ACWR) from real workout totalLoad — workspace-wide and,\n // in the same pass, per athlete (every row already carries `athleteId`).\n const now = Date.now();\n const DAY = 86_400_000;\n const acuteFrom = now - 7 * DAY;\n const chronicFrom = now - 28 * DAY;\n let acute = 0;\n let chronic28 = 0;\n let oldestSampledAt = Number.POSITIVE_INFINITY;\n const perAthlete = new Map<\n string,\n { acute: number; chronic28: number; workoutCount: number; loadedCount: number }\n >();\n for (const w of workouts) {\n const performedAt = new Date(w.performedAt).getTime();\n if (Number.isNaN(performedAt)) {\n continue;\n }\n if (performedAt < oldestSampledAt) {\n oldestSampledAt = performedAt;\n }\n const load = w.totalLoad ?? 0;\n if (performedAt >= acuteFrom) {\n acute += load;\n }\n if (performedAt >= chronicFrom) {\n chronic28 += load;\n }\n if (performedAt < chronicFrom || !w.athleteId) {\n continue;\n }\n const entry = perAthlete.get(w.athleteId) ?? {\n acute: 0,\n chronic28: 0,\n workoutCount: 0,\n loadedCount: 0,\n };\n entry.chronic28 += load;\n entry.workoutCount += 1;\n if (w.totalLoad != null) {\n entry.loadedCount += 1;\n }\n if (performedAt >= acuteFrom) {\n entry.acute += load;\n }\n perAthlete.set(w.athleteId, entry);\n }\n const chronic = chronic28 / 4;\n const ratio = chronic > 0 ? acute / chronic : null;\n\n const loadByAthlete: AthleteTrainingLoad[] = Array.from(perAthlete.entries())\n .map(([athleteId, entry]) => {\n const athleteChronic = entry.chronic28 / 4;\n return {\n athleteId,\n acute: entry.acute,\n chronic: athleteChronic,\n ratio: athleteChronic > 0 ? entry.acute / athleteChronic : null,\n workoutCount: entry.workoutCount,\n loadedCount: entry.loadedCount,\n };\n })\n .sort((a, b) => b.acute - a.acute || b.chronic - a.chronic);\n\n // The svc clamps `limit`, so `hasMore` plus a sample that never reaches\n // back 28 days means every load number above is a floor, not a total.\n const loadSampleTruncated =\n (workoutsQuery.data?.pageInfo.hasMore ?? false) && oldestSampledAt > chronicFrom;\n\n const loadTrend = weeklyTrend(\n workouts,\n (w) => w.performedAt,\n (w) => w.totalLoad ?? 0\n );\n const sessionTrend = weeklyTrend(\n sessions,\n (s) => s.scheduledAt,\n () => 1\n );\n\n return {\n totalSessions,\n ratedCount,\n avgRating,\n completedCount,\n completedPct,\n sessionsByStatus,\n ratingByCoach,\n coachIds,\n trainingLoad: { acute, chronic, ratio },\n loadByAthlete,\n athleteIds: loadByAthlete.map((entry) => entry.athleteId),\n loadSampleTruncated,\n loadTrend,\n sessionTrend,\n standings: standingsQuery.data ?? [],\n competition,\n isLoading:\n sessionsQuery.isLoading ||\n workoutsQuery.isLoading ||\n fixturesQuery.isLoading ||\n (Boolean(competition) && standingsQuery.isLoading),\n isError:\n sessionsQuery.isError ||\n workoutsQuery.isError ||\n fixturesQuery.isError ||\n standingsQuery.isError,\n };\n }, [\n sessions,\n workouts,\n workoutsQuery.data,\n competition,\n standingsQuery.data,\n standingsQuery.isLoading,\n standingsQuery.isError,\n sessionsQuery.isLoading,\n sessionsQuery.isError,\n workoutsQuery.isLoading,\n workoutsQuery.isError,\n fixturesQuery.isLoading,\n fixturesQuery.isError,\n ]);\n};\n\n// ============================================================================\n// AI operations (BOFF-3574) — model selection, the append-only decision ledger\n// and the coach's review actions. Every field below is live in the composed\n// production supergraph; nothing here is mocked.\n// ============================================================================\n\nconst AGENT_RUN_FIELDS = `\n id workspaceId actorId actorType provider model trigger status error\n promptSummary inputRefs decision tokensIn tokensOut latencyMs costUsd\n createdAt\n`;\n\nconst AI_CONFIG_FIELDS = `\n workspaceId provider model autoPilotEnabled autoPilotLastRunAt updatedAt\n`;\n\n/** The selectable (provider, model) pairs, with the platform default marked. */\nexport const useAiModelCatalog = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'ai-model-catalog'],\n queryFn: async () => {\n const result = await athQuery<{ athAiModelCatalog: AthAiModelInfo[] }>(\n `query AthAiModelCatalog {\n athAiModelCatalog { provider model label isDefault capabilities }\n }`,\n {},\n apiGatewayUrl\n );\n return result.athAiModelCatalog;\n },\n // A static catalog — refetching it on every focus is pure noise.\n staleTime: 30 * 60_000,\n });\n};\n\n/**\n * The workspace's saved AI config, or null when it has never overridden the\n * platform default chain (the query is nullable by design — a null here means\n * \"using the platform default\", not \"failed\").\n */\nexport const useWorkspaceAiConfig = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'ai-config'],\n queryFn: async () => {\n const result = await athQuery<{ athWorkspaceAiConfig: AthWorkspaceAiConfig | null }>(\n `query AthWorkspaceAiConfig {\n athWorkspaceAiConfig { ${AI_CONFIG_FIELDS} }\n }`,\n {},\n apiGatewayUrl\n );\n return result.athWorkspaceAiConfig;\n },\n });\n};\n\nexport const useSetWorkspaceAiModel = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ provider, model }: { provider: string; model: string }) => {\n const result = await athQuery<{ athSetWorkspaceAiModel: AthWorkspaceAiConfig }>(\n `mutation AthSetWorkspaceAiModel($provider: String!, $model: String!) {\n athSetWorkspaceAiModel(provider: $provider, model: $model) { ${AI_CONFIG_FIELDS} }\n }`,\n { provider, model },\n apiGatewayUrl\n );\n return result.athSetWorkspaceAiModel;\n },\n onSuccess: (config) => {\n queryClient.setQueryData(['atheletebridge', 'ai-config'], config);\n },\n });\n};\n\n/**\n * Opt this workspace into (or out of) the autonomous coaching loop. The loop is\n * ALSO gated per deployment by `ATHELETEBRIDGE_AGENT_LOOP_ENABLED`, so enabling\n * it here does not on its own start anything — and even when it does run it\n * only ever records a PROPOSED adjustment for a coach to resolve.\n */\nexport const useSetWorkspaceAutoPilot = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (enabled: boolean) => {\n const result = await athQuery<{ athSetWorkspaceAutoPilot: AthWorkspaceAiConfig }>(\n `mutation AthSetWorkspaceAutoPilot($enabled: Boolean!) {\n athSetWorkspaceAutoPilot(enabled: $enabled) { ${AI_CONFIG_FIELDS} }\n }`,\n { enabled },\n apiGatewayUrl\n );\n return result.athSetWorkspaceAutoPilot;\n },\n onSuccess: (config) => {\n queryClient.setQueryData(['atheletebridge', 'ai-config'], config);\n },\n });\n};\n\nexport interface AgentRunFilters {\n /** Rows per page. */\n limit?: number;\n /** Rows to skip. Offset-based: the composed PaginationInput has no cursor. */\n offset?: number;\n}\n\n/** The workspace's AI decision ledger, most recent first. */\nexport const useAgentRuns = (filters: AgentRunFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n const { limit = 10, offset = 0 } = filters;\n return useQuery({\n queryKey: ['atheletebridge', 'agent-runs', limit, offset],\n queryFn: async () => {\n const result = await athQuery<{ athAgentRuns: AthAgentRunConnection }>(\n `query AthAgentRuns($pagination: PaginationInput) {\n athAgentRuns(pagination: $pagination) {\n items { ${AGENT_RUN_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { pagination: { limit, offset } },\n apiGatewayUrl\n );\n return result.athAgentRuns;\n },\n });\n};\n\n/**\n * A single ledger row by id. Used to resolve the `extensions.agentRunId` an\n * `AI_PROVIDER_UNAVAILABLE` error carries, so the user sees the recorded\n * failure instead of a generic toast.\n */\nexport const useAgentRun = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'agent-run', id],\n queryFn: async () => {\n const result = await athQuery<{ athAgentRun: AthAgentRun | null }>(\n `query AthAgentRun($id: ID!) {\n athAgentRun(id: $id) { ${AGENT_RUN_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athAgentRun;\n },\n enabled: Boolean(id),\n retry: false,\n });\n};\n\n/**\n * A program with its AI provenance (`context.lastAiAdjustment`). Kept separate\n * from {@link useProgram} so the `context` selection — which only the AI\n * Operations page needs — stays off every other program read.\n */\nexport const useProgramAiState = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'program-ai-state', id],\n queryFn: async () => {\n const result = await athQuery<{\n athProgram: (Pick<AthProgram, 'id' | 'name' | 'status'> & { context: unknown }) | null;\n }>(\n `query AthProgramAiState($id: ID!) {\n athProgram(id: $id) { id name status context }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athProgram;\n },\n enabled: Boolean(id),\n retry: false,\n });\n};\n\n/**\n * Generate a DRAFT program from a live model call. Throws {@link AthApiError}\n * with code `AI_PROVIDER_UNAVAILABLE` (and the FAILED ledger row's id) when no\n * provider in the chain could answer.\n */\nexport const useGenerateProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthGenerateProgramInput) => {\n const result = await athQuery<{ athGenerateProgram: AthGenerateProgramResult }>(\n `mutation AthGenerateProgram($input: AthGenerateProgramInput!) {\n athGenerateProgram(input: $input) {\n agentRunId\n drillCount\n sessionCount\n program { ${PROGRAM_FIELDS} }\n }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athGenerateProgram;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'agent-runs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n },\n });\n};\n\n/**\n * Ask the AI to read an athlete's recent load/metric trend and PROPOSE an\n * adjustment onto a program. It changes no prescribed training load by itself —\n * a coach resolves it with {@link useOverrideAgentRun}.\n */\nexport const useAdjustPlanFromPerformance = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({\n athleteId,\n programId,\n requestedProvider,\n }: {\n athleteId: string;\n programId: string;\n requestedProvider?: string;\n }) => {\n const result = await athQuery<{ athAdjustPlanFromPerformance: AthPlanAdjustment }>(\n `mutation AthAdjustPlanFromPerformance($athleteId: ID!, $programId: ID!, $requestedProvider: String) {\n athAdjustPlanFromPerformance(athleteId: $athleteId, programId: $programId, requestedProvider: $requestedProvider) {\n agentRunId athleteId programId assessment rationale confidence\n recommendedWeeklyLoadChangePct flags appliedToProgram createdAt\n }\n }`,\n { athleteId, programId, requestedProvider },\n apiGatewayUrl\n );\n return result.athAdjustPlanFromPerformance;\n },\n onSuccess: (adjustment) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'agent-runs'] });\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'program-ai-state', adjustment.programId],\n });\n },\n });\n};\n\n/**\n * A coach's review of a prior AI decision. Recorded as a NEW, append-only\n * ledger row — never an edit of the original — which resolves the program's\n * `lastAiAdjustment` to ACCEPTED (same assessment) or OVERRIDDEN (a different\n * one).\n */\nexport const useOverrideAgentRun = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({\n runId,\n programId,\n assessment,\n note,\n }: {\n runId: string;\n programId: string;\n assessment: AthAiAssessment;\n note: string;\n }) => {\n const result = await athQuery<{ athOverrideAgentRun: AthAgentRunOverride }>(\n `mutation AthOverrideAgentRun($runId: ID!, $programId: ID!, $assessment: AthAiAssessment!, $note: String!) {\n athOverrideAgentRun(runId: $runId, programId: $programId, assessment: $assessment, note: $note) {\n id workspaceId programId supersedesRunId assessment note\n appliedToProgram createdAt\n }\n }`,\n { runId, programId, assessment, note },\n apiGatewayUrl\n );\n return result.athOverrideAgentRun;\n },\n onSuccess: (override) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'agent-runs'] });\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'program-ai-state', override.programId],\n });\n },\n });\n};\n\n// ============================================================================\n// Health probe\n// ============================================================================\n\nexport const useHealth = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'health'],\n queryFn: async () => {\n const result = await athQuery<{ athHealth: string }>(\n `query AthHealth { athHealth }`,\n {},\n apiGatewayUrl\n );\n return result.athHealth;\n },\n });\n};\n"],"mappings":"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"}
@@ -5,13 +5,17 @@
5
5
  * An honest coaching-analytics dashboard built ENTIRELY from real aggregates via
6
6
  * `useCoachAnalytics()` — total sessions, average session rating, completion
7
7
  * rate, session-status mix, average rating by coach, weekly training-load /
8
- * session trends, and a league-standings snapshot.
8
+ * session trends, a per-athlete acute:chronic training-load table, and a
9
+ * league-standings snapshot.
9
10
  *
10
11
  * Deliberately OMITS the mock's fabricated composite indices (Form Index,
11
12
  * Fitness Score, Fatigue Index, Injury Status, Availability %) — there is no
12
13
  * wellness / injury / availability data in the schema, so those are not faked.
13
14
  * The one derived metric shown (ACWR training load) uses standard sports-science
14
- * maths over real `workout.totalLoad` and carries an explanatory tooltip.
15
+ * maths over real `workout.totalLoad` and carries an explanatory tooltip — both
16
+ * workspace-wide and, in the per-athlete table, grouped by each log's real
17
+ * `athleteId`. Where an athlete's workouts carry no recorded load the table
18
+ * says so rather than showing a fabricated zero.
15
19
  * Semantic tokens only; responsive.
16
20
  */
17
21
  export declare function CoachAnalyticsPage(): import("react").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"CoachAnalyticsPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/analytics/CoachAnalyticsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA6DH,wBAAgB,kBAAkB,gCAmUjC"}
1
+ {"version":3,"file":"CoachAnalyticsPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/analytics/CoachAnalyticsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA2FH,wBAAgB,kBAAkB,gCAoVjC"}