civics-cdf-validator 1.61.dev5__tar.gz → 1.61.dev7__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.61.dev5/civics_cdf_validator.egg-info → civics_cdf_validator-1.61.dev7}/PKG-INFO +1 -1
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7/civics_cdf_validator.egg-info}/PKG-INFO +1 -1
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/rules.py +72 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/version.py +1 -1
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/CONTRIBUTING.md +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/LICENSE-2.0.txt +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/MANIFEST.in +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/README.md +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/__init__.py +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/base.py +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/civics_cdf_validator.egg-info/SOURCES.txt +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/civics_cdf_validator.egg-info/dependency_links.txt +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/civics_cdf_validator.egg-info/entry_points.txt +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/civics_cdf_validator.egg-info/requires.txt +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/civics_cdf_validator.egg-info/top_level.txt +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/gpunit_rules.py +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/loggers.py +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/office_utils.py +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/setup.cfg +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/setup.py +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/stats.py +0 -0
- {civics_cdf_validator-1.61.dev5 → civics_cdf_validator-1.61.dev7}/validator.py +0 -0
|
@@ -25,6 +25,7 @@ from civics_cdf_validator import base
|
|
|
25
25
|
from civics_cdf_validator import gpunit_rules
|
|
26
26
|
from civics_cdf_validator import loggers
|
|
27
27
|
from civics_cdf_validator import office_utils
|
|
28
|
+
import dateutil
|
|
28
29
|
from frozendict import frozendict
|
|
29
30
|
import language_tags
|
|
30
31
|
from lxml import etree
|
|
@@ -1631,6 +1632,50 @@ class DuplicateContestNames(base.BaseRule):
|
|
|
1631
1632
|
raise loggers.ElectionError(error_log)
|
|
1632
1633
|
|
|
1633
1634
|
|
|
1635
|
+
class DuplicateBallotTitleSeatPair(base.BaseRule):
|
|
1636
|
+
"""Checks that each English (BallotTitle, Seat) combination is unique.
|
|
1637
|
+
|
|
1638
|
+
Adds Warning/Error if duplicate English (BallotTitle, Seat) combinations are
|
|
1639
|
+
found.
|
|
1640
|
+
"""
|
|
1641
|
+
|
|
1642
|
+
def elements(self):
|
|
1643
|
+
return ["ContestCollection"]
|
|
1644
|
+
|
|
1645
|
+
def check(self, element):
|
|
1646
|
+
error_log = []
|
|
1647
|
+
contest_elements = element.findall("Contest")
|
|
1648
|
+
contests_by_title_and_seat = collections.defaultdict(list)
|
|
1649
|
+
|
|
1650
|
+
for contest in contest_elements:
|
|
1651
|
+
ballot_title_element = get_language_to_text_map(
|
|
1652
|
+
contest.find("BallotTitle")
|
|
1653
|
+
)
|
|
1654
|
+
english_title = ballot_title_element.get("en")
|
|
1655
|
+
seat_element = contest.find("Seat")
|
|
1656
|
+
|
|
1657
|
+
ballot_title = english_title[0].strip() if english_title else ""
|
|
1658
|
+
seat = seat_element.text.strip() if element_has_text(seat_element) else ""
|
|
1659
|
+
|
|
1660
|
+
if not ballot_title:
|
|
1661
|
+
continue
|
|
1662
|
+
|
|
1663
|
+
ballot_title_and_seat = (ballot_title, seat)
|
|
1664
|
+
contests_by_title_and_seat[ballot_title_and_seat].append(contest)
|
|
1665
|
+
|
|
1666
|
+
for ballot_title_and_seat, contests in contests_by_title_and_seat.items():
|
|
1667
|
+
if len(contests) > 1:
|
|
1668
|
+
ballot_title, seat = ballot_title_and_seat
|
|
1669
|
+
error_message = (
|
|
1670
|
+
"Multiple Contests found with the same BallotTitle"
|
|
1671
|
+
f" ('{ballot_title}') and Seat ('{seat}')."
|
|
1672
|
+
)
|
|
1673
|
+
error_log.append(loggers.LogEntry(error_message, contests))
|
|
1674
|
+
|
|
1675
|
+
if error_log:
|
|
1676
|
+
raise loggers.ElectionError(error_log)
|
|
1677
|
+
|
|
1678
|
+
|
|
1634
1679
|
class UniqueStableID(base.TreeRule):
|
|
1635
1680
|
"""Check that every stableID is unique.
|
|
1636
1681
|
|
|
@@ -4786,6 +4831,31 @@ class FeedHasValidCountryCode(base.BaseRule):
|
|
|
4786
4831
|
)
|
|
4787
4832
|
|
|
4788
4833
|
|
|
4834
|
+
class FeedInactiveDateNotOlderThanOneYear(base.BaseRule):
|
|
4835
|
+
"""Feeds should not have a FeedInactiveDate older than 1 year."""
|
|
4836
|
+
|
|
4837
|
+
def elements(self):
|
|
4838
|
+
return ["Feed"]
|
|
4839
|
+
|
|
4840
|
+
def check(self, element):
|
|
4841
|
+
feed_inactive_date_element = element.find("FeedInactiveDate")
|
|
4842
|
+
if not element_has_text(feed_inactive_date_element):
|
|
4843
|
+
return
|
|
4844
|
+
feed_inactive_date = datetime.date.fromisoformat(
|
|
4845
|
+
feed_inactive_date_element.text
|
|
4846
|
+
)
|
|
4847
|
+
one_year_ago = datetime.date.today() - dateutil.relativedelta.relativedelta(
|
|
4848
|
+
years=1
|
|
4849
|
+
)
|
|
4850
|
+
if feed_inactive_date < one_year_ago:
|
|
4851
|
+
feed_id_element = element.find("FeedId")
|
|
4852
|
+
raise loggers.ElectionError.from_message(
|
|
4853
|
+
f"FeedInactiveDate '{feed_inactive_date_element.text}' is older than"
|
|
4854
|
+
f" 1 year for feed '{feed_id_element.text}'.",
|
|
4855
|
+
[element],
|
|
4856
|
+
)
|
|
4857
|
+
|
|
4858
|
+
|
|
4789
4859
|
class FeedInactiveDateSetForNonEvergreenFeed(base.BaseRule):
|
|
4790
4860
|
"""All non-evergreen feeds should have a FeedInactiveDate set."""
|
|
4791
4861
|
|
|
@@ -5538,6 +5608,7 @@ ELECTION_RULES = COMMON_RULES + (
|
|
|
5538
5608
|
ContestStartDateContainsCorrespondingEndDate,
|
|
5539
5609
|
CorrectCandidateSelectionCount,
|
|
5540
5610
|
DateStatusMatches,
|
|
5611
|
+
DuplicateBallotTitleSeatPair,
|
|
5541
5612
|
DuplicateContestNames,
|
|
5542
5613
|
DuplicatedPartyAbbreviation,
|
|
5543
5614
|
DuplicatedPartyName,
|
|
@@ -5618,6 +5689,7 @@ METADATA_RULES = (
|
|
|
5618
5689
|
FeedHasValidCountryCode,
|
|
5619
5690
|
FeedIdsAreUnique,
|
|
5620
5691
|
FeedInactiveDateIsLatestDate,
|
|
5692
|
+
FeedInactiveDateNotOlderThanOneYear,
|
|
5621
5693
|
FeedInactiveDateSetForNonEvergreenFeed,
|
|
5622
5694
|
FeedTypeHasValidFeedLongevity,
|
|
5623
5695
|
OfficeholderSubFeedDatesAreSequential,
|
|
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
|