Source code for election_day.models.election.mixins

from __future__ import annotations

from sqlalchemy import case
from sqlalchemy import cast
from sqlalchemy import Float
from sqlalchemy.ext.hybrid import hybrid_property


from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from sqlalchemy.orm import Mapped
    from sqlalchemy.sql import ColumnElement


[docs] class DerivedAttributesMixin: """ A simple mixin to add commonly used functions to elections and their results. """ if TYPE_CHECKING: # forward declare required columns
[docs] eligible_voters: Mapped[int]
received_ballots: Mapped[int] blank_ballots: Mapped[int] invalid_ballots: Mapped[int] @hybrid_property
[docs] def unaccounted_ballots(self) -> int: """ The number of unaccounted ballots. """ return self.blank_ballots + self.invalid_ballots
@hybrid_property
[docs] def accounted_ballots(self) -> int: """ The number of accounted ballots. """ return self.received_ballots - self.unaccounted_ballots
@hybrid_property
[docs] def turnout(self) -> float: """ The turnout of the election. """ if not self.eligible_voters: return 0 return self.received_ballots / self.eligible_voters * 100
@turnout.inplace.expression @classmethod
[docs] def _turnout_expression(cls) -> ColumnElement[float]: """ The turnout of the election. """ return case( ( cls.eligible_voters > 0, cast(cls.received_ballots, Float) / cast(cls.eligible_voters, Float) * 100 ), else_=0 )