コード例 #1
0
def solutions(request, sid, problem_key):
    statistic = get_object_or_404(Statistics.objects.select_related(
        'account', 'contest', 'contest__resource'),
                                  pk=sid)
    problems = statistic.addition.get('problems', {})
    if problem_key not in problems:
        return HttpResponseNotFound()

    contest_problems = statistic.contest.info['problems']
    if 'division' in contest_problems:
        contest_problems = contest_problems['division'][
            statistic.addition['division']]
    for problem in contest_problems:
        if get_problem_short(problem) == problem_key:
            break
    else:
        problem = None

    stat = problems[problem_key]

    if 'solution' not in stat:
        if stat.get('external_solution'):
            plugin = statistic.contest.resource.plugin
            try:
                source_code = plugin.Statistic.get_source_code(
                    statistic.contest, stat)
                stat.update(source_code)
            except (NotImplementedError, ExceptionParseStandings,
                    FailOnGetResponse):
                return HttpResponseNotFound()
        else:
            return HttpResponseNotFound()

    return render(
        request,
        'solution-source.html' if request.is_ajax() else 'solution.html', {
            'is_modal': request.is_ajax(),
            'statistic': statistic,
            'account': statistic.account,
            'contest': statistic.contest,
            'problem': problem,
            'stat': stat,
            'fields': ['time', 'status', 'language'],
        })
コード例 #2
0
def standings(request,
              title_slug=None,
              contest_id=None,
              template='standings.html',
              extra_context=None):
    context = {}

    groupby = request.GET.get('groupby')
    if groupby == 'none':
        groupby = None

    search = request.GET.get('search')
    if search == '':
        url = request.get_full_path()
        url = re.sub('search=&?', '', url)
        url = re.sub(r'\?$', '', url)
        return redirect(url)
    orderby = request.GET.getlist('orderby')
    if orderby:
        if '--' in orderby:
            updated_orderby = []
        else:
            orderby_set = set()
            unique_orderby = reversed([
                f for k, f in [(f.lstrip('-'), f) for f in reversed(orderby)]
                if k not in orderby_set and not orderby_set.add(k)
            ])
            updated_orderby = [
                f for f in unique_orderby if not f.startswith('--')
            ]

        if updated_orderby != orderby:
            query = request.GET.copy()
            query.setlist('orderby', updated_orderby)
            return redirect(f'{request.path}?{query.urlencode()}')

    contests = Contest.objects
    to_redirect = False
    contest = None
    if contest_id is not None:
        contest = contests.filter(pk=contest_id).first()
        if title_slug is None:
            to_redirect = True
        else:
            if contest is None or slug(contest.title) != title_slug:
                contest = None
                title_slug += f'-{contest_id}'
    if contest is None and title_slug is not None:
        contests_iterator = contests.filter(slug=title_slug).iterator()

        contest = None
        try:
            contest = next(contests_iterator)
            another = next(contests_iterator)
        except StopIteration:
            another = None
        if contest is None:
            return HttpResponseNotFound()
        if another is None:
            to_redirect = True
        else:
            return redirect(
                reverse('ranking:standings_list') +
                f'?search=slug:{title_slug}')
    if contest is None:
        return HttpResponseNotFound()
    if to_redirect:
        query = query_transform(request)
        url = reverse('ranking:standings',
                      kwargs={
                          'title_slug': slug(contest.title),
                          'contest_id': str(contest.pk)
                      })
        if query:
            query = '?' + query
        return redirect(url + query)

    with_detail = request.GET.get('detail', 'true') in ['true', 'on']
    if request.user.is_authenticated:
        coder = request.user.coder
        if 'detail' in request.GET:
            coder.settings['standings_with_detail'] = with_detail
            coder.save()
        else:
            with_detail = coder.settings.get('standings_with_detail', False)
    else:
        coder = None

    with_row_num = False

    contest_fields = list(contest.info.get('fields', []))
    hidden_fields = list(contest.info.get('hidden_fields', []))

    statistics = Statistics.objects.filter(contest=contest)

    options = contest.info.get('standings', {})

    order = None
    resource_standings = contest.resource.info.get('standings', {})
    order = copy.copy(options.get('order', resource_standings.get('order')))
    if order:
        for f in order:
            if f.startswith('addition__') and f.split(
                    '__', 1)[1] not in contest_fields:
                order = None
                break
    if order is None:
        order = ['place_as_int', '-solving']

    # fixed fields
    fixed_fields = (
        ('penalty', 'Penalty'),
        ('total_time', 'Time'),
        ('advanced', 'Advance'),
    )
    fixed_fields += tuple(options.get('fixed_fields', []))
    if not with_detail:
        fixed_fields += (('rating_change', 'Rating change'), )

    statistics = statistics \
        .select_related('account') \
        .select_related('account__resource') \
        .prefetch_related('account__coders')

    has_country = ('country' in contest_fields
                   or '_countries' in contest_fields or
                   statistics.filter(account__country__isnull=False).exists())

    division = request.GET.get('division')
    if division == 'any':
        with_row_num = True
        if 'place_as_int' in order:
            order.remove('place_as_int')
            order.append('place_as_int')
        fixed_fields += (('division', 'Division'), )

    if 'team_id' in contest_fields and not groupby:
        order.append('addition__name')

        statistics = statistics.distinct(*[f.lstrip('-') for f in order])

        # host = resource_standings.get('account_team_resource', contest.resource.host)
        # account_team_resource = Resource.objects.get(host=host)
        # context['account_team_resource'] = account_team_resource

        # statistics = statistics.annotate(
        #     accounts=RawSQL(
        #         '''
        #         SELECT array_agg(array[u2.key, u3.rating::text, u3.url])
        #         FROM       "ranking_statistics" U0
        #         INNER JOIN "ranking_account" U2
        #         ON         (u0."account_id" = u2."id")
        #         INNER JOIN "ranking_account" U3
        #         ON         (u2."key" = u3."key" AND u3."resource_id" = %s)
        #         WHERE      (
        #             u0."contest_id" = %s
        #             AND ("u0"."addition" -> 'team_id') = ("ranking_statistics"."addition" -> 'team_id')
        #         )
        #         ''',
        #         [account_team_resource.pk, contest.pk]
        #     )
        # )

    order.append('pk')
    statistics = statistics.order_by(*order)

    fields = OrderedDict()
    for k, v in fixed_fields:
        if k in contest_fields:
            fields[k] = v

    n_highlight_context = _standings_highlight(statistics, options)

    # field to select
    fields_to_select_defaults = {
        'rating': {
            'options': ['rated', 'unrated'],
            'noajax': True,
            'nomultiply': True,
            'nourl': True
        },
        'advanced': {
            'options': ['true', 'false'],
            'noajax': True,
            'nomultiply': True
        },
        'highlight': {
            'options': ['true', 'false'],
            'noajax': True,
            'nomultiply': True
        },
    }

    fields_to_select = OrderedDict()
    map_fields_to_select = {'rating_change': 'rating'}
    for f in sorted(contest_fields):
        f = f.strip('_')
        if f.lower() in [
                'institution',
                'room',
                'affiliation',
                'city',
                'languages',
                'school',
                'class',
                'job',
                'region',
                'rating_change',
                'advanced',
                'company',
                'language',
                'league',
                'onsite',
                'degree',
                'university',
                'list',
        ]:
            f = map_fields_to_select.get(f, f)
            field_to_select = fields_to_select.setdefault(f, {})
            field_to_select['values'] = [
                v for v in request.GET.getlist(f) if v
            ]
            field_to_select.update(fields_to_select_defaults.get(f, {}))

    if n_highlight_context.get('statistics_ids'):
        f = 'highlight'
        field_to_select = fields_to_select.setdefault(f, {})
        field_to_select['values'] = [v for v in request.GET.getlist(f) if v]
        field_to_select.update(fields_to_select_defaults.get(f, {}))

    chats = coder.chats.all() if coder else None
    if chats:
        options_values = {c.chat_id: c.title for c in chats}
        fields_to_select['chat'] = {
            'values': [
                v for v in request.GET.getlist('chat')
                if v and v in options_values
            ],
            'options':
            options_values,
            'noajax':
            True,
            'nogroupby':
            True,
            'nourl':
            True,
        }

    hidden_fields_values = [v for v in request.GET.getlist('field') if v]
    for v in hidden_fields_values:
        if v not in hidden_fields:
            hidden_fields.append(v)

    for k in contest_fields:
        if (k in fields or k in [
                'problems', 'team_id', 'solved', 'hack', 'challenges', 'url',
                'participant_type', 'division'
        ] or k == 'medal' and '_medal_title_field' in contest_fields
                or 'country' in k and k not in hidden_fields_values
                or k in ['name'] and k not in hidden_fields_values
                or k.startswith('_')
                or k in hidden_fields and k not in hidden_fields_values):
            continue
        if with_detail or k in hidden_fields_values:
            fields[k] = k
        else:
            hidden_fields.append(k)

    for k, field in fields.items():
        if k != field:
            continue
        field = ' '.join(k.split('_'))
        if field and not field[0].isupper():
            field = field.title()
        fields[k] = field

    if hidden_fields:
        fields_to_select['field'] = {
            'values': hidden_fields_values,
            'options': hidden_fields,
            'noajax': True,
            'nogroupby': True,
            'nourl': True,
            'nofilter': True,
        }

    per_page = options.get('per_page', 50)
    if per_page is None:
        per_page = 100500
    elif contest.n_statistics and contest.n_statistics < 500:
        per_page = contest.n_statistics

    mod_penalty = {}
    first = statistics.first()
    if first and all('time' not in k for k in contest_fields):
        penalty = first.addition.get('penalty')
        if penalty and isinstance(penalty,
                                  int) and 'solved' not in first.addition:
            mod_penalty.update({'solving': first.solving, 'penalty': penalty})

    params = {}
    problems = contest.info.get('problems', {})
    if 'division' in problems:
        divisions_order = list(
            problems.get('divisions_order',
                         sorted(contest.info['problems']['division'].keys())))
    elif 'divisions_order' in contest.info:
        divisions_order = contest.info['divisions_order']
    else:
        divisions_order = []

    if divisions_order:
        divisions_order.append('any')
        if division not in divisions_order:
            division = divisions_order[0]
        params['division'] = division
        if 'division' in problems:
            if division == 'any':
                _problems = OrderedDict()
                for div in reversed(divisions_order):
                    for p in problems['division'].get(div, []):
                        k = get_problem_short(p)
                        if k not in _problems:
                            _problems[k] = p
                        else:
                            for f in 'n_accepted', 'n_teams', 'n_partial', 'n_total':
                                if f in p:
                                    _problems[k][f] = _problems[k].get(
                                        f, 0) + p[f]
                problems = list(_problems.values())
            else:
                problems = problems['division'][division]
        if division != 'any':
            statistics = statistics.filter(addition__division=division)

    for p in problems:
        if 'full_score' in p and isinstance(
                p['full_score'],
            (int, float)) and abs(p['full_score'] - 1) > 1e-9:
            mod_penalty = {}
            break

    last = None
    merge_problems = False
    for p in problems:
        if last and (last.get('full_score') or last.get('subname')) and (
                'name' in last and last.get('name') == p.get('name')
                or 'group' in last and last.get('group') == p.get('group')):
            merge_problems = True
            last['colspan'] = last.get('colspan', 1) + 1
            p['skip'] = True
        else:
            last = p
            last['colspan'] = 1

    # own_stat = statistics.filter(account__coders=coder).first() if coder else None

    # filter by search
    search = request.GET.get('search')
    if search:
        with_row_num = True
        if search.startswith('party:'):
            _, party_slug = search.split(':')
            party = get_object_or_404(Party.objects.for_user(request.user),
                                      slug=party_slug)
            statistics = statistics.filter(
                Q(account__coders__in=party.coders.all())
                | Q(account__coders__in=party.admins.all())
                | Q(account__coders=party.author))
        else:
            cond = get_iregex_filter(search,
                                     'account__key',
                                     'addition__name',
                                     logger=request.logger)
            statistics = statistics.filter(cond)

    # filter by country
    countries = request.GET.getlist('country')
    countries = set([c for c in countries if c])
    if countries:
        with_row_num = True
        cond = Q(account__country__in=countries)
        if 'None' in countries:
            cond |= Q(account__country__isnull=True)
        if '_countries' in contest_fields:
            for code in countries:
                name = get_country_name(code)
                if name:
                    cond |= Q(addition___countries__icontains=name)

        statistics = statistics.filter(cond)
        params['countries'] = countries

    # filter by field to select
    for field, field_to_select in fields_to_select.items():
        values = field_to_select.get('values')
        if not values or field_to_select.get('nofilter'):
            continue
        with_row_num = True
        filt = Q()
        if field == 'languages':
            for lang in values:
                if lang == 'any':
                    filt = Q(**{'addition___languages__isnull': False})
                    break
                filt |= Q(**{'addition___languages__contains': [lang]})
        elif field == 'rating':
            for q in values:
                if q not in field_to_select['options']:
                    continue
                q = q == 'unrated'
                if q:
                    filt |= Q(addition__rating_change__isnull=True) & Q(
                        addition__new_rating__isnull=True)
                else:
                    filt |= Q(addition__rating_change__isnull=False) | Q(
                        addition__new_rating__isnull=False)
        elif field == 'advanced':
            for q in values:
                if q not in field_to_select['options']:
                    continue
                filt |= Q(addition__advanced=q == 'true')
        elif field == 'highlight':
            for q in values:
                if q not in field_to_select['options']:
                    continue
                filt = Q(pk__in=n_highlight_context.get('statistics_ids', {}))
                if q == 'false':
                    filt = ~filt
        elif field == 'chat':
            for q in values:
                if q not in field_to_select['options']:
                    continue
                chat = Chat.objects.filter(chat_id=q, is_group=True).first()
                if chat:
                    filt |= Q(account__coders__in=chat.coders.all())
            # subquery = Chat.objects.filter(coder=OuterRef('account__coders'), is_group=False).values('name')[:1]
            # statistics = statistics.annotate(chat_name=Subquery(subquery))
        else:
            query_field = f'addition__{field}'
            statistics = statistics.annotate(**{
                f'{query_field}_str':
                Cast(JSONF(query_field), models.TextField())
            })
            for q in values:
                if q == 'None':
                    filt |= Q(**{f'{query_field}__isnull': True})
                else:
                    filt |= Q(**{f'{query_field}_str': q})
        statistics = statistics.filter(filt)

    # groupby
    if groupby == 'country' or groupby in fields_to_select:
        statistics = statistics.order_by('pk')

        participants_info = n_highlight_context.get('participants_info')
        n_highlight = options.get('n_highlight')
        advanced_by_participants_info = participants_info and n_highlight and groupby != 'languages'

        fields = OrderedDict()
        fields['groupby'] = groupby.title()
        fields['n_accounts'] = 'Num'
        fields['avg_score'] = 'Avg'
        medals = {m['name']: m for m in options.get('medals', [])}
        if 'medal' in contest_fields:
            for medal in settings.ORDERED_MEDALS_:
                fields[f'n_{medal}'] = medals.get(medal, {}).get(
                    'value', medal[0].upper())
        if 'advanced' in contest_fields or advanced_by_participants_info:
            fields['n_advanced'] = 'Adv'

        orderby = [f for f in orderby if f.lstrip('-') in fields
                   ] or ['-n_accounts', '-avg_score']

        if groupby == 'languages':
            _, before_params = statistics.query.sql_with_params()
            querysets = []
            for problem in problems:
                key = get_problem_short(problem)
                field = f'addition__problems__{key}__language'
                score = f'addition__problems__{key}__result'
                qs = statistics \
                    .filter(**{f'{field}__isnull': False, f'{score}__isnull': False}) \
                    .annotate(language=Cast(JSONF(field), models.TextField())) \
                    .annotate(score=Case(
                        When(**{f'{score}__startswith': '+'}, then=1),
                        When(**{f'{score}__startswith': '-'}, then=0),
                        When(**{f'{score}__startswith': '?'}, then=0),
                        default=Cast(JSONF(score), models.FloatField()),
                        output_field=models.FloatField(),
                    )) \
                    .annotate(sid=F('pk'))
                querysets.append(qs)
            merge_statistics = querysets[0].union(*querysets[1:], all=True)
            language_query, language_params = merge_statistics.query.sql_with_params(
            )
            field = 'solving'
            statistics = statistics.annotate(groupby=F(field))
        elif groupby == 'rating':
            statistics = statistics.annotate(groupby=Case(
                When(addition__rating_change__isnull=False,
                     then=Value('Rated')),
                default=Value('Unrated'),
                output_field=models.TextField(),
            ))
        elif groupby == 'country':
            if '_countries' in contest_fields:
                statistics = statistics.annotate(country=RawSQL(
                    '''json_array_elements((("addition" ->> '_countries'))::json)::jsonb''',
                    []))
                field = 'country'
            else:
                field = 'account__country'
            statistics = statistics.annotate(groupby=F(field))
        else:
            field = f'addition__{groupby}'
            types = contest.info.get('fields_types', {}).get(groupby, [])
            if 'int' in types:
                field_type = models.IntegerField()
            elif 'float' in types:
                field_type = models.FloatField()
            else:
                field_type = models.TextField()
            statistics = statistics.annotate(
                groupby=Cast(JSONF(field), field_type))

        statistics = statistics.order_by('groupby')
        statistics = statistics.values('groupby')
        statistics = statistics.annotate(n_accounts=Count('id'))
        statistics = statistics.annotate(avg_score=Avg('solving'))

        if 'medal' in contest_fields:
            for medal in settings.ORDERED_MEDALS_:
                n_medal = f'n_{medal}'
                statistics = statistics.annotate(
                    **{
                        f'{n_medal}':
                        Count(Case(When(addition__medal__iexact=medal,
                                        then=1)))
                    })

        if 'advanced' in contest_fields:
            statistics = statistics.annotate(n_advanced=Count(
                Case(
                    When(addition__advanced=True, then=1),
                    When(~Q(addition__advanced=False)
                         & ~Q(addition__advanced=''),
                         then=1),
                )))
        elif advanced_by_participants_info:
            pks = list()
            for pk, info in participants_info.items():
                if 'n' not in info or info['n'] > info.get(
                        'n_highlight', n_highlight):
                    continue
                pks.append(pk)
            statistics = statistics.annotate(
                n_advanced=Count(Case(When(pk__in=set(pks), then=1))))

        statistics = statistics.order_by(*orderby)

        if groupby == 'languages':
            query, sql_params = statistics.query.sql_with_params()
            query = query.replace(
                f'"ranking_statistics"."{field}" AS "groupby"',
                '"language" AS "groupby"')
            query = query.replace(f'GROUP BY "ranking_statistics"."{field}"',
                                  'GROUP BY "language"')
            query = query.replace('"ranking_statistics".', '')
            query = query.replace('AVG("solving") AS "avg_score"',
                                  'AVG("score") AS "avg_score"')
            query = query.replace('COUNT("id") AS "n_accounts"',
                                  'COUNT("sid") AS "n_accounts"')
            query = re.sub('FROM "ranking_statistics".*GROUP BY',
                           f'FROM ({language_query}) t1 GROUP BY', query)
            sql_params = sql_params[:-len(before_params)] + language_params
            with connection.cursor() as cursor:
                cursor.execute(query, sql_params)
                columns = [col[0] for col in cursor.description]
                statistics = [
                    dict(zip(columns, row)) for row in cursor.fetchall()
                ]
                statistics = ListAsQueryset(statistics)

        problems = []
        labels_groupby = {
            'n_accounts': 'Number of participants',
            'avg_score': 'Average score',
            'n_advanced': 'Number of advanced',
        }
        for medal in settings.ORDERED_MEDALS_:
            labels_groupby[f'n_{medal}'] = 'Number of ' + medals.get(
                medal, {}).get('value', medal)
        num_rows_groupby = statistics.count()
        map_colors_groupby = {
            s['groupby']: idx
            for idx, s in enumerate(statistics)
        }
    else:
        groupby = 'none'
        labels_groupby = None
        num_rows_groupby = None
        map_colors_groupby = None

    my_statistics = []
    if groupby == 'none' and coder:
        statistics = statistics.annotate(
            my_stat=SubqueryExists('account__coders', filter=Q(coder=coder)))
        my_statistics = statistics.filter(account__coders=coder).extra(
            select={'floating': True})

    context.update({
        'standings_options':
        options,
        'mod_penalty':
        mod_penalty,
        'colored_by_group_score':
        mod_penalty or options.get('colored_by_group_score'),
        'contest':
        contest,
        'statistics':
        statistics,
        'my_statistics':
        my_statistics,
        'problems':
        problems,
        'params':
        params,
        'fields':
        fields,
        'fields_types':
        contest.info.get('fields_types', {}),
        'divisions_order':
        divisions_order,
        'has_country':
        has_country,
        'per_page':
        per_page,
        'with_row_num':
        with_row_num,
        'merge_problems':
        merge_problems,
        'fields_to_select':
        fields_to_select,
        'truncatechars_name_problem':
        10 * (2 if merge_problems else 1),
        'with_detail':
        with_detail,
        'groupby':
        groupby,
        'pie_limit_rows_groupby':
        50,
        'labels_groupby':
        labels_groupby,
        'num_rows_groupby':
        num_rows_groupby,
        'map_colors_groupby':
        map_colors_groupby,
        'advance':
        contest.info.get('advance'),
        'timezone':
        get_timezone(request),
        'timeformat':
        get_timeformat(request),
        'with_neighbors':
        request.GET.get('neighbors') == 'on',
        'with_table_inner_scroll':
        not request.user_agent.is_mobile,
    })

    context.update(n_highlight_context)

    if extra_context is not None:
        context.update(extra_context)

    return render(request, template, context)
コード例 #3
0
ファイル: parse_statistic.py プロジェクト: guptaarth87/clist
    def parse_statistic(
        self,
        contests,
        previous_days=None,
        freshness_days=None,
        limit=None,
        with_check=True,
        stop_on_error=False,
        random_order=False,
        no_update_results=False,
        limit_duration_in_secs=7 * 60 * 60,  # 7 hours
        title_regex=None,
        users=None,
        with_stats=True,
        update_without_new_rating=None,
    ):
        now = timezone.now()

        if with_check:
            if previous_days is not None:
                contests = contests.filter(end_time__gt=now - timedelta(days=previous_days), end_time__lt=now)
            else:
                contests = contests.filter(Q(timing__statistic__isnull=True) | Q(timing__statistic__lt=now))
                started = contests.filter(start_time__lt=now, end_time__gt=now, statistics__isnull=False)

                query = Q()
                query &= (
                    Q(end_time__gt=now - F('resource__module__max_delay_after_end'))
                    | Q(timing__statistic__isnull=True)
                )
                query &= Q(end_time__lt=now - F('resource__module__min_delay_after_end'))
                ended = contests.filter(query)

                contests = started.union(ended)
                contests = contests.distinct('id')
        elif title_regex:
            contests = contests.filter(title__iregex=title_regex)
        else:
            contests = contests.filter(end_time__lt=now - F('resource__module__min_delay_after_end'))

        if freshness_days is not None:
            contests = contests.filter(updated__lt=now - timedelta(days=freshness_days))

        if limit:
            contests = contests.order_by('-start_time')[:limit]

        with transaction.atomic():
            for c in contests:
                module = c.resource.module
                delay_on_success = module.delay_on_success or module.max_delay_after_end
                if now < c.end_time:
                    if c.end_time - c.start_time <= timedelta(seconds=limit_duration_in_secs):
                        delay_on_success = timedelta(minutes=0)
                    elif c.end_time < now + delay_on_success:
                        delay_on_success = c.end_time - now + timedelta(seconds=5)
                TimingContest.objects.update_or_create(
                    contest=c,
                    defaults={'statistic': now + delay_on_success}
                )

        if random_order:
            contests = list(contests)
            shuffle(contests)

        countrier = Countrier()

        count = 0
        total = 0
        n_upd_account_time = 0
        progress_bar = tqdm(contests)
        stages_ids = []
        for contest in progress_bar:
            resource = contest.resource
            if not hasattr(resource, 'module'):
                self.logger.error('Not found module contest = %s' % c)
                continue
            progress_bar.set_description(f'contest = {contest.title}')
            progress_bar.refresh()
            total += 1
            parsed = False
            user_info = None
            user_info_has_rating = None
            try:
                r = {}

                if hasattr(contest, 'stage'):
                    contest.stage.update()
                    count += 1
                    parsed = True
                    continue

                plugin = resource.plugin.Statistic(contest=contest)

                with REQ:
                    statistics_by_key = {} if with_stats else None
                    statistics_ids = set()
                    if not no_update_results and (users or users is None):
                        statistics = Statistics.objects.filter(contest=contest).select_related('account')
                        if users:
                            statistics = statistics.filter(account__key__in=users)
                        for s in tqdm(statistics.iterator(), 'getting parsed statistics'):
                            if with_stats:
                                statistics_by_key[s.account.key] = s.addition
                            statistics_ids.add(s.pk)
                    standings = plugin.get_standings(users=users, statistics=statistics_by_key)

                with transaction.atomic():
                    if 'url' in standings and standings['url'] != contest.standings_url:
                        contest.standings_url = standings['url']
                        contest.save()

                    if 'title' in standings and standings['title'] != contest.title:
                        contest.title = standings['title']
                        contest.save()

                    if 'options' in standings:
                        contest_options = contest.info.get('standings', {})
                        standings_options = dict(contest_options)
                        standings_options.update(standings.pop('options'))

                        if canonize(standings_options) != canonize(contest_options):
                            contest.info['standings'] = standings_options
                            contest.save()

                    info_fields = standings.pop('info_fields', []) + ['divisions_order']
                    for field in info_fields:
                        if standings.get(field) is not None and contest.info.get(field) != standings[field]:
                            contest.info[field] = standings[field]
                            contest.save()

                    update_writers(contest, standings.pop('writers', None))

                    problems_time_format = standings.pop('problems_time_format', '{M}:{s:02d}')

                    result = standings.get('result', {})
                    if not no_update_results and (result or users is not None):
                        fields_set = set()
                        fields_types = {}
                        fields = list()
                        calculate_time = False
                        d_problems = {}
                        teams_viewed = set()
                        has_hidden = False
                        languages = set()

                        additions = contest.info.get('additions', {})
                        if additions:
                            for k, v in result.items():
                                for field in [r.get('member'), r.get('name')]:
                                    r.update(OrderedDict(additions.pop(field, [])))
                            for k, v in additions.items():
                                result[k] = dict(v)

                        for r in tqdm(list(result.values()), desc=f'update results {contest}'):
                            for k, v in r.items():
                                if isinstance(v, str) and chr(0x00) in v:
                                    r[k] = v.replace(chr(0x00), '')
                            member = r.pop('member')
                            account_action = r.pop('action', None)

                            if account_action == 'delete':
                                Account.objects.filter(resource=resource, key=member).delete()
                                continue

                            account, created = Account.objects.get_or_create(resource=resource, key=member)

                            if not contest.info.get('_no_update_account_time'):
                                stats = (statistics_by_key or {}).get(member, {})
                                no_rating = with_stats and 'new_rating' not in stats and 'rating_change' not in stats
                                updated = now + timedelta(days=1)
                                wait_rating = contest.resource.info.get('statistics', {}).get('wait_rating')

                                if no_rating and wait_rating:
                                    updated = now + timedelta(hours=1)
                                    title_re = wait_rating.get('title_re')
                                    if (
                                        contest.end_time + timedelta(days=wait_rating['days']) > now
                                        and (not title_re or re.search(title_re, contest.title))
                                        and updated < account.updated
                                    ):
                                        if user_info is None:
                                            generator = plugin.get_users_infos([member], contest.resource, [account])
                                            try:
                                                user_info = next(generator)
                                                params = user_info.get('contest_addition_update_params', {})
                                                field = user_info.get('contest_addition_update_by') or params.get('by') or 'key'  # noqa
                                                updates = user_info.get('contest_addition_update') or params.get('update') or {}  # noqa
                                                user_info_has_rating = getattr(contest, field) in updates
                                            except StopIteration:
                                                user_info = False
                                                user_info_has_rating = False

                                        if user_info_has_rating:
                                            n_upd_account_time += 1
                                            account.updated = updated
                                            account.save()
                                elif (
                                    created
                                    or (not statistics_ids and updated < account.updated)
                                    or (update_without_new_rating and updated < account.updated and no_rating)
                                ):
                                    n_upd_account_time += 1
                                    account.updated = updated
                                    account.save()

                            if r.get('name'):
                                while True:
                                    name = unescape(r['name'])
                                    if name == r['name']:
                                        break
                                    r['name'] = name
                                if len(r['name']) > 1024:
                                    r['name'] = r['name'][:1020] + '...'
                                no_update_name = r.pop('_no_update_name', False)
                                if not no_update_name and account.name != r['name'] and member.find(r['name']) == -1:
                                    account.name = r['name']
                                    account.save()

                            country = r.get('country', None)
                            if country:
                                country = countrier.get(country)
                                if country and country != account.country:
                                    account.country = country
                                    account.save()

                            contest_addition_update = r.pop('contest_addition_update', {})
                            if contest_addition_update:
                                account_update_contest_additions(
                                    account,
                                    contest_addition_update,
                                    timedelta(days=31) if with_check else None
                                )

                            account_info = r.pop('info', {})
                            if account_info:
                                if 'rating' in account_info:
                                    account_info['rating_ts'] = int(now.timestamp())
                                account.info.update(account_info)
                                account.save()

                            default_division = contest.info.get('default_division')
                            if default_division and 'division' not in r:
                                r['division'] = default_division

                            problems = r.get('problems', {})

                            _languages = set()
                            for problem in problems.values():
                                if problem.get('language'):
                                    languages.add(problem['language'])
                                    _languages.add(problem['language'])
                            if '_languages' not in r and _languages:
                                r['_languages'] = list(sorted(_languages))

                            if 'team_id' not in r or r['team_id'] not in teams_viewed:
                                if 'team_id' in r:
                                    teams_viewed.add(r['team_id'])
                                solved = {'solving': 0}
                                for k, v in problems.items():
                                    if 'result' not in v:
                                        continue

                                    p = d_problems
                                    if 'division' in standings.get('problems', {}):
                                        p = p.setdefault(r['division'], {})
                                    p = p.setdefault(k, {})

                                    if 'default_problem_full_score' in contest.info:
                                        full_score = contest.info['default_problem_full_score']
                                        if 'partial' not in v and full_score - float(v['result']) > 1e-9:
                                            v['partial'] = True
                                        if not v.get('partial'):
                                            solved['solving'] += 1
                                        if 'full_score' not in p:
                                            p['full_score'] = full_score
                                    if contest.info.get('without_problem_first_ac'):
                                        v.pop('first_ac', None)
                                        v.pop('first_ac_of_all', None)
                                    if contest.info.get('without_problem_time'):
                                        v.pop('time', None)

                                    if r.get('_skip_for_problem_stat'):
                                        continue

                                    p['n_teams'] = p.get('n_teams', 0) + 1

                                    ac = str(v['result']).startswith('+')
                                    try:
                                        result = float(v['result'])
                                        ac = ac or result > 0 and not v.get('partial', False)
                                    except Exception:
                                        pass
                                    if ac:
                                        p['n_accepted'] = p.get('n_accepted', 0) + 1

                                if 'default_problem_full_score' in contest.info and solved and 'solved' not in r:
                                    r['solved'] = solved

                            calc_time = contest.calculate_time or (
                                contest.start_time <= now < contest.end_time and
                                not contest.resource.info.get('parse', {}).get('no_calculate_time', False)
                            )

                            advance = contest.info.get('advance')
                            if advance:
                                k = 'advanced'
                                r.pop(k, None)
                                for cond in advance['filter']:
                                    field = cond['field']
                                    value = r.get(field)
                                    value = get_number_from_str(value)
                                    if value is None:
                                        continue
                                    r[k] = getattr(operator, cond['operator'])(value, cond['threshold'])

                            medals = contest.info.get('standings', {}).get('medals')
                            if medals:
                                k = 'medal'
                                r.pop(k, None)
                                if 'place' in r:
                                    place = get_number_from_str(r['place'])
                                    if place:
                                        for medal in medals:
                                            if place <= medal['count']:
                                                r[k] = medal['name']
                                                if 'field' in medal:
                                                    r[medal['field']] = medal['value']
                                                    r[f'_{k}_title_field'] = medal['field']
                                                break
                                            place -= medal['count']
                                medal_fields = [m['field'] for m in medals if 'field' in m] or [k]
                                for f in medal_fields:
                                    if f not in fields_set:
                                        fields_set.add(f)
                                        fields.append(f)

                            defaults = {
                                'place': r.pop('place', None),
                                'solving': r.pop('solving', 0),
                                'upsolving': r.pop('upsolving', 0),
                            }
                            defaults = {k: v for k, v in defaults.items() if v != '__unchanged__'}

                            addition = type(r)()
                            for k, v in r.items():
                                if k[0].isalpha() and not re.match('^[A-Z]+$', k):
                                    k = k[0].upper() + k[1:]
                                    k = '_'.join(map(str.lower, re.findall('[A-ZА-Я][^A-ZА-Я]*', k)))

                                if k not in fields_set:
                                    fields_set.add(k)
                                    fields.append(k)

                                if (k in Resource.RATING_FIELDS or k == 'rating_change') and v is None:
                                    continue

                                fields_types.setdefault(k, set()).add(type(v).__name__)
                                addition[k] = v

                                if (
                                    addition.get('rating_change') is None
                                    and addition.get('new_rating') is not None
                                    and addition.get('old_rating') is not None
                                ):
                                    delta = addition['new_rating'] - addition['old_rating']
                                    f = 'rating_change'
                                    addition[f] = f'{"+" if delta > 0 else ""}{delta}'
                                    if f not in fields_set:
                                        fields_set.add(f)
                                        fields.insert(-1, f)

                            if 'is_rated' in addition and not addition['is_rated']:
                                addition.pop('old_rating', None)

                            if not calc_time:
                                defaults['addition'] = addition

                            rating_ts = int(min(contest.end_time, now).timestamp())
                            if 'new_rating' in addition and (
                                'rating_ts' not in account.info or account.info['rating_ts'] <= rating_ts
                            ):
                                account.info['rating_ts'] = rating_ts
                                account.info['rating'] = addition['new_rating']
                                account.save()

                            statistic, created = Statistics.objects.update_or_create(
                                account=account,
                                contest=contest,
                                defaults=defaults,
                            )

                            if not created:
                                statistics_ids.remove(statistic.pk)

                                if calc_time:
                                    p_problems = statistic.addition.get('problems', {})

                                    ts = min(int((now - contest.start_time).total_seconds()), contest.duration_in_secs)
                                    values = {
                                        'D': ts // (24 * 60 * 60),
                                        'H': ts // (60 * 60),
                                        'h': ts // (60 * 60) % 24,
                                        'M': ts // 60,
                                        'm': ts // 60 % 60,
                                        'S': ts,
                                        's': ts % 60,
                                    }
                                    time = problems_time_format.format(**values)

                                    for k, v in problems.items():
                                        v_result = v.get('result', '')
                                        if isinstance(v_result, str) and '?' in v_result:
                                            calculate_time = True
                                        p = p_problems.get(k, {})
                                        if 'time' in v:
                                            continue
                                        has_change = v.get('result') != p.get('result')
                                        if (not has_change or contest.end_time < now) and 'time' in p:
                                            v['time'] = p['time']
                                        else:
                                            v['time'] = time

                            for p in problems.values():
                                p_result = p.get('result', '')
                                if isinstance(p_result, str) and '?' in p_result:
                                    has_hidden = True

                            if calc_time:
                                statistic.addition = addition
                                statistic.save()

                        if users is None:
                            timing_statistic_delta = standings.get(
                                'timing_statistic_delta',
                                timedelta(minutes=30) if has_hidden and contest.end_time < now else None,
                            )
                            if timing_statistic_delta is not None:
                                contest.timing.statistic = timezone.now() + timing_statistic_delta
                                contest.timing.save()

                            if contest.start_time <= now:
                                if now < contest.end_time:
                                    contest.info['last_parse_statistics'] = now.strftime('%Y-%m-%d %H:%M:%S.%f+%Z')
                                elif 'last_parse_statistics' in contest.info:
                                    contest.info.pop('last_parse_statistics')

                            if fields_set and not isinstance(addition, OrderedDict):
                                fields.sort()
                            fields_types = {k: list(v) for k, v in fields_types.items()}

                            if statistics_ids:
                                first = Statistics.objects.filter(pk__in=statistics_ids).first()
                                if first:
                                    self.logger.info(f'First deleted: {first}, account = {first.account}')
                                delete_info = Statistics.objects.filter(pk__in=statistics_ids).delete()
                                self.logger.info(f'Delete info: {delete_info}')
                                progress_bar.set_postfix(deleted=str(delete_info))

                            if canonize(fields) != canonize(contest.info.get('fields')):
                                contest.info['fields'] = fields
                            contest.info['fields_types'] = fields_types

                            if calculate_time and not contest.calculate_time:
                                contest.calculate_time = True

                            problems = standings.pop('problems', None)
                            if problems is not None:
                                if 'division' in problems:
                                    for d, ps in problems['division'].items():
                                        for p in ps:
                                            k = get_problem_short(p)
                                            if k:
                                                p.update(d_problems.get(d, {}).get(k, {}))
                                    if isinstance(problems['division'], OrderedDict):
                                        problems['divisions_order'] = list(problems['division'].keys())
                                else:
                                    for p in problems:
                                        k = get_problem_short(p)
                                        if k:
                                            p.update(d_problems.get(k, {}))

                                update_problems(contest, problems=problems)

                            if languages:
                                languages = list(sorted(languages))
                                if canonize(languages) != canonize(contest.info.get('languages')):
                                    contest.info['languages'] = languages

                            contest.save()

                            progress_bar.set_postfix(n_fields=len(fields))
                        else:
                            problems = standings.pop('problems', None)
                            if problems is not None:
                                problems = plugin.merge_dict(problems, contest.info.get('problems'))
                                if not users:
                                    contest.info['problems'] = {}
                                update_problems(contest, problems=problems)

                    action = standings.get('action')
                    if action is not None:
                        args = []
                        if isinstance(action, tuple):
                            action, *args = action
                        self.logger.info(f'Action {action} with {args}, contest = {contest}, url = {contest.url}')
                        if action == 'delete':
                            if Statistics.objects.filter(contest=contest).exists():
                                self.logger.info(f'No deleted. Contest have statistics')
                            elif now < contest.end_time:
                                self.logger.info(f'No deleted. Try after = {contest.end_time - now}')
                            else:
                                delete_info = contest.delete()
                                self.logger.info(f'Delete info contest: {delete_info}')
                        elif action == 'url':
                            contest.url = args[0]
                            contest.save()
                if 'result' in standings:
                    count += 1
                parsed = True
            except (ExceptionParseStandings, InitModuleException) as e:
                progress_bar.set_postfix(exception=str(e), cid=str(contest.pk))
            except Exception as e:
                self.logger.error(f'contest = {contest.pk}, error = {e}, row = {r}')
                if stop_on_error:
                    self.logger.error(format_exc())
                    break
            if not parsed:
                if now < c.end_time and c.duration_in_secs <= limit_duration_in_secs:
                    delay = timedelta(minutes=0)
                else:
                    delay = resource.module.delay_on_error
                contest.timing.statistic = timezone.now() + delay
                contest.timing.save()
            elif not no_update_results and (users is None or users):
                stages = Stage.objects.filter(
                    ~Q(pk__in=stages_ids),
                    contest__start_time__lte=contest.start_time,
                    contest__end_time__gte=contest.end_time,
                    contest__resource=contest.resource,
                )
                for stage in stages:
                    if Contest.objects.filter(pk=contest.pk, **stage.filter_params).exists():
                        stages_ids.append(stage.pk)

        for stage in tqdm(Stage.objects.filter(pk__in=stages_ids),
                          total=len(stages_ids),
                          desc='getting stages'):
            stage.update()

        progress_bar.close()
        self.logger.info(f'Parsed statistic: {count} of {total}. Updated account time: {n_upd_account_time}')
        return count, total
コード例 #4
0
    def update(self):
        stage = self.contest

        contests = Contest.objects.filter(
            resource=self.contest.resource,
            start_time__gte=self.contest.start_time,
            end_time__lte=self.contest.end_time,
            **self.filter_params,
        ).exclude(pk=self.contest.pk)

        contests = contests.order_by('start_time')
        contests = contests.prefetch_related('writers')

        placing = self.score_params.get('place')
        n_best = self.score_params.get('n_best')
        fields = self.score_params.get('fields', [])
        detail_problems = self.score_params.get('detail_problems')
        order_by = self.score_params['order_by']
        advances = self.score_params.get('advances', {})
        results = collections.defaultdict(collections.OrderedDict)

        mapping_account_by_coder = {}

        problems_infos = collections.OrderedDict()
        divisions_order = []
        for idx, contest in enumerate(tqdm.tqdm(
                contests, desc=f'getting contests for stage {stage}'),
                                      start=1):
            info = {
                'code':
                str(contest.pk),
                'name':
                contest.title,
                'url':
                reverse('ranking:standings',
                        kwargs={
                            'title_slug': slug(contest.title),
                            'contest_id': str(contest.pk)
                        }),
            }

            for division in contest.info.get('divisions_order', []):
                if division not in divisions_order:
                    divisions_order.append(division)

            problems = contest.info.get('problems', [])
            if not detail_problems:
                full_score = None
                if placing:
                    if 'division' in placing:
                        full_score = max([
                            max(p.values())
                            for p in placing['division'].values()
                        ])
                    else:
                        full_score = max(placing.values())
                elif 'division' in problems:
                    full_scores = []
                    for ps in problems['division'].values():
                        full = 0
                        for problem in ps:
                            full += problem.get('full_score', 1)
                        full_scores.append(full)
                    info['full_score'] = max(full_scores)
                else:
                    full_score = 0
                    for problem in problems:
                        full_score += problem.get('full_score', 1)
                if full_score is not None:
                    info['full_score'] = full_score
                if self.score_params.get('regex_problem_name'):
                    match = re.search(
                        self.score_params.get('regex_problem_name'),
                        contest.title)
                    if match:
                        info['short'] = match.group(1)
                if self.score_params.get('abbreviation_problem_name'):
                    info['short'] = ''.join(
                        re.findall(r'(\b[A-Z]|[0-9])',
                                   info.get('short', contest.title)))
                problems_infos[str(contest.pk)] = info
            else:
                for problem in problems:
                    problem = dict(problem)
                    add_prefix_to_problem_short(problem, f'{idx}.')
                    problem['group'] = info['name']
                    problem['url'] = info['url']
                    problems_infos[get_problem_short(problem)] = problem

        exclude_advances = {}
        if advances and advances.get('exclude_stages'):
            qs = Statistics.objects \
                .filter(contest__stage__in=advances['exclude_stages'], addition___advance__isnull=False) \
                .values('account__key', 'addition___advance', 'contest__title') \
                .order_by('contest__end_time')
            for r in qs:
                d = r['addition___advance']
                if 'contest' not in d:
                    d['contest'] = r['contest__title']
                exclude_advances[r['account__key']] = d

        statistics = Statistics.objects \
            .select_related('account', 'account__duplicate') \
            .prefetch_related('account__coders')
        filter_statistics = self.score_params.get('filter_statistics')
        if filter_statistics:
            statistics = statistics.filter(**filter_statistics)

        def get_placing(placing, stat):
            return placing['division'][
                stat.
                addition['division']] if 'division' in placing else placing

        account_keys = dict()
        total = statistics.filter(contest__in=contests).count()
        with tqdm.tqdm(total=total,
                       desc=f'getting statistics for stage {stage}'
                       ) as pbar, print_sql(count_only=True):
            for idx, contest in enumerate(contests, start=1):
                skip_problem_stat = '_skip_for_problem_stat' in contest.info.get(
                    'fields', [])
                contest_unrated = contest.info.get('unrated')

                if not detail_problems:
                    problem_info_key = str(contest.pk)
                    problem_short = get_problem_short(
                        problems_infos[problem_info_key])
                pbar.set_postfix(contest=contest)
                stats = statistics.filter(contest_id=contest.pk)

                if placing:
                    placing_scores = deepcopy(placing)
                    n_rows = 0
                    for s in stats:
                        n_rows += 1
                        placing_ = get_placing(placing_scores, s)
                        key = str(s.place_as_int)
                        if key in placing_:
                            placing_.setdefault('scores', {})
                            placing_['scores'][key] = placing_.pop(key)
                    scores = []
                    for place in reversed(range(1, n_rows + 1)):
                        placing_ = get_placing(placing_scores, s)
                        key = str(place)
                        if key in placing_:
                            scores.append(placing_.pop(key))
                        else:
                            if scores:
                                placing_['scores'][key] += sum(scores)
                                placing_['scores'][key] /= len(scores) + 1
                            scores = []

                for s in stats:
                    if not detail_problems and not skip_problem_stat:
                        problems_infos[problem_info_key].setdefault(
                            'n_total', 0)
                        problems_infos[problem_info_key]['n_total'] += 1

                    if s.solving < 1e-9:
                        score = 0
                    else:
                        if placing:
                            placing_ = get_placing(placing_scores, s)
                            score = placing_['scores'].get(
                                str(s.place_as_int), placing_.get('default'))
                            if score is None:
                                continue
                        else:
                            score = s.solving

                    if not detail_problems and not skip_problem_stat:
                        problems_infos[problem_info_key].setdefault(
                            'n_teams', 0)
                        problems_infos[problem_info_key]['n_teams'] += 1
                        if score:
                            problems_infos[problem_info_key].setdefault(
                                'n_accepted', 0)
                            problems_infos[problem_info_key]['n_accepted'] += 1

                    account = s.account
                    if account.duplicate is not None:
                        account = account.duplicate

                    coders = account.coders.all()
                    has_mapping_account_by_coder = False
                    if len(coders) == 1:
                        coder = coders[0]
                        if coder not in mapping_account_by_coder:
                            mapping_account_by_coder[coder] = account
                        else:
                            account = mapping_account_by_coder[coder]
                            has_mapping_account_by_coder = True

                    row = results[account]
                    row['member'] = account
                    account_keys[account.key] = account

                    problems = row.setdefault('problems', {})
                    if detail_problems:
                        for key, problem in s.addition.get('problems',
                                                           {}).items():
                            problems[f'{idx}.' + key] = problem
                    else:
                        problem = problems.setdefault(problem_short, {})
                        if contest_unrated:
                            problem = problem.setdefault('upsolving', {})
                        problem['result'] = score
                        url = s.addition.get('url')
                        if url:
                            problem['url'] = url
                    if contest_unrated:
                        score = 0

                    if n_best and not detail_problems:
                        row.setdefault('scores', []).append((score, problem))
                    else:
                        row['score'] = row.get('score', 0) + score

                    field_values = {}
                    for field in fields:
                        inp = field['field']
                        out = field.get('out', inp)
                        if field.get('skip_on_mapping_account_by_coder'
                                     ) and has_mapping_account_by_coder:
                            continue
                        if 'type' in field:
                            continue
                        if field.get('first') and out in row or (
                                inp not in s.addition and not hasattr(s, inp)):
                            continue
                        val = s.addition[
                            inp] if inp in s.addition else getattr(s, inp)
                        if not field.get('safe') and isinstance(val, str):
                            val = ast.literal_eval(val)
                        if 'cast' in field:
                            val = locate(field['cast'])(val)
                        field_values[out] = val
                        if field.get('accumulate'):
                            val = round(
                                val + ast.literal_eval(str(row.get(out, 0))),
                                2)
                        row[out] = val

                    if 'solved' in s.addition:
                        solved = row.setdefault('solved', {})
                        for k, v in s.addition['solved'].items():
                            solved[k] = solved.get(k, 0) + v

                    if 'status' in self.score_params:
                        field = self.score_params['status']
                        val = field_values.get(field, row.get(field))
                        if val is None:
                            val = getattr(s, field)
                        if val:
                            problem['status'] = val
                    else:
                        for field in order_by:
                            field = field.lstrip('-')
                            if field in ['score', 'rating']:
                                continue
                            status = field_values.get(field, row.get(field))
                            if status is None:
                                continue
                            problem['status'] = status
                            break

                    pbar.update()

        for writer in contest.writers.all():
            account_keys[writer.key] = writer
        total = sum(
            [len(contest.info.get('writers', [])) for contest in contests])
        with tqdm.tqdm(
                total=total,
                desc=f'getting writers for stage {stage}') as pbar, print_sql(
                    count_only=True):
            writers = set()
            for contest in contests:
                contest_writers = contest.info.get('writers', [])
                if not contest_writers or detail_problems:
                    continue
                problem_info_key = str(contest.pk)
                problem_short = get_problem_short(
                    problems_infos[problem_info_key])
                for writer in contest_writers:
                    if writer in account_keys:
                        account = account_keys[writer]
                    else:
                        try:
                            account = Account.objects.get(
                                resource_id=contest.resource_id,
                                key__iexact=writer)
                        except Account.DoesNotExist:
                            account = None

                    pbar.update()
                    if not account:
                        continue
                    writers.add(account)

                    row = results[account]
                    row['member'] = account
                    row.setdefault('score', 0)
                    if n_best:
                        row.setdefault('scores', [])
                    row.setdefault('writer', 0)

                    row['writer'] += 1

                    problems = row.setdefault('problems', {})
                    problem = problems.setdefault(problem_short, {})
                    problem['status'] = 'W'

        if self.score_params.get('writers_proportionally_score'):
            n_contests = len(contests)
            for account in writers:
                row = results[account]
                if n_contests == row['writer'] or 'score' not in row:
                    continue
                row['score'] = row['score'] / (n_contests -
                                               row['writer']) * n_contests

        for field in fields:
            t = field.get('type')
            if t == 'points_for_common_problems':
                group = field['group']
                inp = field['field']
                out = field.get('out', inp)

                common_problems = dict()
                for account, row in results.items():
                    problems = {
                        k
                        for k, p in row['problems'].items()
                        if p.get('status') != 'W'
                    }
                    key = row[group]
                    common_problems[
                        key] = problems if key not in common_problems else (
                            problems & common_problems[key])

                for account, row in results.items():
                    key = row[group]
                    problems = common_problems[key]
                    value = 0
                    for k in problems:
                        value += float(row['problems'].get(k, {}).get(inp, 0))
                    for k, v in row['problems'].items():
                        if k not in problems and v.get('status') != 'W':
                            v['status_tag'] = 'strike'
                    row[out] = round(value, 2)

        results = list(results.values())
        if n_best:
            for row in results:
                scores = row.pop('scores')
                for index, (score, problem) in enumerate(
                        sorted(scores, key=lambda s: s[0], reverse=True)):
                    if index < n_best:
                        row['score'] = row.get('score', 0) + score
                    else:
                        problem['status'] = problem.pop('result')

        filtered_results = []
        for r in results:
            if r['score'] > 1e-9 or r.get('writer'):
                filtered_results.append(r)
                continue
            if detail_problems:
                continue

            problems = r.setdefault('problems', {})

            for idx, contest in enumerate(contests, start=1):
                skip_problem_stat = '_skip_for_problem_stat' in contest.info.get(
                    'fields', [])
                if skip_problem_stat:
                    continue

                problem_info_key = str(contest.pk)
                problem_short = get_problem_short(
                    problems_infos[problem_info_key])

                if problem_short in problems:
                    problems_infos[problem_info_key].setdefault('n_teams', 0)
                    problems_infos[problem_info_key]['n_teams'] -= 1
        results = filtered_results

        results = sorted(
            results,
            key=lambda r: tuple(
                r.get(k.lstrip('-'), 0) * (-1 if k.startswith('-') else 1)
                for k in order_by),
            reverse=True,
        )

        with transaction.atomic():
            fields_set = set()
            fields = list()

            pks = set()
            placing_infos = {}
            score_advance = None
            place_advance = 0
            for row in tqdm.tqdm(results,
                                 desc=f'update statistics for stage {stage}'):
                row['_no_update_n_contests'] = True
                division = row.get('division', 'none')
                placing_info = placing_infos.setdefault(division, {})
                placing_info['index'] = placing_info.get('index', 0) + 1

                curr_score = tuple(row.get(k.lstrip('-'), 0) for k in order_by)
                if curr_score != placing_info.get('last_score'):
                    placing_info['last_score'] = curr_score
                    placing_info['place'] = placing_info['index']

                if advances and ('divisions' not in advances
                                 or division in advances['divisions']):
                    tmp = score_advance, place_advance
                    if curr_score != score_advance:
                        score_advance = curr_score
                        place_advance += 1

                    for advance in advances.get('options', []):
                        handle = row['member'].key
                        if handle in exclude_advances and advance[
                                'next'] == exclude_advances[handle]['next']:
                            advance = exclude_advances[handle]
                            if 'class' in advance and not advance[
                                    'class'].startswith('text-'):
                                advance['class'] = f'text-{advance["class"]}'
                            row['_advance'] = advance
                            break

                        if 'places' in advance and place_advance in advance[
                                'places']:
                            row['_advance'] = advance
                            for field in advance.get('inplace_fields', []):
                                row[field] = advance[field]
                            tmp = None
                            break

                    if tmp is not None:
                        score_advance, place_advance = tmp
                account = row.pop('member')
                solving = row.pop('score')
                stat, created = Statistics.objects.update_or_create(
                    account=account,
                    contest=stage,
                    defaults={
                        'place': str(placing_info['place']),
                        'place_as_int': placing_info['place'],
                        'solving': solving,
                        'addition': row,
                    },
                )
                pks.add(stat.pk)

                for k in row.keys():
                    if k not in fields_set:
                        fields_set.add(k)
                        fields.append(k)
            stage.statistics_set.exclude(pk__in=pks).delete()
            stage.n_statistics = len(results)

            stage.info['fields'] = list(fields)

        standings_info = self.score_params.get('info', {})
        standings_info['fixed_fields'] = [(f.lstrip('-'), f.lstrip('-'))
                                          for f in order_by]
        stage.info['standings'] = standings_info

        if divisions_order and self.score_params.get('divisions_ordering'):
            stage.info['divisions_order'] = divisions_order

        stage.info['problems'] = list(problems_infos.values())
        stage.save()
コード例 #5
0
ファイル: views.py プロジェクト: guptaarth87/clist
def update_problems(contest, problems=None):
    if problems is not None:
        if canonize(problems) == canonize(contest.info.get('problems')):
            return
        contest.info['problems'] = problems
        contest.save()

    problems = contest.info.get('problems')
    if not problems or hasattr(contest, 'stage'):
        return
    if 'division' in problems:
        problem_sets = problems['division'].items()
    else:
        problem_sets = [(None, problems)]

    old_problem_ids = set(contest.problem_set.values_list('id', flat=True))
    added_problems = dict()
    for division, problem_set in problem_sets:
        for index, problem_info in enumerate(problem_set, start=1):
            key = get_problem_key(problem_info)
            short = get_problem_short(problem_info)
            name = get_problem_name(problem_info)
            if short == name or short == key:
                short = None

            added_problem = added_problems.get(key)

            defaults = {
                'index':
                index if not added_problem else None,
                'short':
                short,
                'name':
                name,
                'divisions':
                getattr(added_problem, 'divisions', []) +
                [division] if division else None,
                'url':
                problem_info.get('url'),
                'n_tries':
                problem_info.get('n_teams', 0) +
                getattr(added_problem, 'n_tries', 0),
                'n_accepted':
                problem_info.get('n_accepted', 0) +
                getattr(added_problem, 'n_accepted', 0),
                'time':
                contest.start_time,
            }

            problem, created = Problem.objects.update_or_create(
                contest=contest,
                key=key,
                defaults=defaults,
            )

            old_tags = set(problem.tags.all())
            if 'tags' in problem_info:
                if '' in problem_info['tags']:
                    problem_info['tags'].remove('')
                    contest.save()

                for name in problem_info['tags']:
                    tag, _ = ProblemTag.objects.get_or_create(name=name)
                    if tag in old_tags:
                        old_tags.discard(tag)
                    else:
                        problem.tags.add(tag)
            for tag in old_tags:
                problem.tags.remove(tag)

            added_problems[key] = problem

            if problem.id in old_problem_ids:
                old_problem_ids.remove(problem.id)
    if old_problem_ids:
        Problem.objects.filter(id__in=old_problem_ids).delete()
コード例 #6
0
    def handle(self, *args, **options):
        self.stdout.write(str(options))
        args = AttrDict(options)

        bot = Bot()

        if args.dump is not None and os.path.exists(args.dump):
            with open(args.dump, 'r') as fo:
                standings = json.load(fo)
        else:
            standings = {}

        problems_info = standings.setdefault('__problems_info', {})

        parser_command = ParserCommand()

        iteration = 1 if args.dump else 0
        while True:
            subprocess.call('clear', shell=True)
            print(now())

            contest = Contest.objects.filter(pk=args.cid)
            parser_command.parse_statistic(contest, without_contest_filter=True)
            contest = contest.first()
            resource = contest.resource
            statistics = list(Statistics.objects.filter(contest=contest))

            for p in problems_info.values():
                if p.get('accepted') or not p.get('n_hidden'):
                    p.pop('show_hidden', None)
                p['n_hidden'] = 0

            updated = False
            has_hidden = False
            numbered = 0

            statistics = [s for s in statistics if s.place_as_int is not None]
            for stat in sorted(statistics, key=lambda s: s.place_as_int):
                name_instead_key = resource.info.get('standings', {}).get('name_instead_key')
                name_instead_key = stat.account.info.get('_name_instead_key', name_instead_key)

                if name_instead_key:
                    name = stat.account.name
                else:
                    name = stat.addition.get('name')
                    if not name or not has_season(stat.account.key, name):
                        name = stat.account.key

                filtered = False
                if args.query is not None and re.search(args.query, name, re.I):
                    filtered = True
                if args.top and stat.place_as_int <= args.top:
                    filtered = True

                contest_problems = contest.info.get('problems')
                division = stat.addition.get('division')
                if division and 'division' in contest_problems:
                    contest_problems = contest_problems['division'][division]
                contest_problems = {get_problem_short(p): p for p in contest_problems}

                message_id = None
                key = str(stat.account.id)
                if key in standings:
                    problems = standings[key]['problems']
                    message_id = standings[key].get('messageId')

                    def delete_message():
                        nonlocal message_id
                        if message_id:
                            for iteration in range(1, 5):
                                try:
                                    bot.delete_message(chat_id=args.tid, message_id=message_id)
                                    message_id = None
                                    break
                                except telegram.error.TimedOut as e:
                                    logger.warning(str(e))
                                    time.sleep(iteration)
                                    continue

                    p = []
                    has_update = False
                    has_first_ac = False
                    has_try_first_ac = False
                    has_new_accepted = False
                    has_top = False

                    for k, v in stat.addition.get('problems', {}).items():
                        p_info = problems_info.setdefault(k, {})
                        p_result = problems.get(k, {}).get('result')
                        result = v['result']

                        is_hidden = str(result).startswith('?')
                        is_accepted = str(result).startswith('+') or v.get('binary', False)
                        try:
                            is_accepted = is_accepted or float(result) > 0 and not v.get('partial')
                        except Exception:
                            pass

                        if is_hidden:
                            p_info['n_hidden'] = p_info.get('n_hidden', 0) + 1

                        if p_result != result or is_hidden:
                            has_new_accepted |= is_accepted
                            short = k
                            if k in contest_problems and k != get_problem_short(contest_problems[k]):
                                short = get_problem_name(contest_problems[k])
                            m = '%s%s %s' % (short, ('. ' + v['name']) if 'name' in v else '', result)

                            if v.get('verdict'):
                                m += ' ' + v['verdict']

                            if p_result != result:
                                m = '*%s*' % m
                                has_update = True
                                if iteration:
                                    if p_info.get('show_hidden') == key:
                                        delete_message()
                                        if not is_hidden:
                                            p_info.pop('show_hidden')
                                    if not p_info.get('accepted'):
                                        if is_accepted:
                                            m += ' FIRST ACCEPTED'
                                            has_first_ac = True
                                        elif is_hidden and not p_info.get('show_hidden'):
                                            p_info['show_hidden'] = key
                                            m += ' TRY FIRST AC'
                                            has_try_first_ac = True
                                if args.top and stat.place_as_int <= args.top:
                                    has_top = True
                            p.append(m)
                        if is_accepted:
                            p_info['accepted'] = True
                        has_hidden = has_hidden or is_hidden

                    prev_place = standings[key].get('place')
                    place = stat.place
                    if has_new_accepted and prev_place:
                        place = '%s->%s' % (prev_place, place)
                    if args.numbered is not None and re.search(args.numbered, stat.account.key, re.I):
                        numbered += 1
                        place = '%s (%s)' % (place, numbered)

                    msg = '%s. _%s_' % (place, telegram.utils.helpers.escape_markdown(name.replace('_', ' ')))
                    if p:
                        msg = '%s, %s' % (', '.join(p), msg)
                    if has_top:
                        msg += f' TOP{args.top}'

                    if abs(standings[key]['solving'] - stat.solving) > 1e-9:
                        msg += ' = %d' % stat.solving
                        if 'penalty' in stat.addition:
                            msg += f' ({stat.addition["penalty"]})'

                    if has_update or has_first_ac or has_try_first_ac:
                        updated = True

                    if filtered:
                        print(stat.place, stat.solving, end=' | ')

                    if filtered:
                        print(msg)

                    if filtered and has_update or has_first_ac or has_try_first_ac:
                        if not args.dryrun:
                            delete_message()
                            for iteration in range(1, 5):
                                try:
                                    message = bot.send_message(msg=msg, chat_id=args.tid)
                                    message_id = message.message_id
                                    break
                                except telegram.error.TimedOut as e:
                                    logger.warning(str(e))
                                    time.sleep(iteration * 3)
                                    continue
                                except telegram.error.BadRequest as e:
                                    logger.error(str(e))
                                    break

                standings[key] = {
                    'solving': stat.solving,
                    'place': stat.place,
                    'problems': stat.addition.get('problems', {}),
                    'messageId': message_id,
                }
            if args.dump is not None and (updated or not os.path.exists(args.dump)):
                standings_dump = json.dumps(standings, indent=2)
                with open(args.dump, 'w') as fo:
                    fo.write(standings_dump)

            if iteration:
                is_over = contest.end_time < now()
                if is_over and not has_hidden:
                    break
                tick = args.delay * 5 if is_over else args.delay
                limit = now() + timedelta(seconds=tick)
                size = 1
                while now() < limit:
                    value = humanize.naturaldelta(limit - now())
                    out = f'{value:{size}s}'
                    size = len(value)
                    print(out, end='\r')
                    time.sleep(1)
                print()

            iteration += 1