diff --git a/archeryutils/classifications/agb_old_indoor_classifications.py b/archeryutils/classifications/agb_old_indoor_classifications.py index 26caf27..d31dcbf 100644 --- a/archeryutils/classifications/agb_old_indoor_classifications.py +++ b/archeryutils/classifications/agb_old_indoor_classifications.py @@ -111,28 +111,23 @@ def calculate_agb_old_indoor_classification( ArcheryGB 2023 Rules of Shooting ArcheryGB Shooting Administrative Procedures - SAP7 (2023) """ - # deal with reduced categories: - age_group = "Adult" - if bowstyle.lower() not in ("compound"): - bowstyle = "Recurve" - - groupname = cls_funcs.get_groupname(bowstyle, gender, age_group) - group_data = agb_old_indoor_classifications[groupname] - - hc_params = hc_eq.HcParams() + # Check score is valid + if score < 0 or score > ALL_INDOOR_ROUNDS[roundname].max_score(): + raise ValueError( + f"Invalid score of {score} for a {roundname}. " + f"Should be in range 0-{ALL_INDOOR_ROUNDS[roundname].max_score()}." + ) # Get scores required on this round for each classification - class_scores = [ - hc_eq.score_for_round( - ALL_INDOOR_ROUNDS[roundname], - group_data["class_HC"][i], - "AGBold", - hc_params, - round_score_up=True, - )[0] - for i, class_i in enumerate(group_data["classes"]) - ] + class_scores = agb_old_indoor_classification_scores( + roundname, + bowstyle, + gender, + age_group, + ) + groupname = cls_funcs.get_groupname(bowstyle, gender, age_group) + group_data = agb_old_indoor_classifications[groupname] class_data: Dict[str, Any] = dict(zip(group_data["classes"], class_scores)) # What is the highest classification this score gets? @@ -150,8 +145,7 @@ def calculate_agb_old_indoor_classification( classification_from_score = list(class_data.keys())[0] return classification_from_score except IndexError: - # return "UC" - return "unclassified" + return "UC" def agb_old_indoor_classification_scores( @@ -187,6 +181,10 @@ def agb_old_indoor_classification_scores( ArcheryGB Rules of Shooting ArcheryGB Shooting Administrative Procedures - SAP7 """ + # enforce compound scoring + if bowstyle.lower() in ("compound"): + roundname = cls_funcs.get_compound_codename(roundname) + # deal with reduced categories: age_group = "Adult" if bowstyle.lower() not in ("compound"): diff --git a/archeryutils/classifications/tests/test_agb_old_indoor.py b/archeryutils/classifications/tests/test_agb_old_indoor.py new file mode 100644 index 0000000..e1fcc78 --- /dev/null +++ b/archeryutils/classifications/tests/test_agb_old_indoor.py @@ -0,0 +1,289 @@ +"""Tests for old agb indoor classification functions""" +from typing import List +import pytest + +from archeryutils import load_rounds +import archeryutils.classifications as class_funcs + + +ALL_INDOOR_ROUNDS = load_rounds.read_json_to_round_dict( + [ + "WA_indoor.json", + "AGB_indoor.json", + ] +) + + +class TestAgbOldIndoorClassificationScores: + """ + Class to test the old_indoor classification scores function. + + Methods + ------- + test_agb_old_indoor_classification_scores_ages() + test if expected scores returned for different ages + test_agb_old_indoor_classification_scores_genders() + test if expected scores returned for different genders + test_agb_old_indoor_classification_scores_bowstyles() + test if expected scores returned for different bowstyles + test_agb_old_indoor_classification_scores_gent_compound_worcester + test supposed loophole in worcester for gent compound + test_agb_old_indoor_classification_scores_invalid() + test invalid inputs + """ + + @pytest.mark.parametrize( + "age_group,scores_expected", + [ + ( + "adult", + [592, 582, 554, 505, 432, 315, 195, 139], + ), + ( + "50+", + [592, 582, 554, 505, 432, 315, 195, 139], + ), + ( + "under21", + [592, 582, 554, 505, 432, 315, 195, 139], + ), + ( + "Under 18", + [592, 582, 554, 505, 432, 315, 195, 139], + ), + ( + "Under 12", + [592, 582, 554, 505, 432, 315, 195, 139], + ), + ], + ) + def test_agb_old_indoor_classification_scores_ages( + self, + age_group: str, + scores_expected: List[int], + ) -> None: + """ + Check that old_indoor classification returns expected value for a case. + ALl ages should return the same values. + """ + scores = class_funcs.agb_old_indoor_classification_scores( + roundname="portsmouth", + bowstyle="recurve", + gender="male", + age_group=age_group, + ) + + assert scores == scores_expected + + def test_agb_old_indoor_classification_scores_genders( + self, + ) -> None: + """ + Check that old_indoor classification returns expected value for a case. + """ + scores = class_funcs.agb_old_indoor_classification_scores( + roundname="portsmouth", + bowstyle="recurve", + gender="female", + age_group="adult", + ) + + assert scores == [582, 569, 534, 479, 380, 255, 139, 93] + + @pytest.mark.parametrize( + "bowstyle,gender,scores_expected", + [ + ( + "compound", + "male", + [581, 570, 554, 529, 484, 396, 279, 206], + ), + ( + "compound", + "female", + [570, 562, 544, 509, 449, 347, 206, 160], + ), + ], + ) + def test_agb_old_indoor_classification_scores_bowstyles( + self, + bowstyle: str, + gender: str, + scores_expected: List[int], + ) -> None: + """ + Check that old_indoor classification returns expected value for a case. + Also checks that compound scoring is enforced. + """ + scores = class_funcs.agb_old_indoor_classification_scores( + roundname="portsmouth", + bowstyle=bowstyle, + gender=gender, + age_group="adult", + ) + + assert scores == scores_expected + + def test_agb_old_indoor_classification_scores_gent_compound_worcester( + self, + ) -> None: + """ + Check gent compound worcester supposed loophole. + """ + scores = class_funcs.agb_old_indoor_classification_scores( + roundname="worcester", + bowstyle="compound", + gender="male", + age_group="adult", + ) + + assert scores == [300, 299, 289, 264, 226, 162, 96, 65] + + @pytest.mark.parametrize( + "bowstyle,gender,age_group", + # Check all systems, different distances, negative and large handicaps. + [ + # No invalid bowstyle as anything non-compound returns non-compound. + # No invalid age as only one table for all ages. + ( + "recurve", + "invalidgender", + "adult", + ), + ], + ) + def test_agb_old_indoor_classification_scores_invalid( + self, + bowstyle: str, + gender: str, + age_group: str, + ) -> None: + """ + Check that old_indoor classification returns expected value for a case. + """ + with pytest.raises( + KeyError, + match=( + f"{age_group.lower().replace(' ','')}_{gender.lower()}_{bowstyle.lower()}" + ), + ): + _ = class_funcs.agb_old_indoor_classification_scores( + roundname="portsmouth", + bowstyle=bowstyle, + gender=gender, + age_group=age_group, + ) + + +class TestCalculateAgbOldIndoorClassification: + """ + Class to test the old_indoor classification function. + + Methods + ------- + test_calculate_agb_old_indoor_classification() + test_calculate_agb_old_indoor_classification_invalid_scores() + """ + + @pytest.mark.parametrize( + "score,gender,class_expected", + [ + ( + 400, + "male", + "F", + ), + ( + 337, + "female", + "F", + ), + ( + 592, + "male", + "A", + ), + ( + 582, + "female", + "A", + ), + ( + 581, + "male", + "C", + ), + ( + 120, + "male", + "UC", + ), + ( + 1, + "male", + "UC", + ), + ], + ) + def test_calculate_agb_old_indoor_classification( + self, + score: float, + gender: str, + class_expected: str, + ) -> None: + """ + Check that old_indoor classification returns expected value for a few cases. + """ + class_returned = class_funcs.calculate_agb_old_indoor_classification( + roundname="portsmouth", + score=score, + bowstyle="recurve", + gender=gender, + age_group="adult", + ) + + assert class_returned == class_expected + + @pytest.mark.parametrize( + "roundname,score", + [ + ( + "portsmouth", + 1000, + ), + ( + "portsmouth", + 601, + ), + ( + "portsmouth", + -1, + ), + ( + "portsmouth", + -100, + ), + ], + ) + def test_calculate_agb_old_indoor_classification_invalid_scores( + self, + roundname: str, + score: float, + ) -> None: + """ + Check that old_indoor classification fails for inappropriate scores. + """ + with pytest.raises( + ValueError, + match=( + f"Invalid score of {score} for a {roundname}. " + f"Should be in range 0-{ALL_INDOOR_ROUNDS[roundname].max_score()}." + ), + ): + _ = class_funcs.calculate_agb_old_indoor_classification( + roundname=roundname, + score=score, + bowstyle="barebow", + gender="male", + age_group="adult", + )