Собесов

Mains Lab — поиск мошенничества клиники в реестре ДМС-услуг (FWA)

Кейсы и метрикиFraud / anomaly detectionСложнаяMiddle

Условие

Дан реестр медицинских услуг по ДМС: row_id, batch_id, service_id, service_name, service_quantity, service_date, service_amount, icd_codes (МКБ-10), insured_id, insured_age, insured_gender, provider_id, service_code, service_code_name.

Найти систематические искажения в реестре. Не системное (один раз ошиблись, или услуга подходит, но цена странная) — не считать. Системные паттерны (каждому пациенту приписывается лишний анализ; всегда дороже, чем по тарифу; и т.д.) — считать.

Решение

Каркас

Мошенничество = систематическое отклонение от ожидаемого распределения. Алгоритм:

  1. Определить «нормальное поведение» (по другим клиникам или средним по системе).
  2. Для каждой клиники посчитать метрики и найти outliers.
  3. Объяснить, почему конкретный паттерн подозрителен.

Признаки FWA

A. Upcoding — услуга оформлена как более дорогая.

  • Считаем для каждой service_code распределение service_amount по всем клиникам.
  • Если у provider_id = X среднее значимо выше — flag.
import pandas as pd
sys_avg = df.groupby('service_code')['service_amount'].agg(['mean', 'std']).reset_index()
prov = df.groupby(['provider_id', 'service_code'])['service_amount'].mean().reset_index()
m = prov.merge(sys_avg, on='service_code', suffixes=('_prov', '_sys'))
m['z'] = (m['service_amount'] - m['mean']) / m['std']
m[m['z'] > 3]   # подозрительные

B. Phantom billing — услуга приписана при отсутствии релевантного диагноза.

  • Кросс-таблица «услуга × ICD-код»: какая частота услуги при ICD у других клиник?
  • Если у provider X услуга часто (>>) встречается без релевантного ICD — flag.
# Например: рентген пазух носа (4212.01) при гриппе (J10) у других — 5%, у X — 60%
suspicious = (df.groupby(['provider_id', 'service_code'])
                .apply(lambda g: (g['icd_codes'].str.contains('J10|J11')).mean())
                .reset_index(name='flu_share'))

B'. Unbundling: одна процедура разбита на несколько «дешёвых», но с большим суммарным чеком.

  • На уровне batch_id: считаем «средний размер счёта» и распределение услуг внутри.
  • Если у клиники характерно X услуг в batch с известными комбинациями — flag.

C. Дубликаты услуг: один и тот же (insured, service_code, date) 2 раза в одной клинике.

  • Нормально: 0.x%. Подозрительно: значимо выше.
dups = (df.groupby(['provider_id', 'insured_id', 'service_code', 'service_date'])
          .size().reset_index(name='cnt'))
dups[dups['cnt'] > 1]['provider_id'].value_counts()

D. Невозможные комбинации: пациент-мужчина с гинекологией; пациент-ребёнок 4 года с услугами для пожилых.

  • На уровне service_code определить «допустимый возраст / пол» (по справочнику или эмпирически).
  • Считать долю «невозможных» по клинике.

E. Завышенный квант услуг: service_quantity > 1 для услуг, которые «должны быть = 1».

  • Прием первичный — 1 раз в день логически. Если у клиники X доля quantity > 1 для приёма выше системного — flag.

F. Pattern «одинаковый набор услуг для всех»:

  • Считаем энтропию набора услуг внутри клиники. Низкая энтропия = всем подряд одно и то же = подозрительно.
from collections import Counter
def case_entropy(g):
    c = Counter(g['service_code'])
    n = sum(c.values())
    p = [v/n for v in c.values()]
    return -sum(pi*math.log(pi+1e-12) for pi in p)

G. Среднее число услуг на batch: клиника-аномалия имеет резко больше услуг на batch.

Объединение в risk-score

Для каждой provider_id:

riskprovider=iwi1[flagi]\text{risk}_{\text{provider}} = \sum_i w_i \cdot \mathbb{1}[\text{flag}_i]

Топ-5 по risk → аналитик расследует руками. Минимум одна реальная схема в ответе обязательна (требование задания).

Пример конкретной находки

«У provider X доля приемов терапевта повторных (1101.02) на каждого insured ≥ 5 в среднем — против системного 1.7. И при этом средний service_amount повторного приема на 30% выше среднесистемного. Гипотеза: провайдер выставляет дополнительные приёмы и upcode-ит их.»

Подводные камни

  1. Размер клиники: маленькая клиника может выглядеть аномально по любому статистическому критерию из-за малой выборки. Нужен фильтр n_services > 100.
  2. Специализация клиник: онкоцентр будет иметь больше дорогих услуг; неотложка — больше срочных. Сравнивайте «подобное с подобным» — после кластеризации.
  3. Sex/age по insured: одна и та же insured_id может иметь разный возраст в разные даты — фильтруйте на текущий visit.
  4. Multiple testing: при сотнях клиник × десятках услуг → много false-positive. Используйте FDR (BH).
  5. Корректность ICD-сопоставления: «релевантность» услуги к диагнозу не всегда тривиальна — нужны медицинские справочники.

Эталонный ответ

Каркас: 7 паттернов FWA (upcoding, phantom, unbundling, duplicates, impossible-combo, quantum-inflate, low-entropy services). Для каждой клиники строим risk-score, ранжируем, далее ручное расследование. Минимум одна находка с конкретным примером и объяснением паттерна — обязательное требование задания.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти