Compare commits

..

20 Commits

Author SHA1 Message Date
Dean Wenstrand
3ad12182d8 Regenerate schema and hooks from backend master
Union of the cohort funnel, Shot Lab picker queries, and transient
homography IDs — converges the three divergent submodule pointers
(gql master, railbird master, railbird-mobile master).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:00:43 -07:00
Dean Wenstrand
4122284bbd Merge Shot Lab queries with cohort funnel 2026-07-12 17:59:33 -07:00
b30dbd01f5 Fetch shot paths and features for the Shot Lab picker
All checks were successful
Tests / Tests (pull_request) Successful in 11s
The recorded-shot picker now renders per-shot table diagrams, so
GetRecordedStreamShots needs serializedShotPaths plus the pocketing and
cue-object features that PlaneShotDiagram consumes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:13:17 -07:00
6d13dc3eb1 Merge remote-tracking branch 'origin/master' into colonelpanic/allow-transient-homography-ids 2026-07-12 00:19:46 -07:00
e3524cab97 Merge pull request 'Add weekly cohort funnel to getUsageStats' (#293) from dean/usage-stats-cohorts into master
Reviewed-on: #293
2026-07-12 04:07:43 +00:00
303261f2d7 Add recorded Shot Lab queries 2026-07-11 20:56:47 -07:00
20a70fad2a Allow transient homography IDs 2026-07-11 20:55:53 -07:00
Dean Wenstrand
35c7c23ba9 Add disjoint activated segment to cohorts for stacking
All checks were successful
Tests / Tests (pull_request) Successful in 10s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 17:45:01 -07:00
Dean Wenstrand
40c4b82a59 Add weekly cohort funnel to getUsageStats
All checks were successful
Tests / Tests (pull_request) Successful in 13s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 17:40:10 -07:00
9ebe2e61df Merge pull request 'dean/usage-stats' (#292) from dean/usage-stats into master
Reviewed-on: #292
2026-07-11 19:26:13 +00:00
Dean Wenstrand
6f356f9535 Add freemium epoch + era conversion to funnel
All checks were successful
Tests / Tests (pull_request) Successful in 12s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:38:20 -07:00
Dean Wenstrand
5c0ff8c31b Add freemium funnel to getUsageStats
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:48:25 -07:00
Dean Wenstrand
582c20170d Add getUsageStats admin query + operation (schema regen on shot-lab tip)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:36:47 -07:00
dc02fd8387 Merge pull request 'Select detected ball colors for Shot Lab' (#291) from colonelpanic/shot-lab-image-colors into master
Reviewed-on: #291
2026-07-10 17:20:37 +00:00
ef4782fc14 Select detected ball colors for Shot Lab
All checks were successful
Tests / Tests (pull_request) Successful in 33s
2026-07-10 00:12:06 -07:00
905a1ca6db Merge pull request 'Add RGBColorGQL and identifierToColor to TableStateGQL' (#290) from shot-lab-ball-colors into master 2026-07-10 03:36:33 +00:00
6f63189703 Add RGBColorGQL and identifierToColor to TableStateGQL
All checks were successful
Tests / Tests (pull_request) Successful in 12s
Regenerated from backend: Shot Lab table-state endpoints now expose a
per-ball dominant color (RGB + hex) index-aligned with ball positions.
2026-07-09 20:35:48 -07:00
958057764c Merge pull request 'Add getLiveTableState query and live-stream operations' (#288) from live-table-state into master 2026-07-10 02:12:31 +00:00
361730cace Merge pull request 'Add per-ball trajectory color to shot serialization proto' (#289) from ball-trajectory-colors into master 2026-07-10 00:07:45 +00:00
a78e793bd6 Add per-ball trajectory color to shot serialization proto
All checks were successful
Tests / Tests (pull_request) Successful in 11s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:46:06 -07:00
9 changed files with 1116 additions and 240 deletions

View File

@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
b'\n\nshot.proto\x12\x0fserialized_shot"?\n\x03\x42ox\x12\x0c\n\x04left\x18\x01 \x01(\x02\x12\x0b\n\x03top\x18\x02 \x01(\x02\x12\r\n\x05width\x18\x03 \x01(\x02\x12\x0e\n\x06height\x18\x04 \x01(\x02"\x1d\n\x05Point\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02"\x7f\n\rBallDetection\x12.\n\x0eplane_position\x18\x01 \x01(\x0b\x32\x16.serialized_shot.Point\x12(\n\nannotation\x18\x02 \x01(\x0b\x32\x14.serialized_shot.Box\x12\x14\n\x0cinterpolated\x18\x03 \x01(\x08"T\n\x10RLEBallDetection\x12\x31\n\tdetection\x18\x01 \x01(\x0b\x32\x1e.serialized_shot.BallDetection\x12\r\n\x05\x63ount\x18\x02 \x01(\r"L\n\x13RLEDetectionHistory\x12\x35\n\ndetections\x18\x01 \x03(\x0b\x32!.serialized_shot.RLEBallDetection"F\n\x10\x44\x65tectionHistory\x12\x32\n\ndetections\x18\x01 \x03(\x0b\x32\x1e.serialized_shot.BallDetection"\xfc\x01\n\rCollisionInfo\x12\x0e\n\x06source\x18\x01 \x01(\r\x12M\n\x10\x62\x61ll_identifiers\x18\x02 \x03(\x0b\x32\x33.serialized_shot.CollisionInfo.BallIdentifiersEntry\x12\x17\n\x0fwall_identifier\x18\x03 \x01(\r\x12\x13\n\x0b\x66rame_index\x18\x04 \x01(\r\x12\x0e\n\x06static\x18\x05 \x01(\x08\x1aN\n\x14\x42\x61llIdentifiersEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.serialized_shot.Point:\x02\x38\x01"\xcc\x02\n\x04Path\x12\x13\n\x0bstart_frame\x18\x01 \x01(\r\x12\x11\n\tend_frame\x18\x02 \x01(\r\x12\x37\n\ndetections\x18\x03 \x01(\x0b\x32!.serialized_shot.DetectionHistoryH\x00\x12>\n\x0erle_detections\x18\x04 \x01(\x0b\x32$.serialized_shot.RLEDetectionHistoryH\x00\x12\x15\n\x0bnot_present\x18\x05 \x01(\x08H\x00\x12\x11\n\tis_static\x18\x06 \x01(\x08\x12\x32\n\nstart_info\x18\x07 \x01(\x0b\x32\x1e.serialized_shot.CollisionInfo\x12\x30\n\x08\x65nd_info\x18\x08 \x01(\x0b\x32\x1e.serialized_shot.CollisionInfoB\x13\n\x11\x64\x65tection_history"R\n\x11IdentifierHistory\x12\x17\n\x0f\x62\x61ll_identifier\x18\x01 \x01(\r\x12$\n\x05paths\x18\x02 \x03(\x0b\x32\x15.serialized_shot.Path"\xf4\x01\n\x12KeyBallIdentifiers\x12\x10\n\x08\x63ue_ball\x18\x01 \x01(\r\x12\x13\n\x0bobject_ball\x18\x02 \x01(\r\x12\x13\n\x0btarget_ball\x18\x03 \x01(\r\x12\x18\n\x10\x63ontact_sequence\x18\x04 \x03(\r\x12\x18\n\x0b\x63ue_ball_id\x18\x05 \x01(\rH\x00\x88\x01\x01\x12\x1b\n\x0eobject_ball_id\x18\x06 \x01(\rH\x01\x88\x01\x01\x12\x1b\n\x0etarget_ball_id\x18\x07 \x01(\rH\x02\x88\x01\x01\x42\x0e\n\x0c_cue_ball_idB\x11\n\x0f_object_ball_idB\x11\n\x0f_target_ball_id"\xa8\x01\n\x04Shot\x12@\n\x14identifier_histories\x18\x03 \x03(\x0b\x32".serialized_shot.IdentifierHistory\x12\x36\n\tkey_balls\x18\x04 \x01(\x0b\x32#.serialized_shot.KeyBallIdentifiers\x12\x13\n\x0bstart_index\x18\x05 \x01(\r\x12\x11\n\tend_index\x18\x06 \x01(\rb\x06proto3'
b'\n\nshot.proto\x12\x0fserialized_shot"?\n\x03\x42ox\x12\x0c\n\x04left\x18\x01 \x01(\x02\x12\x0b\n\x03top\x18\x02 \x01(\x02\x12\r\n\x05width\x18\x03 \x01(\x02\x12\x0e\n\x06height\x18\x04 \x01(\x02"\x1d\n\x05Point\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02"\x7f\n\rBallDetection\x12.\n\x0eplane_position\x18\x01 \x01(\x0b\x32\x16.serialized_shot.Point\x12(\n\nannotation\x18\x02 \x01(\x0b\x32\x14.serialized_shot.Box\x12\x14\n\x0cinterpolated\x18\x03 \x01(\x08"T\n\x10RLEBallDetection\x12\x31\n\tdetection\x18\x01 \x01(\x0b\x32\x1e.serialized_shot.BallDetection\x12\r\n\x05\x63ount\x18\x02 \x01(\r"L\n\x13RLEDetectionHistory\x12\x35\n\ndetections\x18\x01 \x03(\x0b\x32!.serialized_shot.RLEBallDetection"F\n\x10\x44\x65tectionHistory\x12\x32\n\ndetections\x18\x01 \x03(\x0b\x32\x1e.serialized_shot.BallDetection"\xfc\x01\n\rCollisionInfo\x12\x0e\n\x06source\x18\x01 \x01(\r\x12M\n\x10\x62\x61ll_identifiers\x18\x02 \x03(\x0b\x32\x33.serialized_shot.CollisionInfo.BallIdentifiersEntry\x12\x17\n\x0fwall_identifier\x18\x03 \x01(\r\x12\x13\n\x0b\x66rame_index\x18\x04 \x01(\r\x12\x0e\n\x06static\x18\x05 \x01(\x08\x1aN\n\x14\x42\x61llIdentifiersEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.serialized_shot.Point:\x02\x38\x01"\xcc\x02\n\x04Path\x12\x13\n\x0bstart_frame\x18\x01 \x01(\r\x12\x11\n\tend_frame\x18\x02 \x01(\r\x12\x37\n\ndetections\x18\x03 \x01(\x0b\x32!.serialized_shot.DetectionHistoryH\x00\x12>\n\x0erle_detections\x18\x04 \x01(\x0b\x32$.serialized_shot.RLEDetectionHistoryH\x00\x12\x15\n\x0bnot_present\x18\x05 \x01(\x08H\x00\x12\x11\n\tis_static\x18\x06 \x01(\x08\x12\x32\n\nstart_info\x18\x07 \x01(\x0b\x32\x1e.serialized_shot.CollisionInfo\x12\x30\n\x08\x65nd_info\x18\x08 \x01(\x0b\x32\x1e.serialized_shot.CollisionInfoB\x13\n\x11\x64\x65tection_history"I\n\x0c\x43olorCluster\x12\x11\n\tlightness\x18\x01 \x01(\x02\x12\t\n\x01\x61\x18\x02 \x01(\x02\x12\t\n\x01\x62\x18\x03 \x01(\x02\x12\x10\n\x08\x66raction\x18\x04 \x01(\x02"B\n\x0fTrajectoryColor\x12/\n\x08\x63lusters\x18\x01 \x03(\x0b\x32\x1d.serialized_shot.ColorCluster"\x8e\x01\n\x11IdentifierHistory\x12\x17\n\x0f\x62\x61ll_identifier\x18\x01 \x01(\r\x12$\n\x05paths\x18\x02 \x03(\x0b\x32\x15.serialized_shot.Path\x12:\n\x10trajectory_color\x18\x03 \x01(\x0b\x32 .serialized_shot.TrajectoryColor"\xf4\x01\n\x12KeyBallIdentifiers\x12\x10\n\x08\x63ue_ball\x18\x01 \x01(\r\x12\x13\n\x0bobject_ball\x18\x02 \x01(\r\x12\x13\n\x0btarget_ball\x18\x03 \x01(\r\x12\x18\n\x10\x63ontact_sequence\x18\x04 \x03(\r\x12\x18\n\x0b\x63ue_ball_id\x18\x05 \x01(\rH\x00\x88\x01\x01\x12\x1b\n\x0eobject_ball_id\x18\x06 \x01(\rH\x01\x88\x01\x01\x12\x1b\n\x0etarget_ball_id\x18\x07 \x01(\rH\x02\x88\x01\x01\x42\x0e\n\x0c_cue_ball_idB\x11\n\x0f_object_ball_idB\x11\n\x0f_target_ball_id"\xa8\x01\n\x04Shot\x12@\n\x14identifier_histories\x18\x03 \x03(\x0b\x32".serialized_shot.IdentifierHistory\x12\x36\n\tkey_balls\x18\x04 \x01(\x0b\x32#.serialized_shot.KeyBallIdentifiers\x12\x13\n\x0bstart_index\x18\x05 \x01(\r\x12\x11\n\tend_index\x18\x06 \x01(\rb\x06proto3'
)
_globals = globals()
@@ -41,10 +41,14 @@ if _descriptor._USE_C_DESCRIPTORS == False:
_globals["_COLLISIONINFO_BALLIDENTIFIERSENTRY"]._serialized_end = 745
_globals["_PATH"]._serialized_start = 748
_globals["_PATH"]._serialized_end = 1080
_globals["_IDENTIFIERHISTORY"]._serialized_start = 1082
_globals["_IDENTIFIERHISTORY"]._serialized_end = 1164
_globals["_KEYBALLIDENTIFIERS"]._serialized_start = 1167
_globals["_KEYBALLIDENTIFIERS"]._serialized_end = 1411
_globals["_SHOT"]._serialized_start = 1414
_globals["_SHOT"]._serialized_end = 1582
_globals["_COLORCLUSTER"]._serialized_start = 1082
_globals["_COLORCLUSTER"]._serialized_end = 1155
_globals["_TRAJECTORYCOLOR"]._serialized_start = 1157
_globals["_TRAJECTORYCOLOR"]._serialized_end = 1223
_globals["_IDENTIFIERHISTORY"]._serialized_start = 1226
_globals["_IDENTIFIERHISTORY"]._serialized_end = 1368
_globals["_KEYBALLIDENTIFIERS"]._serialized_start = 1371
_globals["_KEYBALLIDENTIFIERS"]._serialized_end = 1615
_globals["_SHOT"]._serialized_start = 1618
_globals["_SHOT"]._serialized_end = 1786
# @@protoc_insertion_point(module_scope)

View File

@@ -160,16 +160,45 @@ class Path(_message.Message):
end_info: _Optional[_Union[CollisionInfo, _Mapping]] = ...,
) -> None: ...
class ColorCluster(_message.Message):
__slots__ = ["lightness", "a", "b", "fraction"]
LIGHTNESS_FIELD_NUMBER: _ClassVar[int]
A_FIELD_NUMBER: _ClassVar[int]
B_FIELD_NUMBER: _ClassVar[int]
FRACTION_FIELD_NUMBER: _ClassVar[int]
lightness: float
a: float
b: float
fraction: float
def __init__(
self,
lightness: _Optional[float] = ...,
a: _Optional[float] = ...,
b: _Optional[float] = ...,
fraction: _Optional[float] = ...,
) -> None: ...
class TrajectoryColor(_message.Message):
__slots__ = ["clusters"]
CLUSTERS_FIELD_NUMBER: _ClassVar[int]
clusters: _containers.RepeatedCompositeFieldContainer[ColorCluster]
def __init__(
self, clusters: _Optional[_Iterable[_Union[ColorCluster, _Mapping]]] = ...
) -> None: ...
class IdentifierHistory(_message.Message):
__slots__ = ["ball_identifier", "paths"]
__slots__ = ["ball_identifier", "paths", "trajectory_color"]
BALL_IDENTIFIER_FIELD_NUMBER: _ClassVar[int]
PATHS_FIELD_NUMBER: _ClassVar[int]
TRAJECTORY_COLOR_FIELD_NUMBER: _ClassVar[int]
ball_identifier: int
paths: _containers.RepeatedCompositeFieldContainer[Path]
trajectory_color: TrajectoryColor
def __init__(
self,
ball_identifier: _Optional[int] = ...,
paths: _Optional[_Iterable[_Union[Path, _Mapping]]] = ...,
trajectory_color: _Optional[_Union[TrajectoryColor, _Mapping]] = ...,
) -> None: ...
class KeyBallIdentifiers(_message.Message):

View File

@@ -50,9 +50,21 @@ message Path {
CollisionInfo end_info = 8;
}
message ColorCluster {
// LAB in OpenCV's 0-255 uint8 scale (see railbird LABCV2Color).
float lightness = 1;
float a = 2;
float b = 3;
// Share of this ball's accumulated color pixels in this cluster (0-1).
float fraction = 4;
}
message TrajectoryColor { repeated ColorCluster clusters = 1; }
message IdentifierHistory {
uint32 ball_identifier = 1;
repeated Path paths = 2;
TrajectoryColor trajectory_color = 3;
}
message KeyBallIdentifiers {
uint32 cue_ball = 1;

View File

@@ -740,9 +740,158 @@ function _decodePath(bb: ByteBuffer): Path {
return message;
}
export interface ColorCluster {
lightness?: number;
a?: number;
b?: number;
fraction?: number;
}
export function encodeColorCluster(message: ColorCluster): Uint8Array {
let bb = popByteBuffer();
_encodeColorCluster(message, bb);
return toUint8Array(bb);
}
function _encodeColorCluster(message: ColorCluster, bb: ByteBuffer): void {
// optional float lightness = 1;
let $lightness = message.lightness;
if ($lightness !== undefined) {
writeVarint32(bb, 13);
writeFloat(bb, $lightness);
}
// optional float a = 2;
let $a = message.a;
if ($a !== undefined) {
writeVarint32(bb, 21);
writeFloat(bb, $a);
}
// optional float b = 3;
let $b = message.b;
if ($b !== undefined) {
writeVarint32(bb, 29);
writeFloat(bb, $b);
}
// optional float fraction = 4;
let $fraction = message.fraction;
if ($fraction !== undefined) {
writeVarint32(bb, 37);
writeFloat(bb, $fraction);
}
}
export function decodeColorCluster(binary: Uint8Array): ColorCluster {
return _decodeColorCluster(wrapByteBuffer(binary));
}
function _decodeColorCluster(bb: ByteBuffer): ColorCluster {
let message: ColorCluster = {} as any;
end_of_message: while (!isAtEnd(bb)) {
let tag = readVarint32(bb);
switch (tag >>> 3) {
case 0:
break end_of_message;
// optional float lightness = 1;
case 1: {
message.lightness = readFloat(bb);
break;
}
// optional float a = 2;
case 2: {
message.a = readFloat(bb);
break;
}
// optional float b = 3;
case 3: {
message.b = readFloat(bb);
break;
}
// optional float fraction = 4;
case 4: {
message.fraction = readFloat(bb);
break;
}
default:
skipUnknownField(bb, tag & 7);
}
}
return message;
}
export interface TrajectoryColor {
clusters?: ColorCluster[];
}
export function encodeTrajectoryColor(message: TrajectoryColor): Uint8Array {
let bb = popByteBuffer();
_encodeTrajectoryColor(message, bb);
return toUint8Array(bb);
}
function _encodeTrajectoryColor(
message: TrajectoryColor,
bb: ByteBuffer,
): void {
// repeated ColorCluster clusters = 1;
let array$clusters = message.clusters;
if (array$clusters !== undefined) {
for (let value of array$clusters) {
writeVarint32(bb, 10);
let nested = popByteBuffer();
_encodeColorCluster(value, nested);
writeVarint32(bb, nested.limit);
writeByteBuffer(bb, nested);
pushByteBuffer(nested);
}
}
}
export function decodeTrajectoryColor(binary: Uint8Array): TrajectoryColor {
return _decodeTrajectoryColor(wrapByteBuffer(binary));
}
function _decodeTrajectoryColor(bb: ByteBuffer): TrajectoryColor {
let message: TrajectoryColor = {} as any;
end_of_message: while (!isAtEnd(bb)) {
let tag = readVarint32(bb);
switch (tag >>> 3) {
case 0:
break end_of_message;
// repeated ColorCluster clusters = 1;
case 1: {
let limit = pushTemporaryLength(bb);
let values = message.clusters || (message.clusters = []);
values.push(_decodeColorCluster(bb));
bb.limit = limit;
break;
}
default:
skipUnknownField(bb, tag & 7);
}
}
return message;
}
export interface IdentifierHistory {
ball_identifier?: number;
paths?: Path[];
trajectory_color?: TrajectoryColor;
}
export function encodeIdentifierHistory(
@@ -776,6 +925,17 @@ function _encodeIdentifierHistory(
pushByteBuffer(nested);
}
}
// optional TrajectoryColor trajectory_color = 3;
let $trajectory_color = message.trajectory_color;
if ($trajectory_color !== undefined) {
writeVarint32(bb, 26);
let nested = popByteBuffer();
_encodeTrajectoryColor($trajectory_color, nested);
writeVarint32(bb, nested.limit);
writeByteBuffer(bb, nested);
pushByteBuffer(nested);
}
}
export function decodeIdentifierHistory(binary: Uint8Array): IdentifierHistory {
@@ -807,6 +967,14 @@ function _decodeIdentifierHistory(bb: ByteBuffer): IdentifierHistory {
break;
}
// optional TrajectoryColor trajectory_color = 3;
case 3: {
let limit = pushTemporaryLength(bb);
message.trajectory_color = _decodeTrajectoryColor(bb);
bb.limit = limit;
break;
}
default:
skipUnknownField(bb, tag & 7);
}

View File

@@ -260,6 +260,15 @@ export type ClusterAssignmentInput = {
userId?: InputMaybe<Scalars["Int"]["input"]>;
};
export type CohortStatsGql = {
__typename?: "CohortStatsGQL";
activated7d: Scalars["Int"]["output"];
activatedNotPaid7d: Scalars["Int"]["output"];
paid7d: Scalars["Int"]["output"];
signups: Scalars["Int"]["output"];
weekStart: Scalars["Date"]["output"];
};
export type CommentGql = {
__typename?: "CommentGQL";
id: Scalars["Int"]["output"];
@@ -2299,6 +2308,20 @@ export type FloatRangeFilter = {
lessThanInclusive?: Scalars["Boolean"]["input"];
};
export type FunnelStatsGql = {
__typename?: "FunnelStatsGQL";
activationActivated: Scalars["Int"]["output"];
activationCohort: Scalars["Int"]["output"];
eraPaying: Scalars["Int"]["output"];
eraSignups: Scalars["Int"]["output"];
freemiumEpoch: Scalars["Date"]["output"];
newPaid7d: Scalars["Int"]["output"];
newPaid30d: Scalars["Int"]["output"];
payingUsers: Scalars["Int"]["output"];
retentionPrevActive: Scalars["Int"]["output"];
retentionReturned: Scalars["Int"]["output"];
};
export type GameTypeTagMetric = {
__typename?: "GameTypeTagMetric";
madeShots: Scalars["Int"]["output"];
@@ -2386,7 +2409,7 @@ export type HomographyInfoGql = {
crop: BoundingBoxGql;
destPoints: PocketPointsGql;
frameIndex: Scalars["Int"]["output"];
id: Scalars["Int"]["output"];
id?: Maybe<Scalars["Int"]["output"]>;
pockets: Array<BoundingBoxGql>;
sourcePoints: PocketPointsGql;
};
@@ -3018,6 +3041,12 @@ export type ProcessingFailedErr = {
processing: VideoProcessingGql;
};
export type ProcessingStatusCountGql = {
__typename?: "ProcessingStatusCountGQL";
count: Scalars["Int"]["output"];
status: Scalars["String"]["output"];
};
export enum ProcessingStatusEnum {
Created = "CREATED",
Failed = "FAILED",
@@ -3060,11 +3089,13 @@ export type Query = {
getResolvedTier: ResolvedTierGql;
getRuns: GetRunsResult;
getShotAnnotationTypes: Array<ShotAnnotationTypeGql>;
getShotTableState: ShotTableStateGql;
getShots: Array<ShotGql>;
getShotsByIds: Array<ShotGql>;
getShotsWithMetadata: GetShotsResult;
getStorageStatus?: Maybe<StorageStatusGql>;
getTableState: TableStateGql;
getUsageStats: UsageStatsGql;
getUser?: Maybe<UserGql>;
getUserRelationshipsMatching: UserRelationshipsResult;
getUserSubscriptionStatus: UserSubscriptionStatusGql;
@@ -3204,6 +3235,10 @@ export type QueryGetShotAnnotationTypesArgs = {
errorTypes?: InputMaybe<Scalars["Boolean"]["input"]>;
};
export type QueryGetShotTableStateArgs = {
shotId: Scalars["Int"]["input"];
};
export type QueryGetShotsArgs = {
countRespectsLimit?: Scalars["Boolean"]["input"];
filterInput: FilterInput;
@@ -3229,6 +3264,10 @@ export type QueryGetTableStateArgs = {
useHomography?: InputMaybe<HomographyInputGql>;
};
export type QueryGetUsageStatsArgs = {
days?: Scalars["Int"]["input"];
};
export type QueryGetUserArgs = {
userId: Scalars["Int"]["input"];
};
@@ -3338,6 +3377,14 @@ export type QuotaStatusGql = {
tierName: Scalars["String"]["output"];
};
export type RgbColorGql = {
__typename?: "RGBColorGQL";
b: Scalars["Int"]["output"];
g: Scalars["Int"]["output"];
hex: Scalars["String"]["output"];
r: Scalars["Int"]["output"];
};
export enum ReactionEnum {
Bullseye = "BULLSEYE",
Heart = "HEART",
@@ -3571,6 +3618,14 @@ export type ShotProjectionGql = {
trajectories: Array<BallTrajectoryGql>;
};
export type ShotTableStateGql = {
__typename?: "ShotTableStateGQL";
frameIndex: Scalars["Int"]["output"];
shotId: Scalars["Int"]["output"];
tableState: TableStateGql;
videoId: Scalars["Int"]["output"];
};
export type ShotsOrderingComponent =
| {
difficulty: FloatOrdering;
@@ -3803,6 +3858,7 @@ export type SyncAppleSubscriptionResultGql = {
export type TableStateGql = {
__typename?: "TableStateGQL";
homography?: Maybe<HomographyInfoGql>;
identifierToColor: Array<Maybe<RgbColorGql>>;
identifierToPosition: Array<Array<Scalars["Float"]["output"]>>;
};
@@ -3935,6 +3991,37 @@ export type UploadStreamGql = {
uploadsCompleted: Scalars["Int"]["output"];
};
export type UsageStatsDailyGql = {
__typename?: "UsageStatsDailyGQL";
activeUsers: Scalars["Int"]["output"];
day: Scalars["Date"]["output"];
newUsers: Scalars["Int"]["output"];
sessions: Scalars["Int"]["output"];
shots: Scalars["Int"]["output"];
};
export type UsageStatsGql = {
__typename?: "UsageStatsGQL";
cohorts: Array<CohortStatsGql>;
daily: Array<UsageStatsDailyGql>;
funnel: FunnelStatsGql;
processingLast24h: Array<ProcessingStatusCountGql>;
totalRuns: Scalars["Int"]["output"];
totalShots: Scalars["Int"]["output"];
totalUsers: Scalars["Int"]["output"];
totalVideos: Scalars["Int"]["output"];
windows: Array<UsageStatsWindowGql>;
};
export type UsageStatsWindowGql = {
__typename?: "UsageStatsWindowGQL";
activeUsers: Scalars["Int"]["output"];
label: Scalars["String"]["output"];
newUsers: Scalars["Int"]["output"];
sessions: Scalars["Int"]["output"];
shots: Scalars["Int"]["output"];
};
export type UserGql = {
__typename?: "UserGQL";
activeVideoId?: Maybe<Scalars["Int"]["output"]>;
@@ -6529,6 +6616,79 @@ export type ReactToVideoMutation = {
reactToVideo: boolean;
};
export type GetRecordedStreamsQueryVariables = Exact<{
limit?: Scalars["Int"]["input"];
}>;
export type GetRecordedStreamsQuery = {
__typename?: "Query";
getFeedVideos: {
__typename?: "VideoHistoryGQL";
videos: Array<{
__typename?: "VideoGQL";
id: number;
name?: string | null;
createdAt?: any | null;
screenshotUri?: string | null;
framesPerSecond: number;
totalShots: number;
owner?: {
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
} | null;
}>;
};
};
export type GetRecordedStreamShotsQueryVariables = Exact<{
videoId: Scalars["Int"]["input"];
limit?: Scalars["Int"]["input"];
}>;
export type GetRecordedStreamShotsQuery = {
__typename?: "Query";
getOrderedShots: {
__typename?: "GetShotsResult";
shots: Array<{
__typename?: "ShotGQL";
id: number;
videoId: number;
startFrame: number;
endFrame: number;
serializedShotPaths?: {
__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;
}>;
};
};
export type GetRunsForHighlightsQueryVariables = Exact<{
filterInput: RunFilterInput;
runIds?: InputMaybe<Array<Scalars["Int"]["input"]> | Scalars["Int"]["input"]>;
@@ -6736,6 +6896,10 @@ export type GetTableStateQuery = {
getTableState: {
__typename?: "TableStateGQL";
identifierToPosition: Array<Array<number>>;
identifierToColor: Array<{
__typename?: "RGBColorGQL";
hex: string;
} | null>;
};
};
@@ -6752,9 +6916,37 @@ export type GetLiveTableStateQuery = {
tableState: {
__typename?: "TableStateGQL";
identifierToPosition: Array<Array<number>>;
identifierToColor: Array<{
__typename?: "RGBColorGQL";
hex: string;
} | null>;
<<<<<<< HEAD
};
};
};
export type GetShotTableStateQueryVariables = Exact<{
shotId: Scalars["Int"]["input"];
}>;
export type GetShotTableStateQuery = {
__typename?: "Query";
getShotTableState: {
__typename?: "ShotTableStateGQL";
shotId: number;
videoId: number;
frameIndex: number;
tableState: {
__typename?: "TableStateGQL";
identifierToPosition: Array<Array<number>>;
identifierToColor: Array<{
__typename?: "RGBColorGQL";
hex: string;
} | null>;
=======
homography?: {
__typename?: "HomographyInfoGQL";
id: number;
id?: number | null;
frameIndex: number;
crop: {
__typename?: "BoundingBoxGQL";
@@ -6780,6 +6972,7 @@ export type GetLiveTableStateQuery = {
bottomRight: { __typename?: "IntPoint2D"; x: number; y: number };
};
} | null;
>>>>>>> 6d13dc3
};
};
};
@@ -7378,6 +7571,63 @@ export type DeleteTagsMutation = {
deleteTags: boolean;
};
export type GetUsageStatsQueryVariables = Exact<{
days?: Scalars["Int"]["input"];
}>;
export type GetUsageStatsQuery = {
__typename?: "Query";
getUsageStats: {
__typename?: "UsageStatsGQL";
totalUsers: number;
totalVideos: number;
totalShots: number;
totalRuns: number;
funnel: {
__typename?: "FunnelStatsGQL";
freemiumEpoch: any;
eraSignups: number;
eraPaying: number;
activationCohort: number;
activationActivated: number;
payingUsers: number;
newPaid7d: number;
newPaid30d: number;
retentionPrevActive: number;
retentionReturned: number;
};
cohorts: Array<{
__typename?: "CohortStatsGQL";
weekStart: any;
signups: number;
activated7d: number;
paid7d: number;
activatedNotPaid7d: number;
}>;
windows: Array<{
__typename?: "UsageStatsWindowGQL";
label: string;
newUsers: number;
activeUsers: number;
sessions: number;
shots: number;
}>;
daily: Array<{
__typename?: "UsageStatsDailyGQL";
day: any;
newUsers: number;
activeUsers: number;
sessions: number;
shots: number;
}>;
processingLast24h: Array<{
__typename?: "ProcessingStatusCountGQL";
status: string;
count: number;
}>;
};
};
export type GetProfileImageUploadLinkMutationVariables = Exact<{
fileExt?: InputMaybe<Scalars["String"]["input"]>;
}>;
@@ -7734,7 +7984,7 @@ export type GetStreamMonitoringDetailsQuery = {
elapsedTime?: number | null;
currentHomography?: {
__typename?: "HomographyInfoGQL";
id: number;
id?: number | null;
frameIndex: number;
crop: {
__typename?: "BoundingBoxGQL";
@@ -8148,7 +8398,7 @@ export type GetVideoQuery = {
} | null;
homographyHistory: Array<{
__typename?: "HomographyInfoGQL";
id: number;
id?: number | null;
frameIndex: number;
crop: {
__typename?: "BoundingBoxGQL";
@@ -8396,7 +8646,7 @@ export type FindPrerecordTableLayoutMutation = {
__typename?: "Mutation";
findPrerecordTableLayout?: {
__typename?: "HomographyInfoGQL";
id: number;
id?: number | null;
frameIndex: number;
crop: {
__typename?: "BoundingBoxGQL";
@@ -8426,7 +8676,7 @@ export type FindPrerecordTableLayoutMutation = {
export type HomographyInfoFragment = {
__typename?: "HomographyInfoGQL";
id: number;
id?: number | null;
frameIndex: number;
crop: {
__typename?: "BoundingBoxGQL";
@@ -14080,6 +14330,203 @@ export type ReactToVideoMutationOptions = Apollo.BaseMutationOptions<
ReactToVideoMutation,
ReactToVideoMutationVariables
>;
export const GetRecordedStreamsDocument = gql`
query GetRecordedStreams($limit: Int! = 25) {
getFeedVideos(
limit: $limit
filters: { isStreamCompleted: true, excludeVideosWithNoShots: true }
includePrivate: MINE
feedInput: { allUsers: true }
) {
videos {
id
name
createdAt
screenshotUri
framesPerSecond
totalShots
owner {
id
username
profileImageUri
}
}
}
}
`;
/**
* __useGetRecordedStreamsQuery__
*
* To run a query within a React component, call `useGetRecordedStreamsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetRecordedStreamsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetRecordedStreamsQuery({
* variables: {
* limit: // value for 'limit'
* },
* });
*/
export function useGetRecordedStreamsQuery(
baseOptions?: Apollo.QueryHookOptions<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>(GetRecordedStreamsDocument, options);
}
export function useGetRecordedStreamsLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>(GetRecordedStreamsDocument, options);
}
export function useGetRecordedStreamsSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>(GetRecordedStreamsDocument, options);
}
export type GetRecordedStreamsQueryHookResult = ReturnType<
typeof useGetRecordedStreamsQuery
>;
export type GetRecordedStreamsLazyQueryHookResult = ReturnType<
typeof useGetRecordedStreamsLazyQuery
>;
export type GetRecordedStreamsSuspenseQueryHookResult = ReturnType<
typeof useGetRecordedStreamsSuspenseQuery
>;
export type GetRecordedStreamsQueryResult = Apollo.QueryResult<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>;
export const GetRecordedStreamShotsDocument = gql`
query GetRecordedStreamShots($videoId: Int!, $limit: Int! = 200) {
getOrderedShots(
filterInput: { videoId: [$videoId] }
shotsOrdering: { orderings: [{ startFrame: { descending: false } }] }
limit: $limit
) {
shots {
id
videoId
startFrame
endFrame
serializedShotPaths {
b64EncodedBuffer
}
cueObjectFeatures {
cueObjectDistance
cueObjectAngle
cueBallSpeed
shotDirection
spinType
}
pocketingIntentionFeatures {
make
targetPocketDistance
targetPocketAngle
targetPocketAngleDirection
marginOfErrorInDegrees
intendedPocketType
difficulty
}
pocketingIntentionInfo {
ballId
pocketId
pathMetadataIndex
}
}
}
}
`;
/**
* __useGetRecordedStreamShotsQuery__
*
* To run a query within a React component, call `useGetRecordedStreamShotsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetRecordedStreamShotsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetRecordedStreamShotsQuery({
* variables: {
* videoId: // value for 'videoId'
* limit: // value for 'limit'
* },
* });
*/
export function useGetRecordedStreamShotsQuery(
baseOptions: Apollo.QueryHookOptions<
GetRecordedStreamShotsQuery,
GetRecordedStreamShotsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<
GetRecordedStreamShotsQuery,
GetRecordedStreamShotsQueryVariables
>(GetRecordedStreamShotsDocument, options);
}
export function useGetRecordedStreamShotsLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetRecordedStreamShotsQuery,
GetRecordedStreamShotsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<
GetRecordedStreamShotsQuery,
GetRecordedStreamShotsQueryVariables
>(GetRecordedStreamShotsDocument, options);
}
export function useGetRecordedStreamShotsSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
GetRecordedStreamShotsQuery,
GetRecordedStreamShotsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<
GetRecordedStreamShotsQuery,
GetRecordedStreamShotsQueryVariables
>(GetRecordedStreamShotsDocument, options);
}
export type GetRecordedStreamShotsQueryHookResult = ReturnType<
typeof useGetRecordedStreamShotsQuery
>;
export type GetRecordedStreamShotsLazyQueryHookResult = ReturnType<
typeof useGetRecordedStreamShotsLazyQuery
>;
export type GetRecordedStreamShotsSuspenseQueryHookResult = ReturnType<
typeof useGetRecordedStreamShotsSuspenseQuery
>;
export type GetRecordedStreamShotsQueryResult = Apollo.QueryResult<
GetRecordedStreamShotsQuery,
GetRecordedStreamShotsQueryVariables
>;
export const GetRunsForHighlightsDocument = gql`
query GetRunsForHighlights(
$filterInput: RunFilterInput!
@@ -14417,6 +14864,9 @@ export const GetTableStateDocument = gql`
useHomography: $useHomography
) {
identifierToPosition
identifierToColor {
hex
}
}
}
`;
@@ -14495,13 +14945,12 @@ export const GetLiveTableStateDocument = gql`
frameIndex
tableState {
identifierToPosition
homography {
...HomographyInfo
identifierToColor {
hex
}
}
}
}
${HomographyInfoFragmentDoc}
`;
/**
@@ -14569,6 +15018,87 @@ export type GetLiveTableStateQueryResult = Apollo.QueryResult<
GetLiveTableStateQuery,
GetLiveTableStateQueryVariables
>;
export const GetShotTableStateDocument = gql`
query GetShotTableState($shotId: Int!) {
getShotTableState(shotId: $shotId) {
shotId
videoId
frameIndex
tableState {
identifierToPosition
identifierToColor {
hex
}
}
}
}
`;
/**
* __useGetShotTableStateQuery__
*
* To run a query within a React component, call `useGetShotTableStateQuery` and pass it any options that fit your needs.
* When your component renders, `useGetShotTableStateQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetShotTableStateQuery({
* variables: {
* shotId: // value for 'shotId'
* },
* });
*/
export function useGetShotTableStateQuery(
baseOptions: Apollo.QueryHookOptions<
GetShotTableStateQuery,
GetShotTableStateQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<
GetShotTableStateQuery,
GetShotTableStateQueryVariables
>(GetShotTableStateDocument, options);
}
export function useGetShotTableStateLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetShotTableStateQuery,
GetShotTableStateQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<
GetShotTableStateQuery,
GetShotTableStateQueryVariables
>(GetShotTableStateDocument, options);
}
export function useGetShotTableStateSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
GetShotTableStateQuery,
GetShotTableStateQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<
GetShotTableStateQuery,
GetShotTableStateQueryVariables
>(GetShotTableStateDocument, options);
}
export type GetShotTableStateQueryHookResult = ReturnType<
typeof useGetShotTableStateQuery
>;
export type GetShotTableStateLazyQueryHookResult = ReturnType<
typeof useGetShotTableStateLazyQuery
>;
export type GetShotTableStateSuspenseQueryHookResult = ReturnType<
typeof useGetShotTableStateSuspenseQuery
>;
export type GetShotTableStateQueryResult = Apollo.QueryResult<
GetShotTableStateQuery,
GetShotTableStateQueryVariables
>;
export const SimulateShotDocument = gql`
query SimulateShot($simulationInput: SimulateShotInputGQL!) {
simulateShot(simulationInput: $simulationInput) {
@@ -15646,6 +16176,119 @@ export type DeleteTagsMutationOptions = Apollo.BaseMutationOptions<
DeleteTagsMutation,
DeleteTagsMutationVariables
>;
export const GetUsageStatsDocument = gql`
query getUsageStats($days: Int! = 30) {
getUsageStats(days: $days) {
funnel {
freemiumEpoch
eraSignups
eraPaying
activationCohort
activationActivated
payingUsers
newPaid7d
newPaid30d
retentionPrevActive
retentionReturned
}
cohorts {
weekStart
signups
activated7d
paid7d
activatedNotPaid7d
}
totalUsers
totalVideos
totalShots
totalRuns
windows {
label
newUsers
activeUsers
sessions
shots
}
daily {
day
newUsers
activeUsers
sessions
shots
}
processingLast24h {
status
count
}
}
}
`;
/**
* __useGetUsageStatsQuery__
*
* To run a query within a React component, call `useGetUsageStatsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetUsageStatsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetUsageStatsQuery({
* variables: {
* days: // value for 'days'
* },
* });
*/
export function useGetUsageStatsQuery(
baseOptions?: Apollo.QueryHookOptions<
GetUsageStatsQuery,
GetUsageStatsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<GetUsageStatsQuery, GetUsageStatsQueryVariables>(
GetUsageStatsDocument,
options,
);
}
export function useGetUsageStatsLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetUsageStatsQuery,
GetUsageStatsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<GetUsageStatsQuery, GetUsageStatsQueryVariables>(
GetUsageStatsDocument,
options,
);
}
export function useGetUsageStatsSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
GetUsageStatsQuery,
GetUsageStatsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<
GetUsageStatsQuery,
GetUsageStatsQueryVariables
>(GetUsageStatsDocument, options);
}
export type GetUsageStatsQueryHookResult = ReturnType<
typeof useGetUsageStatsQuery
>;
export type GetUsageStatsLazyQueryHookResult = ReturnType<
typeof useGetUsageStatsLazyQuery
>;
export type GetUsageStatsSuspenseQueryHookResult = ReturnType<
typeof useGetUsageStatsSuspenseQuery
>;
export type GetUsageStatsQueryResult = Apollo.QueryResult<
GetUsageStatsQuery,
GetUsageStatsQueryVariables
>;
export const GetProfileImageUploadLinkDocument = gql`
mutation getProfileImageUploadLink($fileExt: String = ".png") {
getProfileImageUploadLink(fileExt: $fileExt) {

View File

@@ -0,0 +1,62 @@
# Lightweight two-step picker data for loading a recorded shot into Shot Lab.
query GetRecordedStreams($limit: Int! = 25) {
getFeedVideos(
limit: $limit
filters: { isStreamCompleted: true, excludeVideosWithNoShots: true }
includePrivate: MINE
feedInput: { allUsers: true }
) {
videos {
id
name
createdAt
screenshotUri
framesPerSecond
totalShots
owner {
id
username
profileImageUri
}
}
}
}
query GetRecordedStreamShots($videoId: Int!, $limit: Int! = 200) {
getOrderedShots(
filterInput: { videoId: [$videoId] }
shotsOrdering: { orderings: [{ startFrame: { descending: false } }] }
limit: $limit
) {
shots {
id
videoId
startFrame
endFrame
serializedShotPaths {
b64EncodedBuffer
}
cueObjectFeatures {
cueObjectDistance
cueObjectAngle
cueBallSpeed
shotDirection
spinType
}
pocketingIntentionFeatures {
make
targetPocketDistance
targetPocketAngle
targetPocketAngleDirection
marginOfErrorInDegrees
intendedPocketType
difficulty
}
pocketingIntentionInfo {
ballId
pocketId
pathMetadataIndex
}
}
}
}

View File

@@ -9,6 +9,9 @@ query GetTableState(
useHomography: $useHomography
) {
identifierToPosition
identifierToColor {
hex
}
}
}
@@ -18,8 +21,22 @@ query GetLiveTableState($videoId: Int!) {
frameIndex
tableState {
identifierToPosition
homography {
...HomographyInfo
identifierToColor {
hex
}
}
}
}
query GetShotTableState($shotId: Int!) {
getShotTableState(shotId: $shotId) {
shotId
videoId
frameIndex
tableState {
identifierToPosition
identifierToColor {
hex
}
}
}

View File

@@ -0,0 +1,45 @@
query getUsageStats($days: Int! = 30) {
getUsageStats(days: $days) {
funnel {
freemiumEpoch
eraSignups
eraPaying
activationCohort
activationActivated
payingUsers
newPaid7d
newPaid30d
retentionPrevActive
retentionReturned
}
cohorts {
weekStart
signups
activated7d
paid7d
activatedNotPaid7d
}
totalUsers
totalVideos
totalShots
totalRuns
windows {
label
newUsers
activeUsers
sessions
shots
}
daily {
day
newUsers
activeUsers
sessions
shots
}
processingLast24h {
status
count
}
}
}

View File

@@ -1,7 +1,5 @@
type Query {
getAggregatedShotMetrics(
aggregateInput: AggregateInputGQL!
): [AggregateResultGQL!]!
getAggregatedShotMetrics(aggregateInput: AggregateInputGQL!): [AggregateResultGQL!]!
getBucketSet(keyName: String!): BucketSetGQL
challenges(includeDismissed: Boolean! = false): [Challenge!]!
myDismissedChallenges: [Challenge!]!
@@ -13,44 +11,14 @@ type Query {
myChallengeEntries: [ChallengeEntry!]!
getDeployedConfig: DeployedConfigGQL!
waitFor(duration: Float!): Float!
getFeedVideos(
limit: Int! = 5
after: String = null
includePrivate: IncludePrivateEnum! = MINE
includeCallersVideos: Boolean = true
filters: VideoFilterInput = null
feedInput: VideoFeedInputGQL = null
): VideoHistoryGQL!
getVideoMakePercentageIntervals(
videoId: ID!
intervalDuration: Int! = 300
): [MakePercentageIntervalGQL!]!
getLongestRunsLeaderboard(
interval: TimeInterval = null
when: DateTime = null
limit: Int! = 50
requiredTags: [String!] = null
): RunLeaderboardGQL!
getDrillRunLeaderboard(
drillTag: String!
interval: TimeInterval = null
limit: Int! = 50
tableSizeMin: Float = null
tableSizeMax: Float = null
pocketSizeMin: Float = null
pocketSizeMax: Float = null
): DrillRunLeaderboardGQL!
getFeedVideos(limit: Int! = 5, after: String = null, includePrivate: IncludePrivateEnum! = MINE, includeCallersVideos: Boolean = true, filters: VideoFilterInput = null, feedInput: VideoFeedInputGQL = null): VideoHistoryGQL!
getVideoMakePercentageIntervals(videoId: ID!, intervalDuration: Int! = 300): [MakePercentageIntervalGQL!]!
getLongestRunsLeaderboard(interval: TimeInterval = null, when: DateTime = null, limit: Int! = 50, requiredTags: [String!] = null): RunLeaderboardGQL!
getDrillRunLeaderboard(drillTag: String!, interval: TimeInterval = null, limit: Int! = 50, tableSizeMin: Float = null, tableSizeMax: Float = null, pocketSizeMin: Float = null, pocketSizeMax: Float = null): DrillRunLeaderboardGQL!
getMyDrillRuns(drillTag: String!, limit: Int! = 50): [RunGQL!]!
getMakesLeaderboard(
interval: TimeInterval = null
when: DateTime = null
): CountLeaderboardGQL!
getMakesLeaderboard(interval: TimeInterval = null, when: DateTime = null): CountLeaderboardGQL!
getMedals(scope: MedalScope!, userId: Int = null): RequestedMedalsGQL!
notifications(
limit: Int! = 20
offset: Int! = 0
filters: NotificationFilters = null
): NotificationConnection!
notifications(limit: Int! = 20, offset: Int! = 0, filters: NotificationFilters = null): NotificationConnection!
unreadNotificationCount: Int!
poolHalls: [PoolHall!]!
claimablePoolHalls: [PoolHall!]!
@@ -58,67 +26,25 @@ type Query {
claimableCameras(poolHallId: ID!): [PoolHallCamera!]!
cameraClaimSession(id: ID!): CameraClaimSession
activeCameraLease: CameraLease
getRuns(
filterInput: RunFilterInput!
runIds: [Int!] = null
runsOrdering: GetRunsOrdering = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): GetRunsResult!
getRuns(filterInput: RunFilterInput!, runIds: [Int!] = null, runsOrdering: GetRunsOrdering = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): GetRunsResult!
videoPlayerClusters(videoId: Int!): [PlayerClusterGQL!]!
getShotAnnotationTypes(errorTypes: Boolean = false): [ShotAnnotationTypeGQL!]!
getTableState(
b64Image: String!
tableSize: Float = 100
useHomography: HomographyInputGQL = null
): TableStateGQL!
getTableState(b64Image: String!, tableSize: Float = 100, useHomography: HomographyInputGQL = null): TableStateGQL!
getLiveTableState(videoId: Int!): LiveTableStateGQL!
getShotTableState(shotId: Int!): ShotTableStateGQL!
simulateShot(simulationInput: SimulateShotInputGQL!): ShotProjectionGQL!
computePotAim(
simulationInput: SimulateShotInputGQL!
targetBallId: Int!
pocket: PocketIdentifier!
): PotAimGQL!
getOrderedShots(
filterInput: FilterInput!
ids: [Int!] = null
shotsOrdering: GetShotsOrdering = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): GetShotsResult!
getShotsWithMetadata(
filterInput: FilterInput!
ids: [Int!] = null
shotsPagination: GetShotsPagination = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): GetShotsResult!
getShots(
filterInput: FilterInput!
shotsPagination: GetShotsPagination = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): [ShotGQL!]!
computePotAim(simulationInput: SimulateShotInputGQL!, targetBallId: Int!, pocket: PocketIdentifier!): PotAimGQL!
getOrderedShots(filterInput: FilterInput!, ids: [Int!] = null, shotsOrdering: GetShotsOrdering = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): GetShotsResult!
getShotsWithMetadata(filterInput: FilterInput!, ids: [Int!] = null, shotsPagination: GetShotsPagination = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): GetShotsResult!
getShots(filterInput: FilterInput!, shotsPagination: GetShotsPagination = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): [ShotGQL!]!
getShotsByIds(ids: [Int!]!): [ShotGQL!]!
getUsageStats(days: Int! = 30): UsageStatsGQL!
getUser(userId: Int!): UserGQL
doesUsernameExist(candidateUsername: String!): Boolean!
getLoggedInUser: UserGQL
getUsernames(
matchString: String = null
limit: Int = null
after: String = null
): [String!]!
getUsersMatching(
matchString: String = null
limit: Int = null
after: String = null
): [UserGQL!]!
getUserRelationshipsMatching(
userId: Int!
matchString: String = null
limit: Int = 100
after: String = null
): UserRelationshipsResult!
getUsernames(matchString: String = null, limit: Int = null, after: String = null): [String!]!
getUsersMatching(matchString: String = null, limit: Int = null, after: String = null): [UserGQL!]!
getUserRelationshipsMatching(userId: Int!, matchString: String = null, limit: Int = 100, after: String = null): UserRelationshipsResult!
getAvailableSubscriptionOptions: StripeSubscriptionOptionsGQL!
getUserSubscriptionStatus: UserSubscriptionStatusGQL!
getResolvedTier: ResolvedTierGQL!
@@ -126,12 +52,7 @@ type Query {
getQuotaStatus: QuotaStatusGQL!
getStorageStatus: StorageStatusGQL
getPlayTime(userId: Int!, filters: VideoFilterInput = null): UserPlayTimeGQL!
getUserVideos(
userId: Int = null
limit: Int! = 5
after: String = null
filters: VideoFilterInput = null
): VideoHistoryGQL!
getUserVideos(userId: Int = null, limit: Int! = 5, after: String = null, filters: VideoFilterInput = null): VideoHistoryGQL!
getUserTags(includeRetiredTags: Boolean = false): [TagGQL!]!
getGameTypeTagMetrics(input: GameTypeTagMetricsInput!): [GameTypeTagMetric!]!
videoExportJob(jobId: Int!): VideoExportJobGQL
@@ -203,9 +124,7 @@ input DatetimeRangeAggregationInput {
feature: String! = "created_at"
}
"""
Date with time (isoformat)
"""
"""Date with time (isoformat)"""
scalar DateTime
input TimeInterval @oneOf {
@@ -321,9 +240,7 @@ input DateRangeFilter {
greaterThanInclusive: Boolean! = true
}
"""
Date (isoformat)
"""
"""Date (isoformat)"""
scalar Date
type BucketSetGQL {
@@ -608,7 +525,7 @@ type VideoTagClass {
}
type HomographyInfoGQL {
id: Int!
id: Int
frameIndex: Int!
crop: BoundingBoxGQL!
pockets: [BoundingBoxGQL!]!
@@ -849,8 +766,7 @@ type MedalGQL {
input MedalScope @oneOf {
videoId: Int
interval: TimeInterval
@deprecated(reason: "NO LONGER SUPPORTED, USE DATETIME_RANGE")
interval: TimeInterval @deprecated(reason: "NO LONGER SUPPORTED, USE DATETIME_RANGE")
datetimeRange: DatetimeRangeAggregationInput
}
@@ -1020,6 +936,14 @@ type PlayerClusterShotGQL {
type TableStateGQL {
identifierToPosition: [[Float!]!]!
homography: HomographyInfoGQL
identifierToColor: [RGBColorGQL]!
}
type RGBColorGQL {
r: Int!
g: Int!
b: Int!
hex: String!
}
input HomographyInputGQL {
@@ -1056,6 +980,13 @@ type LiveTableStateGQL {
tableState: TableStateGQL!
}
type ShotTableStateGQL {
shotId: Int!
videoId: Int!
frameIndex: Int!
tableState: TableStateGQL!
}
type ShotProjectionGQL {
trajectories: [BallTrajectoryGQL!]!
events: [SimulationEventGQL!]!
@@ -1160,6 +1091,60 @@ input CreatedAfter @oneOf {
createdAt: DateTime
}
type UsageStatsGQL {
funnel: FunnelStatsGQL!
totalUsers: Int!
totalVideos: Int!
totalShots: Int!
totalRuns: Int!
cohorts: [CohortStatsGQL!]!
windows: [UsageStatsWindowGQL!]!
daily: [UsageStatsDailyGQL!]!
processingLast24h: [ProcessingStatusCountGQL!]!
}
type FunnelStatsGQL {
freemiumEpoch: Date!
eraSignups: Int!
eraPaying: Int!
activationCohort: Int!
activationActivated: Int!
payingUsers: Int!
newPaid7d: Int!
newPaid30d: Int!
retentionPrevActive: Int!
retentionReturned: Int!
}
type CohortStatsGQL {
weekStart: Date!
signups: Int!
activated7d: Int!
paid7d: Int!
activatedNotPaid7d: Int!
}
type UsageStatsWindowGQL {
label: String!
newUsers: Int!
activeUsers: Int!
sessions: Int!
shots: Int!
}
type UsageStatsDailyGQL {
day: Date!
newUsers: Int!
activeUsers: Int!
sessions: Int!
shots: Int!
}
type ProcessingStatusCountGQL {
status: String!
count: Int!
}
type UserRelationshipsResult {
inquiringUser: UserGQL!
relationships: [UserRelationship!]!
@@ -1300,9 +1285,7 @@ type StorageStatusGQL {
lastCalculatedAt: DateTime
}
"""
Integer value that can exceed GraphQL Int's 32-bit range.
"""
"""Integer value that can exceed GraphQL Int's 32-bit range."""
scalar BigInt
type UserPlayTimeGQL {
@@ -1373,34 +1356,14 @@ enum VideoExportStatusEnum {
"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
"""
scalar JSON
@specifiedBy(
url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf"
)
scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf")
type Mutation {
createBucketSet(params: CreateBucketSetInput!): BucketSetGQL!
createRuleSet(name: String!, description: String = null): RuleSet!
createChallenge(
name: String!
ruleSetId: ID!
minimumShots: Int!
startDate: DateTime!
endDate: DateTime!
description: String = null
requiredTableSize: Float = null
requiredPocketSize: Float = null
isPublic: Boolean! = false
maxAttempts: Int = null
): Challenge!
inviteUsersToChallenge(
challengeId: ID!
userIds: [ID!]!
): [ChallengeInvitation!]!
respondToChallengeInvitation(
invitationId: ID!
accept: Boolean!
): ChallengeInvitation!
createChallenge(name: String!, ruleSetId: ID!, minimumShots: Int!, startDate: DateTime!, endDate: DateTime!, description: String = null, requiredTableSize: Float = null, requiredPocketSize: Float = null, isPublic: Boolean! = false, maxAttempts: Int = null): Challenge!
inviteUsersToChallenge(challengeId: ID!, userIds: [ID!]!): [ChallengeInvitation!]!
respondToChallengeInvitation(invitationId: ID!, accept: Boolean!): ChallengeInvitation!
startChallenge(challengeId: ID!): ChallengeEntry!
recalculateChallengeEntry(entryId: ID!): ChallengeEntry!
submitChallengeEntry(entryId: ID!, videoId: ID!): ChallengeEntry!
@@ -1408,61 +1371,30 @@ type Mutation {
undismissChallenge(challengeId: ID!): Boolean!
setLoggerLevel(path: String!, level: String!): Boolean!
reactToVideo(videoId: Int!, reaction: ReactionEnum): Boolean!
commentOnVideo(
videoId: Int!
message: String!
parentCommentId: Int
): Boolean!
commentOnVideo(videoId: Int!, message: String!, parentCommentId: Int): Boolean!
editComment(videoId: Int!, commentId: Int!, newMessage: String!): Boolean!
deleteComment(videoId: Int!, commentId: Int!): Boolean!
blockContent(videoId: Int!): Boolean!
blockUser(userId: Int!): Boolean!
reportContent(
videoId: Int!
reason: ReportReasonEnum!
customReason: String = null
): Boolean!
reportContent(videoId: Int!, reason: ReportReasonEnum!, customReason: String = null): Boolean!
markNotificationAsRead(notificationId: Int!): Boolean!
markAllNotificationsAsRead: Boolean!
markNotificationsAsRead(notificationIds: [Int!]!): Boolean!
deleteNotification(notificationId: Int!): Boolean!
createPoolHall(input: CreatePoolHallInput!): PoolHall!
updatePoolHall(input: UpdatePoolHallInput!): PoolHall!
createPoolHallCamera(
input: CreatePoolHallCameraInput!
): PoolHallCameraStreamCredentials!
createPoolHallCamera(input: CreatePoolHallCameraInput!): PoolHallCameraStreamCredentials!
updatePoolHallCamera(input: UpdatePoolHallCameraInput!): PoolHallCamera!
rotatePoolHallCameraStreamKey(cameraId: ID!): PoolHallCameraStreamCredentials!
createCameraClaimSession(
cameraId: ID!
durationMinutes: Int = null
videoName: String = null
videoPrivate: Boolean = null
agreedTermsVersion: Int = null
tags: [VideoTagInput!] = null
): CameraClaimSession!
createCameraClaimSession(cameraId: ID!, durationMinutes: Int = null, videoName: String = null, videoPrivate: Boolean = null, agreedTermsVersion: Int = null, tags: [VideoTagInput!] = null): CameraClaimSession!
cancelCameraClaimSession(claimSessionId: ID!): CameraClaimSession!
endCameraLease(leaseId: ID!): CameraLease!
extendCameraLease(leaseId: ID!, additionalMinutes: Int! = 60): CameraLease!
finalizePlayerAssignments(
input: FinalizePlayerAssignmentsInput!
): [PlayerClusterGQL!]!
addAnnotationToShot(
shotId: Int!
annotationName: String!
notes: String = null
): AddShotAnnotationReturn!
updateShotAnnotations(
shotId: Int!
annotations: [UpdateAnnotationInputGQL!]!
): UpdateShotAnnotationReturn!
editShot(
shotId: Int!
fieldsToEdit: EditableShotFieldInputGQL!
): EditShotReturn!
getProfileImageUploadLink(
fileExt: String = ".png"
): GetProfileUploadLinkReturn!
finalizePlayerAssignments(input: FinalizePlayerAssignmentsInput!): [PlayerClusterGQL!]!
addAnnotationToShot(shotId: Int!, annotationName: String!, notes: String = null): AddShotAnnotationReturn!
updateShotAnnotations(shotId: Int!, annotations: [UpdateAnnotationInputGQL!]!): UpdateShotAnnotationReturn!
editShot(shotId: Int!, fieldsToEdit: EditableShotFieldInputGQL!): EditShotReturn!
getProfileImageUploadLink(fileExt: String = ".png"): GetProfileUploadLinkReturn!
editProfileImageUri(profileImageUri: String!): UserGQL!
editUser(input: EditUserInputGQL!): UserGQL!
followUser(followedUserId: Int!): UserGQL!
@@ -1470,39 +1402,20 @@ type Mutation {
retireTags(tagIds: [Int!]!): Boolean!
ensureStripeCustomerExists: UserGQL!
deleteUser: Boolean!
syncAppleSubscription(
input: SyncAppleSubscriptionInputGQL!
): SyncAppleSubscriptionResultGQL!
syncAppleSubscription(input: SyncAppleSubscriptionInputGQL!): SyncAppleSubscriptionResultGQL!
createSubscription(priceId: String!): CreateSubscriptionResultGQL!
createCustomerPortalSession: CreateCustomerPortalSessionResultGQL!
cancelSubscription: UserSubscriptionStatusGQL!
grantManualEntitlement(
userId: Int!
tierName: String! = "pro"
startsAt: DateTime = null
endsAt: DateTime = null
reason: String = null
): UserSubscriptionStatusGQL!
grantManualEntitlement(userId: Int!, tierName: String! = "pro", startsAt: DateTime = null, endsAt: DateTime = null, reason: String = null): UserSubscriptionStatusGQL!
revokeManualEntitlement(userId: Int!): UserSubscriptionStatusGQL!
submitCancellationFeedback(
reasons: [CancellationReasonEnum!] = null
feedback: String = null
metadata: CancellationFeedbackMetadataInput = null
): Boolean!
submitCancellationFeedback(reasons: [CancellationReasonEnum!] = null, feedback: String = null, metadata: CancellationFeedbackMetadataInput = null): Boolean!
requestVideoExport(input: RequestVideoExportInput!): VideoExportJobGQL!
dismissVideoExport(jobId: Int!): Boolean!
findPrerecordTableLayout(b64Image: String!, videoId: Int!): HomographyInfoGQL
createUploadStream(
videoMetadata: VideoMetadataInput!
expectedDurationSeconds: Float = null
): CreateUploadStreamReturn!
createUploadStream(videoMetadata: VideoMetadataInput!, expectedDurationSeconds: Float = null): CreateUploadStreamReturn!
getUploadLink(videoId: Int!, segmentIndex: Int!): GetUploadLinkReturn!
getHlsInitUploadLink(videoId: Int!): GetUploadLinkReturn!
setSegmentDuration(
videoId: Int!
segmentIndex: Int!
duration: Float!
): Boolean!
setSegmentDuration(videoId: Int!, segmentIndex: Int!, duration: Float!): Boolean!
editUploadStream(videoId: Int!, videoMetadata: VideoMetadataInput!): Boolean!
deleteVideo(videoId: Int!): Boolean!
deleteTags(videoId: Int!, tagsToDelete: [VideoTagInput!]!): Boolean!
@@ -1594,9 +1507,7 @@ type AddShotAnnotationReturn {
value: SuccessfulAddAddShotAnnotationErrors!
}
union SuccessfulAddAddShotAnnotationErrors =
SuccessfulAdd
| AddShotAnnotationErrors
union SuccessfulAddAddShotAnnotationErrors = SuccessfulAdd | AddShotAnnotationErrors
type SuccessfulAdd {
value: Boolean!
@@ -1606,9 +1517,7 @@ type AddShotAnnotationErrors {
error: DoesNotOwnShotErrOtherErrorNeedsNote!
}
union DoesNotOwnShotErrOtherErrorNeedsNote =
DoesNotOwnShotErr
| OtherErrorNeedsNote
union DoesNotOwnShotErrOtherErrorNeedsNote = DoesNotOwnShotErr | OtherErrorNeedsNote
type DoesNotOwnShotErr {
shotId: Int!
@@ -1649,9 +1558,7 @@ type GetProfileUploadLinkReturn {
value: UploadLinkGetProfileUploadLinkErrors!
}
union UploadLinkGetProfileUploadLinkErrors =
UploadLink
| GetProfileUploadLinkErrors
union UploadLinkGetProfileUploadLinkErrors = UploadLink | GetProfileUploadLinkErrors
type UploadLink {
uploadUrl: String!
@@ -1738,12 +1645,9 @@ input VideoMetadataInput {
videoName: String = null
startTime: DateTime = null
endTime: DateTime = null
gameType: String = null
@deprecated(reason: "`game_type` is deprecated. Use `tags` instead.")
gameType: String = null @deprecated(reason: "`game_type` is deprecated. Use `tags` instead.")
"""
A list of tags associated with the video. Replace `game_type`
"""
"""A list of tags associated with the video. Replace `game_type`"""
tags: [VideoTagInput!] = null
tableSize: Float = null
pocketSize: Float = null
@@ -1772,15 +1676,7 @@ type GetUploadLinkErrors {
error: MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr!
}
union MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr =
MustHaveSetForUploadLinkErr
| SegmentAlreadyUploadedErr
| ProcessingFailedErr
| NoInitForChunkedUploadErr
| TooManyProfileImageUploadsErr
| InitUploadAlreadyCompletedErr
| TooManyInitUploadsErr
| StorageLimitExceededErr
union MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr = MustHaveSetForUploadLinkErr | SegmentAlreadyUploadedErr | ProcessingFailedErr | NoInitForChunkedUploadErr | TooManyProfileImageUploadsErr | InitUploadAlreadyCompletedErr | TooManyInitUploadsErr | StorageLimitExceededErr
type MustHaveSetForUploadLinkErr {
resolution: Boolean