-
Notifications
You must be signed in to change notification settings - Fork 3
Hunts Collection
Om Duggineni edited this page Mar 7, 2024
·
30 revisions
{
"type": "array",
"items": {
"type": "object",
"properties": {
"_id": {
"type": "objectId"
},
"name": {
"type": "string",
"optional": false,
"minLength": 1,
"maxLength": 255,
"description": "Challenge Name. Accessible on the Home Screen as the title of the challenge."
},
"description": {
"type": "string",
"optional": true,
"minLength": 1,
"maxLength": 4096,
"description": "Short challenge description. Accessible on the home screen, visible under the title of the challenge. If no description is provided, no description will be visible."
},
"startDate": {
"type": "string",
"optional": false,
"format": "date-time",
"description": "The date/time at which the challenge fully opens. "
},
"joinableAfterDate": {
"type": "string",
"optional": false,
"format": "date-time",
"description": "The date/time at which challenge members can first form teams. Between this date and startDate, challenge members will be able to form teams but will not actually be able to enter the challenge or view any questions. A default (15 minutes before startDate), may be set on the server-side, but not on the database side."
},
"endDate": {
"type": "string",
"optional": false,
"format": "date-time",
"description": "The date/time at which the challenge closes. At this time, all challenge members (client-side) will be sent to the results screen, and the server will stop accepting responses to questions."
},
"huntLocation": {
"type": "object",
"optional": false,
"description": "Location from which hunt can be joined.",
"properties": {
"locationName": {
"type": "string",
"optional": false,
"minLength": 1,
"maxLength": 255,
"description": "Short name + address of hunt location"
},
"locationInstructions": {
"type": "string",
"optional": true,
"minLength": 1,
"maxLength": 4096,
"description": "Instructions on how to get to hunt location"
},
"geofence": {
"optional": false,
"description": "The geofence that defines the area in which the hunt can be joined. A subset of the GeoJSON format that supports only Polygon types.",
"oneOf": [
{
"title": "Point and Radius",
"type": "object",
"required": [
"type",
"coordinates",
"radius"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Point and Radius"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
},
"radius": {
"type": "number"
}
}
},
{
"type": "null",
"description": "If the geofence is null, the hunt can be joined from anywhere."
}
]
}
}
},
"challenges": {
"type": "array",
"optional": false,
"description": "The challenges that make up the hunt. Each challenge is a question or puzzle that the teams must solve to progress through the hunt. The order of the challenges in this array determines the order in which they will be presented to the teams.",
"items": {
"type": "object",
"properties": {
"_id": {
"type": "objectId"
},
"questionTitle": {
"type": "string",
"optional": false,
"minLength": 1,
"maxLength": 255,
"description": "The title of the question. Accessible on the question screen as the title of the question. Should be as short as possible, while still being descriptive."
},
"description": {
"type": "string",
"optional": true,
"minLength": 1,
"maxLength": 32768,
"description": "The text of the question or puzzle that the teams must solve. Accessible on the question screen, visible under the title of the question. If no description is provided, no description will be visible."
},
"imageUrl": {
"type": "string",
"optional": false,
"minLength": 1,
"maxLength": 32768,
"description": "The URL of the image. Accessible on the question screen, visible under the title of the question. If no description is provided, no description will be visible."
},
"hints": {
"type": "array",
"optional": false,
"description": "The honts that are available to the teams for this question. The order of the hints in this array determines the order in which they will be presented to the teams. Hints should NOT be necessary for solving the question.",
"items": {
"type": "object",
"properties": {
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"string"
],
"optional": false,
"description": "The type of hint. Determines what other properties are required."
},
"text": {
"type": "string",
"optional": false,
"minLength": 1,
"maxLength": 32768,
"description": "The text of the hint. Accessible on the question screen, visible under the title of the question. If no description is provided, no description will be visible."
},
"penalty": {
"type": "double",
"optional": true,
"description": "The number of points that will be deducted from the points awarded for the question if the team views this hint. If not provided, the hint will not have a penalty. If provided, the penalty must be a positive number."
}
}
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"image"
],
"optional": false,
"description": "The type of hint. Determines what other properties are required."
},
"imageUrl": {
"type": "string",
"optional": false,
"minLength": 1,
"maxLength": 32768,
"description": "The URL of the image. Accessible on the question screen, visible under the title of the question. If no description is provided, no description will be visible."
},
"penalty": {
"type": "double",
"optional": true,
"description": "The number of points that will be deducted from the points awarded for the question if the team views this hint. If not provided, the clue will not have a penalty. If provided, the penalty must be a positive number."
}
}
}
]
}
}
},
"scoring": {
"type": "object",
"optional": false,
"properties": {
"points": {
"type": "double",
"optional": false,
"description": "The number of points that the question is worth."
},
"timeDecay": {
"optional": false,
"oneOf": [
{
"type": "object",
"description": "No time decay. The question will be worth the same number of points regardless of how long it takes the team to solve it.",
"properties": {
"type": {
"type": "string",
"enum": [
"none"
],
"optional": false,
"description": "The type of time decay. Determines what other properties are required."
},
"timeLimit": {
"type": "double",
"optional": true,
"description": "The number of minutes that the team has to solve the question. If not provided, there will be no time limit."
}
}
},
{
"type": "object",
"description": "Linear time decay. The number of points awarded for the question will decrease linearly as time elapses after the question is first presented to the team.",
"properties": {
"type": {
"type": "string",
"enum": [
"linear"
],
"optional": false,
"description": "The type of time decay. Determines what other properties are required."
},
"decayRate": {
"type": "double",
"optional": false,
"description": "The number of points that will be deducted from the points awarded for the question for each minute that elapses after the question is first presented to the team."
},
"timeLimit": {
"type": "double",
"optional": true,
"description": "The number of minutes that the team has to solve the question. If not provided, the time limit will be determined by the amount of time it takes for the points awarded for the question to reach zero."
}
}
},
{
"type": "object",
"description": "Exponential time decay. The number of points awarded for the question will decrease exponentially as time elapses after the question is first presented to the team, with the rate of decay decreasing over time.",
"properties": {
"type": {
"type": "string",
"enum": [
"exponential"
],
"optional": false,
"description": "The type of time decay. Determines what other properties are required."
},
"halfLife": {
"type": "double",
"optional": false,
"description": "The number of seconds that will elapse for the points awarded for the question to be halved. Determines the exponential decay rate."
},
"timeLimit": {
"type": "double",
"optional": true,
"description": "The number of minutes that the team has to solve the question. If not provided, there will be no time limit."
}
}
}
]
}
}
},
"placeholderText": {
"type": "string",
"optional": true,
"minLength": 1,
"maxLength": 255,
"description": "Hint text that will be displayed in the text field before the user enters a response. If no placeholder text is provided, a generic placeholder will be displayed."
},
"response": {
"oneOf": [
{
"type": "object",
"description": "This response will be presented as a text field to the user. The user's response will be considered solved if it matches any of the possible answers. If caseSensitive is true, the user's response must match the possible answers case-sensitively. If caseSensitive is false, case will not be considered when matching.",
"properties": {
"type": {
"type": "string",
"enum": [
"string_text_field"
],
"optional": false,
"description": "The type of response. Determines what other properties are required. Determines what type of text field will be presented to the user and what type of validation will be performed."
},
"possibleAnswers": {
"type": "array",
"optional": false,
"minItems": 1,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"description": "The possible answers to the question. The question will be considered solved if the team enters any of these answers, regardless of case."
},
"caseSensitive": {
"type": "boolean",
"optional": true,
"default": false,
"description": "Whether the user's response must match the possible answers case-sensitively. If not provided, the default is false."
}
}
},
{
"type": "object",
"description": "This response will be a comma-separated list of strings (specifically, separated by a comma and an arbitrary number of spaces on both sides). The user's response will be considered solved if it matches any of the possible answers in any order. If caseSensitive is true, the user's response must match the possible answers case-sensitively. If caseSensitive is false, case will not be considered when matching.",
"properties": {
"type": {
"type": "string",
"enum": [
"string_set_matched"
],
"optional": false,
"description": "The type of response. Determines what other properties are required. Determines what type of text field will be presented to the user and what type of validation will be performed."
},
"possibleAnswers": {
"type": "array",
"optional": false,
"minItems": 1,
"items": {
"type": "array",
"minLength": 1,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 255
}
},
"description": "The possible answers to the question. The question will be considered solved if the team enters any of these answers, regardless of case."
},
"caseSensitive": {
"type": "boolean",
"optional": true,
"default": false,
"description": "Whether the user's response must match the possible answers case-sensitively. If not provided, the default is false."
}
}
},
{
"type": "object",
"description": "This response will be presented as a multiple-choice question to the user. The user's response will be considered solved if it matches any of the possible answers. If caseSensitive is true, the user's response must match the possible answers case-sensitively. If caseSensitive is false, case will not be considered when matching.",
"properties": {
"type": {
"type": "string",
"enum": [
"multiple_choice"
],
"optional": false,
"description": "The type of response. Determines what other properties are required. Determines what type of text field will be presented to the user and what type of validation will be performed."
},
"choices": {
"type": "array",
"optional": false,
"minItems": 1,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"description": "The choices that the user can select from. The question will be considered solved if the team selects the correct choice."
},
"correctAnswers": {
"type": "array",
"optional": false,
"minItems": 1,
"items": {
"type": "integer",
"minLength": 1,
"maxLength": 255
},
"description": "The correct choices. The question will be considered solved if the team selects any of the choices with these indices."
}
}
},
{
"type": "object",
"description": "This response will be presented as a number field to the user. The user's response will be considered solved if it matches any of the possible answers.",
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"optional": false,
"description": "The type of response. Determines what other properties are required. Determines what type of text field will be presented to the user and what type of validation will be performed."
},
"possibleAnswers": {
"type": "array",
"optional": false,
"minItems": 1,
"items": {
"type": "double"
},
"description": "The possible answers to the question. The question will be considered solved if the team's response, when parsed as a number, equals any of these answers."
}
}
},
{
"type": "object",
"description": "This response will be presented as a number field to the user. The user's response will be considered solved if it is greater than or equal to minAnswer and less than or equal to maxAnswer.",
"properties": {
"type": {
"type": "string",
"enum": [
"number_range"
],
"optional": false,
"description": "The type of response. Determines what other properties are required. Determines what type of text field will be presented to the user and what type of validation will be performed."
},
"minAnswer": {
"type": "double",
"optional": true,
"description": "The smallest number that the team's response can be. If not provided, there will be no minimum number."
},
"maxAnswer": {
"type": "double",
"optional": true,
"description": "The largest number that the team's response can be. If not provided, there will be no maximum number."
}
}
},
{
"type": "object",
"description": "This response will be presented as a date/time field to the user. The user's response will be considered solved if it is within the range of minAnswer and maxAnswer.",
"properties": {
"type": {
"type": "string",
"enum": [
"date_time"
],
"optional": false,
"description": "The type of response. Determines what other properties are required. Determines what type of text field will be presented to the user and what type of validation will be performed."
},
"minAnswer": {
"type": "string",
"format": "date-time",
"optional": true,
"description": "The earliest date/time that the team's response can be. If not provided, there will be no minimum date/time."
},
"maxAnswer": {
"type": "string",
"format": "date-time",
"optional": true,
"description": "The latest date/time that the team's response can be. If not provided, there will be no maximum date/time."
}
}
},
{
"type": "object",
"description": "The answer to this question is a QR code containing a randomly generated ID as text data. The user's response will be considered solved if it matches the ID of the QR code.",
"properties": {
"type": {
"type": "string",
"enum": [
"qr_code_id_response"
],
"optional": false,
"description": "The type of response. Determines what other properties are required. Determines what type of text field will be presented to the user and what type of validation will be performed."
},
"correctId": {
"type": "string",
"optional": false,
"minLength": 16,
"maxLength": 255,
"description": "The ID of the QR code. The question will be considered solved if the team's response matches this ID."
}
}
}
]
},
"sequence": {
"type": "object",
"description": "Determines how questions are solvable by the user. Sequences are determined uniquely by the num property. Questions are ordered in a sequence by the order property. A question cannot be solved (and may not even be visible to the end user) until the previous question in the sequence has been solved. Questions are sorted by (sequence, order) before being presented to the user.",
"optional": false,
"properties": {
"num": {
"type": "integer",
"optional": false,
"description": "The sequence that this question is a part of. May be any integer."
},
"order": {
"type": "integer",
"optional": true,
"description": "The order of this question within the sequence. May be any integer, should be unique within the sequence."
}
}
}
}
}
},
"gameState": {
"type": "object",
"optional": true,
"description": "Contains all data about a hunt currently in progress. Is null if the hunt is not currently in progress. Delete this key if the hunt questions are exported to a file or if the hunt is copied.",
"properties": {
"teams": {
"type": "array",
"items": {
"type": "object",
"properties": {
"_id": {
"type": "objectId"
},
"name": {
"type": "string",
"optional": false,
"minLength": 1,
"maxLength": 255,
"description": "The name of the team."
},
"teamLead": {
"type": "objectId",
"optional": false,
"description": "The playerId of the team leader. The team leader is the only member of the team who can accept join requests and remove players from the team. If the team leader leaves the team, the player who first joined the team after the team leader will become the new team leader."
},
"players": {
"type": "array",
"optional": false,
"items": {
"type": "object",
"properties": {
"playerId": {
"type": "objectId",
"optional": false
},
"timeJoined": {
"type": "string",
"format": "date-time",
"description": "The date/time at which the player joined the team.",
"optional": false
}
}
}
},
"challengeResults": {
"type": "array",
"optional": true,
"items": {
"type": "object",
"optional": false,
"properties": {
"challengeId": {
"type": "objectId",
"optional": false,
"description": "The _id of the challenge."
},
"solved": {
"type": "string",
"optional": false,
"description": "The date/time at which the challenge was solved."
},
"elaspsedTime": {
"type": "double",
"optional": false,
"description": "The number of seconds that elapsed between the challenge being opened and the challenge being solved."
},
"answerAttempts": {
"type": "double",
"optional": false,
"description": "The number of times that the team attempted to solve the challenge."
},
"hintsViewed": {
"type": "double",
"optional": false,
"description": "The number of hints that the team viewed for the challenge."
},
"pointsAwarded": {
"type": "double",
"optional": false,
"description": "The number of points that the team was awarded for solving the challenge."
},
"answerProvided": {
"oneOf": [
{
"type": "string",
"description": "The answer that the team provided. If the challenge was solved, this will be the correct answer. If the challenge was not solved, this will be the last answer that the team provided."
},
{
"type": "null",
"description": "If the challenge was not solved, the answer that the team provided will be null."
}
]
}
}
}
},
"invitations": {
"type": "array",
"optional": false,
"items": {
"type": "object",
"properties": {
"playerId": {
"type": "objectId",
"optional": false
}
}
}
}
}
}
}
}
}
}
}
}Example Pydantic models: please check before using, as
- there was AI involved in making these
- the JSON schema above is the actual source of truth
from typing import List, Optional, Union
from pydantic import BaseModel, Field
from datetime import datetime
class HuntLocationPointAndRadius(BaseModel):
type: str = Field(..., description="The type of geofence. Must be 'Point and Radius'")
coordinates: List[float] = Field(
...,
description="The coordinates of the center. Must have exactly 2 points, which are a pair of (longitude, latitude)"
)
radius: float = Field(
...,
description="The radius from the center, in meters"
)
class HuntLocationGeoJSONPolygon(BaseModel):
type: str = Field(..., description="The type of geofence. Must be 'Polygon'")
coordinates: List[List[List[float]]] = Field(
...,
description="The coordinates of the Polygon. Must have at least 4 points, where each point is a pair of (longitude, latitude)"
)
bbox: List[float] = Field(
...,
min_items=4,
max_items=4,
description="The bounding box of the Polygon, given as [min lon, min lat, max lon, max lat]"
)
class HuntLocationGeoJSONMultiPolygon(BaseModel):
type: str = Field(..., description="The type of geofence. Must be 'MultiPolygon'")
coordinates: List[List[List[List[float]]]] = Field(
...,
description="The coordinates of the MultiPolygon. Each Polygon must have at least 4 points, where each point is a pair of (longitude, latitude)"
)
bbox: List[float] = Field(
...,
min_items=4,
max_items=4,
description="The bounding box of the MultiPolygon, given as [min lon, min lat, max lon, max lat]"
)
class HuntLocationGeofence(BaseModel):
locationName: str = Field(..., min_length=1, max_length=255, description="Short name + address of hunt location")
locationInstructions: Optional[str] = Field(None, min_length=1, max_length=4096, description="Instructions on how to get to hunt location")
geofence: Optional[Union[HuntLocationGeoJSONPolygon, HuntLocationGeoJSONMultiPolygon]] = Field(
None,
description="The geofence that defines the area in which the hunt can be joined. If None, the hunt can be joined from anywhere."
)
class StringClue(BaseModel):
type: str = Field("string", description="The type of clue. Must be 'string'")
text: str = Field(..., min_length=1, max_length=32768, description="The text of the clue")
penalty: Optional[float] = Field(None, gt=0, description="Points deducted for viewing this clue. Must be positive.")
class ImageClue(BaseModel):
type: str = Field("image", description="The type of clue. Must be 'image'")
imageUrl: str = Field(..., min_length=1, max_length=32768, description="The URL of the image")
penalty: Optional[float] = Field(None, gt=0, description="Points deducted for viewing this clue. Must be positive.")
class Clue(BaseModel):
__root__: Union[StringClue, ImageClue]
class NoTimeDecayScoring(BaseModel):
type: str = Field("none", description="The type of time decay. Must be 'none'")
timeLimit: Optional[float] = Field(None, description="Time limit in minutes. If None, no time limit.")
class LinearTimeDecayScoring(BaseModel):
type: str = Field("linear", description="The type of time decay. Must be 'linear'")
decayRate: float = Field(..., description="Point deduction per minute")
timeLimit: Optional[float] = Field(None, description="Time limit in minutes. If None, decay until 0 points.")
class ExponentialTimeDecayScoring(BaseModel):
type: str = Field("exponential", description="The type of time decay. Must be 'exponential'")
halfLife: float = Field(..., description="Half life in seconds")
timeLimit: Optional[float] = Field(None, description="Time limit in minutes. If None, no time limit.")
class Scoring(BaseModel):
points: float = Field(..., description="Points awarded for solving")
timeDecay: Union[NoTimeDecayScoring, LinearTimeDecayScoring, ExponentialTimeDecayScoring] = Field(...)
class StringResponse(BaseModel):
type: str = Field("string_text_field", const=True, description="Response type")
possibleAnswers: List[str] = Field(..., min_items=1, description="Possible correct answers")
caseSensitive: bool = Field(False, description="Case sensitive matching")
class StringSetMatchedResponse(BaseModel):
type: str = Field("string_set_matched", const=True, description="Response type")
possibleAnswers: List[List[str]] = Field(..., min_items=1, description="Possible correct answer sets")
caseSensitive: bool = Field(False, description="Case sensitive matching")
class MultipleChoiceResponse(BaseModel):
type: str = Field("multiple_choice", const=True, description="Response type")
choices: List[str] = Field(..., min_items=1, description="Choices to display")
correctAnswers: List[int] = Field(..., min_items=1, description="Indices of correct choices")
class NumberResponse(BaseModel):
type: str = Field("number", const=True, description="Response type")
possibleAnswers: List[float] = Field(..., min_items=1, description="Possible correct answers")
class NumberRangeResponse(BaseModel):
type: str = Field("number_range", const=True, description="Response type")
minAnswer: Optional[float] = Field(None, description="Minimum value")
maxAnswer: Optional[float] = Field(None, description="Maximum value")
class DateTimeResponse(BaseModel):
type: str = Field("date_time", const=True, description="Response type")
minAnswer: Optional[datetime] = Field(None, description="Earliest correct datetime")
maxAnswer: Optional[datetime] = Field(None, description="Latest correct datetime")
class QRCodeIdResponse(BaseModel):
type: str = Field("qr_code_id_response", const=True, description="Response type")
correctId: str = Field(..., min_length=16, max_length=255, description="Correct QR code ID")
Response = Union[StringResponse, StringSetMatchedResponse, MultipleChoiceResponse, NumberResponse,
NumberRangeResponse, DateTimeResponse, QRCodeIdResponse]
class Sequence(BaseModel):
num: int = Field(..., description="Sequence number")
order: Optional[int] = Field(None, description="Order within sequence")
class Challenge(BaseModel):
questionTitle: str = Field(..., min_length=1, max_length=255, description="Question title")
description: Optional[str] = Field(None, min_length=1, max_length=32768, description="Question text")
clues: List[Clue] = Field(...)
scoring: Scoring = Field(...)
placeholderText: Optional[str] = Field(None, min_length=1, max_length=255, description="Text field placeholder")
response: Response = Field(...)
sequence: Sequence = Field(...)
class ChallengeResult(BaseModel):
challengeId: str = Field(..., description="Challenge ID")
solved: datetime = Field(..., description="Time solved")
elapsedTime: float = Field(..., description="Time to solve in seconds")
answerAttempts: int = Field(..., description="Number of answer attempts")
hintsViewed: int = Field(..., description="Number of hints viewed")
pointsAwarded: float = Field(..., description="Points awarded")
answerProvided: Optional[Union[str, None]] = Field(None, description="Last answer attempt")
class Player(BaseModel):
playerId: str = Field(..., description="Player ID")
timeJoined: datetime = Field(...)
class Team(BaseModel):
_id: Optional[str] = Field(None, alias="_id")
name: str = Field(..., min_length=1, max_length=255, description="Team name")
teamLead: str = Field(..., description="Team leader player ID")
players: List[Player] = Field(...)
challengeResults: Optional[List[ChallengeResult]] = Field(None)
invitations: List[str] = Field(...)
class HuntGameState(BaseModel):
teams: List[Team] = Field(...)
class ChallengeSchema(BaseModel):
_id: Optional[str] = Field(None, alias="_id")
name: str = Field(..., min_length=1, max_length=255, description="Challenge name")
description: Optional[str] = Field(None, min_length=1, max_length=4096, description="Challenge description")
startDate: datetime = Field(..., description="Start date/time")
joinableAfterDate: datetime = Field(..., description="Team join start date/time")
endDate: datetime = Field(..., description="End date/time")
huntLocation: HuntLocation = Field(...)
challenges: List[Challenge] = Field(...)
gameState: Optional[HuntGameState] = Field(None, description="Populated only during active hunt")