Skip to content
Merged
35 changes: 34 additions & 1 deletion src/onegov/election_day/collections/archived_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
ElectionCompoundCollection
from onegov.election_day.collections.votes import VoteCollection
from onegov.election_day.models import ArchivedResult
from onegov.election_day.models import ComplexVote
from onegov.election_day.models import Election
from onegov.election_day.models import ElectionCompound
from onegov.election_day.models import Vote
Expand Down Expand Up @@ -185,7 +186,8 @@ def group_items(
lambda j: order.get(j.domain, 99),
lambda j: groupbydict(
(item for item in j if item.url not in compounded),
lambda k: 'vote' if k.type == 'vote' else 'election'
lambda k: 'vote'
if k.type in ('vote', 'complex_vote') else 'election'
)
)
)
Expand Down Expand Up @@ -331,6 +333,37 @@ def update(
result.yeas_percentage = item.yeas_percentage
result.direct = item.direct

if isinstance(item, ComplexVote):
result.type = 'complex_vote'
result.title_proposal_translations = (
item.title_translations or {}
)
ballot = item.proposal
result.nays_percentage_proposal = ballot.nays_percentage
result.yeas_percentage_proposal = ballot.yeas_percentage

ballot = item.counter_proposal
result.title_counter_proposal_translations = (
ballot.title_translations or {}
)
result.nays_percentage_counter_proposal = (
ballot.nays_percentage
)
result.yeas_percentage_counter_proposal = (
ballot.yeas_percentage
)

ballot = item.tie_breaker
result.title_tie_breaker_translations = (
ballot.title_translations or {}
)
result.nays_percentage_tie_breaker = (
ballot.nays_percentage
)
result.yeas_percentage_tie_breaker = (
ballot.yeas_percentage
)

if add_result:
self.session.add(result)

Expand Down
133 changes: 125 additions & 8 deletions src/onegov/election_day/models/archived_result.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from copy import deepcopy

from onegov.core.orm import Base
from onegov.core.orm import translation_hybrid
from onegov.core.orm.mixins import ContentMixin
Expand All @@ -15,7 +16,7 @@
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 Vote
from onegov.election_day.models.vote import Vote, ComplexVote
from sqlalchemy import Boolean
from sqlalchemy import Column
from sqlalchemy import Date
Expand All @@ -24,9 +25,9 @@
from sqlalchemy import Text
from uuid import uuid4


from typing import Any
from typing import TYPE_CHECKING

if TYPE_CHECKING:
import datetime
import uuid
Expand All @@ -37,15 +38,15 @@
from typing import Self
from typing import TypeAlias

ResultType: TypeAlias = Literal['election', 'election_compound', 'vote']

ResultType: TypeAlias = Literal[
'election', 'election_compound', 'vote', 'complex_vote'
]

meta_local_property = dictionary_based_property_factory('local')


class ArchivedResult(Base, ContentMixin, TimestampMixin,
DomainOfInfluenceMixin, TitleTranslationsMixin):

""" Stores the result of an election or vote. """

__tablename__ = 'archived_results'
Expand Down Expand Up @@ -75,7 +76,7 @@ class ArchivedResult(Base, ContentMixin, TimestampMixin,
#: Type of the result
type: Column[ResultType] = Column(
Enum( # type:ignore[arg-type]
'vote', 'election', 'election_compound',
'vote', 'complex_vote', 'election', 'election_compound',
name='type_of_result'
),
nullable=False
Expand All @@ -93,6 +94,13 @@ class ArchivedResult(Base, ContentMixin, TimestampMixin,
#: Number of already counted political entities
counted_entities: Column[int | None] = Column(Integer, nullable=True)

@property
def vote_finalized(self) -> bool:
if self.total_entities == 0:
return False

return self.counted_entities == self.total_entities

@property
def progress(self) -> tuple[int, int]:
return self.counted_entities or 0, self.total_entities or 0
Expand All @@ -110,6 +118,31 @@ def progress(self) -> tuple[int, int]:
)
title = translation_hybrid(title_translations)

#: Proposal title of the election/vote (complex votes)
title_proposal_translations: Column[Mapping[str, str] | None] = Column(
HSTORE,
nullable=True
)
title_proposal = translation_hybrid(title_proposal_translations)

#: Counterproposal title of the election/vote (complex votes)
title_counter_proposal_translations: Column[Mapping[str, str] | None] = (
Column(
HSTORE,
nullable=True
)
)
title_counter_proposal = translation_hybrid(
title_counter_proposal_translations
)

#: Tiebreaker title of the election/vote (complex votes)
title_tie_breaker_translations: Column[Mapping[str, str] | None] = Column(
HSTORE,
nullable=True
)
title_tie_breaker = translation_hybrid(title_tie_breaker_translations)

def title_prefix(self, request: ElectionDayRequest) -> str:
if self.is_fetched(request) and self.domain == 'municipality':
return self.name or ''
Expand Down Expand Up @@ -149,6 +182,42 @@ def title_prefix(self, request: ElectionDayRequest) -> str:
default=0.0
)

#: The nays rate of a vote proposal for complex votes.
nays_percentage_proposal: dict_property[float] = meta_property(
'nays_percentage_proposal',
default=100.0
)

#: The yeas rate of a vote.
yeas_percentage_proposal: dict_property[float] = meta_property(
'yeas_percentage_proposal',
default=0.0
)

#: The nays rate of a vote counterproposal for complex votes.
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.
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.
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.
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.
counted: dict_property[bool] = meta_property('counted', default=False)

Expand Down Expand Up @@ -189,15 +258,28 @@ def title_prefix(self, request: ElectionDayRequest) -> str:
)

@property
def type_class(self) -> _type[Election | ElectionCompound | Vote]:
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
def is_complex_vote(self) -> bool:
""" Returns True if this result represents a complex vote. """

if self.type == 'complex_vote':
return True

return False

def is_fetched(self, request: ElectionDayRequest) -> bool:
""" Returns True, if this results has been fetched from another
instance.
Expand Down Expand Up @@ -252,6 +334,30 @@ def display_yeas_percentage(self, request: ElectionDayRequest) -> float:
return self.local_yeas_percentage
return self.yeas_percentage

def display_nays_percentage_proposal(self) -> float:
""" Returns the proposal nays rate for complex votes. """
return self.nays_percentage_proposal

def display_yeas_percentage_proposal(self) -> float:
""" Returns the proposal yeas rate for complex votes. """
return self.yeas_percentage_proposal

def display_nays_percentage_counter_proposal(self) -> float:
""" Returns the counterproposal nays rate for complex votes. """
return self.nays_percentage_counter_proposal

def display_yeas_percentage_counter_proposal(self) -> float:
""" Returns the counterproposal yeas rate for complex votes. """
return self.yeas_percentage_counter_proposal

def display_nays_percentage_tie_breaker(self) -> float:
""" Returns the tiebreaker nays rate for complex votes. """
return self.nays_percentage_tie_breaker

def display_yeas_percentage_tie_breaker(self) -> float:
""" Returns the tiebreaker yeas rate for complex votes. """
return self.yeas_percentage_tie_breaker

def copy_from(self, source: Self) -> None:
self.date = source.date
self.last_modified = source.last_modified
Expand All @@ -263,7 +369,18 @@ def copy_from(self, source: Self) -> None:
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_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))
4 changes: 4 additions & 0 deletions src/onegov/election_day/models/vote/complex_vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class ComplexVote(Vote):

__mapper_args__ = {'polymorphic_identity': 'complex'}

@property
def proposal(self) -> Ballot:
return self.ballot('proposal')

@property
def counter_proposal(self) -> Ballot:
return self.ballot('counter-proposal')
Expand Down
Loading