feat: add admin stats
This commit is contained in:
@@ -18,6 +18,8 @@ from .heatmap import (
|
||||
build_heatmap_png,
|
||||
)
|
||||
from .models import PixelPlacement, PlaceSettings
|
||||
from .schemas import PlaceStatsOut
|
||||
from .stats import build_place_stats
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -104,6 +106,14 @@ def heatmap_decay_gif(
|
||||
)
|
||||
|
||||
|
||||
@router.get(f"{PREFIX}/stats", auth=django_auth_is_staff, response=PlaceStatsOut)
|
||||
@decorate_view(require_game_enabled(path="place"))
|
||||
def stats(request: HttpRequest, event_path: str):
|
||||
"""Admin-only: aggregated stats about the game - totals, coverage, top
|
||||
players, most contested pixels, color usage and activity per hour."""
|
||||
return build_place_stats(request.event)
|
||||
|
||||
|
||||
@router.get(f"{PREFIX}/placements.csv", auth=django_auth_is_staff)
|
||||
@decorate_view(require_game_enabled(path="place"))
|
||||
def placements_csv(request: HttpRequest, event_path: str):
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
from datetime import datetime
|
||||
|
||||
from ninja import Schema
|
||||
|
||||
|
||||
class PlayerCountOut(Schema):
|
||||
username: str
|
||||
placements: int
|
||||
|
||||
|
||||
class PixelCountOut(Schema):
|
||||
x: int
|
||||
y: int
|
||||
placements: int
|
||||
players: int
|
||||
|
||||
|
||||
class ColorCountOut(Schema):
|
||||
color_index: int
|
||||
color: str
|
||||
placements: int
|
||||
# How many pixels of the live board currently have this color
|
||||
on_canvas: int
|
||||
|
||||
|
||||
class HourCountOut(Schema):
|
||||
hour: datetime
|
||||
placements: int
|
||||
players: int
|
||||
|
||||
|
||||
class PlaceStatsOut(Schema):
|
||||
width: int
|
||||
height: int
|
||||
total_placements: int
|
||||
total_players: int
|
||||
first_placement_at: datetime | None
|
||||
last_placement_at: datetime | None
|
||||
# Distinct pixels placed at least once (within the current grid)
|
||||
pixels_touched: int
|
||||
coverage: float
|
||||
# Placements that landed on an already placed pixel
|
||||
overwrites: int
|
||||
avg_placements_per_player: float
|
||||
top_players: list[PlayerCountOut]
|
||||
most_contested_pixels: list[PixelCountOut]
|
||||
colors: list[ColorCountOut]
|
||||
per_hour: list[HourCountOut]
|
||||
@@ -0,0 +1,97 @@
|
||||
"""Aggregated stats about one event's place game, for the admin stats
|
||||
endpoint. Everything comes from the durable PixelPlacement history, except
|
||||
the colors currently on the board, which are read off the live Redis canvas.
|
||||
"""
|
||||
|
||||
from django.db.models import Count, Max, Min
|
||||
from django.db.models.functions import TruncHour
|
||||
|
||||
from events.models import Event
|
||||
|
||||
from .canvas import read_canvas
|
||||
from .models import PALETTE, PixelPlacement, PlaceSettings
|
||||
|
||||
TOP_N = 10
|
||||
|
||||
|
||||
def build_place_stats(event: Event) -> dict:
|
||||
place_settings = PlaceSettings.current(event)
|
||||
width, height = place_settings.width, place_settings.height
|
||||
|
||||
placements = PixelPlacement.objects.filter(event=event)
|
||||
totals = placements.aggregate(
|
||||
total=Count("id"),
|
||||
players=Count("player", distinct=True),
|
||||
first=Min("placed_at"),
|
||||
last=Max("placed_at"),
|
||||
)
|
||||
total, players = totals["total"], totals["players"]
|
||||
|
||||
# Same as heatmap.py, placements outside the current grid are skipped
|
||||
in_grid = placements.filter(x__lt=width, y__lt=height)
|
||||
pixels_touched = in_grid.values("x", "y").distinct().count()
|
||||
|
||||
top_players = (
|
||||
placements.values("player__username")
|
||||
.annotate(count=Count("id"))
|
||||
.order_by("-count", "player__username")[:TOP_N]
|
||||
)
|
||||
contested = (
|
||||
in_grid.values("x", "y")
|
||||
.annotate(count=Count("id"), players=Count("player", distinct=True))
|
||||
.order_by("-count", "-players", "y", "x")[:TOP_N]
|
||||
)
|
||||
color_counts = dict(
|
||||
placements.values_list("color_index")
|
||||
.annotate(count=Count("id"))
|
||||
.order_by()
|
||||
)
|
||||
# Redis only pads up to the last written pixel, and keeps old bytes
|
||||
# after a shrink, so normalize to the current grid size
|
||||
size = width * height
|
||||
canvas = read_canvas(event.path, width, height)[:size].ljust(size, b"\0")
|
||||
per_hour = (
|
||||
placements.annotate(hour=TruncHour("placed_at"))
|
||||
.values("hour")
|
||||
.annotate(count=Count("id"), players=Count("player", distinct=True))
|
||||
.order_by("hour")
|
||||
)
|
||||
|
||||
return {
|
||||
"width": width,
|
||||
"height": height,
|
||||
"total_placements": total,
|
||||
"total_players": players,
|
||||
"first_placement_at": totals["first"],
|
||||
"last_placement_at": totals["last"],
|
||||
"pixels_touched": pixels_touched,
|
||||
"coverage": pixels_touched / size if size else 0.0,
|
||||
"overwrites": in_grid.count() - pixels_touched,
|
||||
"avg_placements_per_player": total / players if players else 0.0,
|
||||
"top_players": [
|
||||
{"username": row["player__username"], "placements": row["count"]}
|
||||
for row in top_players
|
||||
],
|
||||
"most_contested_pixels": [
|
||||
{
|
||||
"x": row["x"],
|
||||
"y": row["y"],
|
||||
"placements": row["count"],
|
||||
"players": row["players"],
|
||||
}
|
||||
for row in contested
|
||||
],
|
||||
"colors": [
|
||||
{
|
||||
"color_index": index,
|
||||
"color": color,
|
||||
"placements": color_counts.get(index, 0),
|
||||
"on_canvas": canvas.count(index),
|
||||
}
|
||||
for index, color in enumerate(PALETTE)
|
||||
],
|
||||
"per_hour": [
|
||||
{"hour": row["hour"], "placements": row["count"], "players": row["players"]}
|
||||
for row in per_hour
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from accounts.models import CustomUser
|
||||
from events.models import Event
|
||||
from games.models import EventGame, Game
|
||||
|
||||
from .models import PixelPlacement, PlaceSettings
|
||||
|
||||
URL = "/api/events/event/place/stats"
|
||||
|
||||
|
||||
@mock.patch("place.stats.read_canvas")
|
||||
class PlaceStatsTests(TestCase):
|
||||
def setUp(self):
|
||||
self.event = Event.objects.create(name="Event", path="event")
|
||||
place = Game.objects.create(name="Place", path="place", custom_path=True)
|
||||
EventGame.objects.create(event=self.event, game=place)
|
||||
PlaceSettings.objects.create(event=self.event, width=4, height=2)
|
||||
self.alice = CustomUser.objects.create_user(username="alice", password="x")
|
||||
self.bob = CustomUser.objects.create_user(username="bob", password="x")
|
||||
self.admin = CustomUser.objects.create_user(
|
||||
username="admin", password="x", is_staff=True
|
||||
)
|
||||
|
||||
def place(self, player, x, y, color_index):
|
||||
PixelPlacement.objects.create(
|
||||
event=self.event, player=player, x=x, y=y, color_index=color_index
|
||||
)
|
||||
|
||||
def test_requires_staff(self, _):
|
||||
self.client.force_login(self.alice)
|
||||
|
||||
response = self.client.get(URL)
|
||||
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
def test_empty_game(self, read_canvas):
|
||||
read_canvas.return_value = b""
|
||||
self.client.force_login(self.admin)
|
||||
|
||||
data = self.client.get(URL).json()
|
||||
|
||||
self.assertEqual(data["total_placements"], 0)
|
||||
self.assertEqual(data["total_players"], 0)
|
||||
self.assertEqual(data["coverage"], 0.0)
|
||||
self.assertEqual(data["avg_placements_per_player"], 0.0)
|
||||
self.assertEqual(data["colors"][0]["on_canvas"], 8)
|
||||
self.assertEqual(data["per_hour"], [])
|
||||
|
||||
def test_stats(self, read_canvas):
|
||||
self.place(self.alice, 0, 0, 5)
|
||||
self.place(self.bob, 0, 0, 3)
|
||||
self.place(self.alice, 0, 0, 5)
|
||||
self.place(self.alice, 1, 0, 3)
|
||||
# Outside the current grid, e.g. from before a shrink
|
||||
self.place(self.bob, 9, 9, 3)
|
||||
# Short canvas, Redis only padded up to the last written pixel
|
||||
read_canvas.return_value = bytes([5, 3])
|
||||
self.client.force_login(self.admin)
|
||||
|
||||
data = self.client.get(URL).json()
|
||||
|
||||
self.assertEqual(data["total_placements"], 5)
|
||||
self.assertEqual(data["total_players"], 2)
|
||||
self.assertEqual(data["pixels_touched"], 2)
|
||||
self.assertEqual(data["coverage"], 0.25)
|
||||
self.assertEqual(data["overwrites"], 2)
|
||||
self.assertEqual(data["avg_placements_per_player"], 2.5)
|
||||
self.assertEqual(
|
||||
data["top_players"],
|
||||
[
|
||||
{"username": "alice", "placements": 3},
|
||||
{"username": "bob", "placements": 2},
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
data["most_contested_pixels"][0],
|
||||
{"x": 0, "y": 0, "placements": 3, "players": 2},
|
||||
)
|
||||
colors = {row["color_index"]: row for row in data["colors"]}
|
||||
self.assertEqual(colors[5]["placements"], 2)
|
||||
self.assertEqual(colors[3]["placements"], 3)
|
||||
self.assertEqual(colors[5]["on_canvas"], 1)
|
||||
self.assertEqual(colors[0]["on_canvas"], 6)
|
||||
self.assertEqual(sum(row["placements"] for row in data["per_hour"]), 5)
|
||||
@@ -3,8 +3,8 @@
|
||||
import { type DefaultError, type InfiniteData, infiniteQueryOptions, queryOptions, type UseMutationOptions } from '@tanstack/vue-query';
|
||||
|
||||
import { client } from '../client.gen';
|
||||
import { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, clockApiCurrentState, clockApiMyStats, gamesApiListEventGames, gamesApiListEventPlanning, gamesApiListEventSteamGames, gamesApiListGames, marketApiBetHistory, marketApiCloseMarket, marketApiCreateBet, marketApiGetUserPoints, marketApiListMarkets, marketApiListUserBets, marketApiResolveMarket, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponseFiles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, placeApiCanvas, placeApiEvolutionGif, placeApiHeatmapDecayGif, placeApiHeatmapEvolutionGif, placeApiHeatmapPng, placeApiPlacementsCsv, polylanSubmitterApiClearCache, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateImageCrop, submissionsApiCreateSubmission, submissionsApiDeleteImageCrop, submissionsApiGetSubmission, submissionsApiListAvailableScopes, submissionsApiListMyScopes, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions, submissionsApiProcessCropOcr, submissionsApiProcessFileOcr, submissionsApiStopSyncScopes, submissionsApiSyncScopes, submissionsApiSyncScopesStatus } from '../sdk.gen';
|
||||
import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, ClockApiCurrentStateData, ClockApiCurrentStateResponse, ClockApiMyStatsData, ClockApiMyStatsResponse, GamesApiListEventGamesData, GamesApiListEventGamesResponse, GamesApiListEventPlanningData, GamesApiListEventPlanningResponse, GamesApiListEventSteamGamesData, GamesApiListEventSteamGamesResponse, GamesApiListGamesData, GamesApiListGamesResponse, MarketApiBetHistoryData, MarketApiBetHistoryResponse, MarketApiCloseMarketData, MarketApiCreateBetData, MarketApiCreateBetResponse, MarketApiGetUserPointsData, MarketApiGetUserPointsResponse, MarketApiListMarketsData, MarketApiListMarketsResponse, MarketApiListUserBetsData, MarketApiListUserBetsResponse, MarketApiResolveMarketData, MarketApiResolveMarketResponse, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileResponse, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionError, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionError, OpusMagnumApiDeleteSubmissionResponse, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetStatsData, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListResponseFilesData, OpusMagnumApiListResponseFilesError, OpusMagnumApiListResponseFilesResponse, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationError, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoError, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseError, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionError, OpusMagnumApiValidateSubmissionResponse, PlaceApiCanvasData, PlaceApiEvolutionGifData, PlaceApiHeatmapDecayGifData, PlaceApiHeatmapEvolutionGifData, PlaceApiHeatmapPngData, PlaceApiPlacementsCsvData, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheError, PolylanSubmitterApiClearCacheResponse, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponse, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropError, SubmissionsApiCreateImageCropResponse, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionError, SubmissionsApiCreateSubmissionResponse, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropError, SubmissionsApiDeleteImageCropResponse, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionError, SubmissionsApiGetSubmissionResponse, SubmissionsApiListAvailableScopesData, SubmissionsApiListAvailableScopesError, SubmissionsApiListAvailableScopesResponse, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesError, SubmissionsApiListMyScopesResponse, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameError, SubmissionsApiListSubmissionsByGameResponse, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsError, SubmissionsApiListUserSubmissionsResponse, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrError, SubmissionsApiProcessCropOcrResponse, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrError, SubmissionsApiProcessFileOcrResponse, SubmissionsApiStopSyncScopesData, SubmissionsApiStopSyncScopesError, SubmissionsApiStopSyncScopesResponse, SubmissionsApiSyncScopesData, SubmissionsApiSyncScopesError, SubmissionsApiSyncScopesResponse, SubmissionsApiSyncScopesStatusData, SubmissionsApiSyncScopesStatusError, SubmissionsApiSyncScopesStatusResponse } from '../types.gen';
|
||||
import { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, clockApiCurrentState, clockApiMyStats, gamesApiListEventGames, gamesApiListEventPlanning, gamesApiListEventSteamGames, gamesApiListGames, marketApiBetHistory, marketApiCloseMarket, marketApiCreateBet, marketApiGetUserPoints, marketApiListMarkets, marketApiListUserBets, marketApiResolveMarket, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponseFiles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, placeApiCanvas, placeApiEvolutionGif, placeApiHeatmapDecayGif, placeApiHeatmapEvolutionGif, placeApiHeatmapPng, placeApiPlacementsCsv, placeApiStats, polylanSubmitterApiClearCache, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateImageCrop, submissionsApiCreateSubmission, submissionsApiDeleteImageCrop, submissionsApiGetSubmission, submissionsApiListAvailableScopes, submissionsApiListMyScopes, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions, submissionsApiProcessCropOcr, submissionsApiProcessFileOcr, submissionsApiStopSyncScopes, submissionsApiSyncScopes, submissionsApiSyncScopesStatus } from '../sdk.gen';
|
||||
import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, ClockApiCurrentStateData, ClockApiCurrentStateResponse, ClockApiMyStatsData, ClockApiMyStatsResponse, GamesApiListEventGamesData, GamesApiListEventGamesResponse, GamesApiListEventPlanningData, GamesApiListEventPlanningResponse, GamesApiListEventSteamGamesData, GamesApiListEventSteamGamesResponse, GamesApiListGamesData, GamesApiListGamesResponse, MarketApiBetHistoryData, MarketApiBetHistoryResponse, MarketApiCloseMarketData, MarketApiCreateBetData, MarketApiCreateBetResponse, MarketApiGetUserPointsData, MarketApiGetUserPointsResponse, MarketApiListMarketsData, MarketApiListMarketsResponse, MarketApiListUserBetsData, MarketApiListUserBetsResponse, MarketApiResolveMarketData, MarketApiResolveMarketResponse, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileResponse, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionError, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionError, OpusMagnumApiDeleteSubmissionResponse, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetStatsData, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListResponseFilesData, OpusMagnumApiListResponseFilesError, OpusMagnumApiListResponseFilesResponse, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationError, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoError, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseError, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionError, OpusMagnumApiValidateSubmissionResponse, PlaceApiCanvasData, PlaceApiEvolutionGifData, PlaceApiHeatmapDecayGifData, PlaceApiHeatmapEvolutionGifData, PlaceApiHeatmapPngData, PlaceApiPlacementsCsvData, PlaceApiStatsData, PlaceApiStatsResponse, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheError, PolylanSubmitterApiClearCacheResponse, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponse, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropError, SubmissionsApiCreateImageCropResponse, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionError, SubmissionsApiCreateSubmissionResponse, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropError, SubmissionsApiDeleteImageCropResponse, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionError, SubmissionsApiGetSubmissionResponse, SubmissionsApiListAvailableScopesData, SubmissionsApiListAvailableScopesError, SubmissionsApiListAvailableScopesResponse, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesError, SubmissionsApiListMyScopesResponse, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameError, SubmissionsApiListSubmissionsByGameResponse, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsError, SubmissionsApiListUserSubmissionsResponse, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrError, SubmissionsApiProcessCropOcrResponse, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrError, SubmissionsApiProcessFileOcrResponse, SubmissionsApiStopSyncScopesData, SubmissionsApiStopSyncScopesError, SubmissionsApiStopSyncScopesResponse, SubmissionsApiSyncScopesData, SubmissionsApiSyncScopesError, SubmissionsApiSyncScopesResponse, SubmissionsApiSyncScopesStatusData, SubmissionsApiSyncScopesStatusError, SubmissionsApiSyncScopesStatusResponse } from '../types.gen';
|
||||
|
||||
export type QueryKey<TOptions extends Options> = [
|
||||
Pick<TOptions, 'baseUrl' | 'body' | 'headers' | 'path' | 'query'> & {
|
||||
@@ -987,6 +987,27 @@ export const placeApiHeatmapDecayGifOptions = (options: Options<PlaceApiHeatmapD
|
||||
queryKey: placeApiHeatmapDecayGifQueryKey(options)
|
||||
});
|
||||
|
||||
export const placeApiStatsQueryKey = (options: Options<PlaceApiStatsData>) => createQueryKey('placeApiStats', options);
|
||||
|
||||
/**
|
||||
* Stats
|
||||
*
|
||||
* Admin-only: aggregated stats about the game - totals, coverage, top
|
||||
* players, most contested pixels, color usage and activity per hour.
|
||||
*/
|
||||
export const placeApiStatsOptions = (options: Options<PlaceApiStatsData>) => queryOptions<PlaceApiStatsResponse, DefaultError, PlaceApiStatsResponse, ReturnType<typeof placeApiStatsQueryKey>>({
|
||||
queryFn: async ({ queryKey, signal }) => {
|
||||
const { data } = await placeApiStats({
|
||||
...options,
|
||||
...queryKey[0],
|
||||
signal,
|
||||
throwOnError: true
|
||||
});
|
||||
return data;
|
||||
},
|
||||
queryKey: placeApiStatsQueryKey(options)
|
||||
});
|
||||
|
||||
export const placeApiPlacementsCsvQueryKey = (options: Options<PlaceApiPlacementsCsvData>) => createQueryKey('placeApiPlacementsCsv', options);
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
|
||||
import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape } from './client';
|
||||
import { client } from './client.gen';
|
||||
import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponses, ClockApiCurrentStateData, ClockApiCurrentStateResponses, ClockApiMyStatsData, ClockApiMyStatsResponses, GamesApiListEventGamesData, GamesApiListEventGamesResponses, GamesApiListEventPlanningData, GamesApiListEventPlanningResponses, GamesApiListEventSteamGamesData, GamesApiListEventSteamGamesResponses, GamesApiListGamesData, GamesApiListGamesResponses, MarketApiBetHistoryData, MarketApiBetHistoryResponses, MarketApiCloseMarketData, MarketApiCloseMarketResponses, MarketApiCreateBetData, MarketApiCreateBetResponses, MarketApiGetUserPointsData, MarketApiGetUserPointsResponses, MarketApiListMarketsData, MarketApiListMarketsResponses, MarketApiListUserBetsData, MarketApiListUserBetsResponses, MarketApiResolveMarketData, MarketApiResolveMarketResponses, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponses, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionErrors, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionErrors, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponseFilesData, OpusMagnumApiListResponseFilesErrors, OpusMagnumApiListResponseFilesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationErrors, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoErrors, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseErrors, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionErrors, OpusMagnumApiValidateSubmissionResponses, PlaceApiCanvasData, PlaceApiCanvasResponses, PlaceApiEvolutionGifData, PlaceApiEvolutionGifResponses, PlaceApiHeatmapDecayGifData, PlaceApiHeatmapDecayGifResponses, PlaceApiHeatmapEvolutionGifData, PlaceApiHeatmapEvolutionGifResponses, PlaceApiHeatmapPngData, PlaceApiHeatmapPngResponses, PlaceApiPlacementsCsvData, PlaceApiPlacementsCsvResponses, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheErrors, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropErrors, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionErrors, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropErrors, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionErrors, SubmissionsApiGetSubmissionResponses, SubmissionsApiListAvailableScopesData, SubmissionsApiListAvailableScopesErrors, SubmissionsApiListAvailableScopesResponses, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesErrors, SubmissionsApiListMyScopesResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameErrors, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsErrors, SubmissionsApiListUserSubmissionsResponses, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrErrors, SubmissionsApiProcessCropOcrResponses, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrErrors, SubmissionsApiProcessFileOcrResponses, SubmissionsApiStopSyncScopesData, SubmissionsApiStopSyncScopesErrors, SubmissionsApiStopSyncScopesResponses, SubmissionsApiSyncScopesData, SubmissionsApiSyncScopesErrors, SubmissionsApiSyncScopesResponses, SubmissionsApiSyncScopesStatusData, SubmissionsApiSyncScopesStatusErrors, SubmissionsApiSyncScopesStatusResponses } from './types.gen';
|
||||
import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponses, ClockApiCurrentStateData, ClockApiCurrentStateResponses, ClockApiMyStatsData, ClockApiMyStatsResponses, GamesApiListEventGamesData, GamesApiListEventGamesResponses, GamesApiListEventPlanningData, GamesApiListEventPlanningResponses, GamesApiListEventSteamGamesData, GamesApiListEventSteamGamesResponses, GamesApiListGamesData, GamesApiListGamesResponses, MarketApiBetHistoryData, MarketApiBetHistoryResponses, MarketApiCloseMarketData, MarketApiCloseMarketResponses, MarketApiCreateBetData, MarketApiCreateBetResponses, MarketApiGetUserPointsData, MarketApiGetUserPointsResponses, MarketApiListMarketsData, MarketApiListMarketsResponses, MarketApiListUserBetsData, MarketApiListUserBetsResponses, MarketApiResolveMarketData, MarketApiResolveMarketResponses, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponses, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionErrors, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionErrors, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponseFilesData, OpusMagnumApiListResponseFilesErrors, OpusMagnumApiListResponseFilesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationErrors, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoErrors, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseErrors, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionErrors, OpusMagnumApiValidateSubmissionResponses, PlaceApiCanvasData, PlaceApiCanvasResponses, PlaceApiEvolutionGifData, PlaceApiEvolutionGifResponses, PlaceApiHeatmapDecayGifData, PlaceApiHeatmapDecayGifResponses, PlaceApiHeatmapEvolutionGifData, PlaceApiHeatmapEvolutionGifResponses, PlaceApiHeatmapPngData, PlaceApiHeatmapPngResponses, PlaceApiPlacementsCsvData, PlaceApiPlacementsCsvResponses, PlaceApiStatsData, PlaceApiStatsResponses, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheErrors, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropErrors, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionErrors, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropErrors, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionErrors, SubmissionsApiGetSubmissionResponses, SubmissionsApiListAvailableScopesData, SubmissionsApiListAvailableScopesErrors, SubmissionsApiListAvailableScopesResponses, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesErrors, SubmissionsApiListMyScopesResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameErrors, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsErrors, SubmissionsApiListUserSubmissionsResponses, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrErrors, SubmissionsApiProcessCropOcrResponses, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrErrors, SubmissionsApiProcessFileOcrResponses, SubmissionsApiStopSyncScopesData, SubmissionsApiStopSyncScopesErrors, SubmissionsApiStopSyncScopesResponses, SubmissionsApiSyncScopesData, SubmissionsApiSyncScopesErrors, SubmissionsApiSyncScopesResponses, SubmissionsApiSyncScopesStatusData, SubmissionsApiSyncScopesStatusErrors, SubmissionsApiSyncScopesStatusResponses } from './types.gen';
|
||||
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, TResponse = unknown> = Options2<TData, ThrowOnError, TResponse> & {
|
||||
/**
|
||||
@@ -414,6 +414,22 @@ export const placeApiHeatmapDecayGif = <ThrowOnError extends boolean = false>(op
|
||||
...options
|
||||
});
|
||||
|
||||
/**
|
||||
* Stats
|
||||
*
|
||||
* Admin-only: aggregated stats about the game - totals, coverage, top
|
||||
* players, most contested pixels, color usage and activity per hour.
|
||||
*/
|
||||
export const placeApiStats = <ThrowOnError extends boolean = false>(options: Options<PlaceApiStatsData, ThrowOnError>) => (options.client ?? client).get<PlaceApiStatsResponses, unknown, ThrowOnError>({
|
||||
security: [{
|
||||
in: 'cookie',
|
||||
name: 'sessionid',
|
||||
type: 'apiKey'
|
||||
}],
|
||||
url: '/api/events/{event_path}/place/stats',
|
||||
...options
|
||||
});
|
||||
|
||||
/**
|
||||
* Placements Csv
|
||||
*
|
||||
|
||||
@@ -1264,6 +1264,144 @@ export type PlayerStatsOut = {
|
||||
points_below: number | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* ColorCountOut
|
||||
*/
|
||||
export type ColorCountOut = {
|
||||
/**
|
||||
* Color Index
|
||||
*/
|
||||
color_index: number;
|
||||
/**
|
||||
* Color
|
||||
*/
|
||||
color: string;
|
||||
/**
|
||||
* Placements
|
||||
*/
|
||||
placements: number;
|
||||
/**
|
||||
* On Canvas
|
||||
*/
|
||||
on_canvas: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* HourCountOut
|
||||
*/
|
||||
export type HourCountOut = {
|
||||
/**
|
||||
* Hour
|
||||
*/
|
||||
hour: string;
|
||||
/**
|
||||
* Placements
|
||||
*/
|
||||
placements: number;
|
||||
/**
|
||||
* Players
|
||||
*/
|
||||
players: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* PixelCountOut
|
||||
*/
|
||||
export type PixelCountOut = {
|
||||
/**
|
||||
* X
|
||||
*/
|
||||
x: number;
|
||||
/**
|
||||
* Y
|
||||
*/
|
||||
y: number;
|
||||
/**
|
||||
* Placements
|
||||
*/
|
||||
placements: number;
|
||||
/**
|
||||
* Players
|
||||
*/
|
||||
players: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* PlaceStatsOut
|
||||
*/
|
||||
export type PlaceStatsOut = {
|
||||
/**
|
||||
* Width
|
||||
*/
|
||||
width: number;
|
||||
/**
|
||||
* Height
|
||||
*/
|
||||
height: number;
|
||||
/**
|
||||
* Total Placements
|
||||
*/
|
||||
total_placements: number;
|
||||
/**
|
||||
* Total Players
|
||||
*/
|
||||
total_players: number;
|
||||
/**
|
||||
* First Placement At
|
||||
*/
|
||||
first_placement_at: string | null;
|
||||
/**
|
||||
* Last Placement At
|
||||
*/
|
||||
last_placement_at: string | null;
|
||||
/**
|
||||
* Pixels Touched
|
||||
*/
|
||||
pixels_touched: number;
|
||||
/**
|
||||
* Coverage
|
||||
*/
|
||||
coverage: number;
|
||||
/**
|
||||
* Overwrites
|
||||
*/
|
||||
overwrites: number;
|
||||
/**
|
||||
* Avg Placements Per Player
|
||||
*/
|
||||
avg_placements_per_player: number;
|
||||
/**
|
||||
* Top Players
|
||||
*/
|
||||
top_players: Array<PlayerCountOut>;
|
||||
/**
|
||||
* Most Contested Pixels
|
||||
*/
|
||||
most_contested_pixels: Array<PixelCountOut>;
|
||||
/**
|
||||
* Colors
|
||||
*/
|
||||
colors: Array<ColorCountOut>;
|
||||
/**
|
||||
* Per Hour
|
||||
*/
|
||||
per_hour: Array<HourCountOut>;
|
||||
};
|
||||
|
||||
/**
|
||||
* PlayerCountOut
|
||||
*/
|
||||
export type PlayerCountOut = {
|
||||
/**
|
||||
* Username
|
||||
*/
|
||||
username: string;
|
||||
/**
|
||||
* Placements
|
||||
*/
|
||||
placements: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* MarketListSchema
|
||||
*/
|
||||
@@ -2823,6 +2961,27 @@ export type PlaceApiHeatmapDecayGifResponses = {
|
||||
200: unknown;
|
||||
};
|
||||
|
||||
export type PlaceApiStatsData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Event Path
|
||||
*/
|
||||
event_path: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/api/events/{event_path}/place/stats';
|
||||
};
|
||||
|
||||
export type PlaceApiStatsResponses = {
|
||||
/**
|
||||
* OK
|
||||
*/
|
||||
200: PlaceStatsOut;
|
||||
};
|
||||
|
||||
export type PlaceApiStatsResponse = PlaceApiStatsResponses[keyof PlaceApiStatsResponses];
|
||||
|
||||
export type PlaceApiPlacementsCsvData = {
|
||||
body?: never;
|
||||
path: {
|
||||
|
||||
Reference in New Issue
Block a user