diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f4174..ba4a491 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +### [1.2.4] - 2021-11-03 + +- Исправил ошибку при проверке контроля с формулой вида `SUM{}|=|1|=|SUM{}`. +- Исправил ошибку из-за которой при фильтрации строк по спецификам не возвращалась ни одна строка. + - Причина была в том, что не учитывалась возможность дефолтного значения специфик для строк отчёта где они явно не указаны. + + ### [1.2.3] - 2021-08-02 - Исправил разбор формулы контроля/условия. В них так же есть коды вида "01", тогда как в отчёте в коде нет нуля. diff --git a/rosstat/report.py b/rosstat/report.py index 8d79d8c..c776917 100644 --- a/rosstat/report.py +++ b/rosstat/report.py @@ -46,13 +46,13 @@ def add_col(self, col_code, col_text): self._blank = False def get_spec(self, idx): - '''Возвращает специфику по её "индексу"''' + '''Возвращает специфику отчёта по её "индексу"''' return getattr(self, f's{idx}') def filter(self, specs): '''Проверка, входит ли строка в список переданных специфик''' for i in range(1, 4): - row_spec = self.get_spec(i) + row_spec = self.get_spec(i) or specs[i].default if row_spec in self._ignore_report_specs: return True if specs[i] not in self._ignore_specs and row_spec not in specs[i]: diff --git a/rosstat/validators/control/parser/elements.py b/rosstat/validators/control/parser/elements.py index 4528f7c..15090ac 100644 --- a/rosstat/validators/control/parser/elements.py +++ b/rosstat/validators/control/parser/elements.py @@ -3,6 +3,7 @@ from itertools import chain from functools import reduce from .value import nullablefloat +from .specific import Specific from ..exceptions import NoElemToCompareError from ....helpers import str_int @@ -51,8 +52,13 @@ def __neg__(self): return self def __repr__(self): - return ('').format( - self.section, self.rows, self.columns, self.val, self.bool) + return ''.format( + list(self.section), + list(self.rows), + list(self.columns), + self.val, + self.bool + ) def __modify(self, elem, op_func): self.rows |= elem.rows @@ -128,14 +134,19 @@ def __init__(self, section, rows, columns, self.rows = set(str_int(v) for v in rows) self.columns = set(str_int(v) for v in columns) - self.specs = {1: s1, 2: s2, 3: s3} + self.specs = {1: Specific(s1), 2: Specific(s2), 3: Specific(s3)} self.funcs = [] self.elems = [] def __repr__(self): - return ''.format( - self.section, self.rows, self.columns, self.funcs, self.elems) + return "".format( + self.section, + list(self.rows), + list(self.columns), + self.funcs, + self.elems + ) def __neg__(self): self._apply_unary('neg') @@ -151,38 +162,24 @@ def _prepare_specs(self, formats, catalogs): '''Подготовка специфик. Если список специфик не "пуст" и не содержит один только ключ "*", пытаемся "развернуть" его к простому списку. Другими словами, все диапазоны специфик привести к явному набору в - соответствии со справочником. Затем конвертируем всё в множество + соответствии со справочником ''' for spec_idx in range(1, 4): - if self.specs[spec_idx] in ([None], ['*']): - self.specs[spec_idx] = set(self.specs[spec_idx]) - else: - spec_list = self.__get_spec_list(formats, catalogs, spec_idx) - self.specs[spec_idx] = set(self.__expand_specs(spec_list, - spec_idx)) + if self.specs[spec_idx].need_expand(): + spec_params = self.__get_spec_params(formats, spec_idx) + spec_list = self.__get_spec_list(catalogs, spec_params) - def __get_spec_list(self, formats, catalogs, spec_idx): - '''Определяем набор параметров для специфики указанной строки, раздела. - Выбираем список специфик по имени справочника из наборапараметров - ''' + self.specs[spec_idx].params(spec_params) + self.specs[spec_idx].expand(spec_list) + + def __get_spec_params(self, formats, spec_idx): + '''Определяем параметры для специфики указанной строки, раздела''' row_code = next(iter(self.rows)) - params = formats.get_spec_params(self.section, row_code, spec_idx) - return catalogs.get(params.get('dic'), {}).get('ids', []) - - def __expand_specs(self, spec_list, spec_idx): - '''Перебираем специфики. Простые специфики сразу возвращаем. Если - имеем диапазон, определяем индекс начальной и конечной специфик - из списка-справочника, итерируемся по определенному диапазону, - возвращая соответствующие специфики из списка-справочника - ''' - for spec in self.specs[spec_idx]: - if '-' in spec: - start, end = spec.split('-') - for i in range(spec_list.index(start.strip()), - spec_list.index(end.strip()) + 1): - yield spec_list[i] - else: - yield spec + return formats.get_spec_params(self.section, row_code, spec_idx) + + def __get_spec_list(self, catalogs, spec_params): + '''Выбираем список специфик по имени справочника из параметров''' + return catalogs.get(spec_params.get('dic'), {}).get('ids', []) def _read_data(self, report, dimension): '''Чтение отчёта и конвертация его в массивы элементов''' @@ -232,13 +229,15 @@ def _apply_funcs(self, report, params, ctx_elem): def _apply_sum(self, ctx_elem): '''Суммирование строк и/или графов''' - if self.columns == ctx_elem.columns: # строк в каждой графе + if isinstance(ctx_elem, ElemLogic): # для случаев SUM{}|=|1|=|SUM{} + self.elems = [[reduce(operator.add, chain(*self.elems))]] + elif self.columns == ctx_elem.columns: # строк в каждой графе self.elems = [[reduce(operator.add, l)] for l in zip(*self.elems)] - elif self.rows == ctx_elem.rows: # граф в каждой строке + elif self.rows == ctx_elem.rows: # граф в каждой строке self.elems = [[reduce(operator.add, l)] for l in self.elems] - elif not self.elems: # всех ячеек (секция пустая) + elif not self.elems: # всех ячеек (секция пустая) self.elems = [[Elem(None, self.section, '*', '*')]] - else: # всех ячеек (секция не пустая) + else: # всех ячеек (секция не пустая) self.elems = [[reduce(operator.add, chain(*self.elems))]] def _apply_unary(self, func): @@ -313,7 +312,10 @@ def __init__(self, l_elem, operator, r_elem): def __repr__(self): return ''.format( - self.l_elem, self.op_name, self.r_elem) + self.l_elem, + self.op_name, + self.r_elem + ) def check(self, report, params, ctx_elem=None): '''Основной метод вызова проверки''' @@ -391,7 +393,10 @@ def __init__(self, action, elems): def __repr__(self): return ''.format( - self.action, self.funcs, self.elems) + self.action, + self.funcs, + self.elems + ) def check(self, *args): self._select(args) diff --git a/rosstat/validators/control/parser/specific.py b/rosstat/validators/control/parser/specific.py new file mode 100644 index 0000000..db152f4 --- /dev/null +++ b/rosstat/validators/control/parser/specific.py @@ -0,0 +1,44 @@ +class Specific: + def __init__(self, specs): + self._specs = set(specs) + self._default = None + + def __repr__(self): + return "".format(self._specs, self._default) + + def __iter__(self): + return iter(self._specs) + + def __contains__(self, spec): + return spec in self._specs + + def __eq__(self, other): + return self._specs == other + + @property + def default(self): + return self._default + + def need_expand(self): + return self._specs not in ({None}, {'*'}) + + def params(self, params): + self._default = params.get('default') + + def expand(self, dic): + self._specs = set(self._expand(dic)) + + def _expand(self, dic): + '''Перебираем специфики. Простые специфики сразу возвращаем. Если + имеем диапазон, определяем индекс начальной и конечной специфик + из списка-справочника, итерируемся по определенному диапазону, + возвращая соответствующие специфики из списка-справочника + ''' + for spec in self._specs: + if '-' in spec: + start, end = spec.split('-') + for i in range(dic.index(start.strip()), + dic.index(end.strip()) + 1): + yield dic[i] + else: + yield spec diff --git a/setup.py b/setup.py index f09d282..74dc239 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='rosstat-flc', - version='1.2.3', + version='1.2.4', packages=find_packages(), description='Tool for format-logistic control of reports sent to RosStat', long_description=open('README.md', 'r').read(), @@ -19,6 +19,7 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9' + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', ] )