Source code for election_day.models.election_compound.mixins

from typing import NamedTuple

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from onegov.election_day.models import Election
    from sqlalchemy import Column
    from sqlalchemy.orm import relationship
    from sqlalchemy.orm import Session
    from typing import TypeAlias

[docs] Elections: TypeAlias = relationship[list[Election]] | list['Election']
[docs] class ResultRow(NamedTuple):
[docs] domain_segment: str
[docs] domain_supersegment: str
[docs] counted: bool
[docs] turnout: float
[docs] eligible_voters: int
[docs] expats: int
[docs] received_ballots: int
[docs] accounted_ballots: int
[docs] blank_ballots: int
[docs] invalid_ballots: int
[docs] accounted_votes: int
[docs] class TotalRow(NamedTuple):
[docs] turnout: float
[docs] eligible_voters: int
[docs] expats: int
[docs] received_ballots: int
[docs] accounted_ballots: int
[docs] blank_ballots: int
[docs] invalid_ballots: int
[docs] accounted_votes: int
[docs] class DerivedAttributesMixin: """ A simple mixin to add commonly used functions to election compounds and parts. Requires an elections and session attribute. """ if TYPE_CHECKING: # forward declare required attributes @property
[docs] def session(self) -> Session: ...
@property def elections(self) -> Elections: ... completes_manually: Column[bool] manually_completed: Column[bool] @property
[docs] def number_of_mandates(self) -> int: """ The (total) number of mandates. """ return sum( election.number_of_mandates for election in self.elections )
@property
[docs] def allocated_mandates(self) -> int: """ Number of already allocated mandates/elected candidates. """ return sum( election.allocated_mandates for election in self.elections )
@property
[docs] def completed(self) -> bool: """ Returns True, if all elections are completed. """ elections = self.elections if not elections: return False for election in elections: if not election.completed: return False if self.completes_manually and not self.manually_completed: return False return True
@property
[docs] def counted(self) -> bool: """ True if all elections have been counted. """ for election in self.elections: if not election.counted: return False return True
@property
[docs] def counted_entities(self) -> list[str | None]: return [ election.domain_segment for election in self.elections if election.completed ]
@property
[docs] def results(self) -> list[ResultRow]: return [ ResultRow( domain_segment=election.domain_segment, domain_supersegment=election.domain_supersegment, counted=election.counted, turnout=election.turnout, eligible_voters=election.eligible_voters, expats=election.expats, received_ballots=election.received_ballots, accounted_ballots=election.accounted_ballots, blank_ballots=election.blank_ballots, invalid_ballots=election.invalid_ballots, accounted_votes=election.accounted_votes, ) for election in self.elections ]
@property
[docs] def totals(self) -> TotalRow: results = [r for r in self.results if r.counted] def _sum(attr: str) -> int: return sum(getattr(r, attr) for r in results) or 0 eligible_voters = _sum('eligible_voters') received_ballots = _sum('received_ballots') turnout = 0.0 if eligible_voters: turnout = 100 * received_ballots / eligible_voters return TotalRow( turnout=turnout, eligible_voters=eligible_voters, expats=_sum('expats'), received_ballots=received_ballots, accounted_ballots=_sum('accounted_ballots'), blank_ballots=_sum('blank_ballots'), invalid_ballots=_sum('invalid_ballots'), accounted_votes=_sum('accounted_votes'), )