Source code for election_day.models.archived_result

from __future__ import annotations

import datetime

from collections.abc import Mapping
from copy import deepcopy

from onegov.core.orm import Base
from onegov.core.orm import translation_hybrid
from onegov.core.orm.mixins import ContentMixin
from onegov.core.orm.mixins import dict_property
from onegov.core.orm.mixins import meta_property
from onegov.core.orm.mixins import TimestampMixin
from onegov.core.orm.mixins.content import dictionary_based_property_factory
from onegov.core.orm.types import HSTORE
from onegov.election_day.models.election import Election
from onegov.election_day.models.election_compound import ElectionCompound
from onegov.election_day.models.mixins import DomainOfInfluenceMixin
from onegov.election_day.models.mixins import TitleTranslationsMixin
from onegov.election_day.models.vote import ComplexVote, Vote
from sqlalchemy import Enum
from sqlalchemy.orm import mapped_column, Mapped
from uuid import uuid4, UUID

from typing import Any
from typing import Literal
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from builtins import type as _type
    from onegov.election_day.request import ElectionDayRequest
    from typing import Self
    from typing import TypeAlias


[docs] ResultType: TypeAlias = Literal[ 'election', 'election_compound', 'complex_vote', 'vote' ]
[docs] meta_local_property = dictionary_based_property_factory('local')
[docs] class ArchivedResult(Base, ContentMixin, TimestampMixin, DomainOfInfluenceMixin, TitleTranslationsMixin): """ Stores the result of an election or vote. """
[docs] __tablename__ = 'archived_results'
#: Identifies the result
[docs] id: Mapped[UUID] = mapped_column( primary_key=True, default=uuid4 )
#: The date of the election/vote
[docs] date: Mapped[datetime.date]
#: The last change of the results election/vote
[docs] last_modified: Mapped[datetime.datetime | None]
#: The last change of election/vote
[docs] last_result_change: Mapped[datetime.datetime | None]
#: Type of the result
[docs] type: Mapped[ResultType] = mapped_column( Enum( 'vote', 'complex_vote', 'election', 'election_compound', name='type_of_result' ) )
#: Origin of the result
[docs] schema: Mapped[str]
#: The name of the principal
[docs] name: Mapped[str]
#: Total number of political entities
[docs] total_entities: Mapped[int | None]
#: Number of already counted political entities
[docs] counted_entities: Mapped[int | None]
@property
[docs] def vote_finalized(self) -> bool: if self.total_entities == 0: return False return self.counted_entities == self.total_entities
@property
[docs] def progress(self) -> tuple[int, int]: return self.counted_entities or 0, self.total_entities or 0
#: Number of already counted political entities
[docs] has_results: Mapped[bool | None]
#: The link to the detailed results
[docs] url: Mapped[str]
#: Title of the election/vote
[docs] title_translations: Mapped[Mapping[str, str]] = mapped_column(HSTORE)
[docs] title = translation_hybrid(title_translations)
#: Proposal title of the election/vote (complex votes)
[docs] title_proposal_translations: Mapped[Mapping[str, str] | None] = ( mapped_column(HSTORE) )
[docs] title_proposal = translation_hybrid(title_proposal_translations)
#: Counterproposal title of the election/vote (complex votes)
[docs] title_counter_proposal_translations: Mapped[Mapping[str, str] | None] = ( mapped_column(HSTORE) )
[docs] title_counter_proposal = translation_hybrid( title_counter_proposal_translations )
#: Tiebreaker title of the election/vote (complex votes)
[docs] title_tie_breaker_translations: Mapped[Mapping[str, str] | None] = ( mapped_column(HSTORE) )
[docs] title_tie_breaker = translation_hybrid(title_tie_breaker_translations)
[docs] def title_prefix(self, request: ElectionDayRequest) -> str: if self.is_fetched(request) and self.domain == 'municipality': return self.name or '' return ''
#: Shortcode for cantons that use it
[docs] shortcode: Mapped[str | None]
#: The id of the election/vote.
[docs] external_id: dict_property[str | None] = meta_property('id')
#: The names of the elected candidates.
[docs] elected_candidates: dict_property[list[tuple[str, str]]] = meta_property( 'elected_candidates', default=list )
#: The URLs of the elections (if it is a compound)
[docs] elections: dict_property[list[str]] = meta_property( 'elections', default=list )
#: The answer of a vote (accepted, rejected, counter-proposal).
[docs] answer: dict_property[str] = meta_property('answer', default='')
#: The nays rate of a vote.
[docs] nays_percentage: dict_property[float] = meta_property( 'nays_percentage', default=100.0 )
#: The yeas rate of a vote.
[docs] yeas_percentage: dict_property[float] = meta_property( 'yeas_percentage', default=0.0 )
#: The nays rate of a vote proposal for complex votes.
[docs] nays_percentage_proposal: dict_property[float] = meta_property( 'nays_percentage_proposal', default=100.0 )
#: The yeas rate of a vote.
[docs] yeas_percentage_proposal: dict_property[float] = meta_property( 'yeas_percentage_proposal', default=0.0 )
#: The nays rate of a vote counterproposal for complex votes.
[docs] nays_percentage_counter_proposal: dict_property[float] = meta_property( 'nays_percentage_counter_proposal', default=100.0 )
#: The yeas rate of a vote counterproposal for complex votes.
[docs] yeas_percentage_counter_proposal: dict_property[float] = meta_property( 'yeas_percentage_counter_proposal', default=0.0 )
#: The nays rate of a vote tiebreaker for complex votes.
[docs] nays_percentage_tie_breaker: dict_property[float] = meta_property( 'nays_percentage_tie_breaker', default=100.0 )
#: The yeas rate of a vote tiebreaker for complex votes.
[docs] yeas_percentage_tie_breaker: dict_property[float] = meta_property( 'yeas_percentage_tie_breaker', default=0.0 )
#: True, if the vote or election has been counted.
[docs] counted: dict_property[bool] = meta_property('counted', default=False)
#: True, if the vote or election has been completed.
[docs] completed: dict_property[bool] = meta_property( 'completed', default=False )
#: Turnout (vote/elections)
[docs] turnout: dict_property[float | None] = meta_property('turnout')
#: True, if this is direct complex vote
[docs] direct: dict_property[bool] = meta_property( 'direct', default=True )
#: The local results (municipal results if fetched from cantonal instance)
[docs] local: dict_property[dict[str, Any] | None] = meta_property('local')
#: The answer if this a fetched cantonal/federal result on a communal #: instance.
[docs] local_answer: dict_property[str] = meta_local_property('answer', '')
#: The nays rate if this a fetched cantonal/federal result on a communal #: instance.
[docs] local_nays_percentage: dict_property[float] = meta_local_property( 'nays_percentage', 100.0 )
#: The yeas rate if this a fetched cantonal/federal result on a communal #: instance.
[docs] local_yeas_percentage: dict_property[float] = meta_local_property( 'yeas_percentage', 0.0 )
@property
[docs] def type_class( self ) -> _type[Election | ElectionCompound | Vote | ComplexVote]: if self.type == 'vote': return Vote elif self.type == 'complex_vote': return ComplexVote elif self.type == 'election': return Election elif self.type == 'election_compound': return ElectionCompound raise NotImplementedError
@property
[docs] def is_complex_vote(self) -> bool: """ Returns True if this result represents a complex vote. """ if self.type == 'complex_vote': return True return False
[docs] def is_fetched(self, request: ElectionDayRequest) -> bool: """ Returns True, if this results has been fetched from another instance. """ return self.schema != request.app.schema
[docs] def is_fetched_by_municipality( self, request: ElectionDayRequest ) -> bool: """ Returns True, if this results has been fetched from another instance by a communal instance. """ return ( self.is_fetched(request) and request.app.principal.domain == 'municipality' )
[docs] def adjusted_url(self, request: ElectionDayRequest) -> str: """ Returns the url adjusted to the current host. Needed if the instance is accessible under different hosts at the same time. """ if self.is_fetched(request): return self.url return request.class_link( self.type_class, {'id': self.external_id} )
[docs] def display_answer(self, request: ElectionDayRequest) -> str: """ Returns the answer (depending on the current instance). """ if self.is_fetched_by_municipality(request): return self.local_answer return self.answer
[docs] def display_nays_percentage(self, request: ElectionDayRequest) -> float: """ Returns the nays rate (depending on the current instance). """ if self.is_fetched_by_municipality(request): return self.local_nays_percentage return self.nays_percentage
[docs] def display_yeas_percentage(self, request: ElectionDayRequest) -> float: """ Returns the yeas rate (depending on the current instance). """ if self.is_fetched_by_municipality(request): return self.local_yeas_percentage return self.yeas_percentage
[docs] def display_nays_percentage_proposal(self) -> float: """ Returns the proposal nays rate for complex votes. """ return self.nays_percentage_proposal
[docs] def display_yeas_percentage_proposal(self) -> float: """ Returns the proposal yeas rate for complex votes. """ return self.yeas_percentage_proposal
[docs] def display_nays_percentage_counter_proposal(self) -> float: """ Returns the counterproposal nays rate for complex votes. """ return self.nays_percentage_counter_proposal
[docs] def display_yeas_percentage_counter_proposal(self) -> float: """ Returns the counterproposal yeas rate for complex votes. """ return self.yeas_percentage_counter_proposal
[docs] def display_nays_percentage_tie_breaker(self) -> float: """ Returns the tiebreaker nays rate for complex votes. """ return self.nays_percentage_tie_breaker
[docs] def display_yeas_percentage_tie_breaker(self) -> float: """ Returns the tiebreaker yeas rate for complex votes. """ return self.yeas_percentage_tie_breaker
[docs] def copy_from(self, source: Self) -> None: self.date = source.date self.last_modified = source.last_modified self.last_result_change = source.last_result_change self.type = source.type self.schema = source.schema self.name = source.name self.total_entities = source.total_entities self.counted_entities = source.counted_entities self.has_results = source.has_results self.url = source.url self.title_translations = deepcopy( dict(source.title_translations) ) self.title_proposal_translations = deepcopy( dict(source.title_proposal_translations or {}) ) self.title_counter_proposal_translations = deepcopy( dict(source.title_counter_proposal_translations or {}) ) self.title_tie_breaker_translations = deepcopy( dict(source.title_tie_breaker_translations or {}) ) self.shortcode = source.shortcode self.domain = source.domain self.meta = deepcopy(dict(source.meta))