civics-cdf-validator 1.49.dev12__py3-none-any.whl → 1.49.dev14__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.
@@ -2989,20 +2989,25 @@ class RemovePersonAndOfficeHolderId60DaysAfterEndDate(base.TreeRule):
2989
2989
  info_log = []
2990
2990
  persons = self.get_elements_by_class(self.election_tree, "Person")
2991
2991
  offices = self.get_elements_by_class(self.election_tree, "Office")
2992
+ officeholder_tenure_collection = self.get_elements_by_class(
2993
+ self.election_tree, "OfficeHolderTenureCollection"
2994
+ )
2995
+ is_post_office_split_feed = bool(officeholder_tenure_collection)
2992
2996
  person_office_dict = dict()
2993
2997
  outdated_offices = []
2994
- for office in offices:
2995
- ohpid = office.find("OfficeHolderPersonIds").text
2996
- if ohpid in person_office_dict:
2997
- person_office_dict[ohpid].append(office.get("objectId"))
2998
- else:
2999
- person_office_dict[ohpid] = [office.get("objectId")]
3000
- term = office.find(".//Term")
3001
- if term is not None:
2998
+ outdated_officeholder_tenures = []
2999
+ outdated_officetenure_persons = dict()
3000
+ if is_post_office_split_feed:
3001
+ officeholder_tenures = self.get_elements_by_class(
3002
+ self.election_tree, "OfficeHolderTenure"
3003
+ )
3004
+ for officeholder_tenure in officeholder_tenures:
3005
+ person_id = officeholder_tenure.find("OfficeHolderPersonId").text
3006
+ object_id = officeholder_tenure.get("objectId")
3002
3007
  date_validator = base.DateRule(None, None)
3003
- date_validator.gather_dates(term)
3004
- end_date_person = date_validator.end_date
3005
- if end_date_person is not None:
3008
+ date_validator.gather_dates(officeholder_tenure)
3009
+ end_date = date_validator.end_date
3010
+ if end_date is not None:
3006
3011
  sixty_days_earlier = datetime.datetime.now() + datetime.timedelta(
3007
3012
  days=-60
3008
3013
  )
@@ -3011,21 +3016,80 @@ class RemovePersonAndOfficeHolderId60DaysAfterEndDate(base.TreeRule):
3011
3016
  sixty_days_earlier.month,
3012
3017
  sixty_days_earlier.day,
3013
3018
  )
3014
- if end_date_person < partial_date_sixty_days:
3015
- outdated_offices.append(office.get("objectId"))
3016
- for person in persons:
3017
- pid = person.get("objectId")
3018
- if person_office_dict.get(pid) is not None:
3019
- check_person_outdated = all(
3020
- item in outdated_offices for item in person_office_dict.get(pid)
3019
+ if end_date < partial_date_sixty_days:
3020
+ outdated_officeholder_tenures.append(object_id)
3021
+ if person_id in outdated_officetenure_persons.keys():
3022
+ outdated_officetenure_persons[person_id].append(
3023
+ (object_id, end_date)
3024
+ )
3025
+ else:
3026
+ outdated_officetenure_persons[person_id] = [(object_id, end_date)]
3027
+ for outdated_officeholder_tenure in outdated_officeholder_tenures:
3028
+ info_message = (
3029
+ "The officeholder tenure end date is more than 60 days"
3030
+ " in the past; this OfficeHolderTenure element can be removed"
3031
+ " from the feed."
3021
3032
  )
3022
- if check_person_outdated:
3033
+ info_log.append(
3034
+ loggers.LogEntry(info_message, [outdated_officeholder_tenure])
3035
+ )
3036
+ for person_id, value_list in outdated_officetenure_persons.items():
3037
+ has_recent_tenure = False
3038
+ for value in value_list:
3039
+ end_date = value[1]
3040
+ sixty_days_earlier = datetime.datetime.now() + datetime.timedelta(
3041
+ days=-60
3042
+ )
3043
+ partial_date_sixty_days = base.PartialDate(
3044
+ sixty_days_earlier.year,
3045
+ sixty_days_earlier.month,
3046
+ sixty_days_earlier.day,
3047
+ )
3048
+ if end_date > partial_date_sixty_days:
3049
+ has_recent_tenure = True
3050
+ if not has_recent_tenure:
3023
3051
  info_message = (
3024
- "The officeholder mandates ended more than 60 days ago. "
3025
- "Therefore, you can remove the person and the related offices "
3026
- "from the feed."
3052
+ "All officeholder tenures ended more than 60 days ago. "
3053
+ "Therefore, you can remove the person and the related "
3054
+ "officeholder tenures from the feed."
3027
3055
  )
3028
- info_log.append(loggers.LogEntry(info_message, [person]))
3056
+ info_log.append(loggers.LogEntry(info_message, [person_id]))
3057
+ else:
3058
+ for office in offices:
3059
+ person_id = office.find("OfficeHolderPersonIds").text
3060
+ if person_id in person_office_dict:
3061
+ person_office_dict[person_id].append(office.get("objectId"))
3062
+ else:
3063
+ person_office_dict[person_id] = [office.get("objectId")]
3064
+ term = office.find(".//Term")
3065
+ if term is not None:
3066
+ date_validator = base.DateRule(None, None)
3067
+ date_validator.gather_dates(term)
3068
+ end_date_person = date_validator.end_date
3069
+ if end_date_person is not None:
3070
+ sixty_days_earlier = datetime.datetime.now() + datetime.timedelta(
3071
+ days=-60
3072
+ )
3073
+ partial_date_sixty_days = base.PartialDate(
3074
+ sixty_days_earlier.year,
3075
+ sixty_days_earlier.month,
3076
+ sixty_days_earlier.day,
3077
+ )
3078
+ if end_date_person < partial_date_sixty_days:
3079
+ outdated_offices.append(office.get("objectId"))
3080
+ for person in persons:
3081
+ pid = person.get("objectId")
3082
+ if person_office_dict.get(pid) is not None:
3083
+ check_person_outdated = all(
3084
+ item in outdated_offices for item in person_office_dict.get(pid)
3085
+ )
3086
+ if check_person_outdated:
3087
+ info_message = (
3088
+ "The officeholder mandates ended more than 60 days ago. "
3089
+ "Therefore, you can remove the person and the related offices "
3090
+ "from the feed."
3091
+ )
3092
+ info_log.append(loggers.LogEntry(info_message, [person]))
3029
3093
  if info_log:
3030
3094
  raise loggers.ElectionInfo(info_log)
3031
3095
 
@@ -4182,34 +4246,32 @@ class ElectionEventDatesAreSequential(base.DateRule):
4182
4246
  raise loggers.ElectionError(self.error_log)
4183
4247
 
4184
4248
 
4185
- class NoSourceDirPathBeforeInitialDeliveryDate(base.BaseRule):
4186
- """SourceDirPath should only be defined if one of the initial delivery dates is in the past."""
4249
+ class SourceDirPathMustBeSetAfterInitialDeliveryDate(base.BaseRule):
4250
+ """SourceDirPath must be set if any InitialDeliveryDate is in the past."""
4187
4251
 
4188
4252
  def elements(self):
4189
4253
  return ["Feed"]
4190
4254
 
4191
4255
  def check(self, element):
4192
- if not element_has_text(element.find("SourceDirPath")):
4256
+ if element_has_text(element.find("SourceDirPath")):
4193
4257
  return
4194
4258
  today = datetime.date.today()
4195
4259
  today_partial_date = base.PartialDate(
4196
4260
  year=today.year, month=today.month, day=today.day
4197
4261
  )
4198
- initial_deliveries = element.findall(".//InitialDeliveryDate")
4199
- if initial_deliveries:
4200
- for initial_delivery in initial_deliveries:
4201
- initial_delivery_date = (
4202
- base.PartialDate.init_partial_date(initial_delivery.text)
4203
- if element_has_text(initial_delivery)
4204
- else None
4205
- )
4206
- if initial_delivery_date and initial_delivery_date < today_partial_date:
4207
- return
4208
- raise loggers.ElectionWarning.from_message(
4209
- "SourceDirPath is defined but all initialDeliveryDate are in the"
4210
- " future.",
4211
- [element],
4262
+ initial_deliveries = element.findall(".//InitialDeliveryDate") or []
4263
+ for initial_delivery in initial_deliveries:
4264
+ initial_delivery_date = (
4265
+ base.PartialDate.init_partial_date(initial_delivery.text)
4266
+ if element_has_text(initial_delivery)
4267
+ else None
4212
4268
  )
4269
+ if initial_delivery_date and initial_delivery_date < today_partial_date:
4270
+ raise loggers.ElectionError.from_message(
4271
+ "SourceDirPath is not set but an InitialDeliveryDate is in the "
4272
+ f"past for feed {element.find('FeedId').text}.",
4273
+ [element],
4274
+ )
4213
4275
 
4214
4276
 
4215
4277
  class OfficeHolderSubFeedDatesAreSequential(base.DateRule):
@@ -4790,10 +4852,10 @@ METADATA_RULES = (
4790
4852
  FeedInactiveDateIsLatestDate,
4791
4853
  FeedInactiveDateSetForNonEvergreenFeed,
4792
4854
  FeedTypeHasValidFeedLongevity,
4793
- NoSourceDirPathBeforeInitialDeliveryDate,
4794
4855
  OfficeHolderSubFeedDatesAreSequential,
4795
4856
  OptionalAndEmpty,
4796
4857
  Schema,
4858
+ SourceDirPathMustBeSetAfterInitialDeliveryDate,
4797
4859
  SourceDirPathsAreUnique,
4798
4860
  UniqueLabel,
4799
4861
  # go/keep-sorted end
@@ -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.dev12'
8
+ __version__ = '1.49.dev14'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: civics_cdf_validator
3
- Version: 1.49.dev12
3
+ Version: 1.49.dev14
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
@@ -3,13 +3,13 @@ civics_cdf_validator/base.py,sha256=5zntjAdAA-bdwYOOStFgUle9LGX_ccelS--thMhUG9A,
3
3
  civics_cdf_validator/gpunit_rules.py,sha256=Cg-qlOeVKTCQ5qUKYSxbVrrh3tdLt1X5Eg-7uyyR_SY,9487
4
4
  civics_cdf_validator/loggers.py,sha256=LS6U9YWzu72V2Q0PwyE9xE03Ul392UAPRnrp2uRBMLA,6923
5
5
  civics_cdf_validator/office_utils.py,sha256=aFxcFQZF2oBn0gPXt_kv1LxHGvwzABScT8X5yaYtVLw,2705
6
- civics_cdf_validator/rules.py,sha256=mQByCHxof6D0Ifw-SpfnlQcIbPLPaO7f6qPSTLIZGj8,159916
6
+ civics_cdf_validator/rules.py,sha256=FZmaGEm52iPFbvDQqvVFc3BZT7SyNOARJ-x7_3qVKwo,162747
7
7
  civics_cdf_validator/stats.py,sha256=YNqv3Khkt_hJxCIunFBRSl23oXqu5gNjCmWMHFbAcSU,3819
8
8
  civics_cdf_validator/validator.py,sha256=2nwlFfQ9AmpTaO5n5ekaO845Rm0JPjNF-Il6FJW_50k,12678
9
- civics_cdf_validator/version.py,sha256=XR8QyYKrxeV1eHqomROga81p7X4JpjgDXULUY7MGvQU,189
10
- civics_cdf_validator-1.49.dev12.dist-info/licenses/LICENSE-2.0.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
11
- civics_cdf_validator-1.49.dev12.dist-info/METADATA,sha256=5cxrxIuKc_9jy4AY3mF2hlOt9br6zBmP57K8S-1xow0,1009
12
- civics_cdf_validator-1.49.dev12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- civics_cdf_validator-1.49.dev12.dist-info/entry_points.txt,sha256=QibgvtMOocwDi7fQ1emgMJ2lIlI41sW0__KIQdnVn90,77
14
- civics_cdf_validator-1.49.dev12.dist-info/top_level.txt,sha256=PYT5Eclxbop5o6DJIcgs4IeU6Ds5jqyftjoFbgkkJYo,21
15
- civics_cdf_validator-1.49.dev12.dist-info/RECORD,,
9
+ civics_cdf_validator/version.py,sha256=3STupAz7hHTsISiw-I-GyuKq9Tt4pHJgyls1yJfSqWM,189
10
+ civics_cdf_validator-1.49.dev14.dist-info/licenses/LICENSE-2.0.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
11
+ civics_cdf_validator-1.49.dev14.dist-info/METADATA,sha256=GsP5SO8H7qeVl2huG8vo976TUBpIWNu8Jh8JtXJioY8,1009
12
+ civics_cdf_validator-1.49.dev14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ civics_cdf_validator-1.49.dev14.dist-info/entry_points.txt,sha256=QibgvtMOocwDi7fQ1emgMJ2lIlI41sW0__KIQdnVn90,77
14
+ civics_cdf_validator-1.49.dev14.dist-info/top_level.txt,sha256=PYT5Eclxbop5o6DJIcgs4IeU6Ds5jqyftjoFbgkkJYo,21
15
+ civics_cdf_validator-1.49.dev14.dist-info/RECORD,,