from __future__ import annotations
from onegov.core.orm import Base
from onegov.core.orm.mixins import TimestampMixin
from onegov.election_day.models.election.mixins import DerivedAttributesMixin
from sqlalchemy import ForeignKey
from sqlalchemy import select
from sqlalchemy import text
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 CandidatePanachageResult
from onegov.election_day.models import CandidateResult
from onegov.election_day.models import Election
from onegov.election_day.models import ListResult
from sqlalchemy.sql import ColumnElement
[docs]
class ElectionResult(Base, TimestampMixin, DerivedAttributesMixin):
""" The election result in a single political entity. """
[docs]
__tablename__ = 'election_results'
#: identifies the result
[docs]
id: Mapped[UUID] = mapped_column(
primary_key=True,
default=uuid4
)
#: the election id this result belongs to
[docs]
election_id: Mapped[str] = mapped_column(
ForeignKey('elections.id', onupdate='CASCADE', ondelete='CASCADE')
)
#: the election this candidate belongs to
[docs]
election: Mapped[Election] = relationship(
back_populates='results'
)
#: entity id (e.g. a BFS number).
#: the name of the entity
#: the district this entity belongs to
[docs]
district: Mapped[str | None]
#: the superregion this entity belongs to
[docs]
superregion: 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.
#: number of eligible voters
[docs]
eligible_voters: Mapped[int] = mapped_column(default=lambda: 0)
#: number of expats
[docs]
expats: Mapped[int | None]
#: number of received ballots
[docs]
received_ballots: Mapped[int] = mapped_column(default=lambda: 0)
#: number of blank ballots
[docs]
blank_ballots: Mapped[int] = mapped_column(default=lambda: 0)
#: number of invalid ballots
[docs]
invalid_ballots: Mapped[int] = mapped_column(default=lambda: 0)
#: number of blank votes
[docs]
blank_votes: Mapped[int] = mapped_column(default=lambda: 0)
#: number of invalid votes
[docs]
invalid_votes: Mapped[int] = mapped_column(default=lambda: 0)
@hybrid_property
[docs]
def accounted_votes(self) -> int:
""" The number of accounted votes. """
return (
self.election.number_of_mandates * self.accounted_ballots
- self.blank_votes
- self.invalid_votes
)
@accounted_votes.inplace.expression
@classmethod
[docs]
def _accounted_votes_expression(cls) -> ColumnElement[int]:
""" The number of accounted votes. """
from onegov.election_day.models import Election # circular
# A bit of a hack :|
number_of_mandates = select(
Election.number_of_mandates
).where(text(
'elections.id = election_results.election_id'
)).scalar_subquery()
return (
number_of_mandates * (
cls.received_ballots - cls.blank_ballots - cls.invalid_ballots
) - cls.blank_votes - cls.invalid_votes
)
#: an election result may contain n list results
[docs]
list_results: Mapped[list[ListResult]] = relationship(
cascade='all, delete-orphan',
back_populates='election_result'
)
#: an election result contains n candidate results
[docs]
candidate_results: Mapped[list[CandidateResult]] = relationship(
cascade='all, delete-orphan',
back_populates='election_result'
)
#: an election result contains n candidate panachage results
[docs]
candidate_panachage_results: Mapped[list[CandidatePanachageResult]] = (
relationship(
cascade='all, delete-orphan',
back_populates='election_result'
)
)