Собесов

Совкомбанк Страхование: преобразование медицинского реестра

PythonETL и pandasЛёгкаяJunior

Условие

Дан исходный реестр медицинских услуг с «вертикальной» структурой: ФИО+полис в одной строке, ниже — даты приёмов, врачи, под датами — услуги. Нужно превратить в плоскую таблицу:

ФИО | НомерПолиса | ДатаОказанияУслуги | КодУслуги | Диагноз | НаименованиеУслуги | Кол-во | ЦенаПоПрейскуранту | СуммаСоСкидкой.

Решение

Подход

Структура иерархическая: верхний уровень — пациент (ФИО + полис), второй — дата + врач, третий — конкретные услуги. Используем forward-fill (ffill) для протяжки заголовков на строки услуг.

Реализация

import re
import pandas as pd
 
raw = pd.read_excel('Задание_1_реестр.xlsx', sheet_name='Исходные данные', header=None)
raw.columns = ['fio_polis','diagnosis','code','service','col1','col2','price','price_disc','qty','sum_disc']
 
# Регулярки
PAT_PATIENT = re.compile(r'^(?P<fio>[А-ЯЁ][\w\s]+)\s+Полис\s*\s*(?P<polis>[\d/-]+)$')
 
# 1) Опознать строки заголовков
def parse_patient(row):
    if pd.notna(row.fio_polis):
        m = PAT_PATIENT.match(str(row.fio_polis).strip())
        if m:
            return m.group('fio').strip(), m.group('polis')
    return pd.NA, pd.NA
 
raw[['fio','polis']] = raw.apply(parse_patient, axis=1, result_type='expand')
 
# 2) ffill пациента
raw[['fio','polis']] = raw[['fio','polis']].ffill()
 
# 3) Дата приёма + врач (в "diagnosis" со столбце или в service-столбце)
def parse_date(val):
    try:
        return pd.to_datetime(val, dayfirst=True, errors='raise')
    except Exception:
        return pd.NaT
raw['date_visit'] = raw.diagnosis.apply(parse_date)
raw['doctor']     = raw.service.where(raw.date_visit.notna())
raw[['date_visit','doctor']] = raw[['date_visit','doctor']].ffill()
 
# 4) Оставляем только строки с реальным кодом услуги
result = raw[raw.code.notna()].copy()
result = result.rename(columns={
    'code':'service_code', 'service':'service_name'
})[['fio','polis','date_visit','service_code','diagnosis',
   'service_name','qty','price','sum_disc']]
 
result.to_excel('result.xlsx', index=False)

Альтернатива

Если столбцы фиксированы — можно итерировать по строкам и накапливать состояние:

state = {'fio': None, 'polis': None, 'date': None, 'doctor': None}
rows = []
for _, r in raw.iterrows():
    if patient_match: state['fio'], state['polis'] = ...
    elif is_date_row: state['date'], state['doctor'] = ...
    elif is_service_row: rows.append({**state, ...service fields})

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

  1. ФИО без шаблона распознается плохо — нужен фолбэк (Иванов Иван Иванович всегда 3 слова).
  2. Даты в формате 13.07.2022dayfirst=True, иначе спутает с американским форматом.
  3. Пустые строки между секциями — нельзя dropna(how='all') сразу, сначала получить иерархию через ffill.
  4. На уровне «услуги» может быть несколько услуг подряд — ffill для пациента и даты сохраняется для всех.
  5. Полис содержит - и / — не опознать как число.

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

Парсинг иерархии: regex для пациента, попытка to_datetime для строк-дат → ffill обоих уровней → отфильтровать строки с заполненным service_code.

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

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

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