То не присказка, то сказка

В полночь холодильник превращается в портал.

Не в сказку — в инвентарную реальность, где за банкой горошка живёт недоеденная надежда, а за пакетом майонеза прячется вопрос, который не задают вслух: «А точно ли мы всё купили?»

У нас было всё как у людей: идея «сделаем по‑простому», семейная традиция «оливье обязательно» и тайный план «в этот раз без хаоса». Но Новый год — хитрый инженер по отказам. Он знает, где у вас слабые места: в 18:40, когда уже пришли гости, внезапно выясняется, что хлеба нет, шампанское “было где‑то”, а огурцы солёные есть, но… не те. И вот вы стоите, смотрите на стол, и ощущаете себя человеком, который пытается скомпилировать праздник без зависимостей.

Я решил сыграть иначе.

Представьте, что новогодний стол — это фэнтези‑квест.

Меню — королевство.

Блюда — герои.

Ингредиенты — артефакты.

Остатки — древние сокровища в разных подземельях (морозилка, шкаф, ящик для овощей).

А закупка — список предметов, без которых финальный босс «Салат‑и‑Гости‑в‑Прихожей» не проходится.

В обычной сказке всё держится на чуде. В нашей — на графе.

Мы завели в Onto маленькую онтологию, чтобы она выполняла роль мапа мира, а OntoAI — роль придворного архивариуса, который не просто помнит “что надо”, а умеет связать “что надо” с “почему надо” и “где это лежит”.

С тех пор любой объект получил паспорт:

  • Меню говорит: «Я включаю блюда».

  • Блюдо говорит: «Я либо готовлюсь по рецепту, либо покупаюсь как продукт».

  • Рецепт признаётся: «Во мне живут компоненты».

  • Компонент честно показывает: «Мне требуется продукт».

  • Остаток шепчет: «Я — не абстрактный “майонез”, я конкретные 100 граммов, и я хранюсь в холодильнике».

  • Закупка сурово фиксирует: «Эти продукты нужны, потому что они требуются блюдам меню и не закрываются остатками».

И случилось странное: подготовка к празднику перестала быть шаманством.

Оливье Даши с авокадо перестало быть просто “рецептом в голове” и стало узлом в сети причинности: от блюда — к рецепту — к ингредиентам — к продуктам — к остаткам — к закупке.

Хлеб и шампанское перестали быть “ну купим потом” и стали покупными позиция��и меню, которые нельзя забыть, потому что они теперь тоже часть королевства.

А холодильник впервые перестал быть мистическим объектом с памятью на 5 секунд — потому что память переехала в модель.

И когда в какой-то момент граф показал:

«Всё для оливье есть, кроме…» —

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

Дальше в статье я покажу:

как мы собрали минимальную онтологию “праздничного стола”, почему без сущности “Блюдо меню” всё ломается, как отделение “Продукта” от “Остатка” спасает психику, и как из этого графа получается закупка, которую не стыдно открыть в магазине — даже когда уже играет «В лесу родилась ёлочка», а очередь напоминает DLC к Dark Souls.

Карта королевства

0) Сначала — не «шаблоны», а вопросы к реальности

Любая онтология, если её не держать в ежовых рукавицах, быстро превращается в «энциклопедию всего». Поэтому мы начали не с сущностей, а с трёх вопросов, на которые модель обязана отвечать:

  1. Что будет на столе? (меню: список позиций, включая “купить” и “приготовить”)

  2. Что уже есть? (остатки + где лежит)

  3. Что нужно докупить — и ради какого блюда? (закупка с объяснимостью)

Всё остальное (калории, нутриенты, любимые бренды и философия майонеза) — потом, если останутся силы и трезвость.

1) Самый важный поворот: «Меню ≠ список рецептов»

Наивная схема выглядит так:

Меню → Рецепты → Ингредиенты → Продукты

И она ломается ровно в тот момент, когда в меню появляется шампанское или хлеб.

У них нет рецепта (и не нужен), но они часть меню и должны попадать в закупку.

Поэтому мы ввели промежуточный слой:

Блюдо меню — сущность “позиция на столе”, которая может быть:

  • приготовлена по рецепту, или

  • куплена как готовый продукт.

Это маленькая вставка, которая спасает модель от странного “Рецепт: Шампанское полусладкое”.

2) Итоговый «скелет» модели: 8 шаблонов и понятные связи

Мы специально держали набор минимальным — ровно то, что нужно для трёх вопросов.

Шаблоны (meta entities):

  • Меню

  • Блюдо меню

  • Рецепт

  • Компонент (ингредиент как объект, с количеством и заметками)

  • Продукт (тип продукта: “Авокадо”, “Майонез”, “Шоколад”…)

  • Остаток (факт наличия конкретного количества продукта)

  • Хранилище (холодильник/шкаф/морозилка…)

  • Закупка

Связи (meta relations):

  • Меню → Блюдо меню: включает

  • Блюдо меню → Рецепт: приготовлено по

  • Блюдо меню → Продукт: покупается как (для “купить готовое”: хлеб, шампанское)

  • Рецепт → Компонент: содержит

  • Компонент → Продукт: требует

  • Блюдо меню → Продукт: требуется (логистический слой: что нужно для этого блюда)

  • Остаток → Продукт: относится к

  • Остаток → Хранилище: хранится в

  • Закупка → Продукт: требуется

  • Закупка → Меню/Блюдо меню: назначена для

Если хочется одной картинкой, то логика такая:

Меню ─включает→ Блюдо меню ─приготовлено по→ Рецепт ─содержит→ Компонент ─требует→ Продукт

│ ├─покупается как→ Продукт

│ └─требуется→ Продукт

Остаток ─относится к→ Продукт

Остаток ─хранится в→ Хранилище

Закупка ─требуется→ Продукт

Закупка ─назначена для→ Меню / Блюдо меню

Рисунок 1. Карта онтологии праздничного стола
Рисунок 1. Карта онтологии праздничного стола

Дальше — не теория, а сборка “по месту”.

Шаг 1. Создали объект Меню

Объект “Меню: Новогодний стол 2025” — это корневой узел, который просто перечисляет блюда.

Шаг 2. Создали объекты Блюдо меню (позиции стола)

В меню завели 7 позиций:

  • Оливье Даши с авокадо

  • Запечённая курица с картофелем и травами

  • Сырные рулетики с вяленой уткой

  • Шоколадный мусс с авокадо

  • Канапе с красной икрой

  • Шампанское

  • Хлеб

И связали их с меню через включает.

Рисунок 2. Нне без курьеза
Рисунок 2. Нне без курьеза

Зачем так: меню — это план, �� план всегда оперирует “позициями”, а не только рецептами.

Рисунок 3. Меню как план мероприятия
Рисунок 3. Меню как план мероприятия

Шаг 3. Отделили «готовим» от «покупаем»

  • Для Хлеб и Шампанское сделали Блюдо меню → Продукт связью покупается как.

  • Для Канапе с красной икрой тоже использовали “покупается как” (как упрощение: купили красную икру — канапе случились).

Зачем так: купленные позиции попадают в ту же систему, что и приготовляемые, и не требуют “фейковых рецептов”.

Шаг 4. Для уникального блюда сделали полноценный Рецепт

Мы детально описали только один рецепт: «Оливье Даши с авокадо» — потому что он нестандартный и там важны детали.

Создали Рецепт и связали блюдо с рецептом: приготовлено по.

Рисунок 4 Рецепт как структура знания (Оливье Даши с авокадо)
Рисунок 4 Рецепт как структура знания (Оливье Даши с авокадо)

Шаг 5. Сделали ингредиенты не строками, а узлами: Компонент

Вместо “майонез” текстом в поле мы завели компоненты как отдельные объекты:

  • Яйцо 4 шт

  • Авокадо 1 шт

  • Горошек 200 г

  • Картофель 3 шт

  • Майонез 150 г

  • Морковь 2 шт

  • Огурцы солёные 150 г

  • Перец чёрный по вкусу

  • Соль по вкусу

И связали:

  • Рецепт → Компонент (содержит)

  • Компонент → Продукт (требует)

Почему так, а не “Рецепт → Продукт”:

потому что количество (“150 г”) и пояснения (“по вкусу”) — это свойства ингредиента в контексте рецепта. Проще хранить это в узле “Компонент”, чем пытаться приклеить атрибуты к связи или городить отдельную таблицу.

Шаг 6. Ввели Продукты как справочник типов

“Авокадо”, “Майонез”, “Шоколад”, “Сливки”, “Курица”, “Зелень”… — это типы, к которым могут относиться:

  • компоненты рецептов,

  • остатки,

  • закупка,

  • покупные позиции меню.

Один продукт — много контекстов. И это нормально.

Шаг 7. Собрали «инвентарь»: Хранилища и Остатки

Создали хранилища:

  • Холодильник

  • Морозилка

  • Шкаф

  • Ящик для овощей

  • Хлебница

И внесли остатки отдельными объектами, например:

  • Остаток: Авокадо 1 шт → (относится к Авокадо) → (хранится в Холодильник)

  • Остаток: Картофель 5 шт → … → (хранится в Ящик для овощей)

  • Остаток: Хлеб 1 батон → … → (хранится в Хлебница)

  • Остаток: Перец чёрный ½ баночки → … → (хранится в Шкаф)

Почему “Остаток” отдельной сущностью:

потому что “Продукт” — это тип, а “Остаток” — это факт наличия с количеством и местом. Один и тот же продукт может существовать в нескольких остатках (или партиях), а также исчезать/появляться со временем.

Шаг 8. Собрали Закупку и сделали её объяснимой

Создали объект “Закупка Новый год 2025” и связали:

  • Закупка → Меню: назначена для

  • Закупка → Блюда меню: назначена для (чтобы видно было, какие блюда обслуживаем)

Дальше добавили продукты, которые надо купить:

сыр, мёд, курица, зелень, шоколад, сливки, красная икра, огурцы солёные…

И вот тут важный UX-ход:

мы добавили прямую связь Блюдо меню → Продукт (требуется).

То есть теперь можно открыть блюдо и сразу увидеть его “логистическую ведомость”, не бегая по рецептам (которые могут быть детализированы не для всех блюд).

4) Почему модель получилась именно такой (а не “ещё одной таблицей”)

1) Разделение уровней: план / знание / действие

  • Блюдо меню — план (“что будет на столе”)

  • Рецепт — знание (“как это готовить”)

  • Закупка — действие (“что покупаем и ради чего”)

Это снимает вечную боль: не всё, что в меню, обязано быть рецептом.

2) Два слоя ингредиентов: кулинарный и логистический

  • Кулинарный: Рецепт → Компонент → Продукт (с количествами, нюансами)

  • Логистический: Блюдо → Продукт (что надо обеспечить)

Так можно начинать грубо (“для мусса нужны шоколад и сливки”), а потом углублять рецепт, когда появится желание.

3) Продукт не хранит “сколько”

Количество живёт:

  • в Компоненте (сколько надо по рецепту),

  • в Остатке (сколько есть и где лежит).

Продукт — это идентичность/тип. Благодаря этому граф не разъезжается в кашу из “майонез_100г”, “майонез_150г”, “майонез_ещё_чуть-чуть”.

4) Трассируемость

В закупке видно:

  • какие продукты покупаем,

  • и для какого меню/блюд это всё.

Это резко снижает шанс купить “просто на всякий случай” ещё одну банку горошка, пока в шкафу уже живёт предыдущая (она просто ждала своего часа).

И вот наш верный Ланселот идёт за провизией

1) Инструкция как контракт: что именно мы заставили OntoAI сделать

Ключевая идея: относиться к OntoAI не как к “чату”, а как к оператору данных, которому выдали протокол.

В протоколе было два важных технических требования:

A. Полная выгрузка данных по шаблонам (meta entities)

Для каждого шаблона (“Меню”, “Блюдо меню”, “Рецепт”, “Компонент”, “Продукт”, “Остаток”, “Хранилище”, “Закупка”) — пройтись find/v2 постранично, пока не выгрузим всё.

B. Для каждого объекта — выгрузить связи и поля

Для каждого entity — дернуть GET ... entity/{id}?relatedEntities=true&relatedDiagrams=true и вытащить:

  • fields (количество, комментарии, “способ получения” и т.д.)

  • related_entities (включает/приготовлено по/требует/покупается как/относится к/хранится в).

Это важно: без “relatedEntities=true” вы получаете “мешок объектов”, а нам нужен граф.

2) Шаг нулевой: OntoAI нашёл UUID всех шаблонов

Первое, что сделал OntoAI — по именам нашёл UUID всех meta entities в пространстве “Кейс Новый год” (realmId указан в инструкции). Это техническая деталь, но она позволяет дальше работать через API детерминированно: любой объек�� можно точно адресовать.

3) Меню как план: что будет на столе

Дальше OntoAI выгрузил объект:

  • Меню: “Новогодний стол 2025”

И вытащил из него все связи включает → Блюдо меню. Получилось 6 позиций:

  1. Хлеб

  2. Шампанское

  3. Шоколадный мусс с авокадо

  4. Сырные рулетики с вяленой уткой

  5. Запечённая курица с картофелем и травами

  6. Оливье Даши с авокадо

Дальше — важная классификация:

  • “Хлеб” и “Шампанское” помечены как покупаются как продукт (то есть без рецепта).

  • “Оливье Даши с авокадо” — приготовлено по рецепту.

  • Остальные три — “домашние” блюда (в этом прогоне без подробных рецептов).

Это как раз демонстрирует, почему в модели нужен слой “Блюдо меню”: он одинаково хорошо держит и приготовляемые, и покупные позиции.

4) Рецепт и ингредиенты: раскрутка цепочки “Рецепт → Компонент → Продукт”

В пространстве был один детализированный рецепт:

  • Рецепт: “Оливье Даши с авокадо”

OntoAI выгрузил его компоненты и для каждого компонента показал, какой Продукт он “требует”. Получилось 9 продуктовых типов:

  • Авокадо

  • Горошек зелёный

  • Картофель

  • Майонез

  • Морковь

  • Огурцы солёные

  • Перец чёрный

  • Соль

  • Яйцо куриное

То есть “рецепт” в графе перестал быть текстом и стал структурой, по которой можно ходить и делать выводы.

5) Остатки как факты: что уже есть и где оно лежит

После этого OntoAI выгрузил все сущности типа Остаток и собрал нормальную “инвентарную таблицу” (продукт, количество, хранилище, комментарий). В текущих остатках оказалось 12 позиций:

Шампанское

Рисунок 5. Остатки продуктов и хранилища
Рисунок 5. Остатки продуктов и хранилища

6) Сопоставление меню и остатков: чего не хватает

Дальше OntoAI сделал то, ради чего всё это затевалось: сверил потребности блюд (по рецептам и “логистическим” продуктам) с остатками.

По итогам сравнения в закупку ушло следующее:

Недостающее по блюдам:

  • Оливье Даши с авокадо → Огурцы солёные

  • Запечённая курица с картофелем и травами → КурицаЗелень

  • Сырные рулетики с вяленой уткой → СырМёд

  • Шоколадный мусс с авокадо → ШоколадСливки

И тут случилась реальность: ты напомнил про красную икру.

OntoAI:

  • создал Продукт “Красная икра”

  • добавил Блюдо меню “Канапе с красной икрой”

  • связал блюдо с продуктом через покупается как

  • и так как остатков нет — автоматически включил икру в закупку.

В результате финальный список покупок стал таким:

И вот почему

(Это уже тот вид, который не стыдно показывать человеку в магазине: “что купить” + “зачем”, ну или настроить интеграцию с сетью магазинов.)

7) Главное: закупка стала объектом в Onto, а не просто текстом

После отчёта OntoAI создал сущность:

  • “Закупка Новый год 2025”

И добавил в граф сразу три слоя связей:

  1. Закупка → Продукт связью требуется

  2. Закупка → Блюдо меню связью назначена для (это “объяснимость” закупки)

  3. по твоему требованию: Блюдо меню → Продукт связью требуется (это “локальная ведомость” прямо на карточке блюда)

Почему пункт 3 важен: иначе закупка “всё знает”, но блюдо “само по себе” — нет.

А так открываешь “Шоколадный мусс” и сразу видишь: шоколад и сливки требуются, без прыжков через сущность “Закупка”.

В процессе можно и фантазировать

Рисунок 6. Не забываем о важном
Рисунок 6. Не забываем о важном

И все равно получить нужный результат

Рисунок 7. План закупки как результат вывода
Рисунок 7. План закупки как результат вывода

8) Полезная честность: текущая логика сравнения пока “булевая”

В этом прогоне сравнение работало по принципу есть/нет.

То есть “100 г майонеза” считается как “майонез есть”, даже если в рецепте “150 г”. Аналогично “утка 100 г — впритык” пока не превращается автоматически в “докупи ещё 100 г”.

Это не баг онтологии — это просто следующий уровень: парсинг количеств + сравнение чисел + пороги (“впритык”). Хорошая новость: структура уже готова, добавляется только вычислительный слой.

Заключение: от онтологии оливье к онтологии Деда Мороза

В какой-то момент я поймал себя на мысли: мы не «готовили Новый год». Мы компилировали его.

Сначала была простая цель — чтобы оливье не стало лотереей «угадай, чего не хватит». Потом появился граф, и внезапно выяснилось, что праздничный стол — это не хаос, а очень даже приличная система с зависимостями, типами сущностей и причинно‑следственными связями.

И вот тут случился главный сюжетный твист этой предпраздничной сказки:

онтология оливье оказалась gateway‑drug.

Потому что если вы однажды честно смоделировали:

  • что меню включает блюда,

  • блюдо готовится по рецепту или покупается как продукт,

  • рецепт содержит компоненты,

  • компоненты требуют продукты,

  • остатки хранятся в шкафу (и это тоже важно!),

  • а закупка требуется потому что

…то остановиться уже сложно.

Следующий уровень — онтология Деда Мороза.

Представьте, как оно ложится:

  • Дед Мороз — актор, который доставляет Подарки

  • Подарок назначен для Получателя

  • Получатель имеет ограничения (возраст, интересы, «не дарить снова носки»)

  • Подарок требует Бюджет и хранится в Шкафу, пока не настало 00:00

  • Событие “Новый год” включает тосты, салаты и обязательное «ой, мы забыли батарейки»

  • Батарейки — вечный архетип недостающей зависимости, которая рушит магию на ровном месте

И где-то там, в правом верхнем углу диаграммы, будет красным мигать узел:

«Подарок: радиоуправляемая штука» → требует → «AA батарейки»

Остаток: батарейки → нет

Закупка → срочно

Праздник спасён. Граф не врёт.

Что я понял по итогам

  1. Онтология — адов труд, конечно, если пытаться “описать мир”.

  2. Но если описывать сценарий (меню/остатки/закупка), она становится удивительно прикладной.

  3. Минимальная модель выигрывает у “идеальной”.

  4. Мы не строили «вселенную еды». Мы построили систему, которая отвечает на конкретные вопросы и помогает не бегать по магазину как NPC в панике.

  5. “Объяснимость” важнее “автоматизации”.

  6. Список покупок без «зачем» — это просто список.

  7. Закупка, привязанная к блюдам — это уже план, а плану доверяют.

Финальное волшебство

В прошлой реальности перед праздником было два состояния:

  • «вр��де всё купили»

  • «почему нет огурцов?»

В новой реальности появился третий режим — спокойствие, потому что теперь:

  • холодильник перестал быть тайной комнатой,

  • шкаф перестал быть архивом забытых банок,

  • а закупка стала не “набором хотелок”, а выводом из модели.

И да — если кто-то скажет, что онтология оливье звучит странно, вы можете ответить максимально научно:

«Странно не то, что мы сделали онтологию оливье.

Странно, что до сих пор никто не сделал онтологию мандаринов».

Поздравление

Пусть в новом году у вас:

  • не будет циклических зависимостей типа «праздник требует настроения, настроение требует праздника»,

  • остатки всегда будут консистентны,

  • закупка — минимальна, но достаточна,

  • а Дед Мороз приносит подарки без missing‑dependencies.

С наступающим!

Пусть ваш Новый год будет как хороший граф: связный, красивый и без неожиданных “огурцов не найдено”. 🎄🥂

Артём Варкулевич