civics-cdf-validator 1.60.dev2__tar.gz → 1.60.dev3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {civics_cdf_validator-1.60.dev2/civics_cdf_validator.egg-info → civics_cdf_validator-1.60.dev3}/PKG-INFO +1 -1
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3/civics_cdf_validator.egg-info}/PKG-INFO +1 -1
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/rules.py +62 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/version.py +1 -1
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/CONTRIBUTING.md +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/LICENSE-2.0.txt +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/MANIFEST.in +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/README.md +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/__init__.py +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/base.py +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/civics_cdf_validator.egg-info/SOURCES.txt +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/civics_cdf_validator.egg-info/dependency_links.txt +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/civics_cdf_validator.egg-info/entry_points.txt +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/civics_cdf_validator.egg-info/requires.txt +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/civics_cdf_validator.egg-info/top_level.txt +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/gpunit_rules.py +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/loggers.py +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/office_utils.py +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/setup.cfg +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/setup.py +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/stats.py +0 -0
- {civics_cdf_validator-1.60.dev2 → civics_cdf_validator-1.60.dev3}/validator.py +0 -0
|
@@ -132,6 +132,11 @@ _UNTYPED_URI_PLATFORMS = frozenset([
|
|
|
132
132
|
])
|
|
133
133
|
_ALL_URI_PLATFORMS = _TYPED_URI_PLATFORMS | _UNTYPED_URI_PLATFORMS
|
|
134
134
|
|
|
135
|
+
_WINNER_POST_ELECTION_STATUSES = frozenset([
|
|
136
|
+
"winner",
|
|
137
|
+
"projected-winner",
|
|
138
|
+
])
|
|
139
|
+
|
|
135
140
|
|
|
136
141
|
def _get_office_roles(element, is_post_office_split_feed=False):
|
|
137
142
|
if is_post_office_split_feed:
|
|
@@ -3547,6 +3552,62 @@ class ImproperCandidateContest(base.TreeRule):
|
|
|
3547
3552
|
raise loggers.ElectionWarning(warning_log)
|
|
3548
3553
|
|
|
3549
3554
|
|
|
3555
|
+
class WinnerCountLimit(base.BaseRule):
|
|
3556
|
+
"""Number of winners must be less than or equal to NumberElected."""
|
|
3557
|
+
|
|
3558
|
+
def setup(self):
|
|
3559
|
+
self.candidate_post_election_status_by_object_id = (
|
|
3560
|
+
self._get_candidate_post_election_status_by_object_id()
|
|
3561
|
+
)
|
|
3562
|
+
|
|
3563
|
+
def _get_candidate_post_election_status_by_object_id(self):
|
|
3564
|
+
candidate_post_election_status_by_object_id = {}
|
|
3565
|
+
candidates = self.get_elements_by_class(
|
|
3566
|
+
self.election_tree, "CandidateCollection//Candidate"
|
|
3567
|
+
)
|
|
3568
|
+
for candidate in candidates:
|
|
3569
|
+
object_id = candidate.get("objectId")
|
|
3570
|
+
post_election_status = candidate.find("PostElectionStatus")
|
|
3571
|
+
if object_id and element_has_text(post_election_status):
|
|
3572
|
+
candidate_post_election_status_by_object_id[object_id] = (
|
|
3573
|
+
post_election_status.text.strip()
|
|
3574
|
+
)
|
|
3575
|
+
return candidate_post_election_status_by_object_id
|
|
3576
|
+
|
|
3577
|
+
def elements(self):
|
|
3578
|
+
return ["CandidateContest"]
|
|
3579
|
+
|
|
3580
|
+
def check(self, element):
|
|
3581
|
+
number_elected_element = element.find("NumberElected")
|
|
3582
|
+
number_elected = (
|
|
3583
|
+
int(number_elected_element.text)
|
|
3584
|
+
if element_has_text(number_elected_element)
|
|
3585
|
+
else 1
|
|
3586
|
+
)
|
|
3587
|
+
|
|
3588
|
+
candidate_ids_elements = element.findall("BallotSelection//CandidateIds")
|
|
3589
|
+
candidate_ids = set()
|
|
3590
|
+
for candidate_ids_element in candidate_ids_elements:
|
|
3591
|
+
if element_has_text(candidate_ids_element):
|
|
3592
|
+
candidate_ids.update(candidate_ids_element.text.split())
|
|
3593
|
+
|
|
3594
|
+
winner_count = 0
|
|
3595
|
+
for candidate_id in candidate_ids:
|
|
3596
|
+
status = self.candidate_post_election_status_by_object_id.get(
|
|
3597
|
+
candidate_id
|
|
3598
|
+
)
|
|
3599
|
+
if status in _WINNER_POST_ELECTION_STATUSES:
|
|
3600
|
+
winner_count += 1
|
|
3601
|
+
|
|
3602
|
+
if winner_count > number_elected:
|
|
3603
|
+
raise loggers.ElectionError.from_message(
|
|
3604
|
+
f"Contest {element.get('objectId')} has {winner_count} candidates"
|
|
3605
|
+
" with PostElectionStatus of 'winner' or 'projected-winner',"
|
|
3606
|
+
f" which exceeds NumberElected: {number_elected}.",
|
|
3607
|
+
[element],
|
|
3608
|
+
)
|
|
3609
|
+
|
|
3610
|
+
|
|
3550
3611
|
class MissingFieldsError(base.MissingFieldRule):
|
|
3551
3612
|
"""Check for missing fields for given entity types and field names.
|
|
3552
3613
|
|
|
@@ -4940,6 +5001,7 @@ ELECTION_RULES = COMMON_RULES + (
|
|
|
4940
5001
|
ValidateInfoUriAnnotation,
|
|
4941
5002
|
VoteCountTypesCoherency,
|
|
4942
5003
|
VoteCountValidSeatsDeltaTypes,
|
|
5004
|
+
WinnerCountLimit,
|
|
4943
5005
|
# go/keep-sorted end
|
|
4944
5006
|
)
|
|
4945
5007
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|