feat: add admin stats
This commit is contained in:
@@ -18,6 +18,8 @@ from .heatmap import (
|
|||||||
build_heatmap_png,
|
build_heatmap_png,
|
||||||
)
|
)
|
||||||
from .models import PixelPlacement, PlaceSettings
|
from .models import PixelPlacement, PlaceSettings
|
||||||
|
from .schemas import PlaceStatsOut
|
||||||
|
from .stats import build_place_stats
|
||||||
|
|
||||||
router = Router()
|
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)
|
@router.get(f"{PREFIX}/placements.csv", auth=django_auth_is_staff)
|
||||||
@decorate_view(require_game_enabled(path="place"))
|
@decorate_view(require_game_enabled(path="place"))
|
||||||
def placements_csv(request: HttpRequest, event_path: str):
|
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 { type DefaultError, type InfiniteData, infiniteQueryOptions, queryOptions, type UseMutationOptions } from '@tanstack/vue-query';
|
||||||
|
|
||||||
import { client } from '../client.gen';
|
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 { 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, 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 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> = [
|
export type QueryKey<TOptions extends Options> = [
|
||||||
Pick<TOptions, 'baseUrl' | 'body' | 'headers' | 'path' | 'query'> & {
|
Pick<TOptions, 'baseUrl' | 'body' | 'headers' | 'path' | 'query'> & {
|
||||||
@@ -987,6 +987,27 @@ export const placeApiHeatmapDecayGifOptions = (options: Options<PlaceApiHeatmapD
|
|||||||
queryKey: placeApiHeatmapDecayGifQueryKey(options)
|
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);
|
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 { type Client, formDataBodySerializer, type Options as Options2, type TDataShape } from './client';
|
||||||
import { client } from './client.gen';
|
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> & {
|
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
|
...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
|
* Placements Csv
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1264,6 +1264,144 @@ export type PlayerStatsOut = {
|
|||||||
points_below: number | null;
|
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
|
* MarketListSchema
|
||||||
*/
|
*/
|
||||||
@@ -2823,6 +2961,27 @@ export type PlaceApiHeatmapDecayGifResponses = {
|
|||||||
200: unknown;
|
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 = {
|
export type PlaceApiPlacementsCsvData = {
|
||||||
body?: never;
|
body?: never;
|
||||||
path: {
|
path: {
|
||||||
|
|||||||
Reference in New Issue
Block a user