Integrate committee ballots with Voting
This commit is contained in:
+159
-16
@@ -18,6 +18,7 @@ from govoplan_core.core.institutional import (
|
||||
MandateResolution,
|
||||
TemporalRevision,
|
||||
)
|
||||
from govoplan_core.core.voting import CAPABILITY_VOTING_BALLOTS, VotingResult
|
||||
from govoplan_committee.backend.db.models import (
|
||||
CommitteeDecisionProjection,
|
||||
CommitteeWorkspaceEvent,
|
||||
@@ -80,6 +81,61 @@ class BallotRegistry:
|
||||
return self.adapter
|
||||
|
||||
|
||||
class VotingBallots:
|
||||
def create_ballot(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_ballot(self, session, principal, *, ballot_id):
|
||||
return {
|
||||
"id": ballot_id,
|
||||
"revision": 2,
|
||||
"state": "open",
|
||||
"context": {"module": "committee", "resource_id": "vote-voting"},
|
||||
}
|
||||
|
||||
def open_ballot(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
def cast_ballot(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
def close_ballot(
|
||||
self, session, principal, *, ballot_id, expected_revision, idempotency_key
|
||||
):
|
||||
if expected_revision != 2:
|
||||
raise AssertionError("unexpected Voting revision")
|
||||
return VotingResult(
|
||||
ballot_id=ballot_id,
|
||||
revision=3,
|
||||
counts={"yes": 2, "no": 1},
|
||||
weighted_counts={"yes": 3, "no": 1},
|
||||
cast_count=3,
|
||||
cast_weight=4,
|
||||
eligible_count=4,
|
||||
eligible_weight=5,
|
||||
quorum_met=True,
|
||||
threshold_met=True,
|
||||
winning_options=("yes",),
|
||||
result_sha256="c" * 64,
|
||||
)
|
||||
|
||||
def certify_ballot(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class VotingRegistry:
|
||||
def __init__(self) -> None:
|
||||
self.provider = VotingBallots()
|
||||
|
||||
def has_capability(self, name: str) -> bool:
|
||||
return name == CAPABILITY_VOTING_BALLOTS
|
||||
|
||||
def capability(self, name: str) -> object:
|
||||
if name != CAPABILITY_VOTING_BALLOTS:
|
||||
raise KeyError(name)
|
||||
return self.provider
|
||||
|
||||
|
||||
def ref(kind: str, object_id: str, owner: str) -> InstitutionalReference:
|
||||
return InstitutionalReference(
|
||||
kind=kind, # type: ignore[arg-type]
|
||||
@@ -320,15 +376,19 @@ class CommitteeWorkspaceTests(unittest.TestCase):
|
||||
idempotency_key="vote-close",
|
||||
)
|
||||
|
||||
decision = CommitteeDecisionPath().decide(
|
||||
self.session,
|
||||
self.principal,
|
||||
proposal=proposal(),
|
||||
mandate_resolution=MandateResolution(
|
||||
competent=True,
|
||||
mandates=(mandate(),),
|
||||
),
|
||||
).decision
|
||||
decision = (
|
||||
CommitteeDecisionPath()
|
||||
.decide(
|
||||
self.session,
|
||||
self.principal,
|
||||
proposal=proposal(),
|
||||
mandate_resolution=MandateResolution(
|
||||
competent=True,
|
||||
mandates=(mandate(),),
|
||||
),
|
||||
)
|
||||
.decision
|
||||
)
|
||||
workspace = SqlCommitteeWorkspace()
|
||||
projected = workspace.record_local_decision(
|
||||
self.session,
|
||||
@@ -387,7 +447,9 @@ class CommitteeWorkspaceTests(unittest.TestCase):
|
||||
parent_id="meeting-1",
|
||||
attributes={
|
||||
"content_ref": ref("record", "minutes-1", "records").to_dict(),
|
||||
"approval_ref": ref("approval", "minutes-ok", "approvals").to_dict(),
|
||||
"approval_ref": ref(
|
||||
"approval", "minutes-ok", "approvals"
|
||||
).to_dict(),
|
||||
},
|
||||
evidence_refs=(evidence("minutes-signature"),),
|
||||
)
|
||||
@@ -400,11 +462,14 @@ class CommitteeWorkspaceTests(unittest.TestCase):
|
||||
self.assertEqual([], events)
|
||||
self.session.commit()
|
||||
|
||||
self.assertEqual("decision-1", get_local_decision(
|
||||
self.session,
|
||||
self.principal,
|
||||
decision_id="decision-1",
|
||||
).reference.object_id)
|
||||
self.assertEqual(
|
||||
"decision-1",
|
||||
get_local_decision(
|
||||
self.session,
|
||||
self.principal,
|
||||
decision_id="decision-1",
|
||||
).reference.object_id,
|
||||
)
|
||||
agenda_history = workspace_history(
|
||||
self.session,
|
||||
self.principal,
|
||||
@@ -488,7 +553,9 @@ class CommitteeWorkspaceTests(unittest.TestCase):
|
||||
idempotency_key="cross-tenant",
|
||||
)
|
||||
|
||||
def test_provider_ballot_finalization_persists_only_aggregate_evidence(self) -> None:
|
||||
def test_provider_ballot_finalization_persists_only_aggregate_evidence(
|
||||
self,
|
||||
) -> None:
|
||||
records = (
|
||||
workspace_record(
|
||||
"body",
|
||||
@@ -592,6 +659,82 @@ class CommitteeWorkspaceTests(unittest.TestCase):
|
||||
self.assertNotIn("ballots", closed.attributes)
|
||||
self.assertEqual("secret-ballot-result", closed.evidence[0].evidence_id)
|
||||
|
||||
def test_voting_ballot_finalization_projects_only_aggregate_result(self) -> None:
|
||||
records = (
|
||||
workspace_record(
|
||||
"body",
|
||||
"body-voting",
|
||||
state="active",
|
||||
attributes={
|
||||
"organization_unit_ref": ref(
|
||||
"organization_unit", "board-1", "organizations"
|
||||
).to_dict(),
|
||||
"function_refs": [],
|
||||
},
|
||||
),
|
||||
workspace_record(
|
||||
"meeting",
|
||||
"meeting-voting",
|
||||
state="open",
|
||||
parent_id="body-voting",
|
||||
attributes={
|
||||
"starts_at": NOW.isoformat(),
|
||||
"ends_at": (NOW + timedelta(hours=2)).isoformat(),
|
||||
},
|
||||
),
|
||||
workspace_record(
|
||||
"agenda_item",
|
||||
"agenda-voting",
|
||||
state="deliberating",
|
||||
parent_id="meeting-voting",
|
||||
attributes={
|
||||
"position": 1,
|
||||
"subject_refs": [ref("case", "case-1", "cases").to_dict()],
|
||||
},
|
||||
),
|
||||
workspace_record(
|
||||
"vote",
|
||||
"vote-voting",
|
||||
state="open",
|
||||
parent_id="agenda-voting",
|
||||
attributes={
|
||||
"method": "recorded",
|
||||
"voting_ballot_id": "ballot-1",
|
||||
"choices": ["yes", "no"],
|
||||
"eligible_count": 4,
|
||||
"cast_count": 0,
|
||||
},
|
||||
),
|
||||
)
|
||||
for index, item in enumerate(records):
|
||||
record_workspace_object(
|
||||
self.session,
|
||||
self.principal,
|
||||
record=item,
|
||||
idempotency_key=f"voting-setup-{index}",
|
||||
)
|
||||
self.session.commit()
|
||||
|
||||
closed = CommitteeBallotFinalizer(VotingRegistry()).finalize_voting_ballot(
|
||||
self.session,
|
||||
self.principal,
|
||||
vote_id="vote-voting",
|
||||
voting_ballot_id="ballot-1",
|
||||
voting_expected_revision=2,
|
||||
approval_ref=ref("approval", "vote-approval", "approvals"),
|
||||
expected_revision=1,
|
||||
recorded_at=NOW + timedelta(minutes=5),
|
||||
change_reason="Closed the governed Voting ballot.",
|
||||
idempotency_key="voting-finalize-1",
|
||||
)
|
||||
self.session.commit()
|
||||
|
||||
self.assertEqual("closed", closed.state)
|
||||
self.assertEqual({"yes": 2, "no": 1}, closed.attributes["counts"])
|
||||
self.assertEqual("c" * 64, closed.attributes["voting_result_sha256"])
|
||||
self.assertEqual("voting", closed.evidence[0].owner_module)
|
||||
self.assertNotIn("selections", closed.attributes)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user