Осенью 2024 года я не планировал начинать новый проект. Тем более связанный с медициной. После тяжёлой пневмонии дочери врач назначил контрольный анализ крови. Среди стандартных показателей оказался анализ на уровень глюкозы. Именно он впервые показал проблему. Вскоре нас направили в детскую больницу, где после обследования поставили диагноз — сахарный диабет первого типа. Наверное, многие родители, столкнувшиеся с этим впервые, испытывают похожие ощущения. За несколько дней приходится освоить огромный объём новой информации: научиться измерять уровень глюкозы, рассчитывать углеводы, понимать действие разных типов инсулина, вести дневник питания и принимать десятки небольших решений каждый день. Параллельно с этим я заканчивал курс Python в Яндекс Практикуме. Днём — работа, вечером — обучение, ночью — медицинские статьи и клинические рекомендации. Не самый простой период, но именно тогда и появилась идея проекта, о котором пойдёт речь дальше.
Когда проблема оказалась не медицинской, а инженерной
Первое время мы использовали обычный бумажный дневник. Довольно быстро стало понятно, что этого недостаточно. Тогда я начал искать электронные приложения для ведения дневника диабета. Их оказалось неожиданно много. Практически все умели хранить историю измерений, вести дневник питания, строить графики и показывать статистику. Некоторые поддерживали интеграцию с системами непрерывного мониторинга глюкозы (CGM), другие помогали рассчитывать хлебные единицы или учитывать введённый инсулин.
На первый взгляд всё выглядело вполне убедительно. Но спустя несколько недель использования я поймал себя на мысли, что основная сложность заключается вовсе не в отсутствии функций. Она заключалась в организации данных. Типичный сценарий выглядел так:

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

Именно в этот момент я впервые посмотрел на проблему уже не как родитель, а как разработчик.
После почти двадцати лет коммерческой разработки я привык начинать не с выбора технологий, а с анализа предметной области. И здесь неожиданно обнаружилась интересная особенность. Проблема заключалась не в том, что приложений мало. И не в том, что им не хватает функций. Проблема заключалась в том, что каждое приложение являлось владельцем только своей части данных.
Одно знало историю глюкозы.
Другое — питание.
Третье — введённый инсулин.
С инженерной точки зрения это означало, что не существует единой модели данных, описывающей происходящее с человеком в течение дня. А значит, невозможно ответить на вполне естественные вопросы:
Что именно ел ребёнок за два часа до роста глюкозы выше целевого диапазона?
Какой инсулин был введён перед этим?
Через какое время после еды начался подъём сахара?
Повторяется ли эта ситуация на одинаковых завтраках?
Для человека подобные вопросы звучат абсолютно естественно. Для большинства существующих приложений — нет, потому что информация хранится в разных системах и связана только временем, которое пользователю приходится сопоставлять самостоятельно. Именно тогда стало понятно, что если писать собственную систему, то её центральной сущностью должны стать не продукты, не инсулин и даже не показатели глюкозы.
Основной сущностью должно стать событие во времени.

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

Это оказалось неожиданно полезным упражнением. Практически все дальнейшие архитектурные решения выросли именно из этого списка.
1. Вся информация должна находиться на одном экране
Это было самое важное требование. В течение дня приходится постоянно принимать небольшие решения:
нужна ли корректировка дозы;
как организм отреагировал на конкретный приём пищи;
сколько времени прошло после введения инсулина;
является ли текущий рост сахара ожидаемым.
Если для ответа на эти вопросы приходится открывать три разных приложения, искать нужное время и мысленно сопоставлять события, когнитивная нагрузка становится слишком высокой. Поэтому основной экран должен был объединить:
график глюкозы;
журнал питания;
введённый инсулин;
расчётные показатели.
2. Система должна хранить собственную историю
Практически все CGM‑системы используют собственные облачные сервисы. Это удобно, пока нужно просто посмотреть текущий уровень глюкозы. Но как только появляется желание анализировать данные за месяцы, строить собственные отчёты или экспериментировать с алгоритмами, возникает серьёзное ограничение — данные находятся не под вашим контролем.
Поэтому одним из первых требований стало собственное локальное хранилище истории. Внешние сервисы должны выступать только источником новых измерений. Источником истины должна стать собственная база данных.
3. Домашние блюда важнее огромных справочников
Большинство существующих приложений содержат базы из тысяч продуктов. На практике мы используем примерно несколько десятков блюд, которые регулярно готовятся дома. Именно они должны быть максимально удобны для повторного использования. Поэтому система проектировалась вокруг собственных ингредиентов и собственных рецептов, а не вокруг огромных универсальных каталогов.
4. Архитектура должна позволять развивать аналитику
На момент написания первой версии никакой аналитики ещё не существовало. Но уже тогда было понятно, что рано или поздно захочется отвечать на вопросы значительно сложнее простого отображения графика.
Например:
какие завтраки чаще всего приводят к гипергликемии;
как меняется скорость роста сахара при разных продуктах;
какие блюда требуют более длинной паузы после инсулина;
насколько стабилен углеводный коэффициент.
Поэтому данные должны были храниться таким образом, чтобы любые новые алгоритмы можно было реализовывать без переработки всей системы.
5. Минимум ручной работы
Любое действие, которое приходится выполнять ежедневно, со временем начинает раздражать. Если информацию можно получить автоматически, значит, её нужно получать автоматически. Поэтому уже на раннем этапе стало понятно, что интеграция с системами непрерывного мониторинга глюкозы — не дополнительная возможность, а обязательное требование.
Именно оно позже привело к интеграции сначала с FreeStyle Libre, а затем и с Medtrum.
Почему именно Django
Когда требования стали более‑менее понятны, пришло время выбирать стек. К этому моменту я только закончил обучение Python в Яндекс Практикуме, поэтому выбор в пользу Django выглядел вполне естественным. Но решающим фактором оказалось совсем не это. После многих лет коммерческой разработки я всё чаще убеждаюсь, что для внутренних систем и небольших сервисов скорость разработки зачастую важнее теоретически более «правильной» архитектуры. Django позволял практически сразу перейти к реализации предметной области, не тратя время на инфраструктуру.
Из коробки были доступны:
ORM;
система миграций;
административная панель;
аутентификация пользователей;
маршрутизация;
готовая экосистема пакетов.
Фактически уже в первый вечер после создания проекта можно было работать с моделями данных, а не заниматься настройкой каркаса приложения. Для pet‑проекта, который развивается по вечерам после основной работы, это оказалось значительно важнее, чем потенциальный выигрыш в производительности или модности выбранного фреймворка.
Почему PostgreSQL
Следующий выбор был ещё проще.
С самого начала было понятно, что система будет работать с временными рядами. Данные непрерывного мониторинга глюкозы поступают каждые несколько минут. Поверх них постепенно начинают накладываться:
события питания;
введение инсулина;
результаты аналитических расчётов;
вычисленные показатели компенсации диабета.
Подобная нагрузка плохо сочетается с простыми встроенными базами данных. PostgreSQL давно стал промышленным стандартом для подобных задач, поэтому дополнительных сомнений практически не возникло. Кроме надёжности, он позволял использовать индексирование временных данных, массовую загрузку записей через bulk_create() и сложные агрегирующие запросы, которые позже понадобились для аналитики.
Оглядываясь назад, могу сказать, что выбор технологий практически ни разу не пришлось пересматривать. Гораздо сложнее оказалось правильно выбрать модель данных. Именно она в итоге определила дальнейшее развитие всего проекта.
Архитектура данных: когда предметная область важнее технологий
Обычно при рассказе об архитектуре начинают перечислять таблицы базы данных или показывать ER‑диаграмму. В этом проекте интереснее другое — почему модель данных получилась именно такой.

На первый взгляд система кажется достаточно простой:
журнал питания;
измерения глюкозы;
введение инсулина;
ингредиенты и блюда.
Но практически сразу стало понятно, что эти данные имеют совершенно разную природу. Одни появляются автоматически каждые несколько минут. Другие пользователь вводит вручную. Третьи вообще являются справочной информацией и практически никогда не изменяются. Поэтому вместо попытки сделать одну «универсальную» модель было принято противоположное решение — разделить систему на несколько независимых доменных сущностей.
Центральными моделями стали:
Diary— журнал событий пользователя;CachedSugar— история измерений глюкозы;CachedInsulin— история введённого инсулина.
Вокруг них постепенно сформировался предметный слой:
Ingredients;Dish;DiaryIngredients;DiaryDishs;UserProfile.
Такое разделение оказалось значительно проще сопровождать, чем универсальные таблицы с большим количеством необязательных полей. Каждая модель отвечает только за одну часть предметной области. Изменения в расчётах хлебных единиц никак не влияют на хранение глюкозы. Добавление нового источника CGM не требует изменения структуры дневника питания. А развитие аналитики практически не затрагивает пользовательские данные. Со временем стало понятно, что именно слабая связанность моделей оказалась одним из самых удачных архитектурных решений проекта.
Почему понадобилось собственное хранилище
Изначально я планировал вообще ничего не хранить. Казалось вполне логичным каждый раз получать данные напрямую из приложений производителей CGM. Первые версии именно так и работали. Однако довольно быстро проявились ограничения такого подхода.
Во‑первых, облачные сервисы производителей создавались для отображения информации пользователю, а не для постоянного выполнения аналитических запросов.
Во‑вторых, любое внешнее API — это зависимость.
Изменение формата ответа, ограничение количества запросов, временная недоступность сервиса или проблемы с сетью сразу отражаются на работе системы. И наконец, появилась задача, которую невозможно решить без собственного хранения данных. Я начал экспериментировать с аналитикой. Появились вопросы, на которые хотелось получать ответы автоматически:
сколько времени сахар находится в целевом диапазоне;
как разные блюда влияют на скорость роста глюкозы;
насколько стабилен углеводный коэффициент;
изменяется ли чувствительность к инсулину со временем.
Такие расчёты требуют постоянной работы с историческими данными. Использовать для этого внешние сервисы было бы неправильно. Так появились модели CachedSugar и CachedInsulin. После этого архитектура естественным образом разделилась на два независимых слоя.
Первый отвечает за получение данных.
Второй — за их хранение и анализ.
Это небольшое изменение оказалось переломным. До него проект был, по сути, удобным интерфейсом поверх сторонних сервисов. После появления собственного хранилища он стал самостоятельной системой.
Кэш как архитектурная граница
Интересно, что слово «кэш» здесь не совсем точное.
На практике CachedSugar и CachedInsulin выполняют значительно более важную роль. Это уже не временное хранение данных ради ускорения запросов. Это собственная историческая база измерений. Именно она становится единственным источником данных для всех аналитических алгоритмов. Внешние сервисы используются только для импорта новых событий. После этого система больше не зависит от того, доступно ли сейчас API производителя. Такое разделение принесло сразу несколько преимуществ.
Во‑первых, любые графики строятся значительно быстрее.
Во‑вторых, аналитика больше не создаёт нагрузку на внешние сервисы.
В‑третьих, появляется возможность пересчитывать исторические данные при изменении алгоритмов.
Например, если позже изменится формула расчёта какого‑либо показателя, достаточно повторно обработать собственную историю, не обращаясь к внешним источникам.
Оглядываясь назад, именно введение собственного слоя хранения оказалось первым по‑настоящему архитектурным решением проекта. Остальные изменения стали естественным продолжением этого подхода.
Единая временная модель: решение, которое определило весь проект
После появления собственного хранилища данных возник следующий вопрос. Как связать между собой совершенно разные сущности? На первый взгляд они никак не связаны.
Измерение глюкозы — это один тип данных.
Приём пищи — другой.
Введение инсулина — третий.
Можно было проектировать каждую подсистему отдельно, а затем пытаться объединять их на уровне пользовательского интерфейса. Но постепенно стало понятно, что у всех этих событий есть одно общее свойство.
Время.
Именно время является тем параметром, который связывает абсолютно всё происходящее. Если посмотреть не на тип данных, а на последовательность событий, картина становится значительно проще. Например, обычный завтрак превращается в такую цепочку:
08:00 — введение инсулина 08:15 — завтрак 08:20 — глюкоза 5.7 08:25 — глюкоза 6.0 08:30 — глюкоза 6.4 08:35 — глюкоза 6.9 ...
В этот момент произошло, пожалуй, самое важное изменение архитектуры. Я перестал воспринимать систему как набор отдельных сущностей. Фактически она превратилась в единый поток событий, происходящих на временной шкале. После этого многие решения стали очевидными сами собой. Не нужно отдельно проектировать экран сахара, экран питания и экран инсулина. Достаточно научиться отображать события разных типов на одной временной оси.
Именно эта идея позже позволила без серьёзных изменений добавить:
новые источники CGM;
импорт инсулина;
аналитические события;
расчётные показатели;
рекомендации алгоритмов.
Для системы они все являются событиями, различающимися только типом и набором атрибутов. Архитектура при этом практически не меняется.
Почему интерфейс построен вокруг одного экрана
Интересно, что решение о едином интерфейсе появилось задолго до этого проекта.
Несколько лет я занимался развитием торгового терминала «Мир инвестиций» БКС в роли Product Owner. Одной из постоянных задач было уменьшение количества действий пользователя до принятия решения. Этот опыт неожиданно оказался очень полезен и здесь.
При контроле диабета человек постоянно отвечает на один и тот же вопрос:
Что происходит сейчас и нужно ли что‑то делать?
Для ответа не нужны десять разных экранов. Нужен один экран, на котором уже собрана вся необходимая информация. Именно поэтому центральная страница системы объединяет:
график глюкозы;
журнал питания;
введённый инсулин;
расчёт хлебных единиц;
фильтрацию по времени;
аналитические показатели.
Все остальные разделы — ингредиенты, блюда, справочники и настройки — используются значительно реже. Поэтому они сознательно вынесены за пределы основного рабочего сценария. Если посмотреть на интерфейс сегодня, становится понятно, что он повторяет архитектуру данных. В центре находится не меню приложения. И не список функций. А единая временная шкала событий.
По сути, пользователь видит не отдельные сущности базы данных, а историю происходящего с организмом.
Вывод
Если попытаться описать развитие проекта одной фразой, то она будет звучать так:
Архитектура появилась как следствие предметной области, а не наоборот.

Когда я начинал этот проект, мне казалось, что главной задачей будет написать веб‑приложение. На практике самым сложным оказалось совсем другое — понять, какие данные действительно важны, как они связаны между собой и какие вопросы система должна помогать решать. Именно поэтому большинство архитектурных решений принималось не заранее, а в тот момент, когда возникала новая практическая задача.
Сначала появился дневник питания.
Потом — собственная база ингредиентов.
Затем — блюда и автоматический расчёт хлебных единиц.
После этого возникла необходимость хранить собственную историю измерений, интегрироваться с CGM и строить аналитику. Если посмотреть на проект сегодня, становится заметно, что практически каждое новое требование не ломало архитектуру, а естественным образом расширяло её.

Наверное, именно это я считаю главным критерием удачной архитектуры. Не количество паттернов. Не модный стек технологий. И даже не производительность. А способность системы спокойно развиваться вместе с предметной областью.
В этой статье я практически не касался интеграции с медицинскими устройствами. А между тем именно она оказалась одной из самых интересных инженерных задач. Пришлось разбираться с различными API, особенностями облачных сервисов производителей CGM, синхронизацией данных, хранением истории измерений и автоматическим импортом новых событий.
Об этом и пойдёт речь в следующей статье.
Мы разберём:
как устроена интеграция с FreeStyle Libre и Medtrum;
почему появился Home Assistant;
как организована синхронизация данных;
почему собственное хранилище оказалось важнее прямой работы с внешними API;
и как вся эта инфраструктура превратилась в единый поток данных для аналитики.
Продолжение следует…
