Compare commits

...

9 Commits

Author SHA1 Message Date
7c795cc5bf Expose Session Coach agent summary
All checks were successful
Tests / Tests (pull_request) Successful in 14s
2026-07-22 15:13:17 -07:00
17a518429a Merge pull request 'Add Session Coach generation operations' (#303) from loewy/session-coach-agent-deploy into master
Reviewed-on: #303
2026-07-22 19:25:51 +00:00
33eb213cc9 Add Session Coach generation operations
All checks were successful
Tests / Tests (pull_request) Successful in 3m24s
2026-07-22 11:41:33 -07:00
3b66cb3270 Make video frame rate nullable 2026-07-22 02:20:15 -07:00
d67fdd9fee Merge pull request 'Speed up Shot Lab video source queries' (#304) from codex/fix-shot-lab-video-picker into master
Reviewed-on: #304
2026-07-22 01:08:33 +00:00
d787e5e9a2 Speed up Shot Lab video source queries
All checks were successful
Tests / Tests (pull_request) Successful in 39s
2026-07-21 17:39:55 -07:00
1d73b2d67b Merge pull request 'Declare taxonomy filter variables on the makes/runs leaderboards' (#302) from dean/leaderboard-filter-operations into master
Reviewed-on: #302
2026-07-21 16:59:34 +00:00
Dean Wenstrand
aa731b85fc Declare taxonomy filter variables on the makes/runs leaderboards
All checks were successful
Tests / Tests (pull_request) Successful in 18s
The backend leaderboard resolvers gained game_type, table size and pocket
size filters, but GetMakesLeaderboard and GetRunsLeaderboard never declared
them — GraphQL silently drops undeclared variables, so anything the client
passed was ignored and both boards stayed global.

Declare and forward the five filter args on both queries, matching the
names the drill run leaderboard already uses.
2026-07-21 09:30:37 -07:00
644f70c3b0 Merge pull request 'Shot Lab: video-first GetShotLabVideos (replaces GetRecordedStreams/GetLiveStreams)' (#301) from colonelpanic/shot-lab-video-picker into master 2026-07-21 06:47:45 +00:00
5 changed files with 495 additions and 71 deletions

View File

@@ -2554,6 +2554,7 @@ export type Mutation = {
reactToVideo: Scalars["Boolean"]["output"];
recalculateChallengeEntry: ChallengeEntry;
reportContent: Scalars["Boolean"]["output"];
requestSessionCoachGeneration: SessionCoachGql;
requestVideoExport: VideoExportJobGql;
respondToChallengeInvitation: ChallengeInvitation;
retireTags: Scalars["Boolean"]["output"];
@@ -2765,6 +2766,10 @@ export type MutationReportContentArgs = {
videoId: Scalars["Int"]["input"];
};
export type MutationRequestSessionCoachGenerationArgs = {
videoId: Scalars["Int"]["input"];
};
export type MutationRequestVideoExportArgs = {
input: RequestVideoExportInput;
};
@@ -3600,6 +3605,19 @@ export enum SessionCoachCandidateKindEnum {
UsagePattern = "USAGE_PATTERN",
}
export type SessionCoachDecisionGql = {
__typename?: "SessionCoachDecisionGQL";
body?: Maybe<Scalars["String"]["output"]>;
drillId?: Maybe<Scalars["String"]["output"]>;
drillReason?: Maybe<Scalars["String"]["output"]>;
practiceAttemptCount?: Maybe<Scalars["Int"]["output"]>;
practiceTarget?: Maybe<Scalars["String"]["output"]>;
selectedCandidateId?: Maybe<Scalars["ID"]["output"]>;
sessionSummary?: Maybe<Scalars["String"]["output"]>;
suggestedAction?: Maybe<Scalars["String"]["output"]>;
title?: Maybe<Scalars["String"]["output"]>;
};
export type SessionCoachEvidenceGql = {
__typename?: "SessionCoachEvidenceGQL";
attemptCount: Scalars["Int"]["output"];
@@ -3618,6 +3636,7 @@ export type SessionCoachEvidenceGql = {
export type SessionCoachGql = {
__typename?: "SessionCoachGQL";
agentGeneration?: Maybe<SessionCoachGenerationGql>;
analysisVersion: Scalars["String"]["output"];
analyzedShotCount: Scalars["Int"]["output"];
candidates: Array<SessionCoachCandidateGql>;
@@ -3636,6 +3655,44 @@ export type SessionCoachGql = {
videoId: Scalars["Int"]["output"];
};
export type SessionCoachGenerationDiagnosticsGql = {
__typename?: "SessionCoachGenerationDiagnosticsGQL";
abstentionReason?: Maybe<Scalars["String"]["output"]>;
analysisVersion: Scalars["String"]["output"];
attemptCount: Scalars["Int"]["output"];
cachedInputTokens?: Maybe<Scalars["Int"]["output"]>;
enqueueCount: Scalars["Int"]["output"];
failureCode?: Maybe<Scalars["String"]["output"]>;
inputHash: Scalars["String"]["output"];
inputTokens?: Maybe<Scalars["Int"]["output"]>;
lastEnqueuedAt?: Maybe<Scalars["DateTime"]["output"]>;
latencyMs?: Maybe<Scalars["Int"]["output"]>;
maxAttempts: Scalars["Int"]["output"];
modelName: Scalars["String"]["output"];
outputTokens?: Maybe<Scalars["Int"]["output"]>;
promptVersion: Scalars["String"]["output"];
providerKey: Scalars["String"]["output"];
startedAt?: Maybe<Scalars["DateTime"]["output"]>;
updatedAt: Scalars["DateTime"]["output"];
};
export type SessionCoachGenerationGql = {
__typename?: "SessionCoachGenerationGQL";
completedAt?: Maybe<Scalars["DateTime"]["output"]>;
createdAt: Scalars["DateTime"]["output"];
decision?: Maybe<SessionCoachDecisionGql>;
diagnostics?: Maybe<SessionCoachGenerationDiagnosticsGql>;
id: Scalars["ID"]["output"];
state: SessionCoachGenerationStateEnum;
};
export enum SessionCoachGenerationStateEnum {
Failed = "FAILED",
Queued = "QUEUED",
Ready = "READY",
Running = "RUNNING",
}
export enum SessionCoachIneligibilityReasonEnum {
Multiplayer = "MULTIPLAYER",
}
@@ -4259,7 +4316,7 @@ export type VideoGql = {
currentProcessing?: Maybe<VideoProcessingGql>;
elapsedTime?: Maybe<Scalars["Float"]["output"]>;
endTime?: Maybe<Scalars["DateTime"]["output"]>;
framesPerSecond: Scalars["Float"]["output"];
framesPerSecond?: Maybe<Scalars["Float"]["output"]>;
homographyHistory: Array<HomographyInfoGql>;
id: Scalars["Int"]["output"];
makePercentage: Scalars["Float"]["output"];
@@ -5236,6 +5293,11 @@ export type GetVideoFeedQuery = {
export type GetMakesLeaderboardQueryVariables = Exact<{
interval?: InputMaybe<TimeInterval>;
when?: InputMaybe<Scalars["DateTime"]["input"]>;
gameType?: InputMaybe<Scalars["String"]["input"]>;
tableSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
tableSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
}>;
export type GetMakesLeaderboardQuery = {
@@ -5260,6 +5322,11 @@ export type GetMakesLeaderboardQuery = {
export type GetRunsLeaderboardQueryVariables = Exact<{
interval?: InputMaybe<TimeInterval>;
when?: InputMaybe<Scalars["DateTime"]["input"]>;
gameType?: InputMaybe<Scalars["String"]["input"]>;
tableSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
tableSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
}>;
export type GetRunsLeaderboardQuery = {
@@ -6686,6 +6753,7 @@ export type ReactToVideoMutation = {
};
export type GetShotLabVideosQueryVariables = Exact<{
userId: Scalars["Int"]["input"];
limit?: Scalars["Int"]["input"];
}>;
@@ -6699,7 +6767,7 @@ export type GetShotLabVideosQuery = {
name?: string | null;
createdAt?: any | null;
screenshotUri?: string | null;
framesPerSecond: number;
framesPerSecond?: number | null;
totalShots: number;
owner?: {
__typename?: "UserGQL";
@@ -6736,29 +6804,13 @@ export type GetRecordedStreamShotsQuery = {
__typename?: "SerializedShotPathsGQL";
b64EncodedBuffer?: string | null;
} | null;
cueObjectFeatures?: {
__typename?: "CueObjectFeaturesGQL";
cueObjectDistance?: number | null;
cueObjectAngle?: number | null;
cueBallSpeed?: number | null;
shotDirection?: ShotDirectionEnum | null;
spinType?: SpinTypeEnum | null;
} | null;
pocketingIntentionFeatures?: {
__typename?: "PocketingIntentionFeaturesGQL";
make?: boolean | null;
targetPocketDistance?: number | null;
targetPocketAngle?: number | null;
targetPocketAngleDirection?: ShotDirectionEnum | null;
marginOfErrorInDegrees?: number | null;
intendedPocketType?: PocketEnum | null;
difficulty?: number | null;
} | null;
pocketingIntentionInfo?: {
__typename?: "PocketingIntentionInfoGQL";
ballId: number;
pocketId: PocketIdentifier;
pathMetadataIndex: number;
} | null;
}>;
};
@@ -6916,6 +6968,96 @@ export type GetSessionCoachQuery = {
rankReason: string;
};
}>;
agentGeneration?: {
__typename?: "SessionCoachGenerationGQL";
id: string;
state: SessionCoachGenerationStateEnum;
createdAt: any;
completedAt?: any | null;
decision?: {
__typename?: "SessionCoachDecisionGQL";
selectedCandidateId?: string | null;
sessionSummary?: string | null;
title?: string | null;
body?: string | null;
suggestedAction?: string | null;
drillId?: string | null;
drillReason?: string | null;
practiceAttemptCount?: number | null;
practiceTarget?: string | null;
} | null;
diagnostics?: {
__typename?: "SessionCoachGenerationDiagnosticsGQL";
analysisVersion: string;
promptVersion: string;
providerKey: string;
modelName: string;
inputHash: string;
attemptCount: number;
maxAttempts: number;
enqueueCount: number;
latencyMs?: number | null;
inputTokens?: number | null;
cachedInputTokens?: number | null;
outputTokens?: number | null;
failureCode?: string | null;
updatedAt: any;
startedAt?: any | null;
lastEnqueuedAt?: any | null;
abstentionReason?: string | null;
} | null;
} | null;
};
};
export type RequestSessionCoachGenerationMutationVariables = Exact<{
videoId: Scalars["Int"]["input"];
}>;
export type RequestSessionCoachGenerationMutation = {
__typename?: "Mutation";
requestSessionCoachGeneration: {
__typename?: "SessionCoachGQL";
state: SessionCoachStateEnum;
agentGeneration?: {
__typename?: "SessionCoachGenerationGQL";
id: string;
state: SessionCoachGenerationStateEnum;
createdAt: any;
completedAt?: any | null;
decision?: {
__typename?: "SessionCoachDecisionGQL";
selectedCandidateId?: string | null;
sessionSummary?: string | null;
title?: string | null;
body?: string | null;
suggestedAction?: string | null;
drillId?: string | null;
drillReason?: string | null;
practiceAttemptCount?: number | null;
practiceTarget?: string | null;
} | null;
diagnostics?: {
__typename?: "SessionCoachGenerationDiagnosticsGQL";
analysisVersion: string;
promptVersion: string;
providerKey: string;
modelName: string;
inputHash: string;
attemptCount: number;
maxAttempts: number;
enqueueCount: number;
latencyMs?: number | null;
inputTokens?: number | null;
cachedInputTokens?: number | null;
outputTokens?: number | null;
failureCode?: string | null;
updatedAt: any;
startedAt?: any | null;
lastEnqueuedAt?: any | null;
abstentionReason?: string | null;
} | null;
} | null;
};
};
@@ -6948,6 +7090,46 @@ export type SessionCoachCandidateFieldsFragment = {
};
};
export type SessionCoachGenerationFieldsFragment = {
__typename?: "SessionCoachGenerationGQL";
id: string;
state: SessionCoachGenerationStateEnum;
createdAt: any;
completedAt?: any | null;
decision?: {
__typename?: "SessionCoachDecisionGQL";
selectedCandidateId?: string | null;
sessionSummary?: string | null;
title?: string | null;
body?: string | null;
suggestedAction?: string | null;
drillId?: string | null;
drillReason?: string | null;
practiceAttemptCount?: number | null;
practiceTarget?: string | null;
} | null;
diagnostics?: {
__typename?: "SessionCoachGenerationDiagnosticsGQL";
analysisVersion: string;
promptVersion: string;
providerKey: string;
modelName: string;
inputHash: string;
attemptCount: number;
maxAttempts: number;
enqueueCount: number;
latencyMs?: number | null;
inputTokens?: number | null;
cachedInputTokens?: number | null;
outputTokens?: number | null;
failureCode?: string | null;
updatedAt: any;
startedAt?: any | null;
lastEnqueuedAt?: any | null;
abstentionReason?: string | null;
} | null;
};
export type PlayerSummaryFieldsFragment = {
__typename?: "PlayerSummaryGQL";
clusterId: number;
@@ -8464,7 +8646,7 @@ export type GetVideosQuery = {
getVideos: Array<{
__typename?: "VideoGQL";
id: number;
framesPerSecond: number;
framesPerSecond?: number | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
@@ -8488,7 +8670,7 @@ export type GetVideosQuery = {
export type VideoStreamMetadataFragment = {
__typename?: "VideoGQL";
id: number;
framesPerSecond: number;
framesPerSecond?: number | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
@@ -8517,7 +8699,7 @@ export type GetVideoForShotTimeQuery = {
getVideo: {
__typename?: "VideoGQL";
id: number;
framesPerSecond: number;
framesPerSecond?: number | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
@@ -8688,7 +8870,7 @@ export type PlaylistWithSegmentStartTimesFragment = {
export type VideoDurationDataFragment = {
__typename?: "VideoGQL";
id: number;
framesPerSecond: number;
framesPerSecond?: number | null;
playlist?: {
__typename?: "HLSPlaylistGQL";
videoId: number;
@@ -8725,7 +8907,7 @@ export type GetVideoForClipTimesQuery = {
getVideo: {
__typename?: "VideoGQL";
id: number;
framesPerSecond: number;
framesPerSecond?: number | null;
playlist?: {
__typename?: "HLSPlaylistGQL";
videoId: number;
@@ -9422,6 +9604,44 @@ export const SessionCoachCandidateFieldsFragmentDoc = gql`
}
}
`;
export const SessionCoachGenerationFieldsFragmentDoc = gql`
fragment SessionCoachGenerationFields on SessionCoachGenerationGQL {
id
state
createdAt
completedAt
decision {
selectedCandidateId
sessionSummary
title
body
suggestedAction
drillId
drillReason
practiceAttemptCount
practiceTarget
}
diagnostics {
analysisVersion
promptVersion
providerKey
modelName
inputHash
attemptCount
maxAttempts
enqueueCount
latencyMs
inputTokens
cachedInputTokens
outputTokens
failureCode
updatedAt
startedAt
lastEnqueuedAt
abstentionReason
}
}
`;
export const PlayerClusterShotFieldsFragmentDoc = gql`
fragment PlayerClusterShotFields on PlayerClusterShotGQL {
shotId
@@ -11754,8 +11974,24 @@ export type GetVideoFeedQueryResult = Apollo.QueryResult<
GetVideoFeedQueryVariables
>;
export const GetMakesLeaderboardDocument = gql`
query GetMakesLeaderboard($interval: TimeInterval, $when: DateTime) {
getMakesLeaderboard(interval: $interval, when: $when) {
query GetMakesLeaderboard(
$interval: TimeInterval
$when: DateTime
$gameType: String
$tableSizeMin: Float
$tableSizeMax: Float
$pocketSizeMin: Float
$pocketSizeMax: Float
) {
getMakesLeaderboard(
interval: $interval
when: $when
gameType: $gameType
tableSizeMin: $tableSizeMin
tableSizeMax: $tableSizeMax
pocketSizeMin: $pocketSizeMin
pocketSizeMax: $pocketSizeMax
) {
entries {
user {
id
@@ -11784,6 +12020,11 @@ export const GetMakesLeaderboardDocument = gql`
* variables: {
* interval: // value for 'interval'
* when: // value for 'when'
* gameType: // value for 'gameType'
* tableSizeMin: // value for 'tableSizeMin'
* tableSizeMax: // value for 'tableSizeMax'
* pocketSizeMin: // value for 'pocketSizeMin'
* pocketSizeMax: // value for 'pocketSizeMax'
* },
* });
*/
@@ -11837,8 +12078,24 @@ export type GetMakesLeaderboardQueryResult = Apollo.QueryResult<
GetMakesLeaderboardQueryVariables
>;
export const GetRunsLeaderboardDocument = gql`
query GetRunsLeaderboard($interval: TimeInterval, $when: DateTime) {
getLongestRunsLeaderboard(interval: $interval, when: $when) {
query GetRunsLeaderboard(
$interval: TimeInterval
$when: DateTime
$gameType: String
$tableSizeMin: Float
$tableSizeMax: Float
$pocketSizeMin: Float
$pocketSizeMax: Float
) {
getLongestRunsLeaderboard(
interval: $interval
when: $when
gameType: $gameType
tableSizeMin: $tableSizeMin
tableSizeMax: $tableSizeMax
pocketSizeMin: $pocketSizeMin
pocketSizeMax: $pocketSizeMax
) {
entries {
id
runLength
@@ -11870,6 +12127,11 @@ export const GetRunsLeaderboardDocument = gql`
* variables: {
* interval: // value for 'interval'
* when: // value for 'when'
* gameType: // value for 'gameType'
* tableSizeMin: // value for 'tableSizeMin'
* tableSizeMax: // value for 'tableSizeMax'
* pocketSizeMin: // value for 'pocketSizeMin'
* pocketSizeMax: // value for 'pocketSizeMax'
* },
* });
*/
@@ -14416,11 +14678,12 @@ export type ReactToVideoMutationOptions = Apollo.BaseMutationOptions<
ReactToVideoMutationVariables
>;
export const GetShotLabVideosDocument = gql`
query GetShotLabVideos($limit: Int! = 25) {
query GetShotLabVideos($userId: Int!, $limit: Int! = 25) {
getFeedVideos(
limit: $limit
includeCallersVideos: false
includePrivate: MINE
feedInput: { allUsers: true }
feedInput: { userId: $userId }
) {
videos {
id
@@ -14456,12 +14719,13 @@ export const GetShotLabVideosDocument = gql`
* @example
* const { data, loading, error } = useGetShotLabVideosQuery({
* variables: {
* userId: // value for 'userId'
* limit: // value for 'limit'
* },
* });
*/
export function useGetShotLabVideosQuery(
baseOptions?: Apollo.QueryHookOptions<
baseOptions: Apollo.QueryHookOptions<
GetShotLabVideosQuery,
GetShotLabVideosQueryVariables
>,
@@ -14524,26 +14788,11 @@ export const GetRecordedStreamShotsDocument = gql`
serializedShotPaths {
b64EncodedBuffer
}
cueObjectFeatures {
cueObjectDistance
cueObjectAngle
cueBallSpeed
shotDirection
spinType
}
pocketingIntentionFeatures {
make
targetPocketDistance
targetPocketAngle
targetPocketAngleDirection
marginOfErrorInDegrees
intendedPocketType
difficulty
}
pocketingIntentionInfo {
ballId
pocketId
pathMetadataIndex
}
}
}
@@ -14838,9 +15087,13 @@ export const GetSessionCoachDocument = gql`
candidates {
...SessionCoachCandidateFields
}
agentGeneration {
...SessionCoachGenerationFields
}
}
}
${SessionCoachCandidateFieldsFragmentDoc}
${SessionCoachGenerationFieldsFragmentDoc}
`;
/**
@@ -14908,6 +15161,61 @@ export type GetSessionCoachQueryResult = Apollo.QueryResult<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>;
export const RequestSessionCoachGenerationDocument = gql`
mutation RequestSessionCoachGeneration($videoId: Int!) {
requestSessionCoachGeneration(videoId: $videoId) {
state
agentGeneration {
...SessionCoachGenerationFields
}
}
}
${SessionCoachGenerationFieldsFragmentDoc}
`;
export type RequestSessionCoachGenerationMutationFn = Apollo.MutationFunction<
RequestSessionCoachGenerationMutation,
RequestSessionCoachGenerationMutationVariables
>;
/**
* __useRequestSessionCoachGenerationMutation__
*
* To run a mutation, you first call `useRequestSessionCoachGenerationMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useRequestSessionCoachGenerationMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [requestSessionCoachGenerationMutation, { data, loading, error }] = useRequestSessionCoachGenerationMutation({
* variables: {
* videoId: // value for 'videoId'
* },
* });
*/
export function useRequestSessionCoachGenerationMutation(
baseOptions?: Apollo.MutationHookOptions<
RequestSessionCoachGenerationMutation,
RequestSessionCoachGenerationMutationVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
RequestSessionCoachGenerationMutation,
RequestSessionCoachGenerationMutationVariables
>(RequestSessionCoachGenerationDocument, options);
}
export type RequestSessionCoachGenerationMutationHookResult = ReturnType<
typeof useRequestSessionCoachGenerationMutation
>;
export type RequestSessionCoachGenerationMutationResult =
Apollo.MutationResult<RequestSessionCoachGenerationMutation>;
export type RequestSessionCoachGenerationMutationOptions =
Apollo.BaseMutationOptions<
RequestSessionCoachGenerationMutation,
RequestSessionCoachGenerationMutationVariables
>;
export const VideoPlayerClustersDocument = gql`
query VideoPlayerClusters($videoId: Int!) {
videoPlayerClusters(videoId: $videoId) {

View File

@@ -1,5 +1,21 @@
query GetMakesLeaderboard($interval: TimeInterval, $when: DateTime) {
getMakesLeaderboard(interval: $interval, when: $when) {
query GetMakesLeaderboard(
$interval: TimeInterval
$when: DateTime
$gameType: String
$tableSizeMin: Float
$tableSizeMax: Float
$pocketSizeMin: Float
$pocketSizeMax: Float
) {
getMakesLeaderboard(
interval: $interval
when: $when
gameType: $gameType
tableSizeMin: $tableSizeMin
tableSizeMax: $tableSizeMax
pocketSizeMin: $pocketSizeMin
pocketSizeMax: $pocketSizeMax
) {
entries {
user {
id
@@ -13,8 +29,24 @@ query GetMakesLeaderboard($interval: TimeInterval, $when: DateTime) {
}
}
query GetRunsLeaderboard($interval: TimeInterval, $when: DateTime) {
getLongestRunsLeaderboard(interval: $interval, when: $when) {
query GetRunsLeaderboard(
$interval: TimeInterval
$when: DateTime
$gameType: String
$tableSizeMin: Float
$tableSizeMax: Float
$pocketSizeMin: Float
$pocketSizeMax: Float
) {
getLongestRunsLeaderboard(
interval: $interval
when: $when
gameType: $gameType
tableSizeMin: $tableSizeMin
tableSizeMax: $tableSizeMax
pocketSizeMin: $pocketSizeMin
pocketSizeMax: $pocketSizeMax
) {
entries {
id
runLength

View File

@@ -1,12 +1,12 @@
# Lightweight two-step picker data for Shot Lab's video-first source: recent
# videos (completed or still uploading — no completion gate, so an in-progress
# recording with no shots yet still appears), then that video's shots. The
# stream fields let the client hint at "actively updating" without gating.
query GetShotLabVideos($limit: Int! = 25) {
# Lightweight two-step picker data for Shot Lab's video-first source: the
# caller's recent videos (completed or still uploading), then that video's
# shots. The stream fields hint at "actively updating" without gating.
query GetShotLabVideos($userId: Int!, $limit: Int! = 25) {
getFeedVideos(
limit: $limit
includeCallersVideos: false
includePrivate: MINE
feedInput: { allUsers: true }
feedInput: { userId: $userId }
) {
videos {
id
@@ -43,26 +43,11 @@ query GetRecordedStreamShots($videoId: Int!, $limit: Int! = 200) {
serializedShotPaths {
b64EncodedBuffer
}
cueObjectFeatures {
cueObjectDistance
cueObjectAngle
cueBallSpeed
shotDirection
spinType
}
pocketingIntentionFeatures {
make
targetPocketDistance
targetPocketAngle
targetPocketAngleDirection
marginOfErrorInDegrees
intendedPocketType
difficulty
}
pocketingIntentionInfo {
ballId
pocketId
pathMetadataIndex
}
}
}

View File

@@ -20,6 +20,18 @@ query GetSessionCoach($videoId: Int!) {
candidates {
...SessionCoachCandidateFields
}
agentGeneration {
...SessionCoachGenerationFields
}
}
}
mutation RequestSessionCoachGeneration($videoId: Int!) {
requestSessionCoachGeneration(videoId: $videoId) {
state
agentGeneration {
...SessionCoachGenerationFields
}
}
}
@@ -49,3 +61,40 @@ fragment SessionCoachCandidateFields on SessionCoachCandidateGQL {
rankReason
}
}
fragment SessionCoachGenerationFields on SessionCoachGenerationGQL {
id
state
createdAt
completedAt
decision {
selectedCandidateId
sessionSummary
title
body
suggestedAction
drillId
drillReason
practiceAttemptCount
practiceTarget
}
diagnostics {
analysisVersion
promptVersion
providerKey
modelName
inputHash
attemptCount
maxAttempts
enqueueCount
latencyMs
inputTokens
cachedInputTokens
outputTokens
failureCode
updatedAt
startedAt
lastEnqueuedAt
abstentionReason
}
}

View File

@@ -716,6 +716,7 @@ type Mutation {
shotId: Int!
fieldsToEdit: EditableShotFieldInputGQL!
): EditShotReturn!
requestSessionCoachGeneration(videoId: Int!): SessionCoachGQL!
getProfileImageUploadLink(
fileExt: String = ".png"
): GetProfileUploadLinkReturn!
@@ -1320,6 +1321,18 @@ enum SessionCoachCandidateKindEnum {
USAGE_PATTERN
}
type SessionCoachDecisionGQL {
selectedCandidateId: ID
sessionSummary: String
title: String
body: String
suggestedAction: String
drillId: String
drillReason: String
practiceAttemptCount: Int
practiceTarget: String
}
type SessionCoachEvidenceGQL {
bucketKey: String!
bucketLabel: String!
@@ -1352,6 +1365,43 @@ type SessionCoachGQL {
makePercentage: Float
primaryCandidate: SessionCoachCandidateGQL
candidates: [SessionCoachCandidateGQL!]!
agentGeneration: SessionCoachGenerationGQL
}
type SessionCoachGenerationDiagnosticsGQL {
analysisVersion: String!
promptVersion: String!
providerKey: String!
modelName: String!
inputHash: String!
attemptCount: Int!
maxAttempts: Int!
enqueueCount: Int!
latencyMs: Int
inputTokens: Int
cachedInputTokens: Int
outputTokens: Int
failureCode: String
updatedAt: DateTime!
startedAt: DateTime
lastEnqueuedAt: DateTime
abstentionReason: String
}
type SessionCoachGenerationGQL {
id: ID!
state: SessionCoachGenerationStateEnum!
createdAt: DateTime!
completedAt: DateTime
decision: SessionCoachDecisionGQL
diagnostics: SessionCoachGenerationDiagnosticsGQL
}
enum SessionCoachGenerationStateEnum {
QUEUED
RUNNING
READY
FAILED
}
enum SessionCoachIneligibilityReasonEnum {
@@ -1880,7 +1930,7 @@ type VideoGQL {
startTime: DateTime
endTime: DateTime
elapsedTime: Float
framesPerSecond: Float!
framesPerSecond: Float
tableSize: Float!
pocketSize: Float
private: Boolean!