コード例 #1
0
def remove_expired_notifications():
    print('Cleaning expired notifications...')
    # удаляем старые шаблоны уведомлений
    notifications = Notification.objects\
        .filter(expired_at__lt=utcnow())\
        .select_related('job', 'job__user')

    session_cache = {}

    i = 0
    for notification in notifications.iterator():
        user = notification.job.user
        if user not in session_cache:
            sess_id = get_wialon_session_key(user)
            session_cache[user] = sess_id
        else:
            sess_id = session_cache[user]

        try:
            remove_notification(notification, user, sess_id)
        except WialonException as e:
            print(e)
        else:
            notification.delete()
        i += 1

    for user, sess_id in session_cache.items():
        logout_session(user, sess_id)

    print('%s expired notifications removed' % i)
コード例 #2
0
    def post(self, request, *args, **kwargs):

        doc = request.data.xpath('/routesRequest')
        if len(doc) < 1:
            return error_response('Не найден объект routesRequest',
                                  code='routesRequest_not_found')

        doc = doc[0]
        doc_id = doc.get('idDoc', '')
        if not doc_id:
            return error_response('Не указан параметр idDoc',
                                  code='idDoc_not_found')

        try:
            org_id = int(doc.get('idOrg', ''))
        except ValueError:
            org_id = 0

        sess_id = get_wialon_session_key(request.user)
        try:
            routes = get_routes(sess_id)
        except APIProcessError as e:
            return error_response(str(e), code=e.code)
        finally:
            logout_session(request.user, sess_id)

        context = self.get_context_data(**kwargs)
        context.update({
            'doc_id': doc_id,
            'create_date': utcnow(),
            'routes': routes,
            'org_id': org_id
        })

        return XMLResponse('ura/routes.xml', context)
コード例 #3
0
def email_reports():
    print('Mailing monthly driving style total report...')
    reports = models.DrivingStyleTotalReportDelivery.objects.published()

    now = utcnow()

    for report in reports:
        print('Monthly VCHM Driving style total report %s' % report)

        for user in report.users.all():
            local_now = utc_to_local_time(now, user.timezone)

            if not user.email:
                print('Skipping user %s (no email)' % user)
                continue

            print('User %s' % user)
            sess_id = None

            try:
                # получаем отчеты через HTTP
                sess_id = get_wialon_session_key(user)
                # если время выполнения - 1е число месяца, то отчет сформируется за прошлый месяц,
                # иначе отчет подготовится за текущий месяц вчерашний день включительно.
                date_to = (local_now - datetime.timedelta(days=1)).date()
                date_from = date_to.replace(day=1)

                res = make_report(report,
                                  user,
                                  sess_id,
                                  date_from=date_from,
                                  date_to=date_to,
                                  attempts=0)

                filename = 'total_vchm_driving_report_%s.xls' % user.pk
                log = models.ReportEmailDeliveryLog(
                    user=user,
                    email=user.email,
                    report_type=EmailDeliveryReportTypeEnum.DRIVING_STYLE,
                    subject='Сводный отчет о качестве вождения (ВЧМ)',
                    body='Здравствуйте, %s. Отчет по вложении.' %
                    user.full_name)
                content = ContentFile(res.content)
                log.report.save(filename, content, save=False)
                log.save()
                log.send(reraise=True)

            except Exception as e:
                print('Error: %s' % e)
                send_trigger_email('Ошибка в работе системы рассылки отчетов',
                                   extra_data={
                                       'Exception': str(e),
                                       'Traceback': traceback.format_exc(),
                                       'report': report,
                                       'user': user
                                   })
            finally:
                if sess_id:
                    logout_session(user, sess_id)
コード例 #4
0
def forwards_func(apps, schema_editor):
    job_model = apps.get_model('ura', 'UraJob')
    user_model = apps.get_model('users', 'User')

    orgs = user_model.objects.filter(is_active=True, supervisor__isnull=False)
    units_cache = {}
    routes_cache = {}

    for org in orgs.iterator():
        print('Organization %s' % org.username)
        sess_id = get_wialon_session_key(org)
        units = get_units(sess_id)
        units_cache[org.pk] = {
            u['id']: '%s (%s) [%s]' % (u['name'], u['number'], u['vin'])
            for u in units
        }

        routes = get_routes(sess_id)
        logout_session(org, sess_id)
        routes_cache[org.pk] = {r['id']: r['name'] for r in routes}

    sess_id = None
    print('Jobs count: %s' % job_model.objects.count())

    i = 0
    for job in job_model.objects.iterator():
        i += 1
        if not job.user_id:
            print('%s: Job %s missed due to lack of user' % (i, job.pk))
            continue

        unit_title = route_title = None

        try:
            unit_id = int(job.unit_id)
            unit_title = units_cache.get(job.user_id, {}).get(unit_id)
            if unit_title:
                job.unit_title = unit_title
        except (ValueError, TypeError, AttributeError):
            pass

        try:
            route_id = int(job.route_id)
            route_title = routes_cache.get(job.user_id, {}).get(route_id)
            if route_title:
                job.route_title = route_title
        except (ValueError, TypeError, AttributeError):
            pass

        if unit_title or route_title:
            job.save()

        print('%s: unit=%s, route=%s (%s)' %
              (i, unit_title, route_title, job.created))
コード例 #5
0
    def get(self, request, **kwargs):
        now = utc_to_local_time(utcnow(), request.user.timezone)
        local_dt_from = now.replace(hour=0, minute=0, second=0)
        local_dt_to = now.replace(hour=23, minute=59, second=59)
        user = User.objects.get(pk=1)
        sess_id = get_wialon_session_key(user)
        moving_service = self.service_class(
            user=user,
            local_dt_from=local_dt_from,
            local_dt_to=local_dt_to,
            sess_id=sess_id
        )
        moving_service.exec_report()
        moving_service.analyze()
        logout_session(user, sess_id)

        return success_response()
コード例 #6
0
def email_reports(period=None):
    print('Mailing %s driving style report...' % period)
    if not period or period not in ('daily', 'weekly', 'monthly'):
        print('Period not specified')
        return

    reports = models.DrivingStyleReportDelivery.objects.published()
    period_verbose = ''
    if period == 'daily':
        period_verbose = 'Ежедневный'
        reports = reports.filter(is_daily=True)
    elif period == 'weekly':
        period_verbose = 'Еженедельный'
        reports = reports.filter(is_weekly=True)
    elif period == 'monthly':
        period_verbose = 'Ежемесячный'
        reports = reports.filter(is_monthly=True)

    now = utcnow()

    for report in reports:
        print('%s VCHM Driving style report %s' % (period, report))

        for user in report.users.all():
            local_now = utc_to_local_time(now, user.timezone)

            if not user.email:
                print('Skipping user %s (no email)' % user)
                continue

            if local_now.hour != SEND_HOUR:
                print('Skipping user %s (%s != %s)' %
                      (user, local_now.hour, SEND_HOUR))
                continue

            print('User %s' % user)
            sess_id = None

            try:
                # получаем отчеты через HTTP
                sess_id = get_wialon_session_key(user)
                date_from = date_to = (local_now -
                                       datetime.timedelta(days=1)).date()

                if period == 'daily':
                    date_from = date_to
                elif period == 'weekly':
                    date_from = (local_now - datetime.timedelta(days=7)).date()
                elif period == 'monthly':
                    date_from = date_to.replace(day=1)

                res = make_report(report,
                                  user,
                                  sess_id,
                                  date_from=date_from,
                                  date_to=date_to,
                                  attempts=0)

                filename = '%s_vchm_driving_report_%s.xls' % (period, user.pk)
                log = models.ReportEmailDeliveryLog(
                    user=user,
                    email=user.email,
                    report_type=EmailDeliveryReportTypeEnum.DRIVING_STYLE,
                    subject='%s отчет о качестве вождения (ВЧМ)' %
                    period_verbose,
                    body='Здравствуйте, %s. Отчет по вложении.' %
                    user.full_name)
                content = ContentFile(res.content)
                log.report.save(filename, content)
                log.save()
                log.send(reraise=True)

            except Exception as e:
                print('Error: %s' % e)
                send_trigger_email('Ошибка в работе системы рассылки отчетов',
                                   extra_data={
                                       'Exception': str(e),
                                       'Traceback': traceback.format_exc(),
                                       'report': report,
                                       'user': user
                                   })
            finally:
                if sess_id:
                    logout_session(user, sess_id)
コード例 #7
0
 def pre_view_trigger(self, request, **kwargs):
     super(BaseUraRidesView, self).pre_view_trigger(request, **kwargs)
     self.sess_id = get_wialon_session_key(request.user)
コード例 #8
0
def cache_geozones():
    print('Caching geozones...')

    with transaction.atomic():
        i = 1
        users = User.objects.filter(supervisor__isnull=False,
                                    wialon_username__isnull=False,
                                    wialon_password__isnull=False).exclude(
                                        wialon_username='', wialon_password='')

        print('%s users found' % len(users))

        for user in users:
            sess_id = get_wialon_session_key(user)
            print('%s) User %s processing' % (i, user))

            routes = get_routes(sess_id, with_points=True)
            logout_session(user, sess_id)
            del sess_id

            print('%s routes found' % len(routes))

            for route in routes:
                print('Route %s' % route['name'])

                name = route['name'].strip()
                job_template, created = StandardJobTemplate.objects.get_or_create(
                    wialon_id=str(route['id']),
                    defaults={
                        'title': name,
                        'user': user
                    })

                existing_points = set()
                if not created:
                    if job_template.title != name:
                        job_template.title = name
                    job_template.user = user
                    job_template.save()

                    existing_points = {p for p in job_template.points.all()}

                # убираем дубли (когда одна и та же геозона дублируется в маршруте)
                route['points'] = {r['id']: r
                                   for r in route['points']}.values()

                print('%s points found, already exist: %s' %
                      (len(route['points']), len(existing_points)))

                for point in route['points']:
                    name = point['name'].strip()
                    standard_point, created = StandardPoint.objects.get_or_create(
                        job_template=job_template,
                        wialon_id=str(point['id']),
                        defaults={'title': name})

                    if not created:
                        existing_points.discard(standard_point)

                        if standard_point.title != name:
                            standard_point.title = name
                            standard_point.save()

                if existing_points:
                    print('Points to remove: %s' %
                          ', '.join([str(x) for x in existing_points]))
                    for existing_point in existing_points:
                        existing_point.delete()

            sleep(.3)
            i += 1
コード例 #9
0
ファイル: utils.py プロジェクト: mindcrimer/geolead
def exec_report(user,
                template_id,
                sess_id,
                dt_from,
                dt_to,
                report_resource_id=None,
                object_id=None,
                attempts=3):

    if report_resource_id is None:
        error = 'не выявлено'
        try:
            report_resource_id = get_wialon_report_resource_id(user, sess_id)
        except WialonException as e:
            error = e

        if not report_resource_id:
            raise ReportException(
                'Не удалось получить ID ресурса для пользователя %s '
                '(наименование ресурса: %s). Ошибка: %s' %
                (str(user), user.wialon_resource_name, error))

    if object_id is None:
        error = 'не выявлено'
        try:
            object_id = get_wialon_report_object_id(user, sess_id)
        except WialonException as e:
            error = e

        if not object_id:
            raise ReportException(
                'Не удалось получить ID группового объекта для пользователя %s '
                '(наименование группового объекта: %s). Ошибка: %s' %
                (str(user), user.wialon_group_object_name, error))

    # замедляем в случае чего, для прохождения лимита
    throttle_report(user)

    r = requests.post(
        settings.WIALON_BASE_URL + '?svc=report/exec_report&sid=' + sess_id, {
            'params':
            json.dumps({
                'reportResourceId': report_resource_id,
                'reportTemplateId': template_id,
                'reportTemplate': None,
                'reportObjectId': object_id,
                'reportObjectSecId': 0,
                'interval': {
                    'flags': 0,
                    'from': dt_from,
                    'to': dt_to
                }
            }),
            'sid':
            sess_id
        })

    result = load_requests_json(r)

    if 'error' in result:
        # сессия неожиданно устарела (такое очень редко и необъяснимо бывает) - отправляем еще раз
        if result['error'] == 1:
            if attempts > 1:
                # генерируем новую сессию
                sess_id = get_wialon_session_key(user, invalidate=True)
                result = exec_report(user,
                                     template_id,
                                     sess_id,
                                     dt_from,
                                     dt_to,
                                     report_resource_id=report_resource_id,
                                     object_id=object_id,
                                     attempts=attempts - 1)
                logout_session(user, sess_id)
                return result

            raise ReportException(WIALON_SESSION_EXPIRED)
        raise ReportException(WIALON_INTERNAL_EXCEPTION % result)

    return result
コード例 #10
0
ファイル: set_jobs.py プロジェクト: mindcrimer/geolead
    def post(self, request, *args, **kwargs):
        jobs = []
        jobs_els = request.data.xpath('/setJobs/job')
        sess_id = get_wialon_session_key(request.user)

        if jobs_els:

            for j in jobs_els:
                data = parse_xml_input_data(request, self.model_mapping, j)

                name = data.get('name')
                if not name:
                    logout_session(request.user, sess_id)
                    return error_response('Не указан параметр jobName',
                                          code='jobName_not_found')

                routes = get_routes(sess_id, with_points=True)
                units = get_units(sess_id)

                routes_ids = [x['id'] for x in routes]
                if data['route_id'] not in routes_ids:
                    return error_response(
                        'Шаблон задания idRoute неверный или не принадлежит текущей организации',
                        code='route_permission')

                units_cache = {
                    u['id']:
                    '%s (%s) [%s]' % (u['name'], u['number'], u['vin'])
                    for u in units
                }

                try:
                    data['unit_title'] = units_cache.get(int(data['unit_id']))
                except (ValueError, TypeError, AttributeError):
                    pass

                if not data['unit_title']:
                    return error_response(
                        'Объект ID=%s не найден в текущем ресурсе организации'
                        % data['unit_id'],
                        code='unit_not_found_permission')

                routes_cache = {r['id']: r for r in routes}
                try:
                    data['route_title'] = routes_cache.get(
                        int(data['route_id']), {}).get('name')
                except (ValueError, TypeError, AttributeError):
                    pass

                data['user'] = request.user
                self.job = self.model.objects.create(**data)
                register_job_notifications(self.job,
                                           sess_id,
                                           routes_cache=routes_cache)
                logout_session(request.user, sess_id)
                jobs.append(self.job)

        context = self.get_context_data(**kwargs)
        context.update({'now': utcnow(), 'acceptedJobs': jobs})

        return XMLResponse('ura/ackjobs.xml', context)
コード例 #11
0
    def get_context_data(self, **kwargs):
        kwargs = super(VchmDrivingStyleView, self).get_context_data(**kwargs)
        self.is_total = bool(self.request.POST.get('total_report'))
        total_report_data = []
        form = kwargs['form']

        sess_id = self.request.session.get('sid')
        if not sess_id:
            raise ReportException(WIALON_NOT_LOGINED)

        try:
            units_list = get_units(sess_id, extra_fields=True)
        except WialonException as e:
            raise ReportException(str(e))

        kwargs['units'] = units_list

        if not self.request.POST:
            return kwargs

        units_dict = {u['name']: u for u in units_list}
        if form.is_valid():
            if self.is_total:
                user = User.objects.filter(
                    is_active=True,
                    username=self.request.session.get('user')
                ).first()

                if not user:
                    raise ReportException(WIALON_USER_NOT_FOUND)

                report_users = UserTotalReportUser.objects\
                    .published()\
                    .order_by('ordering')\
                    .filter(executor_user=user)\
                    .select_related('report_user', 'report_user__ura_user')

                users = set(user.report_user for user in report_users)

            else:
                user = User.objects.filter(
                    is_active=True,
                    wialon_username=self.request.session.get('user')
                ).first()

                if not user:
                    raise ReportException(WIALON_USER_NOT_FOUND)

                users = {user}

            dt_from = local_to_utc_time(datetime.datetime.combine(
                form.cleaned_data['dt_from'],
                datetime.time(0, 0, 0)
            ), user.timezone)
            dt_to = local_to_utc_time(datetime.datetime.combine(
                form.cleaned_data['dt_to'],
                datetime.time(23, 59, 59)
            ), user.timezone)

            for user in users:
                report_data = []
                print('Evaluating report for user %s' % user)
                ura_user = user.ura_user if user.ura_user_id else user
                print('URA user is %s' % ura_user)

                if self.request.POST.get('total_report'):
                    sess_id = get_wialon_session_key(user)
                    if not sess_id:
                        raise ReportException(WIALON_NOT_LOGINED)

                    try:
                        units_list = get_units(sess_id, extra_fields=True)
                    except WialonException as e:
                        raise ReportException(str(e))

                    kwargs['units'] = units_list
                    units_dict = {u['name']: u for u in units_list}

                jobs = Job.objects\
                    .filter(user=ura_user, date_begin__lt=dt_to, date_end__gt=dt_from)

                if form.cleaned_data.get('unit'):
                    jobs = jobs.filter(unit_id=str(form.cleaned_data['unit']))

                self.driver_cache = {
                    j.driver_id: j.driver_fio
                    for j in jobs
                    if j.driver_fio.lower() != 'нет в.а.'
                }

                self.driver_id_cache = {
                    int(j.unit_id): j.driver_id
                    for j in jobs
                    if j.driver_fio.lower() != 'нет в.а.'
                }

                template_id = get_wialon_report_template_id('driving_style', user, sess_id)

                mobile_vehicle_types = set()
                if user.wialon_mobile_vehicle_types:
                    mobile_vehicle_types = set(
                        x.strip() for x in user.wialon_mobile_vehicle_types.lower().split(',')
                    )

                cleanup_and_request_report(user, template_id, sess_id)
                report_kwargs = {}
                if form.cleaned_data.get('unit'):
                    report_kwargs['object_id'] = form.cleaned_data['unit']

                print('Executing report...')
                r = exec_report(
                    user,
                    template_id,
                    sess_id,
                    int(time.mktime(dt_from.timetuple())),
                    int(time.mktime(dt_to.timetuple())),
                    **report_kwargs
                )

                wialon_report_rows = {}
                for table_index, table_info in enumerate(r['reportResult']['tables']):
                    wialon_report_rows[table_info['name']] = get_report_rows(
                        sess_id,
                        table_index,
                        table_info['rows'],
                        level=2 if table_info['name'] == 'unit_group_ecodriving' else 1
                    )

                self.mileage_cache = {
                    row['c'][0]: parse_float(row['c'][1], default=.0)
                    for row in wialon_report_rows.get('unit_group_trips', [])
                }
                self.duration_cache = {
                    row['c'][0]: parse_timedelta(row['c'][2]).total_seconds()
                    for row in wialon_report_rows.get('unit_group_trips', [])
                }

                i = 0
                for row in wialon_report_rows.get('unit_group_ecodriving', []):
                    i += 1
                    violations = [
                        self.parse_report_row(x['c'], user, total=False)
                        for x in row['r']
                    ]
                    row = self.parse_report_row(row['c'], user, total=True)
                    unit = units_dict.get(row.unit_name)

                    if not unit:
                        print('%s) Unit not found: %s' % (i, row.unit_name))
                        continue

                    vehicle_type = unit['vehicle_type'].lower()
                    if mobile_vehicle_types and vehicle_type \
                            and vehicle_type not in mobile_vehicle_types:
                        print('%s) Skip vehicle type "%s" of item %s' % (
                            i, vehicle_type, row.unit_name
                        ))
                        continue

                    print('%s) Processing %s' % (i, row.unit_name))
                    ecodriving = get_drive_rank_settings(unit['id'], sess_id)
                    ecodriving = {k.lower(): v for k, v in ecodriving.items()}
                    report_row = self.new_grouping(row, unit)

                    # собственно расчеты метрик
                    for violation in violations:
                        verbose = violation.violation_name
                        violation_name = ''
                        violation_scope = 'violations_measures'

                        if 'превышение скорости' in verbose:
                            if 'cреднее' in verbose or 'среднее' in verbose:
                                violation_name = 'avg_overspeed'
                            elif 'опасное' in verbose:
                                violation_name = 'critical_overspeed'
                        elif 'ремень' in verbose or 'ремня' in verbose:
                            violation_name = 'belt'
                        elif 'фар' in verbose:
                            violation_name = 'lights'
                        elif 'кму' in verbose or 'стрел' in verbose:
                            violation_name = 'jib'
                        elif 'разгон' in verbose or 'ускорение' in verbose:
                            violation_scope = 'per_100km_count'
                            violation_name = 'accelerations'
                        elif 'торможение' in verbose:
                            violation_scope = 'per_100km_count'
                            violation_name = 'brakings'
                        elif 'поворот' in verbose:
                            violation_scope = 'per_100km_count'
                            violation_name = 'turns'

                        if not violation_name:
                            print(
                                '%s) %s: unknown violaton name %s' % (
                                    i, row.unit_name, verbose
                                )
                            )
                            continue

                        scope = report_row[violation_scope][violation_name]

                        if violation_scope == 'per_100km_count':
                            scope['count'] += violation.violation_count / row.mileage * 100
                            scope['total_count'] += violation.violation_count
                        else:
                            scope['count'] += violation.violation_count
                            scope['total_time_percentage'] += (
                                violation.duration / row.duration * 100
                            )
                            scope['time_sec'] += violation.duration

                        # суммируем штрафы
                        rating_violation_name = violation_name
                        if 'overspeed' in violation_name:
                            rating_violation_name = 'overspeed'

                        # извлечем настройки объектов и узнаем, нужно ли рассчитывать
                        # относительно пробега
                        settings = ecodriving.get(verbose)
                        devider = 1
                        if settings and settings.get('flags', 0) in (2, 3, 7, 10):
                            devider = max(1.0, row.mileage)
                        fine = violation.fine / devider
                        report_row['rating'][rating_violation_name]['fine'] += fine
                        report_row['rating_total']['avg']['fine'] += fine

                        if rating_violation_name in ('belt', 'lights', 'jib', 'brakings'):
                            report_row['rating_total']['critical_avg']['fine'] += fine

                    # расчет статистики (рейтинга)
                    for key in report_row['rating']:
                        scope = report_row['rating'][key]
                        scope['rating'] = self.calculate_rating(scope['fine'])
                    report_row['rating_total']['avg']['rating'] = self.calculate_rating(
                        report_row['rating_total']['avg']['fine']
                    )
                    report_row['rating_total']['critical_avg']['rating'] = self.calculate_rating(
                        report_row['rating_total']['critical_avg']['fine']
                    )

                    report_data.append(report_row)

                # группируем строки по нарушителям и сортируем, самых нарушающих наверх
                groups = defaultdict(lambda: {
                    'rows': [],
                    'driver_id': '',
                    'driver_fio': '',
                    'company_name': ''
                })

                # сначала отсортируем без группировки,
                # чтобы внутри групп была правильная сортировка
                report_data = sorted(
                    report_data,
                    key=lambda x: x['rating_total']['critical_avg']['rating']
                )

                for row in report_data:
                    group = groups[row['driver_id']]
                    group['driver_id'] = row['driver_id']
                    group['driver_fio'] = self.driver_cache.get(row['driver_id'], DRIVER_NO_NAME)
                    group['company_name'] = user.company_name or user.wialon_resource_name or ''
                    group['stats'] = self.new_grouping()
                    group['rows'].append(row)

                # собираем суммарную статистику по каждому водителю
                report_data = list(groups.values())
                for group in report_data:
                    for row in group['rows']:
                        group['stats']['total_mileage'] += row['total_mileage']
                        group['stats']['total_duration'] += row['total_duration']

                    for field in ('avg_overspeed', 'critical_overspeed', 'belt', 'lights', 'jib'):
                        for row in group['rows']:
                            group['stats']['violations_measures'][field]['count'] += \
                                row['violations_measures'][field]['count']

                            group['stats']['violations_measures'][field]['time_sec'] += \
                                row['violations_measures'][field]['time_sec']

                        group['stats']['violations_measures'][field]['total_time_percentage'] = \
                            group['stats']['violations_measures'][field]['time_sec'] \
                            / group['stats']['total_duration'] * 100.0

                    for field in ('brakings', 'accelerations', 'turns'):
                        for row in group['rows']:
                            group['stats']['per_100km_count'][field]['total_count'] += \
                                row['per_100km_count'][field]['total_count']

                        group['stats']['per_100km_count'][field]['count'] = \
                            group['stats']['per_100km_count'][field]['total_count'] \
                            / group['stats']['total_mileage'] * 100

                    for field in (
                        'overspeed', 'belt', 'lights', 'brakings', 'accelerations', 'turns', 'jib'
                    ):
                        for row in group['rows']:
                            fine = row['rating'][field]['fine']
                            group['stats']['rating'][field]['fine'] += fine
                            group['stats']['rating_total']['avg']['fine'] += fine

                            if field in ('belt', 'lights', 'jib', 'brakings'):
                                group['stats']['rating_total']['critical_avg']['fine'] += fine

                    # расчет статистики (рейтинга)
                    for key in group['stats']['rating']:
                        scope = group['stats']['rating'][key]
                        scope['rating'] = self.calculate_rating(scope['fine'])
                    group['stats']['rating_total']['avg']['rating'] = self.calculate_rating(
                        group['stats']['rating_total']['avg']['fine']
                    )
                    group['stats']['rating_total']['critical_avg']['rating'] = \
                        self.calculate_rating(
                            group['stats']['rating_total']['critical_avg']['fine']
                        )

                total_report_data.extend(report_data)

            # финально отсортируем всю группу
            total_report_data = sorted(
                total_report_data,
                key=lambda x: x['stats']['rating_total']['critical_avg']['rating']
            )

        kwargs['report_data'] = total_report_data
        return kwargs