civics-cdf-validator 1.49.dev8__tar.gz → 1.49.dev10__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.49.dev8/civics_cdf_validator.egg-info → civics_cdf_validator-1.49.dev10}/PKG-INFO +1 -1
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/base.py +9 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10/civics_cdf_validator.egg-info}/PKG-INFO +1 -1
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/rules.py +70 -39
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/version.py +1 -1
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/CONTRIBUTING.md +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/LICENSE-2.0.txt +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/MANIFEST.in +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/README.md +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/__init__.py +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/civics_cdf_validator.egg-info/SOURCES.txt +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/civics_cdf_validator.egg-info/dependency_links.txt +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/civics_cdf_validator.egg-info/entry_points.txt +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/civics_cdf_validator.egg-info/requires.txt +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/civics_cdf_validator.egg-info/top_level.txt +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/gpunit_rules.py +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/loggers.py +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/office_utils.py +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/setup.cfg +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/setup.py +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/stats.py +0 -0
- {civics_cdf_validator-1.49.dev8 → civics_cdf_validator-1.49.dev10}/validator.py +0 -0
|
@@ -17,7 +17,9 @@ limitations under the License.
|
|
|
17
17
|
from __future__ import print_function
|
|
18
18
|
|
|
19
19
|
import datetime
|
|
20
|
+
import functools
|
|
20
21
|
import re
|
|
22
|
+
|
|
21
23
|
from civics_cdf_validator import loggers
|
|
22
24
|
from civics_cdf_validator import stats
|
|
23
25
|
from lxml import etree
|
|
@@ -235,6 +237,7 @@ class DateRule(BaseRule):
|
|
|
235
237
|
self.error_log.append(loggers.LogEntry(error_message, [self.end_elem]))
|
|
236
238
|
|
|
237
239
|
|
|
240
|
+
@functools.total_ordering
|
|
238
241
|
class PartialDate():
|
|
239
242
|
"""Check for PartialDate."""
|
|
240
243
|
|
|
@@ -257,6 +260,12 @@ class PartialDate():
|
|
|
257
260
|
else:
|
|
258
261
|
return "Not defined"
|
|
259
262
|
|
|
263
|
+
def __lt__(self, other):
|
|
264
|
+
return self.is_older_than(other) > 0
|
|
265
|
+
|
|
266
|
+
def __eq__(self, other):
|
|
267
|
+
return self.is_older_than(other) == 0
|
|
268
|
+
|
|
260
269
|
@classmethod
|
|
261
270
|
def init_partial_date(cls, date_string):
|
|
262
271
|
"""Initializing partial date."""
|
|
@@ -2683,7 +2683,7 @@ class ElectionDatesSpanContestDates(base.DateRule):
|
|
|
2683
2683
|
if (
|
|
2684
2684
|
election_end_date is not None
|
|
2685
2685
|
and self.end_date is not None
|
|
2686
|
-
and election_end_date
|
|
2686
|
+
and election_end_date < self.end_date
|
|
2687
2687
|
# Only compare election end date to contests that are not canceled
|
|
2688
2688
|
and (
|
|
2689
2689
|
contest_date_status is None
|
|
@@ -2705,7 +2705,7 @@ class ElectionDatesSpanContestDates(base.DateRule):
|
|
|
2705
2705
|
if (
|
|
2706
2706
|
election_start_date is not None
|
|
2707
2707
|
and self.start_date is not None
|
|
2708
|
-
and self.start_date
|
|
2708
|
+
and self.start_date < election_start_date
|
|
2709
2709
|
):
|
|
2710
2710
|
self.error_log.append(
|
|
2711
2711
|
loggers.LogEntry(
|
|
@@ -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
|
-
|
|
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.
|
|
2919
|
-
"
|
|
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
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
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
|
-
|
|
2931
|
-
|
|
2969
|
+
if self.error_log:
|
|
2970
|
+
raise loggers.ElectionError(self.error_log)
|
|
2932
2971
|
|
|
2933
2972
|
|
|
2934
2973
|
class RemovePersonAndOfficeHolderId60DaysAfterEndDate(base.TreeRule):
|
|
@@ -2955,7 +2994,6 @@ class RemovePersonAndOfficeHolderId60DaysAfterEndDate(base.TreeRule):
|
|
|
2955
2994
|
term = office.find(".//Term")
|
|
2956
2995
|
if term is not None:
|
|
2957
2996
|
date_validator = base.DateRule(None, None)
|
|
2958
|
-
limit_check = 0
|
|
2959
2997
|
date_validator.gather_dates(term)
|
|
2960
2998
|
end_date_person = date_validator.end_date
|
|
2961
2999
|
if end_date_person is not None:
|
|
@@ -2967,8 +3005,7 @@ class RemovePersonAndOfficeHolderId60DaysAfterEndDate(base.TreeRule):
|
|
|
2967
3005
|
sixty_days_earlier.month,
|
|
2968
3006
|
sixty_days_earlier.day,
|
|
2969
3007
|
)
|
|
2970
|
-
|
|
2971
|
-
if limit_check < 0:
|
|
3008
|
+
if end_date_person < partial_date_sixty_days:
|
|
2972
3009
|
outdated_offices.append(office.get("objectId"))
|
|
2973
3010
|
for person in persons:
|
|
2974
3011
|
pid = person.get("objectId")
|
|
@@ -3518,11 +3555,11 @@ class SubsequentContestIdIsValidRelatedContest(base.DateRule):
|
|
|
3518
3555
|
subsequent_contest = contest_by_id[subsequent_contest_id]
|
|
3519
3556
|
# Check that the subsequent contest has a later end date
|
|
3520
3557
|
if subsequent_contest is not None and contest is not None:
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
if
|
|
3558
|
+
contest_end_date = contest_end_date_by_id[contest_id]
|
|
3559
|
+
subsequent_contest_end_date = contest_end_date_by_id[
|
|
3560
|
+
subsequent_contest_id
|
|
3561
|
+
]
|
|
3562
|
+
if subsequent_contest_end_date < contest_end_date:
|
|
3526
3563
|
error_log.append(
|
|
3527
3564
|
loggers.LogEntry(
|
|
3528
3565
|
f"Contest {contest_id} references a subsequent contest with"
|
|
@@ -3839,8 +3876,7 @@ class ContestEndDateOccursBeforeSubsequentContestStartDate(base.DateRule):
|
|
|
3839
3876
|
_, contest_end = dates_by_contest_id[contest_id]
|
|
3840
3877
|
subsequent_contest_start, _ = dates_by_contest_id[subsequent_contest_id]
|
|
3841
3878
|
if contest_end is not None and subsequent_contest_start is not None:
|
|
3842
|
-
|
|
3843
|
-
if date_delta < 0:
|
|
3879
|
+
if subsequent_contest_start < contest_end:
|
|
3844
3880
|
self.error_log.append(
|
|
3845
3881
|
loggers.LogEntry(
|
|
3846
3882
|
"Contest {} with end date {} does not occur before"
|
|
@@ -4092,8 +4128,7 @@ class ElectionEventDatesAreSequential(base.DateRule):
|
|
|
4092
4128
|
full_delivery_date = base.PartialDate.init_partial_date(
|
|
4093
4129
|
element.find("FullDeliveryDate").text
|
|
4094
4130
|
)
|
|
4095
|
-
|
|
4096
|
-
if date_delta > 0:
|
|
4131
|
+
if self.start_date < full_delivery_date:
|
|
4097
4132
|
self.error_log.append(
|
|
4098
4133
|
loggers.LogEntry(
|
|
4099
4134
|
"StartDate is older than FullDeliveryDate",
|
|
@@ -4109,8 +4144,7 @@ class ElectionEventDatesAreSequential(base.DateRule):
|
|
|
4109
4144
|
full_delivery_date = base.PartialDate.init_partial_date(
|
|
4110
4145
|
element.find("FullDeliveryDate").text
|
|
4111
4146
|
)
|
|
4112
|
-
|
|
4113
|
-
if date_delta > 0:
|
|
4147
|
+
if full_delivery_date < initial_delivery_date:
|
|
4114
4148
|
self.error_log.append(
|
|
4115
4149
|
loggers.LogEntry(
|
|
4116
4150
|
"FullDeliveryDate is older than InitialDeliveryDate",
|
|
@@ -4143,10 +4177,7 @@ class NoSourceDirPathBeforeInitialDeliveryDate(base.BaseRule):
|
|
|
4143
4177
|
if element_has_text(initial_delivery)
|
|
4144
4178
|
else None
|
|
4145
4179
|
)
|
|
4146
|
-
if
|
|
4147
|
-
initial_delivery_date
|
|
4148
|
-
and initial_delivery_date.is_older_than(today_partial_date) > 0
|
|
4149
|
-
):
|
|
4180
|
+
if initial_delivery_date and initial_delivery_date < today_partial_date:
|
|
4150
4181
|
return
|
|
4151
4182
|
raise loggers.ElectionWarning.from_message(
|
|
4152
4183
|
"SourceDirPath is defined but all initialDeliveryDate are in the"
|
|
@@ -4171,8 +4202,7 @@ class OfficeHolderSubFeedDatesAreSequential(base.DateRule):
|
|
|
4171
4202
|
full_delivery_date = base.PartialDate.init_partial_date(
|
|
4172
4203
|
element.find("FullDeliveryDate").text
|
|
4173
4204
|
)
|
|
4174
|
-
|
|
4175
|
-
if date_delta > 0:
|
|
4205
|
+
if full_delivery_date < initial_delivery_date:
|
|
4176
4206
|
raise loggers.ElectionError.from_message(
|
|
4177
4207
|
"FullDeliveryDate is older than InitialDeliveryDate",
|
|
4178
4208
|
[element],
|
|
@@ -4194,14 +4224,14 @@ class FeedInactiveDateIsLatestDate(base.BaseRule):
|
|
|
4194
4224
|
full_delivery_date = base.PartialDate.init_partial_date(
|
|
4195
4225
|
full_delivery_date_element.text
|
|
4196
4226
|
)
|
|
4197
|
-
if feed_inactive_date
|
|
4227
|
+
if feed_inactive_date < full_delivery_date:
|
|
4198
4228
|
raise loggers.ElectionError.from_message(
|
|
4199
4229
|
"FeedInactiveDate is older than FullDeliveryDate",
|
|
4200
4230
|
[element],
|
|
4201
4231
|
)
|
|
4202
4232
|
for end_date_element in element.iter("EndDate"):
|
|
4203
4233
|
end_date = base.PartialDate.init_partial_date(end_date_element.text)
|
|
4204
|
-
if feed_inactive_date
|
|
4234
|
+
if feed_inactive_date < end_date:
|
|
4205
4235
|
raise loggers.ElectionError.from_message(
|
|
4206
4236
|
"FeedInactiveDate is older than EndDate",
|
|
4207
4237
|
[element],
|
|
@@ -4587,6 +4617,7 @@ ELECTION_RESULTS_RULES = ELECTION_RULES + (
|
|
|
4587
4617
|
OFFICEHOLDER_RULES = COMMON_RULES + (
|
|
4588
4618
|
# go/keep-sorted start
|
|
4589
4619
|
DateOfBirthIsInPast,
|
|
4620
|
+
OfficeHolderTenureTermDates,
|
|
4590
4621
|
OfficeSelectionMethodMatch,
|
|
4591
4622
|
OfficeTermDates,
|
|
4592
4623
|
PersonHasOffice,
|
|
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
|