def test_default_year_constant(self): """Возраст в виде простого числа определяется корректно""" right = AgeRight("5 ") correct_range = ValueRange(5, 5) self.assertEqual(correct_range, right.age_range, f"Диапазон должен быть '{correct_range}'") self.assertEqual(AgeRight.MODE_YEAR, right.mode, "Режим должен быть 'year'")
def test_constant_age_with_sign_and_optional_mode(self): """Возраст в виде числа с опциональным режимом и направлением определяется корректно""" ages = [ ["> 0 дней", (0, ")"), float("inf"), AgeRight.MODE_DAY], ["> 0 дней", (0, ")"), float("inf"), AgeRight.MODE_DAY], ["больше 5 дней", (5, ")"), float("inf"), AgeRight.MODE_DAY], ["более 5 дней", (5, ")"), float("inf"), AgeRight.MODE_DAY], ["от 1 дня", (1, "]"), float("inf"), AgeRight.MODE_DAY], ["≥ 1 дня", (1, "]"), float("inf"), AgeRight.MODE_DAY], ["с 1 дня", (1, "]"), float("inf"), AgeRight.MODE_DAY], [">= 1 дня", (1, "]"), float("inf"), AgeRight.MODE_DAY], ["≥ 1 дня", (1, "]"), float("inf"), AgeRight.MODE_DAY], ["до 7 дней", 0, (7, ")"), AgeRight.MODE_DAY], ["< 7 дней", 0, (7, ")"), AgeRight.MODE_DAY], ["старше 5 лет", (5, ")"), float("inf"), AgeRight.MODE_YEAR], ["младше 5 лет", 0, (5, ")"), AgeRight.MODE_YEAR], ["меньше 5 лет", 0, (5, ")"), AgeRight.MODE_YEAR], ["менее 5 лет", 0, (5, ")"), AgeRight.MODE_YEAR], ["до 20", 0, (20, ")"), AgeRight.MODE_YEAR], ["< 100", 0, (100, ")"), AgeRight.MODE_YEAR], ["< 100", 0, (100, ")"), AgeRight.MODE_YEAR], ["≤ 100", 0, (100, "]"), AgeRight.MODE_YEAR], ["<= 100", 0, (100, "]"), AgeRight.MODE_YEAR], ["≤ 100", 0, (100, "]"), AgeRight.MODE_YEAR], ] for age in ages: correct_range = ValueRange(age[1], age[2]) right = AgeRight(age[0]) self.assertEqual(correct_range, right.age_range, f"Диапазон должен быть '{correct_range}', а не '{right.age_range}' для '{age[0]}'") self.assertEqual(age[3], right.mode, f"Режим должен быть '{age[3]}' для '{age[0]}'")
def test_value_from_to(self): """Значения от или до (больше, меньше, >=, <=)""" rs = [ ["> 1.1", (1.1, ")"), float("inf"), ResultRight.MODE_NUMBER_RANGE], ["> 1,1", (1.1, ")"), float("inf"), ResultRight.MODE_NUMBER_RANGE], ["> 1", (1, ")"), float("inf"), ResultRight.MODE_NUMBER_RANGE], ["> 1", (1, ")"), float("inf"), ResultRight.MODE_NUMBER_RANGE], ["больше 1", (1, ")"), float("inf"), ResultRight.MODE_NUMBER_RANGE], ["больше 10<sup>2</sup>", (100, ")"), float("inf"), ResultRight.MODE_NUMBER_RANGE], [">= 1.1", (1.1, "]"), float("inf"), ResultRight.MODE_NUMBER_RANGE], ["≥ 1.1", (1.1, "]"), float("inf"), ResultRight.MODE_NUMBER_RANGE], ["≥ 1.1", (1.1, "]"), float("inf"), ResultRight.MODE_NUMBER_RANGE], ["от 1.1", (1.1, "]"), float("inf"), ResultRight.MODE_NUMBER_RANGE], ["< 10", float("-inf"), (10, ")"), ResultRight.MODE_NUMBER_RANGE], ["< 10", float("-inf"), (10, ")"), ResultRight.MODE_NUMBER_RANGE], ["меньше 10", float("-inf"), (10, ")"), ResultRight.MODE_NUMBER_RANGE], ["менее 10", float("-inf"), (10, ")"), ResultRight.MODE_NUMBER_RANGE], ["до 10", float("-inf"), (10, ")"), ResultRight.MODE_NUMBER_RANGE], ["<= 10,1", float("-inf"), (10.1, "]"), ResultRight.MODE_NUMBER_RANGE], ["≤ 10", float("-inf"), (10, "]"), ResultRight.MODE_NUMBER_RANGE], ["≤ 10", float("-inf"), (10, "]"), ResultRight.MODE_NUMBER_RANGE], ["по 10", float("-inf"), (10, "]"), ResultRight.MODE_NUMBER_RANGE], ] for r in rs: right = ResultRight(r[0]) valid = ValueRange(r[1], r[2]) self.assertEqual(valid, right.range, f"Диапазон должен быть '{valid}', а не '{right.range}' для '{r[0]}'") self.assertEqual(r[3], right.mode, f"Режим должен быть '{r[3]}' для '{r[0]}'")
def test_default_year_ages(self): """Возраст в виде простого диапазона определяется корректно""" strs = ["1-10", "1 - 10", " 1 - 10 "] correct_range = ValueRange(1, 10) for s in strs: right = AgeRight(s) self.assertEqual(correct_range, right.age_range, f"Диапазон должен быть '{correct_range}'") self.assertEqual(AgeRight.MODE_YEAR, right.mode, "Режим должен быть 'year'")
def test_all_ages(self): """Все возраста определяются корректно""" strs = ["все", "Все", " все ", ""] correct_range = ValueRange(0, float('inf')) for s in strs: right = AgeRight(s) self.assertEqual(correct_range, right.age_range, f"Диапазон должен быть '{correct_range}'") self.assertEqual(AgeRight.MODE_YEAR, right.mode, "Режим должен быть 'year'")
def __init__(self, orig_str: str): self.const_orig = orig_str.strip() orig_str = self.const_orig.lower() self.range = ValueRange(0, 0) if not orig_str: self.mode = ResultRight.MODE_ANY return orig_str = replace_pow(re.sub(' +', ' ', orig_str)) const_range = ResultRight.check_is_constant_with_sign(orig_str) if const_range: self.mode = const_range[0] self.range = ValueRange(const_range[1], const_range[2]) return simple_range = ResultRight.check_is_range(orig_str) if simple_range: self.mode = simple_range[0] self.range = ValueRange(simple_range[1], simple_range[2]) return self.mode = ResultRight.MODE_CONSTANT self.const = orig_str
def test_constant_age_with_mode(self): """Возраст в виде константного числа с режимом определяется корректно""" ages = [ ["0 дней", 0, AgeRight.MODE_DAY], ["6 месяцев", 6, AgeRight.MODE_MONTH], ["5 л.", 5, AgeRight.MODE_YEAR], [" 100 лет", 100, AgeRight.MODE_YEAR], ] for age in ages: correct_range = ValueRange(age[1], age[1]) right = AgeRight(age[0]) self.assertEqual(correct_range, right.age_range, f"Диапазон должен быть '{correct_range}' для '{age[0]}'") self.assertEqual(age[2], right.mode, f"Режим должен быть 'year' для '{age[0]}'")
def get_active_ref(self, raw_ref=True, single=False): if raw_ref: if single: show_only_needed_ref = SettingManager.get("show_only_needed_ref", default='True', default_type='b') if not show_only_needed_ref or not self.raw_ref: return None show_full_needed_ref = SettingManager.get("show_full_needed_ref", default='False', default_type='b') if show_full_needed_ref: return {self.key: self.raw_ref} return {'Все': self.raw_ref} return self.raw_ref if isinstance(self.ref, ResultRight): return self.ref return ValueRange((0, ")"), (0, ")"))
def test_range(self): """Диапазон число - число""" rs = [ ["10-100", 10, 100], ["0.55 – 0.633", 0.55, 0.633], ["2.0-21.0", 2.0, 21.0], ["10 -100", 10, 100], ["-10 - 100", -10, 100], ["-100 - -10", -100, -10], ["10 – 20", 10, 20], ["0,23 - 2,0", 0.23, 2.0], ["от 1 до 2", 1, (2, ")")], ] for r in rs: right = ResultRight(r[0]) valid = ValueRange(r[1], r[2]) self.assertEqual(valid, right.range, f"Диапазон должен быть '{valid}', а не '{right.range}' для '{r[0]}'")
def test_full_defined_range(self): """Проверка полностью определённого диапазона с опциональными частями""" ages = [ ["10 - 20", 10, 20, AgeRight.MODE_YEAR], ["1 г - 2 г", 1, 2, AgeRight.MODE_YEAR], ["10 лет – 20 лет", 10, 20, AgeRight.MODE_YEAR], ["2г. – 3г.", 2, 3, AgeRight.MODE_YEAR], ["от 3 до 5 дней", 3, (5, ")"), AgeRight.MODE_DAY], ["от 3 дней до 5", 3, (5, ")"), AgeRight.MODE_DAY], ["от 3 месяцев до 5 месяцев", 3, (5, ")"), AgeRight.MODE_MONTH], ["с 3 по 5", 3, 5, AgeRight.MODE_YEAR], ] for age in ages: correct_range = ValueRange(age[1], age[2]) right = AgeRight(age[0]) self.assertEqual(correct_range, right.age_range, f"Диапазон должен быть '{correct_range}', а не '{right.age_range}' для '{age[0]}'") self.assertEqual(age[3], right.mode, f"Режим должен быть '{age[3]}' для '{age[0]}'")
class AgeRight: MODE_DAY = "day" MODE_MONTH = "month" MODE_YEAR = "year" MODE_UNKNOWN = "unknow" DAY_ORIGS = ( "дней", "день", "дня", "дн", "дн.", "д", "д.", ) MONTH_ORIGS = ( "месяц", "месяцев", "месяца", "мес", "мес.", "м", "м.", ) YEAR_ORIGS = ( "год", "года", "лет", "г", "г.", "л", "л.", ) MODES_FROM_ORIGS = ( (DAY_ORIGS, MODE_DAY), (MONTH_ORIGS, MODE_MONTH), (YEAR_ORIGS, MODE_YEAR), ) def __init__(self, orig_str: str): orig_str = orig_str.strip().lower() if AgeRight.check_is_all(orig_str): self.age_range = ValueRange(0, float('inf')) self.mode = AgeRight.MODE_YEAR return if "един" in orig_str: orig_str = "0-2" if "отсутств" in orig_str: orig_str = "0-0" constant_simple_year = AgeRight.check_is_constant_simple_year(orig_str) if constant_simple_year: self.age_range = ValueRange(constant_simple_year, constant_simple_year) self.mode = AgeRight.MODE_YEAR return simple_year_age = AgeRight.check_is_simple_year_range(orig_str) if simple_year_age: self.age_range = ValueRange(int(simple_year_age.group(1)), int(simple_year_age.group(2))) self.mode = AgeRight.MODE_YEAR return orig_str = re.sub(' +', ' ', orig_str) constant_age_with_mode = AgeRight.check_is_constant_age_with_mode(orig_str) if constant_age_with_mode: self.age_range = ValueRange(constant_age_with_mode[0], constant_age_with_mode[0]) self.mode = constant_age_with_mode[1] return constant_age_with_sign = AgeRight.check_is_constant_age_with_sign_and_optional_mode(orig_str) if constant_age_with_sign: self.age_range = ValueRange(constant_age_with_sign[1], constant_age_with_sign[2]) self.mode = constant_age_with_sign[0] return full_range = AgeRight.check_is_full_range(orig_str) if full_range: self.age_range = ValueRange(full_range[1], full_range[2]) self.mode = full_range[0] return self.age_range = ValueRange(Value(0, POINT_STRICT), Value(0, POINT_STRICT)) self.mode = AgeRight.MODE_UNKNOWN def test(self, age: List[int]) -> bool: if self.mode == AgeRight.MODE_UNKNOWN: return False if self.mode == AgeRight.MODE_DAY: if age[1] > 0 or age[2] > 0: return False age_var = age[0] elif self.mode == AgeRight.MODE_MONTH: if age[2] > 0: return False age_var = age[1] else: age_var = age[2] return self.age_range.in_range(age_var) == RANGE_IN @staticmethod def check_is_all(orig_str: str) -> bool: return orig_str in ["все", ""] @staticmethod def check_is_simple_year_range(orig_str: str): orig_str = orig_str.replace(" ", "") return re.match(r"^(\d+)-(\d+)$", orig_str) @staticmethod def check_is_constant_simple_year(orig_str: str) -> Union[bool, int]: orig_str = orig_str.replace(" ", "") if not orig_str.isdigit(): return False return int(orig_str) @staticmethod def check_is_constant_age_with_mode(orig_str: str) -> Union[bool, Tuple[int, str]]: matched = re.match(r"^(\d+) ([\w.]+)$", orig_str) if not matched: return False value = int(matched.group(1)) mode_orig = matched.group(2).lower() mode = AgeRight.get_mode_by_string(mode_orig) if mode != AgeRight.MODE_UNKNOWN: return value, mode return False @staticmethod def check_is_constant_age_with_sign_and_optional_mode(orig_str: str) -> Union[bool, Tuple[str, Value, Value]]: matched = re.match(r"^([\w<>≤≥&;=]+) (\d+)( )?(\w+)?$", orig_str) if matched: g = list(matched.groups()) if g[3]: mode = AgeRight.get_mode_by_string(g[3]) if mode == AgeRight.MODE_UNKNOWN: return False else: mode = AgeRight.MODE_YEAR sign_orig = g[0] sign = get_sign_by_string(sign_orig) if not sign: return False value = int(g[1]) if sign == SIGN_GT: return mode, Value(value=value, mode=POINT_STRICT), Value(value=float('inf')) if sign == SIGN_GTE: return mode, Value(value=value), Value(value=float('inf')) if sign == SIGN_LT: return mode, Value(value=0), Value(value=value, mode=POINT_STRICT) if sign == SIGN_LTE: return mode, Value(value=0), Value(value=value) return False @staticmethod def check_is_full_range(orig_str: str) -> Union[bool, Tuple[str, Union[int, Value], Union[int, Value]]]: matched = re.match(RANGE_REGEXP, orig_str) if matched: g = list(map(lambda x: x if not x else x.strip(), matched.groups())) if g[3] or g[7]: mode = AgeRight.get_mode_by_string(g[3] or g[7]) if mode == AgeRight.MODE_UNKNOWN: return False else: mode = AgeRight.MODE_YEAR if g[4] == 'до': return mode, int(g[1]), Value(int(g[5]), mode=POINT_STRICT) return mode, int(g[1]), int(g[5]) return False @staticmethod def get_mode_by_string(s: str) -> Union[bool, str]: for mode_origs, mode in AgeRight.MODES_FROM_ORIGS: if s in mode_origs: return mode return AgeRight.MODE_UNKNOWN
def __init__(self, orig_str: str): orig_str = orig_str.strip().lower() if AgeRight.check_is_all(orig_str): self.age_range = ValueRange(0, float('inf')) self.mode = AgeRight.MODE_YEAR return if "един" in orig_str: orig_str = "0-2" if "отсутств" in orig_str: orig_str = "0-0" constant_simple_year = AgeRight.check_is_constant_simple_year(orig_str) if constant_simple_year: self.age_range = ValueRange(constant_simple_year, constant_simple_year) self.mode = AgeRight.MODE_YEAR return simple_year_age = AgeRight.check_is_simple_year_range(orig_str) if simple_year_age: self.age_range = ValueRange(int(simple_year_age.group(1)), int(simple_year_age.group(2))) self.mode = AgeRight.MODE_YEAR return orig_str = re.sub(' +', ' ', orig_str) constant_age_with_mode = AgeRight.check_is_constant_age_with_mode(orig_str) if constant_age_with_mode: self.age_range = ValueRange(constant_age_with_mode[0], constant_age_with_mode[0]) self.mode = constant_age_with_mode[1] return constant_age_with_sign = AgeRight.check_is_constant_age_with_sign_and_optional_mode(orig_str) if constant_age_with_sign: self.age_range = ValueRange(constant_age_with_sign[1], constant_age_with_sign[2]) self.mode = constant_age_with_sign[0] return full_range = AgeRight.check_is_full_range(orig_str) if full_range: self.age_range = ValueRange(full_range[1], full_range[2]) self.mode = full_range[0] return self.age_range = ValueRange(Value(0, POINT_STRICT), Value(0, POINT_STRICT)) self.mode = AgeRight.MODE_UNKNOWN
class ResultRight: MODE_NUMBER_RANGE = 'number_range' MODE_CONSTANT = 'constant' MODE_ANY = 'any' RESULT_MODE_NORMAL = 'normal' RESULT_MODE_MAYBE = 'maybe' RESULT_MODE_NOT_NORMAL = 'not_normal' def __init__(self, orig_str: str): self.const_orig = orig_str.strip() orig_str = self.const_orig.lower() self.range = ValueRange(0, 0) if not orig_str: self.mode = ResultRight.MODE_ANY return orig_str = replace_pow(re.sub(' +', ' ', orig_str)) const_range = ResultRight.check_is_constant_with_sign(orig_str) if const_range: self.mode = const_range[0] self.range = ValueRange(const_range[1], const_range[2]) return simple_range = ResultRight.check_is_range(orig_str) if simple_range: self.mode = simple_range[0] self.range = ValueRange(simple_range[1], simple_range[2]) return self.mode = ResultRight.MODE_CONSTANT self.const = orig_str def test(self, value: str) -> Tuple[str, str]: if self.mode == ResultRight.MODE_ANY: return ResultRight.RESULT_MODE_NORMAL, RANGE_IN value = replace_pow(value.strip().lower().replace("''", "\"")) if "един" in value: value = "1" if "отсутств" in value or "нет" in value: value = "0" if self.mode == ResultRight.MODE_CONSTANT: return (ResultRight.RESULT_MODE_NORMAL, RANGE_IN) if value == self.const else ( ResultRight.RESULT_MODE_MAYBE, RANGE_NEQ) if "сплошь" in value.lower( ) or "++" in value or "+ +" in value or "++++" in value or "+" == value.strip( ) or "оксал ед" in value: value = "inf" numbers = re.findall(r"-?\d*[.,]\d+|-?\d+|inf", value) if numbers: for n in numbers: n = float(n.replace(',', '.')) rv = self.range.in_range(n) if rv != RANGE_IN: return ResultRight.RESULT_MODE_NOT_NORMAL, rv elif value: return ResultRight.RESULT_MODE_MAYBE, RANGE_NEQ return ResultRight.RESULT_MODE_NORMAL, RANGE_IN @staticmethod def check_is_range( s: str ) -> Union[bool, Tuple[str, Union[float, Value], Union[float, Value]]]: matched = re.match(RANGE_REGEXP, s) if matched: g = list(map(lambda x: x if not x else x.strip(), matched.groups())) mode = ResultRight.MODE_NUMBER_RANGE if g[4] == 'до': return mode, Value(g[1]), Value(float(g[5]), mode=POINT_STRICT) return mode, Value(g[1]), Value(g[5]) return False @staticmethod def check_is_constant_with_sign( orig_str: str) -> Union[bool, Tuple[str, Value, Value]]: matched = re.match(r"^([\w<>≤≥&;=]+) ?(-?\d+[.,]\d+|-?\d+)$", orig_str) if matched: g = list(matched.groups()) mode = ResultRight.MODE_NUMBER_RANGE sign_orig = g[0] sign = get_sign_by_string(sign_orig) if not sign: return False value = g[1] if sign == SIGN_GT: return mode, Value( value=value, mode=POINT_STRICT), Value(value=float('inf')) if sign == SIGN_GTE: return mode, Value(value=value), Value(value=float('inf')) if sign == SIGN_LT: return mode, Value(value=float('-inf')), Value( value=value, mode=POINT_STRICT) if sign == SIGN_LTE: return mode, Value(value=float('-inf')), Value(value=value) return False