22from __future__ import annotations
33
44from datetime import date
5- from typing import Any , Collection , Dict , Optional , Sequence
5+ from typing import Any , Collection , Dict , Optional , Sequence , Literal
66
77from humps import camelize
88from pydantic import root_validator
99
1010from mavedb .lib .validation import urn_re
11- from mavedb .lib .validation .constants .score_set import default_ranges
1211from mavedb .lib .validation .exceptions import ValidationError
1312from mavedb .lib .validation .utilities import inf_or_float , is_null
1413from mavedb .models .enums .mapping_state import MappingState
@@ -55,23 +54,13 @@ class Config:
5554class ScoreRange (BaseModel ):
5655 label : str
5756 description : Optional [str ]
58- classification : str
57+ classification : Literal [ "normal" , "abnormal" , "not_specified" ]
5958 # Purposefully vague type hint because of some odd JSON Schema generation behavior.
6059 # Typing this as tuple[Union[float, None], Union[float, None]] will generate an invalid
6160 # jsonschema, and fail all tests that access the schema. This may be fixed in pydantic v2,
6261 # but it's unclear. Even just typing it as Tuple[Any, Any] will generate an invalid schema!
6362 range : list [Any ] # really: tuple[Union[float, None], Union[float, None]]
6463
65- @validator ("classification" )
66- def range_classification_value_is_accepted (cls , field_value : str ):
67- classification = field_value .strip ().lower ()
68- if classification not in default_ranges :
69- raise ValidationError (
70- f"Unexpected classification value(s): { classification } . Permitted values: { default_ranges } "
71- )
72-
73- return classification
74-
7564 @validator ("range" )
7665 def ranges_are_not_backwards (cls , field_value : tuple [Any ]):
7766 if len (field_value ) != 2 :
@@ -89,7 +78,7 @@ def ranges_are_not_backwards(cls, field_value: tuple[Any]):
8978
9079
9180class ScoreRanges (BaseModel ):
92- wt_score : float
81+ wt_score : Optional [ float ]
9382 ranges : list [ScoreRange ] # type: ignore
9483
9584
@@ -209,17 +198,16 @@ def score_range_labels_must_be_unique(cls, field_value: Optional[ScoreRanges]):
209198 return field_value
210199
211200 @validator ("score_ranges" )
212- def ranges_contain_normal_and_abnormal (cls , field_value : Optional [ScoreRanges ]):
201+ def score_range_normal_classification_exists_if_wild_type_score_provided (cls , field_value : Optional [ScoreRanges ]):
213202 if field_value is None :
214203 return None
215204
216- ranges = set ([range_model .classification for range_model in field_value .ranges ])
217- if not set (default_ranges ).issubset (ranges ):
218- raise ValidationError (
219- "Both `normal` and `abnormal` ranges must be provided." ,
220- # Raise this error inside the first classification provided by the model.
221- custom_loc = ["body" , "scoreRanges" , "ranges" , 0 , "classification" ],
222- )
205+ if field_value .wt_score is not None :
206+ if not any ([range_model .classification == "normal" for range_model in field_value .ranges ]):
207+ raise ValidationError (
208+ "A wild type score has been provided, but no normal classification range exists." ,
209+ custom_loc = ["body" , "scoreRanges" , "wtScore" ],
210+ )
223211
224212 return field_value
225213
@@ -264,6 +252,16 @@ def wild_type_score_in_normal_range(cls, field_value: Optional[ScoreRanges]):
264252 normal_ranges = [
265253 range_model .range for range_model in field_value .ranges if range_model .classification == "normal"
266254 ]
255+
256+ if normal_ranges and field_value .wt_score is None :
257+ raise ValidationError (
258+ "A normal range has been provided, but no wild type score has been provided." ,
259+ custom_loc = ["body" , "scoreRanges" , "wtScore" ],
260+ )
261+
262+ if field_value .wt_score is None :
263+ return field_value
264+
267265 for range in normal_ranges :
268266 if field_value .wt_score >= inf_or_float (range [0 ], lower = True ) and field_value .wt_score < inf_or_float (
269267 range [1 ], lower = False
0 commit comments