Source code for mllm_shap.domain.players

"""Player indexing for cooperative-game explainability.

@domain domain
@see [[knowledge/architecture/domain]]
@tested-by [[tests/domain/test_players.py]]
"""

from dataclasses import dataclass


[docs] @dataclass(frozen=True, slots=True) class PlayerSpan: """Where one player sits in the conversation it was partitioned from.""" message_index: int """Index of the message in conversation order (turns flattened).""" start: int """Start character offset of the player inside ``Message.text``.""" end: int """End character offset (exclusive) of the player inside ``Message.text``.""" text: str """Text the player covers, ``Message.text[start:end]``.""" speaker: str | None = None """Speaker of the message the player sits in.""" label: str | None = None """Display label set by the partitioner; :attr:`display_label` falls back to the text.""" samples: tuple[int, int] | None = None """Sample range ``[start, end)`` of an audio player in the message's first :class:`~mllm_shap.AudioPart`; ``None`` for text-only players.""" @property def display_label(self) -> str: """Return :attr:`label`, else the text prefixed with ``"<speaker>: "``.""" if self.label is not None: return self.label return self.text if self.speaker is None else f"{self.speaker}: {self.text}"
[docs] @dataclass(frozen=True, slots=True) class PlayerSet: """Ordered explainability players aligned with coalition masks.""" player_ids: tuple[str, ...] """Stable player identifiers in coalition-mask order.""" spans: tuple[PlayerSpan, ...] = () """Player locations in mask order, filled by the partitioner. Absence policies and label helpers need them; estimators do not. """ hidden_text: tuple[int, ...] = () """Message indices whose text parts absence policies drop from every coalition: a transcript used only to align the audio, which the model must not read.""" def __post_init__(self) -> None: """Reject empty, duplicated or span-misaligned player sets.""" if not self.player_ids: msg = "player set must contain at least one player" raise ValueError(msg) if len(set(self.player_ids)) != len(self.player_ids): msg = "player ids must be unique" raise ValueError(msg) if self.spans and len(self.spans) != len(self.player_ids): msg = "player set needs one span per player" raise ValueError(msg) @property def num_players(self) -> int: """Return the number of players in the set.""" return len(self.player_ids)