241 lines
7.7 KiB
Python
241 lines
7.7 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
from datetime import UTC, datetime, timedelta
|
|
|
|
from govoplan_core.core.institutional import (
|
|
CAPABILITY_DECISION_REGISTRY,
|
|
CAPABILITY_MANDATE_RESOLVER,
|
|
ActorRepresentationReference,
|
|
DecisionEffectReference,
|
|
EvidenceReference,
|
|
InformationGovernanceReference,
|
|
InstitutionalContextError,
|
|
InstitutionalReference,
|
|
LegalBasisReference,
|
|
MandateDefinition,
|
|
MandateResolution,
|
|
TemporalRevision,
|
|
)
|
|
from govoplan_committee.backend.decision_path import (
|
|
CommitteeDecisionPath,
|
|
CommitteeDecisionProposal,
|
|
)
|
|
|
|
|
|
NOW = datetime(2026, 8, 1, 10, 0, tzinfo=UTC)
|
|
|
|
|
|
def reference(kind: str, object_id: str) -> InstitutionalReference:
|
|
return InstitutionalReference(
|
|
kind=kind, # type: ignore[arg-type]
|
|
owner_module=(
|
|
"committee"
|
|
if kind in {"decision", "record"}
|
|
else "organizations"
|
|
if kind in {"organization_unit", "function", "jurisdiction"}
|
|
else "approvals"
|
|
if kind == "approval"
|
|
else "cases"
|
|
),
|
|
object_id=object_id,
|
|
tenant_id="tenant-1",
|
|
valid_at=NOW,
|
|
)
|
|
|
|
|
|
def mandate() -> MandateDefinition:
|
|
return MandateDefinition(
|
|
reference=InstitutionalReference(
|
|
kind="mandate",
|
|
owner_module="committee",
|
|
object_id="mandate-1",
|
|
tenant_id="tenant-1",
|
|
version="7",
|
|
valid_at=NOW,
|
|
),
|
|
temporal=TemporalRevision(
|
|
revision="7",
|
|
valid_from=NOW - timedelta(days=30),
|
|
valid_to=NOW + timedelta(days=30),
|
|
recorded_at=NOW - timedelta(days=40),
|
|
),
|
|
task_types=("committee.formal_decision",),
|
|
authority_types=("committee.resolution",),
|
|
organization_unit_refs=(reference("organization_unit", "board-1"),),
|
|
function_refs=(reference("function", "chair"),),
|
|
jurisdiction_refs=(reference("jurisdiction", "city-1"),),
|
|
legal_bases=(legal_basis(),),
|
|
evidence=(evidence("mandate-evidence"),),
|
|
)
|
|
|
|
|
|
def legal_basis() -> LegalBasisReference:
|
|
return LegalBasisReference(
|
|
kind="statute",
|
|
authority="Example council",
|
|
reference="rules:committee:12",
|
|
version="2026-01",
|
|
effective_from=NOW - timedelta(days=100),
|
|
)
|
|
|
|
|
|
def evidence(evidence_id: str) -> EvidenceReference:
|
|
return EvidenceReference(
|
|
kind="record",
|
|
owner_module="committee",
|
|
evidence_id=evidence_id,
|
|
tenant_id="tenant-1",
|
|
version="1",
|
|
checksum="sha256:example",
|
|
captured_at=NOW,
|
|
)
|
|
|
|
|
|
def proposal() -> CommitteeDecisionProposal:
|
|
return CommitteeDecisionProposal(
|
|
tenant_id="tenant-1",
|
|
decision_id="decision-1",
|
|
revision="1",
|
|
effective_at=NOW,
|
|
meeting_ref="meeting-4",
|
|
agenda_item_ref="item-7",
|
|
decision_type="committee.resolution",
|
|
subject_refs=(reference("case", "case-1"),),
|
|
organization_unit_ref=reference("organization_unit", "board-1"),
|
|
function_ref=reference("function", "chair"),
|
|
actor=ActorRepresentationReference(
|
|
tenant_id="tenant-1",
|
|
account_id="account-1",
|
|
identity_id="identity-1",
|
|
represented_function_ref=reference("function", "chair"),
|
|
mandate_ref=mandate().reference,
|
|
),
|
|
approval_refs=(reference("approval", "vote-approval-1"),),
|
|
fact_evidence=(evidence("minutes-4"),),
|
|
legal_bases=(legal_basis(),),
|
|
operative_result="The proposal is accepted.",
|
|
reasoning="The submitted evidence satisfies the applicable rule.",
|
|
case_ref=reference("case", "case-1"),
|
|
jurisdiction_refs=(reference("jurisdiction", "city-1"),),
|
|
record_refs=(reference("record", "minutes-4"),),
|
|
requested_effects=(
|
|
DecisionEffectReference(
|
|
effect_key="postbox.notify_parties",
|
|
state="requested",
|
|
resource_refs=("postbox:case-1",),
|
|
),
|
|
),
|
|
review_refs=("review:administrative-court",),
|
|
information_governance=InformationGovernanceReference(
|
|
classification="restricted",
|
|
purposes=("formal_decision",),
|
|
legal_basis_refs=("rules:committee:12@2026-01",),
|
|
disclosure_state="partly_disclosable",
|
|
),
|
|
assurance_level="human_reviewed_automation",
|
|
automation_preparation_refs=("dataflow:recommendation-1",),
|
|
)
|
|
|
|
|
|
class FakeDecisionRegistry:
|
|
def __init__(self) -> None:
|
|
self.recorded = None
|
|
|
|
def get_decision(self, session, principal, *, reference):
|
|
return self.recorded
|
|
|
|
def record_decision(self, session, principal, *, decision, expected_revision=None):
|
|
self.recorded = decision
|
|
return decision
|
|
|
|
|
|
class FakeMandateResolver:
|
|
def __init__(self, resolution: MandateResolution) -> None:
|
|
self.resolution = resolution
|
|
self.request = None
|
|
|
|
def resolve_mandate(self, session, principal, *, request):
|
|
self.request = request
|
|
return self.resolution
|
|
|
|
|
|
class FakeRegistry:
|
|
def __init__(self, capabilities: dict[str, object]) -> None:
|
|
self.capabilities = capabilities
|
|
|
|
def has_capability(self, name: str) -> bool:
|
|
return name in self.capabilities
|
|
|
|
def capability(self, name: str) -> object:
|
|
return self.capabilities[name]
|
|
|
|
|
|
class CommitteeDecisionPathTests(unittest.TestCase):
|
|
def test_decision_reconstructs_authority_approval_evidence_and_effects(self) -> None:
|
|
registry = FakeDecisionRegistry()
|
|
resolution = MandateResolution(
|
|
competent=True,
|
|
mandates=(mandate(),),
|
|
explanation="Chair is competent for this agenda item.",
|
|
)
|
|
mandate_resolver = FakeMandateResolver(resolution)
|
|
path = CommitteeDecisionPath(
|
|
FakeRegistry(
|
|
{
|
|
CAPABILITY_MANDATE_RESOLVER: mandate_resolver,
|
|
CAPABILITY_DECISION_REGISTRY: registry,
|
|
}
|
|
)
|
|
)
|
|
|
|
result = path.decide(None, None, proposal=proposal())
|
|
payload = result.reconstruction_payload()
|
|
|
|
self.assertTrue(result.persisted_by_decision_registry)
|
|
self.assertIs(registry.recorded, result.decision)
|
|
self.assertEqual("mandate-1", payload["mandate_ref"]["object_id"])
|
|
self.assertEqual(
|
|
"vote-approval-1",
|
|
payload["decision"]["authority_context"]["approval_refs"][0]["object_id"],
|
|
)
|
|
self.assertEqual(
|
|
"postbox.notify_parties",
|
|
payload["decision"]["requested_effects"][0]["effect_key"],
|
|
)
|
|
self.assertEqual(
|
|
"The submitted evidence satisfies the applicable rule.",
|
|
payload["decision"]["reasoning"],
|
|
)
|
|
self.assertEqual(
|
|
"city-1", mandate_resolver.request.jurisdiction_refs[0].object_id
|
|
)
|
|
self.assertEqual(
|
|
"human_reviewed_automation",
|
|
payload["decision"]["assurance_level"],
|
|
)
|
|
self.assertEqual(
|
|
["dataflow:recommendation-1"],
|
|
payload["decision"]["automation_preparation_refs"],
|
|
)
|
|
|
|
def test_decision_rejects_ambiguous_or_ineffective_mandate(self) -> None:
|
|
accepted = mandate()
|
|
with self.assertRaisesRegex(
|
|
InstitutionalContextError,
|
|
"exactly one effective active mandate",
|
|
):
|
|
CommitteeDecisionPath().decide(
|
|
None,
|
|
None,
|
|
proposal=proposal(),
|
|
mandate_resolution=MandateResolution(
|
|
competent=True,
|
|
mandates=(accepted, accepted),
|
|
),
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|