Skip to content

RIS light initial commit #1843

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repos:
exclude: .pre-commit-config.yaml
- id: pt_structure
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.12
rev: v0.11.13
hooks:
- id: ruff
args: [ "--fix" ]
Expand Down Expand Up @@ -70,6 +70,6 @@ repos:
files: '^stubs/.*\.pyi$'
pass_filenames: false
- repo: https://github.com/gitleaks/gitleaks
rev: v8.27.0
rev: v8.27.2
hooks:
- id: gitleaks
4 changes: 3 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ packages =
onegov.onboarding
onegov.org
onegov.page
onegov.parliament
onegov.pas
onegov.pay
onegov.pdf
Expand Down Expand Up @@ -296,7 +297,7 @@ console_scripts =
onegov-gazette = onegov.gazette.cli:cli
onegov-landsgemeinde = onegov.org.cli:cli
onegov-org = onegov.org.cli:cli
onegov-people = onegov.people.cli:cli
onegov-parliament = onegov.parliament.cli:cli
onegov-pas = onegov.pas.cli:cli
onegov-search = onegov.search.cli:cli
onegov-server = onegov.server.cli:run
Expand Down Expand Up @@ -328,6 +329,7 @@ onegov_upgrades =
onegov.onboarding = onegov.onboarding.upgrade
onegov.org = onegov.org.upgrade
onegov.page = onegov.page.upgrade
onegov.parliament = onegov.parliament.upgrade
onegov.pas = onegov.pas.upgrade
onegov.pay = onegov.pay.upgrade
onegov.pdf = onegov.pdf.upgrade
Expand Down
13 changes: 13 additions & 0 deletions src/onegov/parliament/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

import logging

log = logging.getLogger('onegov.parliament')
log.addHandler(logging.NullHandler())

from onegov.parliament.i18n import _

__all__ = (
'_',
'log'
)
5 changes: 5 additions & 0 deletions src/onegov/parliament/i18n.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from __future__ import annotations

from onegov.core.i18n.translation_string import TranslationStringFactory

_ = TranslationStringFactory('onegov.parliament')
36 changes: 36 additions & 0 deletions src/onegov/parliament/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import annotations

from onegov.parliament.models.attendence import Attendence
from onegov.parliament.models.change import Change
from onegov.parliament.models.commission import Commission
from onegov.parliament.models.commission_membership import CommissionMembership
from onegov.parliament.models.legislative_period import LegislativePeriod
from onegov.parliament.models.meeting import Meeting
from onegov.parliament.models.parliamentarian import (
Parliamentarian,
RISParliamentarian
)
from onegov.parliament.models.parliamentarian_role import ParliamentarianRole
from onegov.parliament.models.parliamentary_group import ParliamentaryGroup
from onegov.parliament.models.party import Party
from onegov.parliament.models.political_business import (
PoliticalBusiness,
PoliticalBusinessParticipation,
)


__all__ = (
'Attendence',
'Change',
'Commission',
'CommissionMembership',
'LegislativePeriod',
'Meeting',
'Parliamentarian',
'RISParliamentarian',
'ParliamentarianRole',
'ParliamentaryGroup',
'Party',
'PoliticalBusiness',
'PoliticalBusinessParticipation',
)
181 changes: 181 additions & 0 deletions src/onegov/parliament/models/attendence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
from __future__ import annotations

from decimal import ROUND_HALF_UP, Decimal

from sqlalchemy import Column
from sqlalchemy import Date
from sqlalchemy import Enum
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import Text
from sqlalchemy.orm import relationship
from uuid import uuid4

from onegov.core.orm import Base
from onegov.core.orm.mixins import TimestampMixin
from onegov.core.orm.types import UUID
from onegov.parliament import _

from typing import TYPE_CHECKING

if TYPE_CHECKING:
import uuid
import datetime
from typing import Literal
from typing import TypeAlias

from onegov.parliament.models import Parliamentarian, Commission

AttendenceType: TypeAlias = Literal[
'plenary',
'commission',
'study',
'shortest',
]

TYPES: dict[AttendenceType, str] = {
'plenary': _('Plenary session'),
'commission': _('Commission meeting'),
'study': _('File study'),
'shortest': _('Shortest meeting'),
}


class Attendence(Base, TimestampMixin):

__tablename__ = 'par_attendence'

attendence_type: Column[str] = Column(
Text,
nullable=False,
default=lambda: 'generic'
)

__mapper_args__ = {
'polymorphic_on': attendence_type,
'polymorphic_identity': 'generic',
}

#: Internal ID
id: Column[uuid.UUID] = Column(
UUID, # type:ignore[arg-type]
primary_key=True,
default=uuid4
)

#: The date
date: Column[datetime.date] = Column(
Date,
nullable=False
)

#: The duration in minutes
duration: Column[int] = Column(
Integer,
nullable=False
)

#: The type
type: Column[AttendenceType] = Column(
Enum(
*TYPES.keys(), # type:ignore[arg-type]
name='par_attendence_type'
),
nullable=False,
default='plenary'
)

#: The type as translated text
@property
def type_label(self) -> str:
return TYPES.get(self.type, '')

Check warning on line 91 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L91

Added line #L91 was not covered by tests

#: The id of the parliamentarian
parliamentarian_id: Column[uuid.UUID] = Column(
UUID, # type:ignore[arg-type]
ForeignKey('par_parliamentarians.id'),
nullable=False
)

#: The parliamentarian
parliamentarian: relationship[Parliamentarian] = relationship(
'Parliamentarian',
back_populates='attendences'
)

#: the id of the commission
commission_id: Column[uuid.UUID | None] = Column(
UUID, # type:ignore[arg-type]
ForeignKey('par_commissions.id'),
nullable=True
)

#: the related commission (which may have any number of memberships)
commission: relationship[Commission | None] = relationship(
'Commission',
back_populates='attendences'
)

def calculate_value(self) -> Decimal:
"""Calculate the value (in hours) for an attendance record.

The calculation follows these business rules:
- Plenary sessions:
* Always counted as 0.5 (half day), regardless of actual duration
This is the special case!

- Everything else is counted as actual hours:
* First 2 hours are counted as given
* After 2 hours, time is rounded to nearest 30-minute increment,
* and there is another rate applied for the additional time
* Example: 2h 40min would be calculated as 2.5 hours

Examples:
>>> # Plenary session
>>> attendence.type = 'plenary'
>>> calculate_value(attendence)
'0.5'

>>> # Commission meeting, 2 hours
>>> attendence.type = 'commission'
>>> attendence.duration = 120 # minutes
>>> calculate_value(attendence)
'2.0'

>>> # Study session, 2h 40min
>>> attendence.type = 'study'
>>> attendence.duration = 160 # minutes
>>> calculate_value(attendence)
'2.5'
"""
if self.duration < 0:
raise ValueError('Duration cannot be negative')

Check warning on line 152 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L151-L152

Added lines #L151 - L152 were not covered by tests

if self.type == 'plenary':
return Decimal('0.5')

Check warning on line 155 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L154-L155

Added lines #L154 - L155 were not covered by tests

if self.type in ('commission', 'study', 'shortest'):

Check warning on line 157 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L157

Added line #L157 was not covered by tests
# Convert minutes to hours with Decimal for precise calculation
duration_hours = Decimal(str(self.duration)) / Decimal('60')

Check warning on line 159 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L159

Added line #L159 was not covered by tests

if duration_hours <= Decimal('2'):

Check warning on line 161 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L161

Added line #L161 was not covered by tests
# Round to 1 decimal place
return duration_hours.quantize(

Check warning on line 163 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L163

Added line #L163 was not covered by tests
Decimal('0.1'), rounding=ROUND_HALF_UP
)
else:
base_hours = Decimal('2')
additional_hours = (duration_hours - base_hours)

Check warning on line 168 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L167-L168

Added lines #L167 - L168 were not covered by tests
# Round additional time to nearest 0.5
additional_hours = (additional_hours * 2).quantize(

Check warning on line 170 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L170

Added line #L170 was not covered by tests
Decimal('1.0'), rounding=ROUND_HALF_UP
) / 2
total_hours = base_hours + additional_hours
return total_hours.quantize(

Check warning on line 174 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L173-L174

Added lines #L173 - L174 were not covered by tests
Decimal('0.1'), rounding=ROUND_HALF_UP
)

raise ValueError(f'Unknown attendance type: {self.type}')

Check warning on line 178 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L178

Added line #L178 was not covered by tests

def __repr__(self) -> str:
return f'<Attendence {self.date} {self.type}>'

Check warning on line 181 in src/onegov/parliament/models/attendence.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/parliament/models/attendence.py#L181

Added line #L181 was not covered by tests
Loading
Loading