Skip to content

src.interface

IGameResult dataclass

IGameResult(winners: frozenset[UUID])

One game of a match: the players who did not lose it.

One UID means that player won the game. Two or more mean the game was drawn among them. Every other player seated in the pod lost the game.

IHashable

IHashable(uid: UUID | None = None)

Interface for hashable objects with UUIDs.

Initializes the IHashable object.

Parameters:

Name Type Description Default
uid UUID | None

The UUID of the object. If None, a new UUID is generated.

None

Raises:

Type Description
ValueError

If the UUID has a collision or is of invalid type.

Source code in src/interface.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(self, uid: UUID | None = None):
    """Initializes the IHashable object.

    Args:
        uid: The UUID of the object. If None, a new UUID is generated.

    Raises:
        ValueError: If the UUID has a collision or is of invalid type.
    """
    if uid:
        if uid in self.CACHE:
            raise ValueError("UUID collision.")
        else:
            self.uid = uid
    else:
        self.uid: UUID = uuid4()
    self.CACHE[self.uid] = self

get classmethod

get(ID: UUID) -> IHashable

Retrieves an object by its UUID.

Parameters:

Name Type Description Default
ID UUID

The UUID of the object.

required

Returns:

Type Description
IHashable

The object with the specified UUID.

Source code in src/interface.py
67
68
69
70
71
72
73
74
75
76
77
@classmethod
def get(cls, ID: UUID) -> IHashable:
    """Retrieves an object by its UUID.

    Args:
        ID: The UUID of the object.

    Returns:
        The object with the specified UUID.
    """
    return cls.CACHE[ID]

IPairingLogic

Bases: ABC

Interface for pairing logic.

advance_topcut

advance_topcut(tour_round: IRound, standings: list[IPlayer]) -> None

Called once per playoff round before make_pairings.

The base implementation is a no-op, correct for ordinary Swiss pairing. Top-cut pairing logics override this to give seeded byes.

Parameters:

Name Type Description Default
tour_round IRound

The current round.

required
standings list[IPlayer]

The list of players sorted by standing.

required
Source code in src/interface.py
304
305
306
307
308
309
310
311
312
313
314
def advance_topcut(self, tour_round: IRound, standings: list[IPlayer]) -> None:
    """Called once per playoff round before make_pairings.

    The base implementation is a no-op, correct for ordinary Swiss
    pairing. Top-cut pairing logics override this to give seeded byes.

    Args:
        tour_round: The current round.
        standings: The list of players sorted by standing.
    """
    return None

make_pairings abstractmethod

make_pairings(tour_round: IRound, players: set[IPlayer], pods: Sequence[IPod]) -> set[IPlayer]

Creates pairings for a round.

Parameters:

Name Type Description Default
tour_round IRound

The current round.

required
players set[IPlayer]

The set of players to pair.

required
pods Sequence[IPod]

The list of available pods.

required

Returns:

Type Description
set[IPlayer]

A set of players who could not be paired (if any).

Source code in src/interface.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
@abstractmethod
def make_pairings(
    self, tour_round: IRound, players: set[IPlayer], pods: Sequence[IPod]
) -> set[IPlayer]:
    """Creates pairings for a round.

    Args:
        tour_round: The current round.
        players: The set of players to pair.
        pods: The list of available pods.

    Returns:
        A set of players who could not be paired (if any).
    """
    ...

supports_pod_sizes classmethod

supports_pod_sizes(pod_sizes: Sequence[int]) -> bool

Whether this algorithm can pair a tournament with these pod sizes.

True if it supports any size (SUPPORTED_POD_SIZES is None) or every given size is in its supported set (an empty list supports nothing).

Source code in src/interface.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
@classmethod
def supports_pod_sizes(cls, pod_sizes: Sequence[int]) -> bool:
    """Whether this algorithm can pair a tournament with these pod sizes.

    True if it supports any size (SUPPORTED_POD_SIZES is None) or every
    given size is in its supported set (an empty list supports nothing).
    """
    if cls.SUPPORTED_POD_SIZES is None:
        return True
    # An empty pod_sizes means no sizes are chosen yet, so vacuous
    # subset truth must not let a size-restricted algorithm through -
    # only ones that support any size should be offered.
    if not pod_sizes:
        return False
    return set(pod_sizes).issubset(cls.SUPPORTED_POD_SIZES)

IPlayer

IPlayer(uid: UUID | None = None)

Bases: IHashable, ABC

Interface for a player.

Source code in src/interface.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(self, uid: UUID | None = None):
    """Initializes the IHashable object.

    Args:
        uid: The UUID of the object. If None, a new UUID is generated.

    Raises:
        ValueError: If the UUID has a collision or is of invalid type.
    """
    if uid:
        if uid in self.CACHE:
            raise ValueError("UUID collision.")
        else:
            self.uid = uid
    else:
        self.uid: UUID = uuid4()
    self.CACHE[self.uid] = self

ELocation

Bases: IntEnum

Enum for player location.

EResult

Bases: IntEnum

Enum for match result.

IPod

IPod(uid: UUID | None = None)

Bases: IHashable, ABC

Interface for a pod.

Source code in src/interface.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(self, uid: UUID | None = None):
    """Initializes the IHashable object.

    Args:
        uid: The UUID of the object. If None, a new UUID is generated.

    Raises:
        ValueError: If the UUID has a collision or is of invalid type.
    """
    if uid:
        if uid in self.CACHE:
            raise ValueError("UUID collision.")
        else:
            self.uid = uid
    else:
        self.uid: UUID = uuid4()
    self.CACHE[self.uid] = self

EResult

Bases: IntEnum

Enum for pod result.

IRound

IRound(uid: UUID | None = None)

Bases: IHashable, ABC

Interface for a round.

Source code in src/interface.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(self, uid: UUID | None = None):
    """Initializes the IHashable object.

    Args:
        uid: The UUID of the object. If None, a new UUID is generated.

    Raises:
        ValueError: If the UUID has a collision or is of invalid type.
    """
    if uid:
        if uid in self.CACHE:
            raise ValueError("UUID collision.")
        else:
            self.uid = uid
    else:
        self.uid: UUID = uuid4()
    self.CACHE[self.uid] = self

IRuleset

IRuleset(name: str)

Bases: ABC

Interface for a game's rules: match reports and playoffs.

Same plugin shape as IScoringLogic/IPairingLogic (IS_COMPLETE, name, PARAM_SPEC/DEFAULT_PARAMS loaded from the sidecar in init_subclass). See docs/tournament-log-spec.md, "Rulesets", for the full contract.

Source code in src/interface.py
465
466
def __init__(self, name: str):
    self.name = name

match_winners abstractmethod

match_winners(pod: IPod) -> frozenset[UUID]

Players who did not lose the match: one = winner, several = drew.

Called only when pod.games is non-empty. Must be total: it runs on stored data (old files, rosters edited after reporting) and must never raise.

Source code in src/interface.py
492
493
494
495
496
497
498
499
500
@abstractmethod
def match_winners(self, pod: IPod) -> frozenset[UUID]:
    """Players who did not lose the match: one = winner, several = drew.

    Called only when pod.games is non-empty. Must be total: it runs on
    stored data (old files, rosters edited after reporting) and must
    never raise.
    """
    ...

params

params(tour_round: IRound) -> dict[str, Any]

This round's ruleset params: overrides on top of the class defaults. Cold path only (call once, not per player/pod).

Source code in src/interface.py
473
474
475
476
477
def params(self, tour_round: IRound) -> dict[str, Any]:
    """This round's ruleset params: overrides on top of the class
    defaults. Cold path only (call once, not per player/pod)."""
    config = tour_round.tour.config  # type: ignore[attr-defined]
    return {**self.DEFAULT_PARAMS, **config.ruleset_overrides(tour_round)}

random_report abstractmethod

random_report(pod: IPod) -> list[IGameResult]

A plausible valid report for Tournament.random_results.

Uses the random module so tests can seed it.

Source code in src/interface.py
512
513
514
515
516
517
518
@abstractmethod
def random_report(self, pod: IPod) -> list[IGameResult]:
    """A plausible valid report for Tournament.random_results.

    Uses the `random` module so tests can seed it.
    """
    ...

report_from_winners abstractmethod

report_from_winners(pod: IPod, winners: Iterable[UUID]) -> list[IGameResult]

Turns "A won" / "A and B drew" into a canonical report.

Backs the report_win/report_draw shorthand.

Source code in src/interface.py
502
503
504
505
506
507
508
509
510
@abstractmethod
def report_from_winners(
    self, pod: IPod, winners: Iterable[UUID]
) -> list[IGameResult]:
    """Turns "A won" / "A and B drew" into a canonical report.

    Backs the report_win/report_draw shorthand.
    """
    ...

swiss_pairing_logic abstractmethod

swiss_pairing_logic(tour: ITournament, seq: int) -> str

Default pairing logic for Swiss round seq when config.pairing_rounds sets none.

Source code in src/interface.py
520
521
522
523
524
@abstractmethod
def swiss_pairing_logic(self, tour: ITournament, seq: int) -> str:
    """Default pairing logic for Swiss round seq when
    config.pairing_rounds sets none."""
    ...

validate_report abstractmethod

validate_report(pod: IPod, games: Sequence[IGameResult]) -> None

Raises ValueError if games is not a valid complete report for pod in its round. Never mutates. The message should be fit for a tournament organiser to read.

Source code in src/interface.py
485
486
487
488
489
490
@abstractmethod
def validate_report(self, pod: IPod, games: Sequence[IGameResult]) -> None:
    """Raises ValueError if games is not a valid complete report for pod
    in its round. Never mutates. The message should be fit for a
    tournament organiser to read."""
    ...

IScoringLogic

Bases: ABC

Interface for points, standings tiebreakers, and their export columns.

compute_ratings abstractmethod

compute_ratings(tour: ITournament, tour_round: IRound) -> Mapping[UUID, float]

Computes every player's point total as of a given round.

Parameters:

Name Type Description Default
tour ITournament

The tournament.

required
tour_round IRound

The round up to which to compute points.

required

Returns:

Type Description
Mapping[UUID, float]

A mapping of player UID to point total.

Source code in src/interface.py
354
355
356
357
358
359
360
361
362
363
364
365
366
367
@abstractmethod
def compute_ratings(
    self, tour: ITournament, tour_round: IRound
) -> Mapping[UUID, float]:
    """Computes every player's point total as of a given round.

    Args:
        tour: The tournament.
        tour_round: The round up to which to compute points.

    Returns:
        A mapping of player UID to point total.
    """
    ...

pointrate_denominator abstractmethod

pointrate_denominator(tour_round: IRound) -> float

The value a player's rating is divided by to get a 0-1 pointrate.

Parameters:

Name Type Description Default
tour_round IRound

The round the pointrate is being computed for.

required

Returns:

Type Description
float

The denominator to divide a rating by.

Source code in src/interface.py
407
408
409
410
411
412
413
414
415
416
417
@abstractmethod
def pointrate_denominator(self, tour_round: IRound) -> float:
    """The value a player's rating is divided by to get a 0-1 pointrate.

    Args:
        tour_round: The round the pointrate is being computed for.

    Returns:
        The denominator to divide a rating by.
    """
    ...

rating abstractmethod

rating(player: IPlayer, tour_round: IRound) -> float

Computes one player's point total as of a given round.

Parameters:

Name Type Description Default
player IPlayer

The player to compute the rating for.

required
tour_round IRound

The round up to which to compute points.

required

Returns:

Type Description
float

The player's point total.

Source code in src/interface.py
369
370
371
372
373
374
375
376
377
378
379
380
@abstractmethod
def rating(self, player: IPlayer, tour_round: IRound) -> float:
    """Computes one player's point total as of a given round.

    Args:
        player: The player to compute the rating for.
        tour_round: The round up to which to compute points.

    Returns:
        The player's point total.
    """
    ...

standings_columns

standings_columns(tour: ITournament, tour_round: IRound) -> list[tuple[str, Mapping[UUID, str]]]

Extra standings-export columns: (header, formatted cells by UID).

Source code in src/interface.py
401
402
403
404
405
def standings_columns(
    self, tour: ITournament, tour_round: IRound
) -> list[tuple[str, Mapping[UUID, str]]]:
    """Extra standings-export columns: (header, formatted cells by UID)."""
    return []

standings_keys

standings_keys(tour: ITournament, tour_round: IRound, ratings: Mapping[Any, float]) -> Mapping[UUID, tuple]

Swiss sort keys, descending: points, then scoring tiebreakers.

The default has no tiebreakers. UID order keeps exact ties stable without assigning competitive significance to that order.

Source code in src/interface.py
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def standings_keys(
    self,
    tour: ITournament,
    tour_round: IRound,
    ratings: Mapping[Any, float],
) -> Mapping[UUID, tuple]:
    """Swiss sort keys, descending: points, then scoring tiebreakers.

    The default has no tiebreakers. UID order keeps exact ties stable
    without assigning competitive significance to that order.
    """
    return {
        p.uid: (
            ratings.get(p.uid, 0),
            -p.uid if isinstance(p.uid, int) else -int(p.uid.int),
        )
        for p in tour.players
    }

supports_pod_sizes classmethod

supports_pod_sizes(pod_sizes: Sequence[int]) -> bool

Whether this algorithm can score a tournament with these pod sizes.

True if it supports any size (SUPPORTED_POD_SIZES is None) or every given size is in its supported set (an empty list supports nothing).

Source code in src/interface.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
@classmethod
def supports_pod_sizes(cls, pod_sizes: Sequence[int]) -> bool:
    """Whether this algorithm can score a tournament with these pod sizes.

    True if it supports any size (SUPPORTED_POD_SIZES is None) or every
    given size is in its supported set (an empty list supports nothing).
    """
    if cls.SUPPORTED_POD_SIZES is None:
        return True
    # An empty pod_sizes means no sizes are chosen yet, so vacuous
    # subset truth must not let a size-restricted algorithm through -
    # only ones that support any size should be offered.
    if not pod_sizes:
        return False
    return set(pod_sizes).issubset(cls.SUPPORTED_POD_SIZES)

IStandingsExport

Bases: ABC

Interface for standings export configuration.

ITournamentConfiguration

Bases: ABC

ruleset_overrides abstractmethod

ruleset_overrides(tour_round: IRound) -> Mapping[str, Any]

This round's ruleset param overrides (see IRuleset.params()).

A Swiss round reads pairing_rounds[seq]["ruleset_params"]; a playoff round reads playoff_rounds[stage.value]["ruleset_params"].

Source code in src/interface.py
573
574
575
576
577
578
579
580
@abstractmethod
def ruleset_overrides(self, tour_round: IRound) -> Mapping[str, Any]:
    """This round's ruleset param overrides (see IRuleset.params()).

    A Swiss round reads pairing_rounds[seq]["ruleset_params"]; a
    playoff round reads playoff_rounds[stage.value]["ruleset_params"].
    """
    ...

SortMethod

Bases: IntEnum

Enum for sorting methods.

SortOrder

Bases: IntEnum

Enum for sorting order.