civics-cdf-validator 1.49.dev13__py3-none-any.whl → 1.49.dev15__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.
@@ -2365,17 +2365,24 @@ class OfficesHaveJurisdictionID(base.BaseRule):
2365
2365
  return ["Office"]
2366
2366
 
2367
2367
  def check(self, element):
2368
- jurisdiction_values = get_entity_info_for_value_type(
2369
- element, "jurisdiction-id"
2370
- )
2368
+ jurisdiction_values = []
2369
+ post_office_split_jurisdiction_element = element.find("JurisdictionId")
2370
+ if element_has_text(post_office_split_jurisdiction_element):
2371
+ jurisdiction_values.append(post_office_split_jurisdiction_element.text)
2372
+ else:
2373
+ jurisdiction_values = get_entity_info_for_value_type(
2374
+ element, "jurisdiction-id"
2375
+ )
2376
+
2371
2377
  jurisdiction_values = [j_id for j_id in jurisdiction_values if j_id.strip()]
2372
2378
  if not jurisdiction_values:
2373
2379
  raise loggers.ElectionError.from_message(
2374
- "Office is missing a jurisdiction-id.", [element]
2380
+ "Office is missing a jurisdiction ID.", [element]
2375
2381
  )
2376
2382
  if len(jurisdiction_values) > 1:
2377
2383
  raise loggers.ElectionError.from_message(
2378
- "Office has more than one jurisdiction-id.", [element]
2384
+ "Office has more than one jurisdiction ID.",
2385
+ [element],
2379
2386
  )
2380
2387
 
2381
2388
 
@@ -2431,11 +2438,17 @@ class OfficesHaveValidOfficeLevel(base.BaseRule):
2431
2438
  return ["Office"]
2432
2439
 
2433
2440
  def check(self, element):
2434
- office_levels = [
2435
- ol_id.strip()
2436
- for ol_id in get_external_id_values(element, "office-level")
2437
- if ol_id.strip()
2438
- ]
2441
+ office_levels = []
2442
+ post_office_split_office_level_element = element.find("Level")
2443
+ if element_has_text(post_office_split_office_level_element):
2444
+ office_levels.append(post_office_split_office_level_element.text)
2445
+ else:
2446
+ office_levels = [
2447
+ ol_id.strip()
2448
+ for ol_id in get_external_id_values(element, "office-level")
2449
+ if ol_id.strip()
2450
+ ]
2451
+
2439
2452
  if not office_levels:
2440
2453
  raise loggers.ElectionError.from_message(
2441
2454
  "Office is missing an office level.",
@@ -2461,10 +2474,13 @@ class OfficesHaveValidOfficeRole(base.BaseRule):
2461
2474
  return ["Office"]
2462
2475
 
2463
2476
  def check(self, element):
2464
- office_roles = [
2465
- office_role.strip()
2466
- for office_role in get_external_id_values(element, "office-role")
2467
- ]
2477
+ office_roles = [role.text for role in element.findall("Role")]
2478
+ if not office_roles:
2479
+ office_roles = [
2480
+ office_role.strip()
2481
+ for office_role in get_external_id_values(element, "office-role")
2482
+ ]
2483
+
2468
2484
  if not office_roles:
2469
2485
  raise loggers.ElectionError.from_message(
2470
2486
  "Office is missing an office role.",
@@ -2989,20 +3005,25 @@ class RemovePersonAndOfficeHolderId60DaysAfterEndDate(base.TreeRule):
2989
3005
  info_log = []
2990
3006
  persons = self.get_elements_by_class(self.election_tree, "Person")
2991
3007
  offices = self.get_elements_by_class(self.election_tree, "Office")
3008
+ officeholder_tenure_collection = self.get_elements_by_class(
3009
+ self.election_tree, "OfficeHolderTenureCollection"
3010
+ )
3011
+ is_post_office_split_feed = bool(officeholder_tenure_collection)
2992
3012
  person_office_dict = dict()
2993
3013
  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:
3014
+ outdated_officeholder_tenures = []
3015
+ outdated_officetenure_persons = dict()
3016
+ if is_post_office_split_feed:
3017
+ officeholder_tenures = self.get_elements_by_class(
3018
+ self.election_tree, "OfficeHolderTenure"
3019
+ )
3020
+ for officeholder_tenure in officeholder_tenures:
3021
+ person_id = officeholder_tenure.find("OfficeHolderPersonId").text
3022
+ object_id = officeholder_tenure.get("objectId")
3002
3023
  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:
3024
+ date_validator.gather_dates(officeholder_tenure)
3025
+ end_date = date_validator.end_date
3026
+ if end_date is not None:
3006
3027
  sixty_days_earlier = datetime.datetime.now() + datetime.timedelta(
3007
3028
  days=-60
3008
3029
  )
@@ -3011,21 +3032,80 @@ class RemovePersonAndOfficeHolderId60DaysAfterEndDate(base.TreeRule):
3011
3032
  sixty_days_earlier.month,
3012
3033
  sixty_days_earlier.day,
3013
3034
  )
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)
3035
+ if end_date < partial_date_sixty_days:
3036
+ outdated_officeholder_tenures.append(object_id)
3037
+ if person_id in outdated_officetenure_persons.keys():
3038
+ outdated_officetenure_persons[person_id].append(
3039
+ (object_id, end_date)
3040
+ )
3041
+ else:
3042
+ outdated_officetenure_persons[person_id] = [(object_id, end_date)]
3043
+ for outdated_officeholder_tenure in outdated_officeholder_tenures:
3044
+ info_message = (
3045
+ "The officeholder tenure end date is more than 60 days"
3046
+ " in the past; this OfficeHolderTenure element can be removed"
3047
+ " from the feed."
3048
+ )
3049
+ info_log.append(
3050
+ loggers.LogEntry(info_message, [outdated_officeholder_tenure])
3021
3051
  )
3022
- if check_person_outdated:
3052
+ for person_id, value_list in outdated_officetenure_persons.items():
3053
+ has_recent_tenure = False
3054
+ for value in value_list:
3055
+ end_date = value[1]
3056
+ sixty_days_earlier = datetime.datetime.now() + datetime.timedelta(
3057
+ days=-60
3058
+ )
3059
+ partial_date_sixty_days = base.PartialDate(
3060
+ sixty_days_earlier.year,
3061
+ sixty_days_earlier.month,
3062
+ sixty_days_earlier.day,
3063
+ )
3064
+ if end_date > partial_date_sixty_days:
3065
+ has_recent_tenure = True
3066
+ if not has_recent_tenure:
3023
3067
  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."
3068
+ "All officeholder tenures ended more than 60 days ago. "
3069
+ "Therefore, you can remove the person and the related "
3070
+ "officeholder tenures from the feed."
3027
3071
  )
3028
- info_log.append(loggers.LogEntry(info_message, [person]))
3072
+ info_log.append(loggers.LogEntry(info_message, [person_id]))
3073
+ else:
3074
+ for office in offices:
3075
+ person_id = office.find("OfficeHolderPersonIds").text
3076
+ if person_id in person_office_dict:
3077
+ person_office_dict[person_id].append(office.get("objectId"))
3078
+ else:
3079
+ person_office_dict[person_id] = [office.get("objectId")]
3080
+ term = office.find(".//Term")
3081
+ if term is not None:
3082
+ date_validator = base.DateRule(None, None)
3083
+ date_validator.gather_dates(term)
3084
+ end_date_person = date_validator.end_date
3085
+ if end_date_person is not None:
3086
+ sixty_days_earlier = datetime.datetime.now() + datetime.timedelta(
3087
+ days=-60
3088
+ )
3089
+ partial_date_sixty_days = base.PartialDate(
3090
+ sixty_days_earlier.year,
3091
+ sixty_days_earlier.month,
3092
+ sixty_days_earlier.day,
3093
+ )
3094
+ if end_date_person < partial_date_sixty_days:
3095
+ outdated_offices.append(office.get("objectId"))
3096
+ for person in persons:
3097
+ pid = person.get("objectId")
3098
+ if person_office_dict.get(pid) is not None:
3099
+ check_person_outdated = all(
3100
+ item in outdated_offices for item in person_office_dict.get(pid)
3101
+ )
3102
+ if check_person_outdated:
3103
+ info_message = (
3104
+ "The officeholder mandates ended more than 60 days ago. "
3105
+ "Therefore, you can remove the person and the related offices "
3106
+ "from the feed."
3107
+ )
3108
+ info_log.append(loggers.LogEntry(info_message, [person]))
3029
3109
  if info_log:
3030
3110
  raise loggers.ElectionInfo(info_log)
3031
3111
 
@@ -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.dev13'
8
+ __version__ = '1.49.dev15'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: civics_cdf_validator
3
- Version: 1.49.dev13
3
+ Version: 1.49.dev15
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=Ucw1vURByvUoyMVdBUDnzggO15kExbnpsBZU0Wlg-EE,159895
6
+ civics_cdf_validator/rules.py,sha256=MfNt3RnztLEUuf6-Ksd9O484Ihrn5GzE3rlWIF6KzFc,163372
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=VFMdAjvjwr34E1vqDkhcL8mmJWGxF_XUDagoSEfJ11Y,189
10
- civics_cdf_validator-1.49.dev13.dist-info/licenses/LICENSE-2.0.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
11
- civics_cdf_validator-1.49.dev13.dist-info/METADATA,sha256=MBpl8nej6fgkR0c0jy0owtovKInrOYSLDPpp1k8viaw,1009
12
- civics_cdf_validator-1.49.dev13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- civics_cdf_validator-1.49.dev13.dist-info/entry_points.txt,sha256=QibgvtMOocwDi7fQ1emgMJ2lIlI41sW0__KIQdnVn90,77
14
- civics_cdf_validator-1.49.dev13.dist-info/top_level.txt,sha256=PYT5Eclxbop5o6DJIcgs4IeU6Ds5jqyftjoFbgkkJYo,21
15
- civics_cdf_validator-1.49.dev13.dist-info/RECORD,,
9
+ civics_cdf_validator/version.py,sha256=-opmfDTRJeiPpxkg4oi9RUmaqlgTkt2qPKUoiRXDQVc,189
10
+ civics_cdf_validator-1.49.dev15.dist-info/licenses/LICENSE-2.0.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
11
+ civics_cdf_validator-1.49.dev15.dist-info/METADATA,sha256=2FV0eKQ4cTQXIIGfsB8jI45EdmXqxv1pS0KvEkcZ1uA,1009
12
+ civics_cdf_validator-1.49.dev15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ civics_cdf_validator-1.49.dev15.dist-info/entry_points.txt,sha256=QibgvtMOocwDi7fQ1emgMJ2lIlI41sW0__KIQdnVn90,77
14
+ civics_cdf_validator-1.49.dev15.dist-info/top_level.txt,sha256=PYT5Eclxbop5o6DJIcgs4IeU6Ds5jqyftjoFbgkkJYo,21
15
+ civics_cdf_validator-1.49.dev15.dist-info/RECORD,,