civics-cdf-validator 1.49.dev9__py3-none-any.whl → 1.49.dev11__py3-none-any.whl

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.
@@ -2898,37 +2898,76 @@ class OfficeSelectionMethodMatch(base.BaseRule):
2898
2898
  )
2899
2899
 
2900
2900
 
2901
+ class OfficeHolderTenureTermDates(base.DateRule):
2902
+ """OfficeHolderTenure elements should contain valid term dates."""
2903
+
2904
+ def elements(self):
2905
+ return ["OfficeHolderTenure"]
2906
+
2907
+ def check(self, element):
2908
+ start_date = element.find("StartDate")
2909
+ end_date = element.find("EndDate")
2910
+ if element_has_text(start_date) and element_has_text(end_date):
2911
+ start_date_obj = base.PartialDate.init_partial_date(start_date.text)
2912
+ end_date_obj = base.PartialDate.init_partial_date(end_date.text)
2913
+ if end_date_obj < start_date_obj:
2914
+ raise loggers.ElectionError.from_message(
2915
+ "OfficeHolderTenure element has an EndDate that is before the"
2916
+ " StartDate.",
2917
+ [element],
2918
+ )
2919
+
2920
+
2901
2921
  class OfficeTermDates(base.DateRule):
2902
2922
  """Office elements should contain valid term dates.
2903
2923
 
2904
2924
  Offices with OfficeHolderPersonIds should have a Term declared. Given
2905
2925
  term should have a start date. If term also has an end date then end date
2906
- should come after start date.
2926
+ should come after start date. Post Office split feed office objects should
2927
+ not have a Term element.
2907
2928
  """
2908
2929
 
2930
+ def __init__(self, election_tree, schema_tree, **kwargs):
2931
+ self.is_post_office_split_feed = False
2932
+ officeholder_tenure_collection_element = self.get_elements_by_class(
2933
+ election_tree, "OfficeHolderTenureCollection"
2934
+ )
2935
+ if officeholder_tenure_collection_element:
2936
+ self.is_post_office_split_feed = True
2937
+
2909
2938
  def elements(self):
2910
2939
  return ["Office"]
2911
2940
 
2912
2941
  def check(self, element):
2913
2942
  self.reset_instance_vars()
2914
- off_per_id = element.find("OfficeHolderPersonIds")
2915
- if element_has_text(off_per_id):
2943
+ if self.is_post_office_split_feed:
2916
2944
  term = element.find("Term")
2917
- if term is None:
2918
- raise loggers.ElectionWarning.from_message(
2919
- "The Office is missing a Term.", [element]
2945
+ if term is not None:
2946
+ raise loggers.ElectionError.from_message(
2947
+ "Office should not contain Term data in post Office split feed.",
2948
+ [element],
2920
2949
  )
2950
+ if self.error_log:
2951
+ raise loggers.ElectionError(self.error_log)
2952
+ else:
2953
+ off_per_id = element.find("OfficeHolderPersonIds")
2954
+ if element_has_text(off_per_id):
2955
+ term = element.find("Term")
2956
+ if term is None:
2957
+ raise loggers.ElectionWarning.from_message(
2958
+ "The Office is missing a Term.", [element]
2959
+ )
2921
2960
 
2922
- self.gather_dates(term)
2923
- if self.start_date is None:
2924
- raise loggers.ElectionWarning.from_message(
2925
- "The Office is missing a Term > StartDate.", [element]
2926
- )
2927
- elif self.end_date is not None:
2928
- self.check_end_after_start()
2961
+ self.gather_dates(term)
2962
+ if self.start_date is None:
2963
+ raise loggers.ElectionWarning.from_message(
2964
+ "The Office is missing a Term > StartDate.", [element]
2965
+ )
2966
+ elif self.end_date is not None:
2967
+ self.check_end_after_start()
2929
2968
 
2930
- if self.error_log:
2931
- raise loggers.ElectionError(self.error_log)
2969
+ if self.error_log:
2970
+ raise loggers.ElectionError(self.error_log)
2932
2971
 
2933
2972
 
2934
2973
  class RemovePersonAndOfficeHolderId60DaysAfterEndDate(base.TreeRule):
@@ -4451,6 +4490,118 @@ class UnsupportedOfficeHolderTenureSchema(base.BaseRule):
4451
4490
  )
4452
4491
 
4453
4492
 
4493
+ class ElectoralCommissionCollectionExists(base.BaseRule):
4494
+ """ElectoralCommissionCollection should exist."""
4495
+
4496
+ def elements(self):
4497
+ return ["ElectionReport"]
4498
+
4499
+ def check(self, element):
4500
+ if element.find("ElectoralCommissionCollection") is None:
4501
+ raise loggers.ElectionError.from_message(
4502
+ "ElectoralCommissionCollection should exist."
4503
+ )
4504
+
4505
+
4506
+ class VoterInformationCollectionExists(base.BaseRule):
4507
+ """Warn if there is no VoterInformationCollection."""
4508
+
4509
+ def elements(self):
4510
+ return ["ElectionReport"]
4511
+
4512
+ def check(self, element):
4513
+ if element.find("VoterInformationCollection") is None:
4514
+ raise loggers.ElectionWarning.from_message(
4515
+ "VoterInformationCollection should exist."
4516
+ )
4517
+
4518
+
4519
+ class NoExtraElectionElements(base.BaseRule):
4520
+ """Elections for voter information feeds should not have certain elements.
4521
+
4522
+ BallotStyleCollection, CandidateCollection, ContestCollection, CountStatus
4523
+ should all be excluded.
4524
+ """
4525
+
4526
+ def elements(self):
4527
+ return ["Election"]
4528
+
4529
+ def check(self, element):
4530
+ if element.find("BallotStyleCollection") is not None:
4531
+ raise loggers.ElectionError.from_message(
4532
+ "BallotStyleCollection should not exist."
4533
+ )
4534
+
4535
+ if element.find("CandidateCollection") is not None:
4536
+ raise loggers.ElectionError.from_message(
4537
+ "CandidateCollection should not exist."
4538
+ )
4539
+ if element.find("ContestCollection") is not None:
4540
+ raise loggers.ElectionError.from_message(
4541
+ "ContestCollection should not exist."
4542
+ )
4543
+
4544
+ if element.find("CountStatus") is not None:
4545
+ raise loggers.ElectionError.from_message("CountStatus should not exist.")
4546
+
4547
+
4548
+ class WarnOnElementsNotRecommendedForElection(base.BaseRule):
4549
+ """Warn on ContactInformation on an Election for voter information feeds."""
4550
+
4551
+ def elements(self):
4552
+ return ["Election"]
4553
+
4554
+ def check(self, element):
4555
+ if element.find("ContactInformation") is not None:
4556
+ raise loggers.ElectionWarning.from_message(
4557
+ "ContactInformation is not recommended for Election, prefer using an"
4558
+ " ElectionAdministration."
4559
+ )
4560
+
4561
+
4562
+ class NoExtraElectionReportCollections(base.BaseRule):
4563
+ """ElectionReports for voter information feeds should not have certain elements.
4564
+
4565
+ CommitteeCollection, GovernmentBodyCollection, OfficeCollection,
4566
+ OfficeHolderTenureCollection, PartyCollection, PersonCollection should all be
4567
+ excluded.
4568
+ """
4569
+
4570
+ def elements(self):
4571
+ return ["ElectionReport"]
4572
+
4573
+ def check(self, element):
4574
+ if element.find("CommitteeCollection") is not None:
4575
+ raise loggers.ElectionError.from_message(
4576
+ "CommitteeCollection should not exist."
4577
+ )
4578
+
4579
+ if element.find("GovernmentBodyCollection") is not None:
4580
+ raise loggers.ElectionError.from_message(
4581
+ "GovernmentBodyCollection should not exist."
4582
+ )
4583
+
4584
+ if element.find("OfficeCollection") is not None:
4585
+ raise loggers.ElectionError.from_message(
4586
+ "OfficeCollection should not exist."
4587
+ )
4588
+
4589
+ if element.find("OfficeHolderTenureCollection") is not None:
4590
+ raise loggers.ElectionError.from_message(
4591
+ "OfficeHolderTenureCollection should not exist."
4592
+ )
4593
+
4594
+ if element.find("PartyCollection") is not None:
4595
+ raise loggers.ElectionError.from_message(
4596
+ "PartyCollection should not exist."
4597
+ )
4598
+
4599
+ if element.find("PersonCollection") is not None:
4600
+ raise loggers.ElectionError.from_message(
4601
+ "PersonCollection should not exist."
4602
+ )
4603
+
4604
+
4454
4605
  class RuleSet(enum.Enum):
4455
4606
  """Names for sets of rules used to validate a particular feed type."""
4456
4607
 
@@ -4460,6 +4611,7 @@ class RuleSet(enum.Enum):
4460
4611
  ELECTION_DATES = 4
4461
4612
  ELECTION_RESULTS = 5
4462
4613
  METADATA = 6
4614
+ VOTER_INFORMATION = 7
4463
4615
 
4464
4616
 
4465
4617
  # To add new rules, create a new class, inherit the base rule,
@@ -4578,6 +4730,7 @@ ELECTION_RESULTS_RULES = ELECTION_RULES + (
4578
4730
  OFFICEHOLDER_RULES = COMMON_RULES + (
4579
4731
  # go/keep-sorted start
4580
4732
  DateOfBirthIsInPast,
4733
+ OfficeHolderTenureTermDates,
4581
4734
  OfficeSelectionMethodMatch,
4582
4735
  OfficeTermDates,
4583
4736
  PersonHasOffice,
@@ -4620,6 +4773,16 @@ METADATA_RULES = (
4620
4773
  # go/keep-sorted end
4621
4774
  )
4622
4775
 
4776
+ VOTER_INFORMATION_RULES = COMMON_RULES + (
4777
+ # go/keep-sorted start
4778
+ ElectoralCommissionCollectionExists,
4779
+ NoExtraElectionElements,
4780
+ NoExtraElectionReportCollections,
4781
+ VoterInformationCollectionExists,
4782
+ WarnOnElementsNotRecommendedForElection,
4783
+ # go/keep-sorted end
4784
+ )
4785
+
4623
4786
  ALL_RULES = frozenset(
4624
4787
  COMMON_RULES
4625
4788
  + ELECTION_RULES
@@ -4627,5 +4790,6 @@ ALL_RULES = frozenset(
4627
4790
  + OFFICEHOLDER_RULES
4628
4791
  + COMMITTEE_RULES
4629
4792
  + ELECTION_DATES_RULES
4793
+ + VOTER_INFORMATION_RULES
4630
4794
  + METADATA_RULES
4631
4795
  )
@@ -260,6 +260,8 @@ def filter_all_rules_using_user_arg(rules_allowlist, rule_set, rules_blocklist):
260
260
  rule_names = [x.__name__ for x in rules.ELECTION_RESULTS_RULES]
261
261
  elif rule_set == rules.RuleSet.METADATA:
262
262
  rule_names = [x.__name__ for x in rules.METADATA_RULES]
263
+ elif rule_set == rules.RuleSet.VOTER_INFORMATION:
264
+ rule_names = [x.__name__ for x in rules.VOTER_INFORMATION_RULES]
263
265
  else:
264
266
  raise AssertionError("Invalid rule_set: " + rule_set)
265
267
  if rules_blocklist:
@@ -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.49.dev9'
8
+ __version__ = '1.49.dev11'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: civics_cdf_validator
3
- Version: 1.49.dev9
3
+ Version: 1.49.dev11
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
@@ -0,0 +1,15 @@
1
+ civics_cdf_validator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ civics_cdf_validator/base.py,sha256=5zntjAdAA-bdwYOOStFgUle9LGX_ccelS--thMhUG9A,16954
3
+ civics_cdf_validator/gpunit_rules.py,sha256=Cg-qlOeVKTCQ5qUKYSxbVrrh3tdLt1X5Eg-7uyyR_SY,9487
4
+ civics_cdf_validator/loggers.py,sha256=LS6U9YWzu72V2Q0PwyE9xE03Ul392UAPRnrp2uRBMLA,6923
5
+ civics_cdf_validator/office_utils.py,sha256=aFxcFQZF2oBn0gPXt_kv1LxHGvwzABScT8X5yaYtVLw,2705
6
+ civics_cdf_validator/rules.py,sha256=Hn7qk3qLhidhQrb15iq1ZwN4XLJ4HVhY4VHBbNGY9u4,158898
7
+ civics_cdf_validator/stats.py,sha256=YNqv3Khkt_hJxCIunFBRSl23oXqu5gNjCmWMHFbAcSU,3819
8
+ civics_cdf_validator/validator.py,sha256=2nwlFfQ9AmpTaO5n5ekaO845Rm0JPjNF-Il6FJW_50k,12678
9
+ civics_cdf_validator/version.py,sha256=5WTRUaJiARj_r2UPhcMKa9SSvt8tRFJt6pCR0zFyGpc,189
10
+ civics_cdf_validator-1.49.dev11.dist-info/licenses/LICENSE-2.0.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
11
+ civics_cdf_validator-1.49.dev11.dist-info/METADATA,sha256=9odJ6J0QGbWLOgkjRweufw1myU5Tg_CX3ts5z_zLbCI,1009
12
+ civics_cdf_validator-1.49.dev11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ civics_cdf_validator-1.49.dev11.dist-info/entry_points.txt,sha256=QibgvtMOocwDi7fQ1emgMJ2lIlI41sW0__KIQdnVn90,77
14
+ civics_cdf_validator-1.49.dev11.dist-info/top_level.txt,sha256=PYT5Eclxbop5o6DJIcgs4IeU6Ds5jqyftjoFbgkkJYo,21
15
+ civics_cdf_validator-1.49.dev11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,15 +0,0 @@
1
- civics_cdf_validator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- civics_cdf_validator/base.py,sha256=5zntjAdAA-bdwYOOStFgUle9LGX_ccelS--thMhUG9A,16954
3
- civics_cdf_validator/gpunit_rules.py,sha256=Cg-qlOeVKTCQ5qUKYSxbVrrh3tdLt1X5Eg-7uyyR_SY,9487
4
- civics_cdf_validator/loggers.py,sha256=LS6U9YWzu72V2Q0PwyE9xE03Ul392UAPRnrp2uRBMLA,6923
5
- civics_cdf_validator/office_utils.py,sha256=aFxcFQZF2oBn0gPXt_kv1LxHGvwzABScT8X5yaYtVLw,2705
6
- civics_cdf_validator/rules.py,sha256=2Kjtp2sTltj71w_pQa9tAAvdwpxQJYa16NuHL4_wD4Y,153532
7
- civics_cdf_validator/stats.py,sha256=YNqv3Khkt_hJxCIunFBRSl23oXqu5gNjCmWMHFbAcSU,3819
8
- civics_cdf_validator/validator.py,sha256=SXPsYLu0G_JT2B03i24-yJFh41YzaSYecan4LQ_Xqm4,12553
9
- civics_cdf_validator/version.py,sha256=XOa4xnNoLYpXsmOeZNQZtWSb-PQRJvLkfm36T1fBwBI,188
10
- civics_cdf_validator-1.49.dev9.dist-info/licenses/LICENSE-2.0.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
11
- civics_cdf_validator-1.49.dev9.dist-info/METADATA,sha256=3J9MrvSscqntXR4MFxPwzqHYDOU4TqLftz9_F_mgZrM,1008
12
- civics_cdf_validator-1.49.dev9.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
13
- civics_cdf_validator-1.49.dev9.dist-info/entry_points.txt,sha256=QibgvtMOocwDi7fQ1emgMJ2lIlI41sW0__KIQdnVn90,77
14
- civics_cdf_validator-1.49.dev9.dist-info/top_level.txt,sha256=PYT5Eclxbop5o6DJIcgs4IeU6Ds5jqyftjoFbgkkJYo,21
15
- civics_cdf_validator-1.49.dev9.dist-info/RECORD,,