Source code for election_day.models.vote.mixins

from __future__ import annotations

from sqlalchemy import case
from sqlalchemy import cast
from sqlalchemy import Float
from sqlalchemy import func
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 ballots and their results. """ if TYPE_CHECKING: # forward declare required attributes
[docs] yeas: Mapped[int]
nays: Mapped[int] counted: Mapped[bool] @hybrid_property
[docs] def yeas_percentage(self) -> float: """ The percentage of yeas (discounts empty/invalid ballots). """ return self.yeas / ((self.yeas + self.nays) or 1) * 100
@yeas_percentage.inplace.expression @classmethod
[docs] def _yeas_percentage_expression(cls) -> ColumnElement[float]: # coalesce will pick the first non-null result # nullif will return null if division by zero # => when all yeas and nays are zero the yeas percentage is 0% return 100 * ( cast(cls.yeas, Float) / cast( func.coalesce( func.nullif(cls.yeas + cls.nays, 0), 1 ), Float ) )
@hybrid_property
[docs] def nays_percentage(self) -> float: """ The percentage of nays (discounts empty/invalid ballots). """ return 100 - self.yeas_percentage
@hybrid_property
[docs] def accepted(self) -> bool | None: return self.yeas > self.nays if self.counted else None
@accepted.inplace.expression @classmethod
[docs] def _accepted_expression(cls) -> ColumnElement[bool | None]: return case( (cls.counted.is_(False), None), (cls.yeas > cls.nays, True), else_=False )
[docs] class DerivedBallotsCountMixin: """ A simple mixin to add commonly used functions to votes, ballots and their results. """ if TYPE_CHECKING: # forward declare required columns
[docs] yeas: Mapped[int]
nays: Mapped[int] empty: Mapped[int] invalid: Mapped[int] eligible_voters: Mapped[int] @hybrid_property
[docs] def cast_ballots(self) -> int: return ( (self.yeas or 0) + (self.nays or 0) + (self.empty or 0) + (self.invalid or 0) )
@hybrid_property
[docs] def turnout(self) -> float: return ( self.cast_ballots / self.eligible_voters * 100 if self.eligible_voters else 0 )
@turnout.inplace.expression @classmethod
[docs] def _turnout_expression(cls) -> ColumnElement[float]: return case( ( cls.eligible_voters > 0, cast(cls.cast_ballots, Float) / cast(cls.eligible_voters, Float) * 100 ), else_=0 )