示例#1
0
 def parse_report_row(self, row, user, total=False):
     return ReportRow(
         row[0],  # unit_name
         row[1].lower().strip(),  # violation
         int(row[2]) if row[2] else 1,  # violation_count
         self.duration_cache.get(row[0], 0)
         if total else parse_timedelta(row[3]).total_seconds(),  # duration
         parse_float(row[4], default=.0),  # fine
         parse_float(row[5], default=.0),  # rating
         local_to_utc_time(
             parse_wialon_report_datetime(
                 row[6]['t'] if isinstance(row[6], dict) else row[6]
             ), user.timezone
         ),  # from_dt
         local_to_utc_time(
             parse_wialon_report_datetime(
                 row[7]['t'] if isinstance(row[7], dict) else row[7]
             ), user.timezone
         ),  # to_dt
         self.mileage_cache.get(row[0], .0)
         if total else parse_float(row[9], default=.0)  # mileage_corrected
     )
示例#2
0
    def get_report_data(self):
        self.get_report_data_tables()

        self.request_dt_from, self.request_dt_to = get_period(
            self.input_data['date_begin'], self.input_data['date_end'])

        cleanup_and_request_report(self.request.user,
                                   self.report_template_id,
                                   self.sess_id,
                                   item_id=self.unit_id)

        try:
            r = exec_report(self.request.user,
                            self.report_template_id,
                            self.sess_id,
                            self.request_dt_from,
                            self.request_dt_to,
                            object_id=self.unit_id)
        except ReportException as e:
            raise WialonException(
                'Не удалось получить в Wialon отчет о поездках: %s' % e)

        for table_index, table_info in enumerate(r['reportResult']['tables']):
            if table_info['name'] not in self.report_data:
                continue

            try:
                rows = get_report_rows(self.sess_id,
                                       table_index,
                                       table_info['rows'],
                                       level=1)

                if table_info['name'] != 'unit_sensors_tracing':
                    self.report_data[table_info['name']] = rows
                else:
                    for row in rows:
                        if isinstance(row['c'][0], dict):
                            key = row['c'][0]['v']
                        else:
                            key = parse_wialon_report_datetime(row['c'][0])
                            key = int(
                                local_to_utc_time(
                                    key,
                                    self.request.user.timezone).timestamp())
                        if key and row['c'][1]:
                            self.fuel_data[key] = parse_float(
                                row['c'][1]) or .0

            except ReportException as e:
                raise WialonException(
                    'Не удалось получить в Wialon отчет о поездках: %s' % e)
示例#3
0
    def report_post_processing(self, unit_info):
        job_date_begin = utc_to_local_time(
            self.job.date_begin.replace(tzinfo=None),
            self.request.user.timezone)
        job_date_end = utc_to_local_time(
            self.job.date_end.replace(tzinfo=None), self.request.user.timezone)

        for point in self.ride_points:
            point['time_in'] = utc_to_local_time(
                datetime.datetime.utcfromtimestamp(point['time_in']),
                self.request.user.timezone)
            point['time_out'] = utc_to_local_time(
                datetime.datetime.utcfromtimestamp(point['time_out']),
                self.request.user.timezone)

            if point['time_in'] >= job_date_begin and point[
                    'time_out'] <= job_date_end:
                point['job_id'] = self.job.pk

        for row in self.report_data['unit_thefts']:
            volume = parse_float(row['c'][2])

            if volume > .0 and row['c'][1]:
                dt = utc_to_local_time(
                    parse_wialon_report_datetime(
                        row['c'][1]['t'] if isinstance(row['c'][1], dict
                                                       ) else row['c'][1]),
                    self.request.user.timezone)

                for point in self.ride_points:
                    if point['time_in'] <= dt <= point['time_out']:
                        point['params']['fuelDrain'] += volume
                        break

        for row in self.report_data['unit_fillings']:
            volume = parse_float(row['c'][1])

            if volume > .0:
                dt = utc_to_local_time(
                    parse_wialon_report_datetime(
                        row['c'][0]['t'] if isinstance(row['c'][0], dict
                                                       ) else row['c'][0]),
                    self.request.user.timezone)

                for point in self.ride_points:
                    if point['time_in'] <= dt <= point['time_out']:
                        point['params']['fuelRefill'] += volume
                        break

        # рассчитываем моточасы пропорционально интервалам
        for row in self.report_data['unit_engine_hours']:
            time_from = utc_to_local_time(
                parse_wialon_report_datetime(row['c'][0]['t'] if isinstance(
                    row['c'][0], dict) else row['c'][0]),
                self.request.user.timezone)

            time_until_value = row['c'][1]['t'] \
                if isinstance(row['c'][1], dict) else row['c'][1]

            if 'unknown' in time_until_value.lower():
                time_until = utc_to_local_time(self.input_data['date_end'],
                                               self.request.user.timezone)
            else:
                time_until = utc_to_local_time(
                    parse_wialon_report_datetime(time_until_value),
                    self.request.user.timezone)

            for point in self.ride_points:
                if point['time_in'] > time_until:
                    # дальнейшие строки точно не совпадут (виалон все сортирует по дате)
                    break

                # если интервал точки меньше даты начала моточасов, значит еще не дошли
                if point['time_out'] < time_from:
                    continue

                delta = min(time_until, point['time_out']) - max(
                    time_from, point['time_in'])
                # не пересекаются:
                if delta.total_seconds() <= 0:
                    continue

                point['params']['motoHours'] += delta.total_seconds()

        for row in self.report_data['unit_chronology']:
            row_data = row['c']
            if not isinstance(row_data[0], str):
                send_trigger_email(
                    'В хронологии первое поле отчета не строка!',
                    extra_data={
                        'POST': self.request.body,
                        'row_data': row_data,
                        'user': self.request.user
                    })
                continue

            time_from = utc_to_local_time(
                parse_wialon_report_datetime(row_data[1]['t'] if isinstance(
                    row_data[1], dict) else row_data[1]),
                self.request.user.timezone)

            time_until_value = row_data[2]['t'] \
                if isinstance(row_data[2], dict) else row_data[2]

            if 'unknown' in time_until_value.lower():
                time_until = self.input_data['date_end']
            else:
                time_until = utc_to_local_time(
                    parse_wialon_report_datetime(time_until_value),
                    self.request.user.timezone)

            for point in self.ride_points:
                if point['time_in'] > time_until:
                    # дальнейшие строки точно не совпадут (виалон все сортирует по дате)
                    break

                # если интервал точки меньше даты начала хронологии, значит еще не дошли
                if point['time_out'] < time_from:
                    continue

                delta = min(time_until, point['time_out']) - max(
                    time_from, point['time_in'])
                # не пересекаются:
                if delta.total_seconds() < 0:
                    continue

                if row_data[0].lower() in ('поездка', 'trip'):
                    point['params']['moveMinutes'] += delta.total_seconds()

        # рассчитываем время работы крановой установки пропорционально интервалам
        # (только лишь ради кэша, необходимости в расчетах нет)
        for row in self.report_data['unit_digital_sensors']:
            time_from = utc_to_local_time(
                parse_wialon_report_datetime(row['c'][0]['t'] if isinstance(
                    row['c'][0], dict) else row['c'][0]),
                self.request.user.timezone)

            time_until_value = row['c'][1]['t'] \
                if isinstance(row['c'][1], dict) else row['c'][1]

            if 'unknown' in time_until_value.lower():
                time_until = utc_to_local_time(self.input_data['date_end'],
                                               self.request.user.timezone)
            else:
                time_until = utc_to_local_time(
                    parse_wialon_report_datetime(time_until_value),
                    self.request.user.timezone)

            for point in self.ride_points:
                if point['time_in'] > time_until:
                    # дальнейшие строки точно не совпадут (виалон все сортирует по дате)
                    break

                # если интервал точки меньше даты начала моточасов, значит еще не дошли
                if point['time_out'] < time_from:
                    continue

                delta = min(time_until, point['time_out']) - max(
                    time_from, point['time_in'])
                # не пересекаются:
                if delta.total_seconds() <= 0:
                    continue

                point['params']['GPMTime'] += delta.total_seconds()

        for point in self.ride_points:
            point['params']['stopMinutes'] = (
                point['time_out'] - point['time_in']
            ).total_seconds() - point['params']['moveMinutes']
示例#4
0
 def __init__(self, dt, volume, *args, **kwargs):
     tz = kwargs.pop('tz')
     self.dt = local_to_utc_time(parse_wialon_report_datetime(dt), tz)
     self.volume = parse_float(volume)
示例#5
0
    def get_context_data(self, **kwargs):
        kwargs = super(DischargeView, self).get_context_data(**kwargs)
        form = kwargs['form']
        report_data = None

        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 self.request.POST:
            if form.is_valid():
                report_data = OrderedDict()

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

                if not self.user:
                    raise ReportException(WIALON_USER_NOT_FOUND)

                normal_ratio = 1 + (
                    form.cleaned_data['overspanding_percentage'] / 100)

                units_dict = OrderedDict((u['id'], u) for u in units_list)
                selected_unit = form.cleaned_data.get('unit')

                if selected_unit and selected_unit in units_dict:
                    units_dict = {selected_unit: units_dict[selected_unit]}

                jobs_count = len(units_dict)
                print('Всего ТС: %s' % jobs_count)

                dt_from_utc = local_to_utc_time(form.cleaned_data['dt_from'],
                                                self.user.timezone)
                dt_to_utc = local_to_utc_time(
                    form.cleaned_data['dt_to'].replace(hour=23,
                                                       minute=59,
                                                       second=59),
                    self.user.timezone)

                ura_user = self.user.ura_user if self.user.ura_user_id else self.user
                jobs = Job.objects.filter(user=ura_user,
                                          date_begin__lt=dt_to_utc,
                                          date_end__gt=dt_from_utc).order_by(
                                              'date_begin', 'date_end')

                jobs_cache = defaultdict(list)
                for job in jobs:
                    try:
                        jobs_cache[int(job.unit_id)].append(job)
                    except ValueError:
                        pass

                template_id = get_wialon_report_template_id(
                    'discharge_individual', self.user, sess_id)
                device_fields = defaultdict(lambda: {'extras': .0, 'idle': .0})

                i = 0
                for unit_id, unit in units_dict.items():
                    i += 1
                    unit_name = unit['name']
                    print('%s/%s) %s' % (i, jobs_count, unit_name))

                    # норматив потребления доп.оборудования, л / час
                    extras_values = [
                        x['v'] for x in unit.get('fields', [])
                        if x.get('n') == 'механизм'
                    ]
                    # норматив потребления на холостом ходу, л / час
                    idle_values = [
                        x['v'] for x in unit.get('fields', [])
                        if x.get('n') == 'хх'
                    ]

                    if extras_values:
                        try:
                            device_fields[unit_name]['extras'] = float(
                                extras_values[0])
                        except ValueError:
                            pass

                    if idle_values:
                        try:
                            device_fields[unit_name]['idle'] = float(
                                idle_values[0])
                        except ValueError:
                            pass

                    if unit_id not in report_data:
                        report_data[unit_id] = self.get_new_grouping()

                    report_row = report_data[unit_id]
                    report_row['unit_name'] = unit_name
                    report_row['unit_number'] = unit.get('number', '')
                    report_row['vehicle_type'] = unit.get('vehicle_type', '')

                    unit_jobs = jobs_cache.get(unit_id)
                    if not unit_jobs:
                        report_row['periods'].append(
                            self.get_new_period(dt_from_utc, dt_to_utc))
                    else:
                        if unit_jobs[0].date_begin > dt_from_utc:
                            # если начало периода не попадает на смену
                            report_row['periods'].append(
                                self.get_new_period(dt_from_utc,
                                                    unit_jobs[0].date_begin))

                        previous_job = None
                        for unit_job in unit_jobs:
                            # если между сменами есть перерыв, то тоже добавляем период
                            if previous_job and unit_job.date_begin > previous_job.date_end:
                                report_row['periods'].append(
                                    self.get_new_period(
                                        previous_job.date_end,
                                        unit_job.date_begin))

                            report_row['periods'].append(
                                self.get_new_period(unit_job.date_begin,
                                                    unit_job.date_end,
                                                    unit_job))

                            previous_job = unit_job

                        if unit_jobs[-1].date_end < dt_to_utc:
                            # если смена закончилась до конца периода
                            report_row['periods'].append(
                                self.get_new_period(unit_jobs[-1].date_end,
                                                    dt_to_utc))

                    # получим полный диапазон запроса
                    dt_from = int(
                        time.mktime(
                            report_row['periods'][0]['dt_from'].timetuple()))
                    dt_to = int(
                        time.mktime(
                            report_row['periods'][-1]['dt_to'].timetuple()))

                    cleanup_and_request_report(self.user, template_id, sess_id)

                    r = exec_report(self.user,
                                    template_id,
                                    sess_id,
                                    dt_from,
                                    dt_to,
                                    object_id=unit_id)

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

                        wialon_report_rows[table_info['name']] = [
                            row['c'] for row in rows
                        ]

                    for period in report_row['periods']:
                        for row in wialon_report_rows.get('unit_trips', []):
                            row_dt_from, row_dt_to = self.parse_wialon_report_datetime(
                                row)
                            if row_dt_to is None:
                                row_dt_to = period['dt_to']

                            if period['dt_from'] < row_dt_from and period[
                                    'dt_to'] > row_dt_to:
                                delta = (min(row_dt_to, period['dt_to']) -
                                         max(row_dt_from, period['dt_from'])
                                         ).total_seconds()
                                if not delta:
                                    print('empty trip period')
                                    continue

                                trip_ratio = 1
                                total_delta = (row_dt_to -
                                               row_dt_from).total_seconds()
                                if total_delta > 0:
                                    trip_ratio = delta / total_delta
                                period['mileage'] += parse_float(
                                    row[3]) * trip_ratio
                                period['move_hours'] += parse_timedelta(row[4]).total_seconds()\
                                    * trip_ratio

                        for row in wialon_report_rows.get(
                                'unit_digital_sensors', []):
                            row_dt_from, row_dt_to = self.parse_wialon_report_datetime(
                                row)
                            if row_dt_to is None:
                                row_dt_to = period['dt_to']

                            if period['dt_from'] < row_dt_from and period[
                                    'dt_to'] > row_dt_to:
                                delta = min(row_dt_to, period['dt_to']) - \
                                        max(row_dt_from, period['dt_from'])
                                period[
                                    'extra_device_hours'] += delta.total_seconds(
                                    )

                        for row in wialon_report_rows.get('unit_thefts', []):
                            dt = parse_wialon_report_datetime(
                                row[1]['t'] if isinstance(row[1], dict
                                                          ) else row[1])
                            utc_dt = local_to_utc_time(dt, self.user.timezone)
                            if period['dt_from'] <= utc_dt <= period['dt_to']:

                                place = row[0]['t'] if isinstance(
                                    row[0], dict) else (row[0] or '')
                                if place and not period['discharge']['place']:
                                    period['discharge']['place'] = place

                                if not period['discharge']['dt']:
                                    period['discharge']['dt'] = dt

                                try:
                                    volume = float(
                                        row[2].split(' ')[0] if row[2] else .0)
                                except ValueError:
                                    volume = .0

                                period['discharge']['volume'] += volume
                                self.stats['discharge_total'] += volume
                                self.stats['overspanding_count'] += 1

                                period['details'].append({
                                    'place': place,
                                    'dt': dt,
                                    'volume': volume
                                })

                        extras_value = device_fields.get(unit_name,
                                                         {}).get('extras', .0)
                        idle_value = device_fields.get(unit_name,
                                                       {}).get('idle', .0)

                        for row in wialon_report_rows.get(
                                'unit_engine_hours', []):
                            row_dt_from, row_dt_to = self.parse_wialon_report_datetime(
                                row)
                            if row_dt_to is None:
                                row_dt_to = period['dt_to']

                            if period['dt_from'] < row_dt_from and period[
                                    'dt_to'] > row_dt_to:
                                delta = (min(row_dt_to, period['dt_to']) -
                                         max(row_dt_from, period['dt_from'])
                                         ).total_seconds()

                                if not delta:
                                    print('empty motohours period')
                                    continue

                                total_delta = (row_dt_to -
                                               row_dt_from).total_seconds()

                                period['moto_hours'] += delta
                                # доля моточасов в периоде и общих моточасов в строке
                                # данный множитель учтем в расчетах потребления
                                moto_ratio = 1
                                if total_delta > 0:
                                    moto_ratio = delta / total_delta

                                try:
                                    period['fact_dut'] += (float(
                                        parse_float(row[3])) if row[3] else
                                                           .0) * moto_ratio

                                except ValueError:
                                    pass

                                try:
                                    period['fact_mileage'] += (float(
                                        parse_float(row[4])) if row[4] else
                                                               .0) * moto_ratio

                                except ValueError:
                                    pass

                                try:
                                    period['idle_hours'] += (parse_timedelta(
                                        row[6]).total_seconds() if row[6] else
                                                             .0) * moto_ratio

                                except ValueError:
                                    pass

                        if idle_value:
                            period['fact_motohours'] = max(
                                period['moto_hours'] - period['move_hours'] -
                                period['extra_device_hours'],
                                .0) / 3600.0 * idle_value

                        if extras_value:
                            period['fact_extra_device'] = \
                                period['extra_device_hours'] / 3600.0 * extras_value

                        total_facts = period['fact_extra_device'] \
                            + period['fact_motohours'] \
                            + period['fact_mileage']

                        if total_facts:
                            ratio = period['fact_dut'] / total_facts
                            if ratio >= normal_ratio:
                                overspanding = period['fact_dut'] \
                                   - total_facts

                                period['overspanding'] = overspanding
                                self.stats[
                                    'overspanding_total'] += overspanding

                        period['dt_from'] = utc_to_local_time(
                            period['dt_from'], self.user.timezone)
                        period['dt_to'] = utc_to_local_time(
                            period['dt_to'], self.user.timezone)

                if report_data:
                    for k, v in report_data.items():
                        v['periods'] = [
                            p for p in v['periods']
                            if p['discharge']['volume'] > .0
                            or p['overspanding'] > 0
                        ]

                    report_data = OrderedDict(
                        (k, v) for k, v in report_data.items() if v['periods'])

        kwargs.update(enumerate=enumerate,
                      report_data=report_data,
                      today=datetime.date.today(),
                      stats=self.stats)

        return kwargs
示例#6
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