Разработка AI-системы для государственных закупок Public Procurement AI

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Разработка AI-системы для государственных закупок Public Procurement AI
Сложная
~2-4 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

Разработка AI-системы для государственных закупок Public Procurement AI

Государственные закупки — сфера с высоким уровнем злоупотреблений: картельные сговоры, аффилированность поставщиков, завышение НМЦК. ML-система анализирует закупки по ЕИС (Единая информационная система) и выявляет признаки нарушений, недоступные при ручном аудите.

Данные и источники

Информационная база закупок:

data_sources = {
    'eis_zakupki_gov_ru': {
        'api': 'Открытые данные ЕИС API (44-ФЗ, 223-ФЗ)',
        'entities': ['ContractNotice', 'ContractAward', 'Supplier', 'OKPD2'],
        'volume': '~4 млн закупок в год'
    },
    'egrul_fns': {
        'source': 'ЕГРЮЛ ФНС — данные о юрлицах',
        'use': 'связи между поставщиками, аффилированность, учредители'
    },
    'rosstat': {
        'source': 'финансовая отчётность компаний',
        'use': 'реальные возможности поставщика vs. объём контракта'
    },
    'sudrf_ru': {
        'source': 'арбитражные дела',
        'use': 'история судебных споров поставщиков'
    }
}

Детекция картельного сговора

Признаки картеля на торгах:

import pandas as pd
import numpy as np
from itertools import combinations

def detect_collusion_in_auction(auction_bids: pd.DataFrame,
                                 auction_id: str) -> dict:
    """
    Классические паттерны картеля:
    1. Cover bidding: один выигрывает, остальные делают заведомо проигрышные ставки
    2. Bid suppression: конкуренты отказываются от участия
    3. Bid rotation: участники по очереди побеждают в разных лотах
    4. Market allocation: географическое или секторальное разделение
    """
    bids = auction_bids[auction_bids['auction_id'] == auction_id]

    if len(bids) < 2:
        return {'status': 'insufficient_bidders'}

    nmck = bids['start_price'].max()  # НМЦК — начальная (максимальная) цена

    # Снижение цены каждого участника
    bids['reduction_pct'] = (nmck - bids['bid_price']) / nmck * 100
    bids_sorted = bids.sort_values('bid_price')

    # Признак 1: минимальный шаг снижения от предыдущего участника
    # Cover bids: остальные снижаются ровно на 0.5% (разрешённый минимум)
    reductions = bids_sorted['bid_price'].diff().abs() / bids_sorted['bid_price'].shift(1)
    min_step_count = (reductions < 0.006).sum()  # почти все снизились на минимум

    # Признак 2: победитель снизил на большой %, остальные — на минимум
    winner_reduction = bids_sorted.iloc[0]['reduction_pct']
    losers_reductions = bids_sorted.iloc[1:]['reduction_pct']
    reduction_gap = winner_reduction - losers_reductions.mean()

    # Признак 3: временные паттерны подачи заявок
    if 'bid_timestamp' in bids.columns:
        time_deltas = bids['bid_timestamp'].sort_values().diff().dt.total_seconds()
        very_fast_submissions = (time_deltas < 60).sum()  # < 1 минуты между заявками
    else:
        very_fast_submissions = 0

    collusion_indicators = {
        'cover_bids': min_step_count >= len(bids) - 2,
        'large_reduction_gap': reduction_gap > 10,
        'fast_sequential_bids': very_fast_submissions > 1,
        'few_participants': len(bids) <= 2
    }

    collusion_score = sum(collusion_indicators.values()) / len(collusion_indicators)

    return {
        'auction_id': auction_id,
        'n_bidders': len(bids),
        'winner_reduction_pct': round(winner_reduction, 2),
        'collusion_indicators': collusion_indicators,
        'collusion_score': round(collusion_score, 2),
        'flag_for_review': collusion_score > 0.5
    }

Анализ аффилированности поставщиков

Граф связей через реестры:

import networkx as nx

def build_supplier_affiliation_graph(suppliers: pd.DataFrame,
                                      egrul_data: pd.DataFrame) -> nx.Graph:
    """
    Связи: общие учредители, адреса, телефоны, директора.
    Аффилированные компании = один бенефициар = конкурент только формально.
    """
    G = nx.Graph()

    for _, supplier in suppliers.iterrows():
        G.add_node(supplier['inn'], type='supplier', name=supplier['company_name'])

    # Связи через общих учредителей
    founders_data = egrul_data.groupby('founder_inn')['company_inn'].apply(list)
    for founder, companies in founders_data.items():
        if len(companies) > 1:
            for c1, c2 in combinations(companies, 2):
                if G.has_node(c1) and G.has_node(c2):
                    G.add_edge(c1, c2, relation='common_founder', founder_inn=founder)

    # Связи через адрес регистрации (массовые регистрации)
    address_counts = egrul_data.groupby('legal_address')['company_inn'].apply(list)
    for address, companies in address_counts.items():
        if len(companies) > 5:  # адрес массовой регистрации
            for c1, c2 in combinations(companies, 2):
                if G.has_node(c1) and G.has_node(c2):
                    G.add_edge(c1, c2, relation='shared_address')

    return G

def find_affiliated_bidders(auction_bidders: list, affiliation_graph: nx.Graph) -> dict:
    """
    Проверяем: есть ли прямая связь между участниками?
    """
    affiliated_pairs = []
    for i, bidder1 in enumerate(auction_bidders):
        for bidder2 in auction_bidders[i+1:]:
            if affiliation_graph.has_edge(bidder1, bidder2):
                path = nx.shortest_path(affiliation_graph, bidder1, bidder2)
                affiliated_pairs.append({
                    'bidder1': bidder1,
                    'bidder2': bidder2,
                    'connection_path': path,
                    'connection_type': affiliation_graph[bidder1][bidder2].get('relation')
                })

    return {
        'affiliated_pairs': affiliated_pairs,
        'has_affiliation': len(affiliated_pairs) > 0,
        'risk_level': 'high' if len(affiliated_pairs) > 0 else 'low'
    }

Анализ НМЦК (обоснование цены)

Детекция завышенной НМЦК:

from sklearn.ensemble import GradientBoostingRegressor

def detect_inflated_nmck(procurement: dict, similar_procurements: pd.DataFrame) -> dict:
    """
    НМЦК должна отражать рыночную стоимость.
    Сравниваем с аналогичными закупками: тот же ОКПД2, регион, объём.
    """
    # Фильтрация аналогов
    comparable = similar_procurements[
        (similar_procurements['okpd2_code'].str[:4] == procurement['okpd2_code'][:4]) &
        (similar_procurements['region_code'] == procurement['region_code']) &
        (similar_procurements['quantity'] >= procurement['quantity'] * 0.5) &
        (similar_procurements['quantity'] <= procurement['quantity'] * 2.0)
    ]

    if len(comparable) < 5:
        return {'status': 'insufficient_comparables'}

    unit_prices = comparable['contract_price'] / comparable['quantity']
    expected_unit_price = unit_prices.median()
    std_unit_price = unit_prices.std()

    nmck_unit = procurement['nmck'] / procurement['quantity']
    z_score = (nmck_unit - expected_unit_price) / (std_unit_price + 1e-9)

    overpricing_pct = (nmck_unit - expected_unit_price) / expected_unit_price * 100

    return {
        'nmck_unit_price': round(nmck_unit, 2),
        'market_median_price': round(expected_unit_price, 2),
        'overpricing_pct': round(overpricing_pct, 1),
        'z_score': round(z_score, 2),
        'inflation_detected': z_score > 3,
        'comparable_contracts_n': len(comparable)
    }

Bid Rotation — паттерн выигрышей

Статистический тест на ротацию победителей:

from scipy.stats import chi2_contingency

def detect_bid_rotation(procurement_history: pd.DataFrame,
                          supplier_group: list) -> dict:
    """
    Картельная ротация: каждый поставщик выигрывает "свою" долю лотов.
    Статистически: равномерное распределение побед при видимой конкуренции.
    """
    group_wins = procurement_history[
        procurement_history['winner_inn'].isin(supplier_group)
    ]['winner_inn'].value_counts()

    if len(group_wins) < 2:
        return {'status': 'insufficient_data'}

    # Chi-square тест: равномерность распределения побед
    observed = group_wins.values
    expected = np.full_like(observed, np.mean(observed), dtype=float)
    chi2_stat, p_value = chi2_contingency([observed, expected])[:2]

    # Низкий chi2 = слишком равномерное распределение = подозрительно
    rotation_detected = chi2_stat < 2 and p_value > 0.9

    return {
        'supplier_win_counts': group_wins.to_dict(),
        'chi2_statistic': round(chi2_stat, 3),
        'p_value': round(p_value, 3),
        'rotation_detected': rotation_detected,
        'interpretation': 'Слишком равномерное распределение побед' if rotation_detected else 'Распределение нормальное'
    }

Интеграция с регуляторами: Экспорт выявленных нарушений в форматах ФАС России (антикартельный отдел), Счётная Палата, Росфинмониторинг. REST API для интеграции с системами автоматизированного контроля закупок (АСФК, АЦК-Финансы).

Сроки: Базовая аналитика коллюзии + НМЦК сравнение + аффилированность по ЕГРЮЛ — 5-6 недель. Граф аффилированности, bid rotation тест, ML-модель на признаках коррупции, ФАС API — 3-4 месяца.