def build_declarant_incomes(self, year, max_income=5000000 ) -> TAllRegionStatsForOneYear: region_data = TAllRegionStatsForOneYear( year, file_name=self.options.get('output_json')) minOboronyId = 450 query = """ select o.region_id, i.size from declarations_section s join declarations_office o on s.office_id=o.id join declarations_income i on i.section_id=s.id where s.income_year = {} and i.size < {} and i.size > 0 and i.relative='{}' and o.id != {} and o.region_id is not null and o.region_id != {} order by o.region_id, i.size """.format(year, max_income, models.Relative.main_declarant_code, minOboronyId, TRussianRegions.Russia_as_s_whole_region_id) regions = TRussianRegions() mrot = RUSSIA.get_mrot(year) assert mrot is not None with connection.cursor() as cursor: cursor.execute(query) for region_id, items in groupby(cursor, itemgetter(0)): incomes = list(income for _, income in items if income / 12 > mrot) if region_id == TRussianRegions.Baikonur: continue region = regions.get_region_by_id(region_id) if region.joined_to is not None: region = regions.get_region_by_id(region.joined_to) stat_info = region_data.ross_stat.get_data(region.id, year) if stat_info is None: raise Exception( "cannot find stat_info for region.id={}, region.name={}" .format(region.id, region.name)) population = stat_info.population population_median_income = region_data.ross_stat.get_or_predict_median_salary( region.id, year) if population_median_income is None: raise Exception( "cannot estimate population median_income for region.id={}, region.name={}" .format(region.id, region.name)) s = TRegionYearStats( region.id, region.name, incomes, population_median_income, population, region_data.ross_stat.get_data(region.id, 2021).er_election_2021) region_data.add_snapshot(s) region_data.calc_aux_params() return region_data
class TDeclarationTitleParser: def __init__(self, title): self.title = normalize_whitespace(title) self.input_title = title self.type = None self.decl_objects = list() self.decl_time = None self.declarant_positions = list() self.fio = None self.regions = TRussianRegions() self.region_name = None self.org_name = None def find_starter(self): for p in ['сведения о доходах']: i = self.title.lower().find(p) if i != -1: self.title = self.title[i:] break type_regexp = "^((Уточненные Сведения)|(Сведения)|(Справка)|(Информация)|(С В Е Д Е Н И Я))" match = re.search(type_regexp, self.title, re.IGNORECASE) if match is not None: self.type = self.title[:match.end()] self.title = self.title[match.end():].strip() match = re.search("^(о|об)", self.title, re.IGNORECASE) if match is not None: self.title = self.title[match.end():].strip() return True return False def to_json(self): return { "input": self.input_title, "type": self.type, "objects": self.decl_objects, "time": self.decl_time, "positions": self.declarant_positions, "org_name": self.org_name, "fio": self.fio, "region": self.region_name } @staticmethod def from_json(j): r = TDeclarationTitleParser(j['input']) r.type = j['type'] r.decl_objects = j['objects'] r.decl_time = j['time'] r.declarant_positions = j['positions'] r.org_name = j['org_name'] r.fio = j.get('fio') r.region_name = j.get('region') return r def find_decl_objects(self): match = re.search("^ *(о|об) ", self.title, re.IGNORECASE) if match is not None: self.title = self.title[match.end():].strip() objs = [ 'доходах', 'расходах', 'имуществе', 'обязательствах ?имущественного ?характера', 'сведения ?об ?источниках ?получения ?средств', 'источниках ?получения ?средств' ] objects_regexps = "^({})".format('|'.join( map(lambda x: "({})".format(x), objs))) match = re.search(objects_regexps, self.title, re.IGNORECASE) objects_cnt = 0 while match is not None: objects_cnt += 1 self.decl_objects.append(self.title[:match.end()].strip().lower()) self.title = self.title[match.end():].strip() match = re.search("^(и|,)( (о|об))? ", self.title, re.IGNORECASE) if match is not None: self.title = self.title[match.end():].strip() match = re.search(objects_regexps, self.title, re.IGNORECASE) return objects_cnt > 0 #Сведения ведущий специалист комитета по ФК, спорту, туризму и молодёжной политике администрации Зеленчукского муниципального района Карачаево-Черкесской Республики, и членов его семьи за период с 1 января по 31 декабря 2011 года Беланова сергея Николаевича Декларированный годовой доход за 2011 г. (руб.) Перечень объектов недвижимого имущества и транспортных средств, принадлежащих на праве собственности Перечень объектов недвижимого имущества, находящихся в пользовании def find_declarant_position(self): self.find_declarant_position_const() r = '^(, )?представленные' match = re.search(r, self.title, re.IGNORECASE) if match is not None: self.title = self.title[match.end():].strip() words = self.title.split(" ") i = 0 while i < len(words): w = words[i].strip(', ') if TRussianFio.can_start_fio(w): break if TRussianDictWrapper.is_morph_animative_noun( w) and w != "заместителя": offset = self.title.find(words[i]) + len(words[i]) self.declarant_positions.append( self.title[:offset].strip(' ,')) self.title = self.title[offset:].strip() self.find_declarant_position_const() return True i += 1 if i > 4: break return len(self.declarant_positions) > 0 def find_fio_at_start(self): words = self.title.split(" ") if len(words) >= 3: if TRussianFio.is_fio_in_text(words[:3]): offset = self.title.find(words[2]) + len(words[2]) self.fio = self.title[:offset].strip(' ,') self.title = self.title[offset:].strip() return True return False def find_fio_in_the_end(self): words = self.title.strip().split(" ") if len(words) >= 3: if TRussianFio.is_fio_in_text(words[-3:]): offset = self.title.find(words[-3]) self.fio = self.title[offset:].strip(' ,') self.title = self.title[:offset].strip() return True return False def find_declarant_position_const(self): for s in [ "лиц, замещающих муниципальные должности", "лиц, замещающих муниципальные и выборные должности", "лица, замещающего государственную должность", "лиц, замещающих должности", "лиц, замещающих должности государственной гражданской службы", "представленные лицами, замещающими государственные должности", "лицами, замещающими должности" ]: if self.title.startswith(s): self.declarant_positions.append(s) self.title = self.title[len(s):].strip(' ,') region_id, start, end = self.regions.get_region_all_forms_at_start( self.title) if region_id is not None: self.region_name = self.regions.get_region_by_id( region_id).name self.title = self.title[end:].strip(' ,') if self.title.startswith('и '): self.title = self.title[2:].strip(' ,') if not self.find_declarant_position_const(): self.title = "и " + self.title return True #'СВЕДЕНИЯ о доходах, об имуществе и обязательствах имущественного характера лица, замещающего государственную должность Российской Федерации, Короткова Алексея Владимировича и членов его семьи' def find_time(self, search_everywhere=True): if self.decl_time is not None: return True time_starter = "за (отчетный )?(финансовый )?((период)|(год))" time_regexps = [ time_starter + " с [0-9]+ января [0-9]+ ?(года|г.) по [0-9]+ декабря [0-9]+ ?(года|г.)", time_starter + " с [0-9]+ января по [0-9]+ декабря [0-9]+ ?(года|г.)", time_starter + " с [0-9]+.[0-9]+.[0-9]+ по [0-9]+.[0-9]+.[0-9]+", "за [0-9]+ год" ] for r in time_regexps: if not search_everywhere: r = "^({})".format(r) match = re.search(r, self.title, re.IGNORECASE) if match is not None: self.decl_time = self.title[match.start():match.end()].strip() if search_everywhere: self.title = self.title[:match.start()].strip() else: self.title = self.title[match.end():].strip() return True return False def delete_unused(self): for p in ['а также', 'его супруг', 'ее супруг']: s = self.title.find(p) if s != -1: break for p in ['детей', 'ребенка']: e = self.title.rfind(p) if e != -1: e += len(p) break if s != -1 and e != -1: self.title = self.title[:s].strip() + " " + self.title[e:].strip() self.title = self.title.strip(' ,') return regexps = [ r"и членов (ее|его|их) сем..", r"для размещения на официальном сайте и средствах массовой информации" ] for x in regexps: self.title = re.sub(x, "", self.title) def looks_like_orgname(self, orgname): starter = "^(структурно)|(территориал)" if re.search(starter, orgname, re.IGNORECASE) is None: return False ender = "((республики)|(области)|(края))$" if re.search(ender, orgname, re.IGNORECASE) is None: return False return True def parse(self, raise_exception=False): self.find_starter() if not self.find_decl_objects(): if self.type is None: if raise_exception: raise Exception("cannot parse decl objects {}".format( self.title)) else: return False self.find_time(search_everywhere=False) if not self.find_declarant_position(): if not self.find_fio_at_start(): if self.looks_like_orgname(self.title): self.org_name = self.title return True if raise_exception: raise Exception( "cannot parse declarant position {}".format( self.title)) else: return False else: self.find_fio_at_start() self.find_time() self.delete_unused() self.find_fio_in_the_end() self.org_name = self.title.strip(' ,') if self.org_name.startswith("в "): self.org_name = self.org_name[2:] return True
class TRossStatData: def __init__(self, regions=None): self.region_stat = dict() self.regions = regions if self.regions is None: self.regions = TRussianRegions() self.file_path = os.path.join(os.path.dirname(__file__), "data/ross_stat.json") def load_from_disk(self): with open(self.file_path) as inp: for key, years in json.load(inp).items(): region = self.regions.get_region_by_id(int(key)) assert region is not None region_id = region.id if region_id not in self.region_stat: self.region_stat[region_id] = dict() for year, stat in years.items(): self.region_stat[int(region_id)][int( year)] = TRegionYearInfo.from_json(stat) def save_to_disk(self, postfix=""): d = dict() with open(self.file_path + postfix, "w") as outp: for region_id, years in self.region_stat.items(): d[region_id] = dict() for year, info in years.items(): d[region_id][year] = info.to_json() json.dump(d, outp, indent=3, ensure_ascii=False) def check(self, year: int): for r in self.regions.iterate_regions(): if r.id not in self.region_stat: raise Exception("region {}, region_id={} is missing".format( r.name, r.id)) if year not in self.region_stat[r.id]: raise Exception( "year {} region {}, region_id={} is missing".format( year, r.name, r.id)) def get_data(self, region_id, year: int) -> TRegionYearInfo: return self.region_stat.get(region_id, dict()).get(year) def get_or_create_data(self, region_id, year: int) -> TRegionYearInfo: return self.region_stat.get(region_id, dict()).get(year, TRegionYearInfo()) def set_data(self, region_id, year: int, info: TRegionYearInfo): info.check() r = self.region_stat.get(region_id) assert r is not None r[year] = info def get_or_predict_median_salary(self, region_id: int, year: int) -> int: d = self.get_data(region_id, year) if d is not None and d.median_salary2 is not None: return d.median_salary2 d1 = self.get_data(region_id, year - 1) d2 = self.get_data(region_id, year + 1) if d1 is not None and d1.median_salary2 is not None and d2 is not None and d2.median_salary2 is not None: return int((d1.median_salary2 + d2.median_salary2) / 2) return None