sidequest_common.schemas.epgp#

EPGP loot system schemas.

Rules constants, the addon export/import wire shapes, and the HTTP API’s request/response schemas (local://addon-contract.md is the source of truth; field names and string prefixes here are binding).

Attributes#

AWARD_AMOUNT_MAX

AWARD_AMOUNT_MIN

BASE_GP

CHASE_MULTIPLIER

DECAY_PERCENT

Weekly decay applied to EP and GP at every reset, read-time only (amount * (1 - DECAY_PERCENT/100) ** resets).

DEFAULT_REALM

The guild's single WoW: Forever realm, used when a member claims a character without specifying one.

EPGP_RULES

Exact shape of the rules object in the SQI1: import string.

EP_VALUES

EP awarded per AwardReason. standby earns the same as the raid (on_time/full_night/boss kills).

GP_BY_INVTYPE

GP by INVTYPE_* at main-spec cost. A 1H "main caster weapon" uses the 2H cost (200) by officer toggle; tier

LEGENDARY_GP

MAX_AWARDS

MAX_DEVICE_NAME_LENGTH

Cap on EpgpDeviceAuthorizationCreate.device_name, trimmed; also the sync token name length it feeds into.

MAX_ENCOUNTERS

MAX_IMPORT_BYTES

MAX_LOOT

MAX_NAME_LENGTH

MAX_NOTE_LENGTH

MAX_SESSIONS

MIN_GP

OS_PERCENT

Off-spec pays this percent of main-spec GP, rounded half up. Alt fill and disenchant/bank pay 0.

TRIAL_DAYS

WEEKLY_RESET_HOUR_UTC

WEEKLY_RESET_WEEKDAY

datetime.weekday() value for Tuesday.

Classes#

AwardImportCounts

ImportCounts plus rows newly voided by this import.

AwardReason

Why an EP/GP ledger entry was written.

CharacterSource

How a WowCharacter got linked to a member.

DeviceAuthorizationPollStatus

status on POST /api/epgp/device-authorizations/token.

DeviceAuthorizationStatus

status on GET /api/epgp/device-authorizations/{user_code}.

EpgpAdjustmentCreate

Body for POST /api/epgp/adjustments (officer).

EpgpCharacter

A WowCharacter, as returned by the characters admin screen and /api/me/characters.

EpgpCharacterList

Response for GET /api/epgp/characters (officer) and GET /api/me/characters (member).

EpgpCharacterUpdate

Body for PUT /api/epgp/characters/{id} (officer).

EpgpDeviceAuthorizationCreate

Body for POST /api/epgp/device-authorizations (public): the device starting a browser sign-in.

EpgpDeviceAuthorizationCreated

Response for POST /api/epgp/device-authorizations (public): poll .../token with device_code.

EpgpDeviceAuthorizationPoll

Body for POST /api/epgp/device-authorizations/token (public).

EpgpDeviceAuthorizationPollResult

Response for POST /api/epgp/device-authorizations/token (public): always 200, never an oracle.

EpgpDeviceAuthorizationStatus

Response for GET /api/epgp/device-authorizations/{user_code} (any guild member).

EpgpGameImport

Response for GET /api/epgp/game-import (officer).

EpgpImportBody

Body for POST /api/epgp/imports: any text containing one SQX1: string.

EpgpImportResult

Response for POST /api/epgp/imports.

EpgpLedgerPage

Response for GET /api/epgp/ledger (public), newest first.

EpgpLedgerRow

One row of GET /api/epgp/ledger.

EpgpLootPage

Response for GET /api/epgp/loot (public), newest first.

EpgpLootRow

One row of GET /api/epgp/loot.

EpgpProgression

Response for GET /api/epgp/progression (public).

EpgpProgressionEncounter

One boss's kill/wipe tally on the progression board.

EpgpProgressionInstance

One instance's bosses on the progression board.

EpgpRoster

Response for GET /api/epgp/roster (officer).

EpgpRosterRow

One row of GET /api/epgp/roster (officer).

EpgpRosterUpdate

Body for PUT /api/epgp/roster/{member_id} (officer). Setting "trial" stamps trial_started_at now.

EpgpRules

The rules object as returned by GET /api/epgp/standings (same shape as the game-import payload).

EpgpStandings

Response for GET /api/epgp/standings (public).

EpgpStandingsRow

One row of GET /api/epgp/standings.

EpgpSyncTokenCreate

Body for POST /api/epgp/sync-tokens (officer).

EpgpSyncTokenCreated

Response for POST /api/epgp/sync-tokens: the token shown once, never stored in plaintext.

EpgpSyncTokenList

Response for GET /api/epgp/sync-tokens (officer).

EpgpSyncTokenMe

Response for GET /api/epgp/sync-tokens/me (Bearer only).

EpgpSyncTokenSummary

One non-revoked sync token, without the secret (GET /api/epgp/sync-tokens, officer, own tokens only).

ExportAward

One EP/GP ledger entry in the export payload.

ExportEncounter

One boss pull entry in the export payload. session_id/ended_at may be absent (see ExportSession).

ExportLoot

One loot award entry in the export payload.

ExportPayload

The full decoded SQX1: payload, bounded per the contract.

ExportSession

One raid session entry in the export payload.

ExportSessionMember

One session member entry in the export payload.

GameImportEvent

One upcoming scheduled event in the game-import payload.

GameImportPayload

The full SQI1: payload built by GET /api/epgp/game-import.

GameImportSignup

One event sign-up in the game-import payload. character is the free-text sign-up name, no realm.

GameImportStandingsRow

One standings row in the game-import payload.

ImportAttendanceResult

What the attendance hand-off did for one import call.

ImportCounts

New vs. updated (re-imported, unchanged external_id) row counts.

LedgerKind

Whether a ledger entry is an EP or GP change.

LedgerSource

Where a ledger entry came from: the addon export, or a site-made officer adjustment.

LootImportCounts

ImportCounts plus rows newly voided by this import.

LootResponse

How a character rolled on an item.

MyCharacterCreate

Body for POST /api/me/characters (member). 409 if another member already claimed this name-realm.

RaidStatus

EPGP roster status, settable by an officer (PUT /api/epgp/roster/{member_id}).

Module Contents#

class sidequest_common.schemas.epgp.AwardImportCounts#

Bases: ImportCounts

ImportCounts plus rows newly voided by this import.

voided: int = 0#
class sidequest_common.schemas.epgp.AwardReason#

Bases: enum.StrEnum

Why an EP/GP ledger entry was written.

Initialize self. See help(type(self)) for accurate signature.

ADJUST = 'adjust'#
BOSS_KILL = 'boss_kill'#
FULL_NIGHT = 'full_night'#
LOOT = 'loot'#
ON_TIME = 'on_time'#
PROGRESS = 'progress'#
STANDBY = 'standby'#
class sidequest_common.schemas.epgp.CharacterSource#

Bases: enum.StrEnum

How a WowCharacter got linked to a member.

Initialize self. See help(type(self)) for accurate signature.

CLAIM = 'claim'#
IMPORT = 'import'#
OFFICER = 'officer'#
SIGNUP = 'signup'#
class sidequest_common.schemas.epgp.DeviceAuthorizationPollStatus#

Bases: enum.StrEnum

status on POST /api/epgp/device-authorizations/token.

Initialize self. See help(type(self)) for accurate signature.

APPROVED = 'approved'#
DENIED = 'denied'#
EXPIRED = 'expired'#
PENDING = 'pending'#
SLOW_DOWN = 'slow_down'#
class sidequest_common.schemas.epgp.DeviceAuthorizationStatus#

Bases: enum.StrEnum

status on GET /api/epgp/device-authorizations/{user_code}.

Initialize self. See help(type(self)) for accurate signature.

APPROVED = 'approved'#
DENIED = 'denied'#
EXPIRED = 'expired'#
PENDING = 'pending'#
class sidequest_common.schemas.epgp.EpgpAdjustmentCreate#

Bases: pydantic.BaseModel

Body for POST /api/epgp/adjustments (officer).

amount: int#
kind: LedgerKind#
member_id: int#
reason: str#
class sidequest_common.schemas.epgp.EpgpCharacter#

Bases: pydantic.BaseModel

A WowCharacter, as returned by the characters admin screen and /api/me/characters.

id: uuid.UUID#
is_main: bool#
last_seen_at: datetime.datetime | None#
member_id: int | None#
member_name: str | None#
model_config#
name: str#
realm: str#
source: CharacterSource#
wow_class: sidequest_common.schemas.events.WowClass | None#
class sidequest_common.schemas.epgp.EpgpCharacterList#

Bases: pydantic.BaseModel

Response for GET /api/epgp/characters (officer) and GET /api/me/characters (member).

characters: list[EpgpCharacter]#
class sidequest_common.schemas.epgp.EpgpCharacterUpdate#

Bases: pydantic.BaseModel

Body for PUT /api/epgp/characters/{id} (officer).

is_main: bool#
member_id: int | None#
class sidequest_common.schemas.epgp.EpgpDeviceAuthorizationCreate#

Bases: pydantic.BaseModel

Body for POST /api/epgp/device-authorizations (public): the device starting a browser sign-in.

device_name: str#
class sidequest_common.schemas.epgp.EpgpDeviceAuthorizationCreated#

Bases: pydantic.BaseModel

Response for POST /api/epgp/device-authorizations (public): poll .../token with device_code.

device_code: str#
expires_in: int#
interval: int#
user_code: str#
verification_uri: str#
verification_uri_complete: str#
class sidequest_common.schemas.epgp.EpgpDeviceAuthorizationPoll#

Bases: pydantic.BaseModel

Body for POST /api/epgp/device-authorizations/token (public).

device_code: str#
class sidequest_common.schemas.epgp.EpgpDeviceAuthorizationPollResult#

Bases: pydantic.BaseModel

Response for POST /api/epgp/device-authorizations/token (public): always 200, never an oracle.

token/name are set only once, on the first "approved" poll; a later poll for the same device code gets "expired".

name: str | None = None#
status: DeviceAuthorizationPollStatus#
token: str | None = None#
class sidequest_common.schemas.epgp.EpgpDeviceAuthorizationStatus#

Bases: pydantic.BaseModel

Response for GET /api/epgp/device-authorizations/{user_code} (any guild member).

created_at: datetime.datetime#
device_name: str#
expires_at: datetime.datetime#
status: DeviceAuthorizationStatus#
user_code: str#
class sidequest_common.schemas.epgp.EpgpGameImport#

Bases: pydantic.BaseModel

Response for GET /api/epgp/game-import (officer).

generated_at: datetime.datetime#
payload: str#
class sidequest_common.schemas.epgp.EpgpImportBody#

Bases: pydantic.BaseModel

Body for POST /api/epgp/imports: any text containing one SQX1: string.

payload: str#
class sidequest_common.schemas.epgp.EpgpImportResult#

Bases: pydantic.BaseModel

Response for POST /api/epgp/imports.

announcements: sidequest_common.schemas.guild_hub.AnnouncementImportCounts#
attendance: ImportAttendanceResult#
awards: AwardImportCounts#
bank: sidequest_common.schemas.guild_hub.BankImportCounts#
conflicts: list[str]#

Session external ids skipped by the one-recorder-per-raid-night conflict rule.

encounters: ImportCounts#
loot: LootImportCounts#
member_notes: sidequest_common.schemas.guild_hub.MemberNoteImportCounts#
professions: sidequest_common.schemas.guild_hub.ProfessionImportCounts#
recorder: str#
recruits: sidequest_common.schemas.guild_hub.RecruitImportCounts#
roster: sidequest_common.schemas.guild_hub.RosterImportCounts#
sessions: ImportCounts#
signups: sidequest_common.schemas.guild_hub.SignupImportCounts#
unlinked_characters: list[str]#
class sidequest_common.schemas.epgp.EpgpLedgerPage#

Bases: pydantic.BaseModel

Response for GET /api/epgp/ledger (public), newest first.

entries: list[EpgpLedgerRow]#
class sidequest_common.schemas.epgp.EpgpLedgerRow#

Bases: pydantic.BaseModel

One row of GET /api/epgp/ledger.

amount: int#
at: datetime.datetime#
character: str | None#
decayed: float#
id: uuid.UUID#
item_name: str | None#
kind: LedgerKind#
member_id: int | None#
member_name: str | None#
note: str | None#
officer: str#
reason: str#
voided: bool#
class sidequest_common.schemas.epgp.EpgpLootPage#

Bases: pydantic.BaseModel

Response for GET /api/epgp/loot (public), newest first.

items: list[EpgpLootRow]#
class sidequest_common.schemas.epgp.EpgpLootRow#

Bases: pydantic.BaseModel

One row of GET /api/epgp/loot.

at: datetime.datetime#
character: str | None#
encounter_name: str | None#
gp: int#
id: uuid.UUID#
instance_name: str | None#
item_id: int#
item_name: str#
member_id: int | None#
member_name: str | None#
quality: int#
response: LootResponse#
voided: bool#
class sidequest_common.schemas.epgp.EpgpProgression#

Bases: pydantic.BaseModel

Response for GET /api/epgp/progression (public).

instances: list[EpgpProgressionInstance]#
class sidequest_common.schemas.epgp.EpgpProgressionEncounter#

Bases: pydantic.BaseModel

One boss’s kill/wipe tally on the progression board.

encounter_id: int#
first_kill_at: datetime.datetime | None#
kills: int#
last_attempt_at: datetime.datetime#
name: str#
wipes: int#
class sidequest_common.schemas.epgp.EpgpProgressionInstance#

Bases: pydantic.BaseModel

One instance’s bosses on the progression board.

encounters: list[EpgpProgressionEncounter]#
instance_id: int#
name: str#
size: int#
class sidequest_common.schemas.epgp.EpgpRoster#

Bases: pydantic.BaseModel

Response for GET /api/epgp/roster (officer).

members: list[EpgpRosterRow]#
class sidequest_common.schemas.epgp.EpgpRosterRow#

Bases: pydantic.BaseModel

One row of GET /api/epgp/roster (officer).

member_id: int#
name: str#
raid_status: RaidStatus | None#
trial_started_at: datetime.datetime | None#
class sidequest_common.schemas.epgp.EpgpRosterUpdate#

Bases: pydantic.BaseModel

Body for PUT /api/epgp/roster/{member_id} (officer). Setting "trial" stamps trial_started_at now.

raid_status: RaidStatus | None#
class sidequest_common.schemas.epgp.EpgpRules#

Bases: pydantic.BaseModel

The rules object as returned by GET /api/epgp/standings (same shape as the game-import payload).

base_gp: int#
chase_multiplier: float#
decay_percent: int#
ep: dict[str, int]#
gp: dict[str, int]#
legendary_gp: int#
min_gp: int#
os_percent: int#
trial_days: int#
class sidequest_common.schemas.epgp.EpgpStandings#

Bases: pydantic.BaseModel

Response for GET /api/epgp/standings (public).

generated_at: datetime.datetime#
rows: list[EpgpStandingsRow]#
rules: EpgpRules#
class sidequest_common.schemas.epgp.EpgpStandingsRow#

Bases: pydantic.BaseModel

One row of GET /api/epgp/standings.

characters: list[str]#
ep: float#
gp: float#
main: str | None#
member_id: int | None#
model_config#
name: str#
pr: float#
trial: bool#
wow_class: sidequest_common.schemas.events.WowClass | None#
class sidequest_common.schemas.epgp.EpgpSyncTokenCreate#

Bases: pydantic.BaseModel

Body for POST /api/epgp/sync-tokens (officer).

name: str#
class sidequest_common.schemas.epgp.EpgpSyncTokenCreated#

Bases: pydantic.BaseModel

Response for POST /api/epgp/sync-tokens: the token shown once, never stored in plaintext.

id: uuid.UUID#
name: str#
token: str#
class sidequest_common.schemas.epgp.EpgpSyncTokenList#

Bases: pydantic.BaseModel

Response for GET /api/epgp/sync-tokens (officer).

tokens: list[EpgpSyncTokenSummary]#
class sidequest_common.schemas.epgp.EpgpSyncTokenMe#

Bases: pydantic.BaseModel

Response for GET /api/epgp/sync-tokens/me (Bearer only).

What the presenting token’s owner can see about themselves. is_guild_member is always true: a token whose owner left the guild gets 403 instead.

avatar_url: str | None#
calendar_url: str | None#
display_name: str#
is_guild_member: bool#
is_officer: bool#
token_name: str#
class sidequest_common.schemas.epgp.EpgpSyncTokenSummary#

Bases: pydantic.BaseModel

One non-revoked sync token, without the secret (GET /api/epgp/sync-tokens, officer, own tokens only).

created_at: datetime.datetime#
id: uuid.UUID#
last_used_at: datetime.datetime | None#
name: str#
class sidequest_common.schemas.epgp.ExportAward#

Bases: pydantic.BaseModel

One EP/GP ledger entry in the export payload.

amount: int#
at: int#
character: str#
id: str#
kind: LedgerKind#
loot_id: str | None = None#
note: str | None#
officer: str#
reason: AwardReason#
session_id: str | None = None#
voided: bool = False#
class sidequest_common.schemas.epgp.ExportEncounter#

Bases: pydantic.BaseModel

One boss pull entry in the export payload. session_id/ended_at may be absent (see ExportSession).

characters: list[str]#
difficulty_id: int#
encounter_id: int#
ended_at: int | None = None#
id: str#
name: str#
session_id: str | None = None#
size: int#
standby: list[str]#
started_at: int#
success: bool#
class sidequest_common.schemas.epgp.ExportLoot#

Bases: pydantic.BaseModel

One loot award entry in the export payload.

at: int#
character: str#
encounter_row_id: str | None = None#
gp: int#
id: str#
inv_type: str#
item_id: int#
item_name: str#
officer: str#
quality: int#
response: LootResponse#
session_id: str | None = None#
voided: bool = False#
class sidequest_common.schemas.epgp.ExportPayload#

Bases: pydantic.BaseModel

The full decoded SQX1: payload, bounded per the contract.

v: 2 adds the guild-hub sections below; every v2 section is optional so a v: 1 addon keeps working unchanged.

addon_version: str#
announcements: list[sidequest_common.schemas.guild_hub.ExportAnnouncement]#
awards: list[ExportAward]#
bank: list[sidequest_common.schemas.guild_hub.ExportBankEntry]#
encounters: list[ExportEncounter]#
exported_at: int#
guild: str#
loot: list[ExportLoot]#
member_notes: list[sidequest_common.schemas.guild_hub.ExportMemberNote]#
professions: list[sidequest_common.schemas.guild_hub.ExportProfession]#
realm: str#
recorder: str#
recruits: list[sidequest_common.schemas.guild_hub.ExportRecruit]#
roster: sidequest_common.schemas.guild_hub.ExportRoster | None = None#
sessions: list[ExportSession]#
signups: list[sidequest_common.schemas.guild_hub.ExportSignupV2]#
v: int#
class sidequest_common.schemas.epgp.ExportSession#

Bases: pydantic.BaseModel

One raid session entry in the export payload.

Lua json encoders drop nil keys rather than emitting null, so every nullable contract field here is a pydantic default, not a required key: the addon may omit event_id/ended_at entirely (e.g. a session exported before the raid ended) rather than sending them as JSON null.

difficulty_id: int#
ended_at: int | None = None#
event_id: uuid.UUID | None = None#
id: str#
instance_id: int#
instance_name: str#
members: list[ExportSessionMember]#
size: int#
started_at: int#
class sidequest_common.schemas.epgp.ExportSessionMember#

Bases: pydantic.BaseModel

One session member entry in the export payload.

class arrives as the addon’s uppercase token (e.g. "MAGE", Blizzard’s UnitClass return), normalized to lowercase before validation.

character: str#
first_seen: int#
last_seen: int#
model_config#
standby: bool = False#
wow_class: sidequest_common.schemas.events.WowClass | None#
class sidequest_common.schemas.epgp.GameImportEvent#

Bases: pydantic.BaseModel

One upcoming scheduled event in the game-import payload.

id: uuid.UUID#
kind: sidequest_common.schemas.events.EventKind#
signups: list[GameImportSignup]#
size: int | None#
starts_at: int#
title: str#
class sidequest_common.schemas.epgp.GameImportPayload#

Bases: pydantic.BaseModel

The full SQI1: payload built by GET /api/epgp/game-import.

members/news are the public part (the addon may broadcast them); sheets/crafters/ recipe_names are on-demand (an officer’s addon answers members’ requests, never broadcasts the whole thing); officer stays on officers’ addons only. All four v2 additions are always present since the endpoint already requires an officer.

crafters: list[sidequest_common.schemas.guild_hub.GameImportCrafter]#
events: list[GameImportEvent]#
generated_at: int#
killed_encounters: list[int]#
members: list[sidequest_common.schemas.guild_hub.GameImportMember]#
news: list[sidequest_common.schemas.guild_hub.GameImportNews]#
officer: sidequest_common.schemas.guild_hub.GameImportOfficerPart#
recipe_names: dict[str, str]#
rules: dict#
sheets: dict[str, sidequest_common.schemas.guild_hub.GameImportSheet]#
standings: list[GameImportStandingsRow]#
v: int#
class sidequest_common.schemas.epgp.GameImportSignup#

Bases: pydantic.BaseModel

One event sign-up in the game-import payload. character is the free-text sign-up name, no realm.

bench: bool#
character: str#
group: int | None#
model_config#
role: sidequest_common.schemas.events.SignupRole#
status: sidequest_common.schemas.events.SignupStatus#
wow_class: sidequest_common.schemas.events.WowClass#
class sidequest_common.schemas.epgp.GameImportStandingsRow#

Bases: pydantic.BaseModel

One standings row in the game-import payload.

characters: list[str]#
ep: float#
gp: float#
main: str | None#
member_id: int | None#
model_config#
name: str#
pr: float#
trial: bool#
wow_class: sidequest_common.schemas.events.WowClass | None#
class sidequest_common.schemas.epgp.ImportAttendanceResult#

Bases: pydantic.BaseModel

What the attendance hand-off did for one import call.

event_id: uuid.UUID | None#
marked: int = 0#
class sidequest_common.schemas.epgp.ImportCounts#

Bases: pydantic.BaseModel

New vs. updated (re-imported, unchanged external_id) row counts.

new: int = 0#
updated: int = 0#
class sidequest_common.schemas.epgp.LedgerKind#

Bases: enum.StrEnum

Whether a ledger entry is an EP or GP change.

Initialize self. See help(type(self)) for accurate signature.

EP = 'ep'#
GP = 'gp'#
class sidequest_common.schemas.epgp.LedgerSource#

Bases: enum.StrEnum

Where a ledger entry came from: the addon export, or a site-made officer adjustment.

Initialize self. See help(type(self)) for accurate signature.

ADDON = 'addon'#
SITE = 'site'#
class sidequest_common.schemas.epgp.LootImportCounts#

Bases: ImportCounts

ImportCounts plus rows newly voided by this import.

voided: int = 0#
class sidequest_common.schemas.epgp.LootResponse#

Bases: enum.StrEnum

How a character rolled on an item.

Initialize self. See help(type(self)) for accurate signature.

ALT = 'alt'#
BANK = 'bank'#
COUNCIL = 'council'#
DE = 'de'#
MS = 'ms'#
OS = 'os'#
class sidequest_common.schemas.epgp.MyCharacterCreate#

Bases: pydantic.BaseModel

Body for POST /api/me/characters (member). 409 if another member already claimed this name-realm.

model_config#
name: str#
realm: str | None = None#
wow_class: sidequest_common.schemas.events.WowClass | None#
class sidequest_common.schemas.epgp.RaidStatus#

Bases: enum.StrEnum

EPGP roster status, settable by an officer (PUT /api/epgp/roster/{member_id}).

Initialize self. See help(type(self)) for accurate signature.

RAIDER = 'raider'#
TRIAL = 'trial'#
sidequest_common.schemas.epgp.AWARD_AMOUNT_MAX = 1000#
sidequest_common.schemas.epgp.AWARD_AMOUNT_MIN = -1000#
sidequest_common.schemas.epgp.BASE_GP = 100#
sidequest_common.schemas.epgp.CHASE_MULTIPLIER = 1.5#
sidequest_common.schemas.epgp.DECAY_PERCENT = 15#

Weekly decay applied to EP and GP at every reset, read-time only (amount * (1 - DECAY_PERCENT/100) ** resets).

sidequest_common.schemas.epgp.DEFAULT_REALM = 'Whitemane'#

The guild’s single WoW: Forever realm, used when a member claims a character without specifying one.

sidequest_common.schemas.epgp.EPGP_RULES: dict[str, object]#

Exact shape of the rules object in the SQI1: import string.

sidequest_common.schemas.epgp.EP_VALUES: dict[str, int]#

EP awarded per AwardReason. standby earns the same as the raid (on_time/full_night/boss kills).

sidequest_common.schemas.epgp.GP_BY_INVTYPE: dict[str, int]#

GP by INVTYPE_* at main-spec cost. A 1H “main caster weapon” uses the 2H cost (200) by officer toggle; tier tokens use the cost of the slot the officer picks; chase items are slot cost * CHASE_MULTIPLIER; legendary pieces are flat LEGENDARY_GP. None of those three cases key off inv_type alone, so they aren’t in this map.

sidequest_common.schemas.epgp.LEGENDARY_GP = 300#
sidequest_common.schemas.epgp.MAX_AWARDS = 20000#
sidequest_common.schemas.epgp.MAX_DEVICE_NAME_LENGTH = 60#

Cap on EpgpDeviceAuthorizationCreate.device_name, trimmed; also the sync token name length it feeds into.

sidequest_common.schemas.epgp.MAX_ENCOUNTERS = 500#
sidequest_common.schemas.epgp.MAX_IMPORT_BYTES = 2097152#
sidequest_common.schemas.epgp.MAX_LOOT = 1000#
sidequest_common.schemas.epgp.MAX_NAME_LENGTH = 64#
sidequest_common.schemas.epgp.MAX_NOTE_LENGTH = 200#
sidequest_common.schemas.epgp.MAX_SESSIONS = 50#
sidequest_common.schemas.epgp.MIN_GP = 100#
sidequest_common.schemas.epgp.OS_PERCENT = 10#

Off-spec pays this percent of main-spec GP, rounded half up. Alt fill and disenchant/bank pay 0.

sidequest_common.schemas.epgp.TRIAL_DAYS = 14#
sidequest_common.schemas.epgp.WEEKLY_RESET_HOUR_UTC = 15#
sidequest_common.schemas.epgp.WEEKLY_RESET_WEEKDAY = 1#

datetime.weekday() value for Tuesday.