-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create AgeRange class for age ranges #2714
- Loading branch information
Showing
4 changed files
with
120 additions
and
10 deletions.
There are no files selected for viewing
41 changes: 41 additions & 0 deletions
41
data-serving/reusable-data-service/data_service/model/age_range.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import dataclasses | ||
|
||
from data_service.model.document import Document | ||
from data_service.util.errors import ValidationError | ||
|
||
@dataclasses.dataclass | ||
class AgeRange(Document): | ||
"""I represent a numerical range within which a person's age lies (inclusive of both limits). | ||
To avoid reidentifying people who have been anonymised by this | ||
application, I will only tell you their age to within five years (unless they are infants).""" | ||
lower: int = None | ||
upper: int = None | ||
|
||
def __post_init__(self): | ||
"""Massage the supplied lower and upper bounds to fit our requirements. That doesn't | ||
preclude somebody changing the values after initialisation so please do remember to | ||
validate() me.""" | ||
if self.lower is not None and self.lower != 0: | ||
self.lower = (self.lower // 5) * 5 + 1 | ||
if self.upper is not None and self.upper != 1 and self.upper % 5 != 0: | ||
self.upper = ((self.upper // 5) + 1) * 5 | ||
|
||
def validate(self): | ||
"""I must represent the range [0,1], or a range greater than five years, and must | ||
have a positive lower bound and an upper bound below 121.""" | ||
if self.lower is None: | ||
raise ValidationError("Age Range must have a lower bound") | ||
if self.upper is None: | ||
raise ValidationError("Age Range must have an upper bound") | ||
if self.lower < 0: | ||
raise ValidationError(f"Lower bound {self.lower} is below the minimum permissible 0") | ||
if self.upper < 1: | ||
raise ValidationError(f"Upper bound {self.upper} is below the minimum permissible 1") | ||
if self.upper > 120: | ||
raise ValidationError(f"Upper bound {self.upper} is above the maximum permissible 120") | ||
# deal with the special case first | ||
if self.lower == 0 and self.upper == 1: | ||
return | ||
if self.upper - self.lower < 4: | ||
# remember range is inclusive of bounds so e.g. 1-5 is five years | ||
raise ValidationError(f"Range [{self.lower}, {self.upper}] is too small") |
68 changes: 68 additions & 0 deletions
68
data-serving/reusable-data-service/tests/test_age_range.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import pytest | ||
|
||
from data_service.model.age_range import AgeRange | ||
from data_service.util.errors import ValidationError | ||
|
||
from tests.util import does_not_raise | ||
|
||
|
||
def test_age_range_massages_input_to_match_buckets(): | ||
ages = AgeRange(2,6) | ||
assert ages.lower == 1 | ||
assert ages.upper == 10 | ||
|
||
|
||
def test_age_range_leaves_input_if_it_is_already_on_bucket_boundary(): | ||
ages = AgeRange(6, 10) | ||
assert ages.lower == 6 | ||
assert ages.upper == 10 | ||
|
||
|
||
def test_age_range_invalid_if_boundaries_are_None(): | ||
ages = AgeRange(None, None) | ||
with pytest.raises(ValidationError): | ||
ages.validate() | ||
ages = AgeRange(None, 12) | ||
with pytest.raises(ValidationError): | ||
ages.validate() | ||
ages = AgeRange(5, None) | ||
with pytest.raises(ValidationError): | ||
ages.validate() | ||
|
||
|
||
def test_age_range_invalid_if_lower_bound_negative(): | ||
ages = AgeRange(-12, 4) | ||
with pytest.raises(ValidationError): | ||
ages.validate() | ||
|
||
|
||
def test_age_range_invalid_if_upper_bound_negative(): | ||
ages = AgeRange(5, -27) | ||
with pytest.raises(ValidationError): | ||
ages.validate() | ||
|
||
|
||
def test_age_range_invalid_if_upper_bound_methuselan(): | ||
ages = AgeRange(15, 150) | ||
with pytest.raises(ValidationError): | ||
ages.validate() | ||
|
||
|
||
def test_age_range_invalid_if_gap_too_small(): | ||
ages = AgeRange(None, None) | ||
ages.lower = 10 | ||
ages.upper = 11 | ||
with pytest.raises(ValidationError): | ||
ages.validate() | ||
|
||
|
||
def test_age_range_ok_for_infants(): | ||
ages = AgeRange(0, 1) | ||
with does_not_raise(ValidationError): | ||
ages.validate() | ||
|
||
|
||
def test_age_range_ok_for_large_positive_range(): | ||
ages = AgeRange(30, 45) | ||
with does_not_raise(ValidationError): | ||
ages.validate() |
11 changes: 1 addition & 10 deletions
11
data-serving/reusable-data-service/tests/test_geojson_model.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import pytest | ||
|
||
from contextlib import contextmanager | ||
|
||
@contextmanager | ||
def does_not_raise(exception): | ||
try: | ||
yield | ||
except exception: | ||
raise pytest.fail(f"Exception {exception} expected not to be raised") |