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]
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
#: The name of the principal
#: 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
#: 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))