civics-cdf-validator 1.61.dev4__tar.gz → 1.61.dev6__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.
Files changed (22) hide show
  1. {civics_cdf_validator-1.61.dev4/civics_cdf_validator.egg-info → civics_cdf_validator-1.61.dev6}/PKG-INFO +1 -1
  2. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6/civics_cdf_validator.egg-info}/PKG-INFO +1 -1
  3. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/rules.py +57 -5
  4. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/version.py +1 -1
  5. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/CONTRIBUTING.md +0 -0
  6. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/LICENSE-2.0.txt +0 -0
  7. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/MANIFEST.in +0 -0
  8. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/README.md +0 -0
  9. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/__init__.py +0 -0
  10. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/base.py +0 -0
  11. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/civics_cdf_validator.egg-info/SOURCES.txt +0 -0
  12. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/civics_cdf_validator.egg-info/dependency_links.txt +0 -0
  13. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/civics_cdf_validator.egg-info/entry_points.txt +0 -0
  14. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/civics_cdf_validator.egg-info/requires.txt +0 -0
  15. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/civics_cdf_validator.egg-info/top_level.txt +0 -0
  16. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/gpunit_rules.py +0 -0
  17. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/loggers.py +0 -0
  18. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/office_utils.py +0 -0
  19. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/setup.cfg +0 -0
  20. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/setup.py +0 -0
  21. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/stats.py +0 -0
  22. {civics_cdf_validator-1.61.dev4 → civics_cdf_validator-1.61.dev6}/validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: civics_cdf_validator
3
- Version: 1.61.dev4
3
+ Version: 1.61.dev6
4
4
  Summary: Checks if an election feed follows best practices
5
5
  Home-page: https://github.com/google/civics_cdf_validator
6
6
  Author: Google Civics
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: civics_cdf_validator
3
- Version: 1.61.dev4
3
+ Version: 1.61.dev6
4
4
  Summary: Checks if an election feed follows best practices
5
5
  Home-page: https://github.com/google/civics_cdf_validator
6
6
  Author: Google Civics
@@ -141,6 +141,13 @@ _WINNER_POST_ELECTION_STATUSES = frozenset([
141
141
  ])
142
142
 
143
143
 
144
+ def _is_xml_true(element):
145
+ return (
146
+ element_has_text(element)
147
+ and element.text.strip().lower() in _XML_TRUE_VALUES
148
+ )
149
+
150
+
144
151
  def _get_office_roles(element, is_post_office_split_feed=False):
145
152
  if is_post_office_split_feed:
146
153
  return [element.text for element in element.findall("Role")]
@@ -1624,6 +1631,50 @@ class DuplicateContestNames(base.BaseRule):
1624
1631
  raise loggers.ElectionError(error_log)
1625
1632
 
1626
1633
 
1634
+ class DuplicateBallotTitleSeatPair(base.BaseRule):
1635
+ """Checks that each English (BallotTitle, Seat) combination is unique.
1636
+
1637
+ Adds Warning/Error if duplicate English (BallotTitle, Seat) combinations are
1638
+ found.
1639
+ """
1640
+
1641
+ def elements(self):
1642
+ return ["ContestCollection"]
1643
+
1644
+ def check(self, element):
1645
+ error_log = []
1646
+ contest_elements = element.findall("Contest")
1647
+ contests_by_title_and_seat = collections.defaultdict(list)
1648
+
1649
+ for contest in contest_elements:
1650
+ ballot_title_element = get_language_to_text_map(
1651
+ contest.find("BallotTitle")
1652
+ )
1653
+ english_title = ballot_title_element.get("en")
1654
+ seat_element = contest.find("Seat")
1655
+
1656
+ ballot_title = english_title[0].strip() if english_title else ""
1657
+ seat = seat_element.text.strip() if element_has_text(seat_element) else ""
1658
+
1659
+ if not ballot_title:
1660
+ continue
1661
+
1662
+ ballot_title_and_seat = (ballot_title, seat)
1663
+ contests_by_title_and_seat[ballot_title_and_seat].append(contest)
1664
+
1665
+ for ballot_title_and_seat, contests in contests_by_title_and_seat.items():
1666
+ if len(contests) > 1:
1667
+ ballot_title, seat = ballot_title_and_seat
1668
+ error_message = (
1669
+ "Multiple Contests found with the same BallotTitle"
1670
+ f" ('{ballot_title}') and Seat ('{seat}')."
1671
+ )
1672
+ error_log.append(loggers.LogEntry(error_message, contests))
1673
+
1674
+ if error_log:
1675
+ raise loggers.ElectionError(error_log)
1676
+
1677
+
1627
1678
  class UniqueStableID(base.TreeRule):
1628
1679
  """Check that every stableID is unique.
1629
1680
 
@@ -4716,6 +4767,10 @@ class FeedInactiveDateIsLatestDate(base.BaseRule):
4716
4767
  return ["Feed"]
4717
4768
 
4718
4769
  def check(self, element):
4770
+ # Skip this rule for test feeds.
4771
+ if _is_xml_true(element.find("IsTest")):
4772
+ return
4773
+
4719
4774
  if element_has_text(element.find("FeedInactiveDate")):
4720
4775
  feed_inactive_date = base.PartialDate.init_partial_date(
4721
4776
  element.find("FeedInactiveDate").text
@@ -5404,11 +5459,7 @@ class ValidateIncludeInAggregationBallotSelections(base.BaseRule):
5404
5459
 
5405
5460
  included_selections = []
5406
5461
  for selection in all_selections:
5407
- included_in_aggregation_element = selection.find("IncludedInAggregation")
5408
- if (
5409
- element_has_text(included_in_aggregation_element)
5410
- and included_in_aggregation_element.text in _XML_TRUE_VALUES
5411
- ):
5462
+ if _is_xml_true(selection.find("IncludedInAggregation")):
5412
5463
  included_selections.append(selection)
5413
5464
  if not included_selections:
5414
5465
  return
@@ -5531,6 +5582,7 @@ ELECTION_RULES = COMMON_RULES + (
5531
5582
  ContestStartDateContainsCorrespondingEndDate,
5532
5583
  CorrectCandidateSelectionCount,
5533
5584
  DateStatusMatches,
5585
+ DuplicateBallotTitleSeatPair,
5534
5586
  DuplicateContestNames,
5535
5587
  DuplicatedPartyAbbreviation,
5536
5588
  DuplicatedPartyName,
@@ -5,4 +5,4 @@ No dependencies should be added to this module.
5
5
  See https://packaging.python.org/guides/single-sourcing-package-version/
6
6
  """
7
7
 
8
- __version__ = '1.61.dev4'
8
+ __version__ = '1.61.dev6'