Как я сделал программу для стоматологии, которая работает без интернета — с одной флешки

Ко мне обратилась государственная стоматологическая клиника (не та, где работает мама, о чем я писал ранее).
Задача звучала достаточно просто, а по факту оказалась с занятным набором ограничений: инвентаризацию кабинетов и табель рабочего времени вели вручную в Excel и Word, и это всё хотелось автоматизировать.

Условия сразу отсекали большинство обычных решений. Сеть в клинике закрытая — ничего стороннего ставить нельзя, никаких мессенджеров, никаких облачных сервисов.
Нужна была программа, которая просто работает — воткнул флешку в любой компьютер, запустил, всё функционирует, без интернета и без установки чего-либо.
Мне конечно потребовалось время, чтобы сообразить как такое "сокровище" создать))

Архитектура: одна программа, ноль интернета

В итоге получилось локальное веб-приложение, упакованное в один exe-файл. Логика такая: запускаешь — поднимается локальный сервер, автоматически открывается браузер с интерфейсом.

import webbrowser import threading def find_free_port(start=8080, end=8090): import socket for port in range(start, end): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: if s.connect_ex(('127.0.0.1', port)) != 0: return port raise RuntimeError("Нет свободных портов") port = find_free_port() threading.Timer(1.0, lambda: webbrowser.open(f"http://127.0.0.1:{port}")).start() app.run(host="127.0.0.1", port=port)

Сервер слушает только 127.0.0.1 — то есть даже теоретически недоступен из остальной сети клиники. Это было не для галочки, а прямое требование безопасности для закрытого контура.

Все данные — в одном файле, обычная SQLite-база рядом с программой:

CREATE TABLE items ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, room TEXT, quantity INTEGER, price REAL, status TEXT DEFAULT 'в наличии', updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );

Бэкап — это буквально скопировать один файл clinic.db на другую флешку раз в месяц. Ничего умнее для клиники, где основной пользователь не особо дружит с компьютером, придумывать не стал — чем проще процедура бэкапа, тем больше шанс, что её реально будут делать.

Импорт из чего угодно

Кабинеты годами вели данные в разных форматах — где-то xlsx, где-то старый xls, где-то вообще Word-таблицы. Переучивать всех вносить данные по-новому было не вариантом, так что программа сама определяет формат и пытается угадать колонки по заголовкам:

def detect_columns(headers): mapping = {} for i, h in enumerate(headers): h_lower = h.strip().lower() if any(w in h_lower for w in ["назв", "наимен", "инструмент"]): mapping["name"] = i elif any(w in h_lower for w in ["кол-во", "количество", "шт"]): mapping["quantity"] = i elif any(w in h_lower for w in ["цена", "стоимость"]): mapping["price"] = i elif any(w in h_lower for w in ["кабинет", "каб."]): mapping["room"] = i return mapping

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

Табель — не ИИ, а обычная детерминированная логика

Тут специально хочу быть честным: табель не "придумывает" никакой алгоритм машинного обучения. Это просто программа, которая знает календарь, знает сотрудников (один раз запоминает их из образца заполненного табеля) и раскладывает коды по правилам — рабочий день, выходной, а дальше корректировки вручную:

def build_month_schedule(year, month, employees): import calendar days_in_month = calendar.monthrange(year, month)[1] schedule = {} for emp in employees: codes = {} for day in range(1, days_in_month + 1): weekday = date(year, month, day).weekday() codes[day] = "В" if weekday >= 5 else "Я" schedule[emp.id] = codes return schedule def apply_leave(schedule, employee_id, day_range, code): # day_range вида "10-24" или "5" start, end = parse_range(day_range) for day in range(start, end + 1): schedule[employee_id][day] = code # "ОТ" отпуск, "Б" больничный recalc_totals(schedule, employee_id)

Вписал "10-24" — коды и итоговые часы в этом диапазоне пересчитываются автоматически. Никакой магии, просто нормально спроектированная логика вместо ручного пересчёта в экселе.

Что было не гладко по пути

Список для честности, а не для красоты: сломался pip, чинил через ensurepip. PyInstaller не хотел находиться в PATH — пришлось гонять через python -m PyInstaller. Парсинг docx и html без сторонних тяжёлых библиотек — отдельное упражнение на терпение. И отдельно пришлось повозиться, чтобы exe нормально запускался не только на моей машине, а на любом чужом компьютере, куда воткнут флешку — там вылезают свои сюрпризы с путями и правами доступа.

Почему не готовый сервис

Готовых сервисов учёта полно, но почти все — облачные и требуют либо интернет, либо установку. Тут условие было прямо обратное. Проще было написать своё под эти конкретные ограничения, чем городить костыли вокруг продукта, который на такое вообще не рассчитан.

Комплект передачи

Флешка с exe-файлом и pdf-инструкцией на простом языке, с подсказками и шпаргалкой в конце. Ничего лишнего — ровно то, что нужно человеку, который просто хочет, чтобы это работало, не разбираясь, что там внутри.