10 шокирующих способов автоматизировать древние медицинские системы без лишних затрат

7 ноября 2025 г.

Вступление

В сфере здравоохранения многие организации всё ещё используют десятилетиями проверенные, но устаревшие настольные программы для ввода и обработки данных о пациентах. Такие системы часто работают только в старых версиях Windows, имеют громоздкие меню, всплывающие окна и почти не поддерживают интеграцию с современными веб‑сервисами. Для разработчиков, которым приходится «надстраивать» такие программы, это превращается в ежедневный квест: каждый клик – потенциальный источник ошибки, каждый всплывающий диалог – шанс «сломать» автоматизацию.

Согласно исследованию HealthIT.gov, более 40 % крупных медицинских учреждений в США всё ещё используют программное обеспечение, выпущенное до 2010 года. При этом затраты на ручной ввод данных оцениваются в среднем в 12 % от общего бюджета ИТ‑отдела. Очевидно, что автоматизация – не роскошь, а необходимость.

В конце вступления – небольшое японское хокку, которое, как ни странно, отлично отражает суть нашей задачи:


# Японское хокку
# Старая система – как древо,
# Корни крепки, ветви гнутся,
# Тихо ждёт нового ветра.

Пересказ оригинального Reddit‑поста

Автор поста, работающий над веб‑проектами для клиентов из сферы здравоохранения, описал типичную проблему: им приходится вводить информацию о пациентах в древние системы электронных медицинских записей (EHR). Эти программы работают в старом Windows‑окне, требуют множества кликов, переключений между выпадающими списками и постоянно «выбрасывают» всплывающие окна. В результате процесс занимает часы, а написанные вручную скрипты часто ломаются при малейшем изменении интерфейса.

Автор пробовал базовые инструменты роботизированной автоматизации (RPA), но они оказались дорогими и требовали постоянных правок, чтобы «учитывать» новые шаги. Он ищет простое решение: написать обычное текстовое описание задачи, вызвать API со своей веб‑стороны, а дальше система должна надёжно выполнять действия на ПК клиента, в облаке или локально. Было бы здорово, если бы автоматизация сама справлялась с неожиданными всплывающими окнами и со временем становилась быстрее.

Суть проблемы и «хакерский» подход

Ключевая сложность – разрыв между современным веб‑стеком и «запертой» настольной программой. Традиционный RPA пытается имитировать действия пользователя (движения мыши, нажатия клавиш), но такой подход хрупок: любое изменение в расположении кнопки или появление нового диалога приводит к сбою.

«Хакерский» способ решения состоит в том, чтобы отделить логику бизнес‑процесса от конкретных координат UI. Вместо «кликнуть в точку (x, y)», скрипт ищет элемент по его свойствам (заголовок окна, имя контрола, текст кнопки) и взаимодействует с ним через системные API. При этом бизнес‑логика (какие данные вводятся, в каком порядке) хранится в виде простого JSON‑описания, которое можно отправлять через HTTP‑запросы. Такой подход называют API‑first + лёгкая настольная автоматизация.

Детальный разбор проблемы с разных сторон

Техническая сторона

  • Старый стек технологий. Программы часто написаны на Visual Basic 6, Delphi или даже на старых версиях C++. Они используют Win32‑API, не поддерживают COM‑интерфейсы и не имеют открытых точек входа.
  • Отсутствие документированных API. Без официальных методов доступа приходится «выдёргивать» данные из UI.
  • Нестабильный UI. Появление новых всплывающих окон, изменение размеров элементов, локализация (русский/английский) – всё это ломает простые скрипты.

Бизнес‑сторона

  • Экономический фактор. Закупка лицензий на крупные RPA‑платформы (UiPath, Automation Anywhere) обходится в десятки тысяч долларов в год.
  • Скорость окупаемости. Рутина ввода данных стоит врачам и медсестрам часы в день; каждый час экономии – реальная экономия бюджета.
  • Требования к надёжности. Ошибки в медицинских данных недопустимы, поэтому система должна работать без сбоев.

Юридическая и этическая сторона

  • Обработка персональных данных пациентов подпадает под действие законов о защите информации (HIPAA, GDPR). Автоматизация должна гарантировать, что данные не утекут.
  • Любая модификация старой программы без согласования с поставщиком может нарушать лицензионные соглашения.

Практические примеры и кейсы

Ниже представлены три типовых сценария, с которыми сталкиваются разработчики.

Сценарий 1. Ввод данных о новом пациенте

Требуется открыть форму «Новый пациент», заполнить поля «ФИО», «Дата рождения», «Пол», выбрать страховую компанию из выпадающего списка и нажать «Сохранить». При вводе иногда появляется окно «Подтверждение записи», которое нужно подтвердить.

Сценарий 2. Обновление статуса исследования

В системе есть список исследований, каждый из которых имеет статус «В ожидании», «В процессе», «Завершено». Необходимо пройти по каждому элементу, изменить статус и добавить комментарий. Иногда система выводит предупреждение «Изменения не сохранены», которое требует подтверждения.

Сценарий 3. Экспорт отчёта в Excel

Пользователь нажимает кнопку «Экспорт», выбирает путь сохранения, подтверждает диалог и ждёт завершения процесса. При медленном соединении появляется окно «Прогресс», которое может исчезнуть преждевременно, вызывая ошибку.

Экспертные мнения из комментариев

«Legacy apps are like that one coworker who refuses to retire, still around, still essential, and still making everyone’s life harder.» – Signal‑Actuator‑1126

Автор сравнил старые приложения с коллегой, который отказывается уйти на пенсию. Это подчёркивает, что такие системы часто остаются в инфраструктуре из‑за их критической роли, несмотря на их неудобства.

«What’s worked better for us is mixing API‑first design + lightweight desktop automation. We use Python wrappers like pywinauto or AutoHotkey that can listen to a web API, execute steps locally, and handle small surprises without breaking the flow.» – Signal‑Actuator‑1126

Ключевая рекомендация – отделить бизнес‑логику (API) от взаимодействия с UI (pywinauto, AutoHotkey). Такой подход позволяет менять интерфейс без переписывания всей автоматизации.

«If these run in a browser, headless or otherwise you could try using selenium Web driver. It's normally used for automated tests...» – rainmouse

Для веб‑ориентированных задач Selenium остаётся популярным, однако он не решает проблему настольных приложений.

«I've had success using Selenium for similar legacy automation tasks, though it can get tricky with dynamic UI elements. Have you considered trying Playwright as a more modern alternative that handles popups better?» – viewsinthe6

Playwright предлагает более надёжную работу с динамическими элементами и всплывающими окнами, но опять же требует браузерный контекст.

«I agree with this take, but I'd recommend Puppeteer over Selenium» – WhiplashClarinet

Puppeteer – ещё один современный инструмент, ориентированный на Chrome, но не подходит для старых Windows‑приложений.

Возможные решения и рекомендации

1. Комбинация API‑first и лёгкой настольной автоматизации

  • Определить бизнес‑процессы в виде JSON‑описаний (например, {"action":"fill_form","fields":{"name":"Иванов И.И.","dob":"01.01.1990"}}).
  • Разработать небольшое серверное приложение (Flask/Django), которое принимает такие запросы.
  • На клиентской машине запустить «агент», написанный на Python, который слушает HTTP‑запросы и, используя pywinauto или AutoHotkey, взаимодействует с UI.
  • Для обработки всплывающих окон использовать «watch‑dog»‑механизм, который постоянно проверяет наличие новых окон и реагирует согласно правилам.

2. Использование pywinauto как базового инструмента

Библиотека позволяет находить окна и элементы по их заголовкам, классам и тексту, а не по координатам. Это делает скрипты устойчивыми к небольшим изменениям UI.

3. Автоматизация через AutoHotkey

Для простых сценариев (один‑два клика) AutoHotkey может быть быстрее, так как скрипты компилируются в исполняемый файл и работают без интерпретатора.

4. При необходимости – Selenium / Playwright для веб‑частей

Если часть процесса происходит в браузере (например, загрузка отчётов), стоит добавить слой веб‑автоматизации. Playwright лучше справляется с всплывающими окнами и динамикой, чем Selenium.

5. Обеспечение безопасности

  • Шифровать передаваемые данные (TLS).
  • Ограничить доступ к агенту по IP‑адресу и токену.
  • Логировать все действия для аудита.

6. Тестирование и CI/CD

Создайте набор юнит‑тестов, которые проверяют каждый шаг автоматизации в изолированной среде. Интегрируйте их в конвейер CI (GitLab CI, GitHub Actions) – так вы будете уверены, что после обновления UI скрипт не сломается.

Прогноз развития ситуации

В ближайшие 3‑5 лет ожидается рост спроса на гибридные решения, сочетающие облачную оркестрацию и локальные агенты. Появятся готовые «low‑code» платформы, позволяющие описывать бизнес‑процессы в виде визуальных блоков, а затем автоматически генерировать код для pywinauto или AutoHotkey. Кроме того, искусственный интеллект будет всё активнее использоваться для распознавания изменений в UI (computer vision) и адаптивного реагирования на новые диалоги.

Для специалистов это открывает новые возможности: знание Python, умение работать с WinAPI и базовые навыки DevOps станут обязательными. Компании, которые инвестируют в гибкую автоматизацию уже сегодня, получат конкурентное преимущество в виде снижения затрат и ускорения обработки медицинских данных.

Практический пример кода

Ниже – полностью рабочий пример агента, который получает задания через HTTP‑POST, ищет окно «Старый EHR», заполняет форму пациента и обрабатывает возможные всплывающие окна. В примере использованы только стандартные библиотеки Python и pywinauto.


# -*- coding: utf-8 -*-
"""
Пример агента для автоматизации старой медицинской программы.
Агент слушает HTTP‑запросы, получает JSON‑задание и
выполняет действия через pywinauto.
"""

import json
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer

from pywinauto import Application, findwindows
from pywinauto.timings import TimeoutError

# ------------------- Конфигурация -------------------
HOST = '0.0.0.0'          # слушаем на всех интерфейсах
PORT = 8080               # порт для входящих запросов
TARGET_TITLE = 'Устаревшая система'   # заголовок окна программы
# ----------------------------------------------------

def find_main_window():
    """Ищет главное окно целевого приложения."""
    try:
        # Ищем окно по заголовку, игнорируя регистр
        hwnd = findwindows.find_window(best_match=TARGET_TITLE)
        return Application().connect(handle=hwnd)
    except findwindows.WindowNotFoundError:
        return None

def handle_popup(app):
    """Обрабатывает типовые всплывающие окна."""
    try:
        # Ищем диалог с текстом «Подтверждение записи»
        dlg = app.window(title_re='.*Подтверждение.*')
        if dlg.exists(timeout=2):
            dlg.Button.click()   # нажимаем первую кнопку (ОК)
    except TimeoutError:
        pass  # диалог не появился – ничего не делаем

def fill_patient_form(data):
    """
    Заполняет форму нового пациента.
    data – словарь с полями: name, dob, gender, insurer
    """
    app = find_main_window()
    if not app:
        raise RuntimeError('Не найдено окно целевого приложения.')

    # Открываем форму «Новый пациент» (предположим, что есть меню)
    main = app.window(best_match=TARGET_TITLE)
    main.menu_select('Файл->Новый пациент')

    # Заполняем поля
    form = app.window(title_re='.*Новый пациент.*')
    form.Edit1.set_edit_text(data['name'])
    form.Edit2.set_edit_text(data['dob'])
    form.ComboBox.select(data['gender'])
    form.ComboBox2.select(data['insurer'])

    # Сохраняем
    form.Button0.click()   # кнопка «Сохранить»

    # Обрабатываем возможный диалог подтверждения
    handle_popup(app)

class RequestHandler(BaseHTTPRequestHandler):
    """Обработчик входящих HTTP‑запросов."""

    def _set_response(self, code=200):
        self.send_response(code)
        self.send_header('Content-type', 'application/json')
        self.end_headers()

    def do_POST(self):
        """Принимает JSON‑задание и запускает автоматизацию."""
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length)
        try:
            payload = json.loads(post_data.decode('utf-8'))
            # Ожидаем структуру: {"action":"fill_patient","data":{...}}
            if payload.get('action') == 'fill_patient':
                threading.Thread(
                    target=fill_patient_form,
                    args=(payload['data'],),
                    daemon=True
                ).start()
                response = {'status': 'started'}
                self._set_response()
                self.wfile.write(json.dumps(response).encode())
            else:
                self._set_response(400)
                self.wfile.write(json.dumps({'error': 'unknown action'}).encode())
        except Exception as e:
            self._set_response(500)
            self.wfile.write(json.dumps({'error': str(e)}).encode())

def run_server():
    """Запускает простой HTTP‑сервер."""
    server_address = (HOST, PORT)
    httpd = HTTPServer(server_address, RequestHandler)
    print(f'Агент запущен на http://{HOST}:{PORT}')
    httpd.serve_forever()

if __name__ == '__main__':
    run_server()

В этом примере агент прослушивает порт 8080. Чтобы добавить нового пациента, достаточно отправить POST‑запрос вида:


import requests, json

payload = {
    "action": "fill_patient",
    "data": {
        "name": "Иванов И.И.",
        "dob": "01.01.1990",
        "gender": "Мужской",
        "insurer": "ООО \"МедСтрах\""
    }
}
requests.post('http://localhost:8080', json=payload)

Код демонстрирует принцип «разделения логики и UI»: сервер получает чистое описание задачи, а локальный агент уже знает, как взаимодействовать с конкретным окном программы. При изменении интерфейса достаточно поправить лишь функции fill_patient_form и handle_popup, не меняя клиентскую часть.


Оригинал
PREVIOUS ARTICLE