Source code for election_day.models.vote.ballot_result

from __future__ import annotations

from onegov.core.orm import Base
from onegov.core.orm.mixins import TimestampMixin
from onegov.election_day.models.vote.mixins import DerivedAttributesMixin
from onegov.election_day.models.vote.mixins import DerivedBallotsCountMixin
from sqlalchemy import func
from sqlalchemy import ForeignKey
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped
from uuid import uuid4
from uuid import UUID

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from onegov.election_day.models import Ballot
    from sqlalchemy.sql import ColumnElement


[docs] class BallotResult(Base, TimestampMixin, DerivedAttributesMixin, DerivedBallotsCountMixin): """ The result of a specific ballot. Each ballot may have multiple results. Those results may be aggregated or not. """
[docs] __tablename__ = 'ballot_results'
#: identifies the result, may be used in the url
[docs] id: Mapped[UUID] = mapped_column( primary_key=True, default=uuid4 )
#: The entity id (e.g. BFS number).
[docs] entity_id: Mapped[int]
#: the name of the entity
[docs] name: Mapped[str]
#: the district this entity belongs to
[docs] district: Mapped[str | None]
#: True if the result has been counted and no changes will be made anymore. #: If the result is definite, all the values below must be specified.
[docs] counted: Mapped[bool]
#: number of yeas, in case of variants, the number of yeas for the first #: option of the tie breaker
[docs] yeas: Mapped[int] = mapped_column(default=lambda: 0)
#: number of nays, in case of variants, the number of nays for the first #: option of the tie breaker (so a yay for the second option)
[docs] nays: Mapped[int] = mapped_column(default=lambda: 0)
#: number of empty votes
[docs] empty: Mapped[int] = mapped_column(default=lambda: 0)
#: number of invalid votes
[docs] invalid: Mapped[int] = mapped_column(default=lambda: 0)
#: number of eligible voters
[docs] eligible_voters: Mapped[int] = mapped_column(default=lambda: 0)
#: number of expats
[docs] expats: Mapped[int | None]
#: number of votes received (total cast ballots from source data)
[docs] received: Mapped[int | None] = mapped_column(default=lambda: None)
@hybrid_property
[docs] def cast_ballots(self) -> int: if self.received is not None: return self.received return ( (self.yeas or 0) + (self.nays or 0) + (self.empty or 0) + (self.invalid or 0) )
@cast_ballots.inplace.expression @classmethod
[docs] def _cast_ballots_expression(cls) -> ColumnElement[int]: return func.coalesce( cls.received, cls.yeas + cls.nays + cls.empty + cls.invalid )
#: the id of the ballot this result belongs to
[docs] ballot_id: Mapped[UUID] = mapped_column( ForeignKey('ballots.id', ondelete='CASCADE') )
#: the ballot this result belongs to
[docs] ballot: Mapped[Ballot] = relationship(back_populates='results')