Дисклеймер: я разрабатываю Calk — приложение, из которого выросла эта задача.
Я хотел показать человеку одно число: сколько разных растений он съел за неделю. Задача выглядела так:
meals.flatMap { it.ingredients } .filter { it.isPlant } .distinct() .count()
Четыре строки. Проблема спряталась в третьей: что именно значит distinct?
Помидор и черри — одно растение или два? Сырой и запечённый шпинат? Три грамма петрушки в борще?
Я хотел дать человеку понятный способ заметить однообразие и увидеть, в каком направлении можно расширить рацион. Для этого понадобились три разных инструмента: счётчик продуктов, карта пробелов и порядок вариантов внутри каждой группы.
Так рядом с недельным числом появились цветные ячейки, а внутри каждой — свой порядок продуктов.

Откуда вообще взялось число 30
Популярный совет «ешьте 30 разных растений в неделю» вырос из American Gut Project. В работе сравнивали участников, которые по анкете ели больше 30 типов растений в неделю, с теми, кто ел 10 и меньше, и нашли различия в микробиоме и метаболоме.

Но 30 — граница двух анкетных категорий, а не обнаруженный биологический порог. Исследование наблюдательное, участники приходили сами, питание оценивалось по самоотчётам. Из него не следует, что 29 растений мало, а 31 достаточно.
В систематическом обзоре 161 уникальной публикации 50 работ касались нутриентной достаточности, 137 — медицинских исходов; одна публикация могла попасть в обе группы. Авторы пришли к осторожному выводу: метрики разнообразия пригодны прежде всего как приближённая оценка достаточности микронутриентов, а связи с медицинскими исходами оказались менее последовательными.
Даже сам объект счёта не определён раз и навсегда. FAO MDD-W проверяет десять пищевых групп за сутки, а Dietary Species Richness считает биологические виды.
Иными словами, я взялся считать разнообразие, не определив единицу счёта.
Если в дневнике записан «борщ», уже поздно
В обычном пищевом дневнике борщ часто хранится одной строкой: название, масса, калории, белки, жиры и углеводы. Для суммы калорий и даже для оценки съеденного белка этого достаточно.
Для разнообразия — нет.
В тарелке могли быть свёкла, капуста, морковь, лук, картофель и томат. В строке «борщ, 250 ккал» их не осталось. Из калорий и названия нельзя восстановить рецепт: борщ бывает постным и мясным, с фасолью и без, с майонезом или сметаной, с зеленью или без неё.
В больших базах состав смешанного блюда можно восстановить, только если его рецепт известен заранее. Например, в FNDDS 2021–2023 есть 5 432 кода еды и напитков, а отдельная таблица хранит ингредиенты и их количество. FPED переводит такие рецепты в 37 компонентов рациона. Но состав произвольного домашнего или ресторанного борща базе неизвестен.
В моём случае дневник уже хранил блюда по ингредиентам. Сначала надо было решить, какие из этих частей считать одним продуктом.
Блюдо, ингредиент и базовый продукт — не одно и то же
Для пользователя достаточно трёх уровней:
Блюдо — борщ или салат. Оно состоит из частей.
Ингредиент — 80 г варёной свёклы или 20 г сырого лука.
Базовый продукт — свёкла или лук независимо от приготовления.
Для калорий важен ингредиент: варёная и жареная картошка дают разный результат. Для недельного счётчика сырой, варёный и жареный картофель — один базовый продукт. В каталоге 638 растительных строк описывают 341 базовый продукт, и у 100 из них есть несколько форм приготовления.
Разбор заканчивается на уровне ингредиентов, сохранённых в дневнике. Домашний рецепт хумуса можно разложить на нут, тахини и масло; готовый хумус, записанный одной строкой, останется одним ингредиентом.
Границы базового продукта не всегда совпадают с границами биологического вида:
Коллизия |
Как считаю |
Почему |
|---|---|---|
белокочанная капуста, краснокочанная, брокколи, цветная, кейл и брюссельская |
как разные продукты |
ботанически это формы одного Brassica oleracea, но человек ест разные части растения; у красных и фиолетовых форм отдельно накапливаются антоцианы |
свёкла и мангольд |
как разные продукты |
обе относятся к Beta vulgaris, но в еду идут корень и листья |
горошек, стручки и побеги гороха |
как разные продукты |
это семена, створки и зелёные побеги одного Pisum sativum; минералы распределяются по этим тканям по-разному |
красное и зелёное яблоко, красный и зелёный виноград, перцы разных цветов |
раздельно, только если цвет указан |
цвет здесь сообщает о пигментах: у красного яблока антоцианы сосредоточены главным образом в кожуре, у красного винограда — тоже в кожице, а профиль перца меняется при созревании |
Недельный счётчик разбирает известные блюда до сохранённых ингредиентов, объединяет способы приготовления одного продукта, но сохраняет привычно разные съедобные части и явно указанные цветовые варианты.
Килограмм картошки против 20 граммов петрушки
Наивный DISTINCT ставит их вничью: по одной единице каждому.
В обновлённой методике MDD-W группа обычно засчитывается, только если за день съедено не меньше 15 г. В GDQS у разных пищевых групп свои диапазоны количества.
Обе методики считают пищевые группы за день, а не уникальные растения за неделю. Они подтверждают, что количество нельзя игнорировать, но не дают готового порога для моего счётчика: 15 г легко отсекают укроп и петрушку, а нулевой порог позволяет набрать разнообразие декоративной щепоткой.
Для недельного интерфейса остаются три варианта: учитывать присутствие, ввести минимальную массу или взвешивать вклад по количеству и типу продукта. Я выбрал первый. Счётчик отвечает только на вопрос «был ли этот продукт на неделе?», а его количество отдельно участвует в расчёте калорий и нутриентов.
На тестовой неделе счётчик показал 16 растений. Из них только одно было фруктом.
Одного числа недостаточно: нужна карта пробелов
Сначала я взял самый понятный человеку язык — цвет овощей, фруктов и ягод. Цветовые рекомендации существуют давно, но единой разбивки нет. Pennington и Fisher, например, проверяли, как с химическим составом связаны цвет, ботаническое семейство, съедобная часть и общая антиоксидантная способность. Полученные кластеры часто лучше всего описывались сочетанием нескольких признаков.
У меня получилось семь групп: красные, оранжевые, жёлтые, светло-зелёные, тёмно-зелёные, бело-бежевые и сине-фиолетовые. Светлую и тёмную зелень пришлось разделить: зелёных продуктов слишком много, а MDD-W тоже выделяет тёмные листовые овощи в отдельную группу.
Сначала я раскладывал группы свободными «островами». Но с каждым исключением появлялся новый блок, близость блоков ничего не означала, а пустое место расползалось между ними. По такой схеме трудно понять, чего именно не хватает.
Тогда появилась сетка — пока только из семи цветовых колонок. У каждой области появилось постоянное место, а пустые колонки стало легче замечать.
Но цветом классифицируется не вся еда. Яйцо, лосось, фасоль, овсянка и кефир в радугу не укладываются.
Почему снизу ещё семь
Можно было просто добавить стандартные пищевые группы. Я проверил несколько вариантов:
Кандидат |
Что он решает |
Почему не стал картой |
|---|---|---|
Валидированный популяционный индикатор |
Слишком крупно для личной навигации: почти все овощи быстро слипаются |
|
Хорошая аналитическая классификация |
На экране либо слишком грубо, либо слишком много |
|
24 нутриентных показателя |
Объясняет состав |
Один продукт попадает сразу в десяток мест; карта превращается в лабораторный отчёт |
Опирается на родство растений |
Не покрывает животную еду и плохо отвечает на вопрос «что добавить на ужин?» |
|
Показывает естественные кластеры |
Хороша для исследования, плохо читается как недельный чек-лист |
На одном экране без прокрутки помещались семь колонок и два ряда. Поэтому для второго ряда я искал ещё семь направлений.
Во второй ряд я отобрал семь коротких направлений, которые не дублируют цветовую радугу и отвечают на вопрос «что попробовать?»: морские омега-3, жирорастворимые витамины, растительные омега-жиры, B-комплекс, минералы, клетчатка и пробиотики.
Сахар, соль и насыщенные жиры сюда не входят: разнообразная коллекция сладостей от этого полезнее не станет. Цельные злаки, бобовые, орехи, яйца, молочные продукты и мясо находятся внутри функциональных групп, без собственных колонок.
Семь пар «цвет — функция» образуют три более крупные зоны.
Каротиноиды и жиры. Красные, оранжевые и жёлтые растения стоят над тремя соседними направлениями. Связь здесь практическая: пищевой жир повышает биодоступность каротиноидов, а нижний ряд разводит морские омега-3, источники жирорастворимых витаминов и растительные омега-жиры.
Зелень и обмен веществ. Светлая и тёмная зелень находятся над B-комплексом и минералами. Листовые овощи относятся к источникам фолата и магния; нижние группы расширяют выбор за пределы зелени — на бобовые, орехи, семена, яйца, мясо и молочные продукты.
Кишечник и иммунный ответ. Бело-бежевые и сине-фиолетовые растения дают клетчатку, инулин и полифенолы; снизу находятся клетчатка и продукты с живыми культурами. В 17-недельном рандомизированном исследовании с 36 участниками рационы с большим количеством клетчатки и ферментированной еды по-разному меняли микробиоту и иммунные маркеры. На карте эти направления стоят рядом.
Так карта выглядит сейчас, в закрытом тестировании:

Клетки внутри колонок понадобились потому, что даже после сжатия продуктов слишком много. Петрушка, укроп и кинза дают недельному счётчику три растения, но карте достаточно одной клетки «зелень». Счётчик сохраняет различия. Карта намеренно их сжимает.
Где здесь Менделеев — и где его точно нет
У периодической таблицы я позаимствовал постоянные координаты, группы и число в углу клетки. У Менделеева соседство помогало предсказывать ещё не найденные элементы; здесь оно только связывает уже известные пищевые направления и помогает запомнить карту. Пустая клетка сообщает, что в этой области на неделе ничего не было.
Менделеев раскладывал элементы по атомной массе и иногда менял порядок ради химических свойств. Физический смысл порядкового номера появился только после работ Мозли в 1913 году — через шесть лет после смерти Менделеева. История таблицы показывает этот переход: сначала был рабочий порядок, затем нашлось свойство, которое его объяснило.
У еды такого свойства я не нашёл. Число в углу сортирует варианты внутри каждой из 14 групп.

Когда появилась карта, клеткам понадобился порядок
Если группа пустует, совет «добавьте что-нибудь зелёное» мало помогает: нужны несколько конкретных вариантов и порядок между ними. Для этой сортировки я ввёл балл нутриентной насыщенности продукта.
Здесь пригодились те самые 24 показателя, которые оказались слишком подробными для карты:
жирные кислоты: EPA + DHA и ALA;
витамины и связанные вещества: D, K2, K1, B12, A, E, C, фолат, B6, тиамин, холин;
минералы: йод, железо, цинк, селен, магний, кальций, марганец и калий;
другие компоненты: клетчатка, лютеин и спермидин.
Эти 24 показателя различают продукты внутри выбранных 14 направлений: жирные кислоты, витамины, минералы, клетчатка и несколько биоактивных веществ. Белок, незаменимые аминокислоты и компоненты, которые принято ограничивать, в сортировку не входят. Для K2, лютеина и спермидина используются оценочные ориентиры: отдельных общепринятых дневных норм для них нет.
Для каждого показателя считается доля дневной нормы в 100 г продукта:
вклад показателя = min(доля дневной нормы, 100%) нутриентная сумма = Σ вкладов
Ограничение min(..., 100%) — потолок вклада: один рекордный показатель не может перевесить весь остальной профиль продукта.

В 100 г тушёной говяжьей печени по USDA FoodData Central, FDC ID 168626 около 29 дневных норм B12 и 10,5 нормы витамина A. Без потолка вклад только этих двух витаминов составил бы 39,5 дневной нормы. С потолком они дают 1 + 1, после чего остальные показатели снова влияют на порядок. Печень остаётся богатой, но два рекорда не определяют весь рейтинг.
Подход вдохновлён NRF9.3. NRF9.3 считает девять положительных нутриентов, вычитает три компонента, которые желательно ограничивать, и использует референсную порцию продукта. Здесь 24 показателя суммируются без вычитания, а расчёт идёт на 100 г.
Что действительно меняет рейтинг
Насколько рейтинг зависит от каждого принятого решения?

Без оценочных ориентиров порядок почти тот же: ρ = 0,988, максимальный сдвиг — пять мест. Выбор одной формы приготовления вместо максимума по всем формам даёт ρ = 0,934 и сдвиг до 15 мест. Без потолка ρ падает до 0,899, а отдельная ячейка перемещается на 40 мест. Сильнее всего работает база сравнения — 100 г или 100 ккал.

Ранговая корреляция всего ρ = 0,459. На 100 ккал вверх уходят шпинат, мангольд и руккола; вниз — сыры и масла.
Расчёты отвечают на разные вопросы:
на 100 ккал — сколько нутриентов продукт даёт на единицу энергии;
на 100 г — насколько насыщена нутриентами одинаковая масса продукта.
Cronometer тоже предлагает продукты отдельно по граммам и по калориям: это два полезных, но разных рейтинга.
Карта использует расчёт на 100 г: она предлагает продукт, а не строит рацион с фиксированной калорийностью. Расчёт на 100 ккал систематически поднимает очень низкокалорийную зелень и опускает масла, орехи и сыр. Но зелень редко едят стограммовыми порциями, поэтому её реальный вклад в неделю всё равно должен считаться из съеденных граммов, а не из высокого места в рейтинге.
Что карта показывает на практике
Полная карта — справочник: по ней видны сильные и пустые направления в целом. В недельной детализации остаются уже конкретные продукты — что было съедено и чем можно заполнить пробел.

История по тем же 14 направлениям отделяет случайный пропуск от привычки. На одной неделе пустая клетка мало что значит; полоса за полгода показывает, что целая группа почти не появляется.

Возвращаемся к борщу
Я собрал три порции борща по 400 г и сравнил их счётчиком, картой и обычным расчётом нутриентов. Воды в каждом рецепте ровно столько, чтобы получить 400 г; третий борщ поэтому заметно плотнее первых двух.
Постный и простой: свёкла, картофель, морковь и немного капусты.
Мясной с майонезом: те же овощи, лук, говядина и 20 г майонеза.
Собранный по карте: свёкла, белокочанная и краснокочанная капуста, картофель, морковь, лук, томат, фасоль, говядина, чеснок, мангольд, укроп, петрушка и сметана.

Порция 400 г |
ккал |
белок |
клетчатка |
разных растений |
направлений карты |
|---|---|---|---|---|---|
постный и простой |
91 |
2,7 г |
3,9 г |
4 |
4 |
мясной с майонезом |
308 |
13,4 г |
4,4 г |
5 |
5 |
собранный по карте |
219 |
14,3 г |
7,9 г |
12 |
8 |
Третий борщ три модели считают по-разному. Счётчик находит 12 привычных растительных продуктов: две капусты здесь считаются раздельно по правилу выше. При пороге MDD-W в 15 г останется восемь: чеснок, мангольд, укроп и петрушка его не проходят. Карта показывает красное, оранжевое, два зелёных направления, бело-бежевое, сине-фиолетовое, B-комплекс и пробиотики. Расчёт по граммам добавляет калории, белок и клетчатку.
В третьем рецепте стало больше источников витаминов, минералов, клетчатки и фитосоединений. Замена части картофеля и майонеза на овощи, фасоль и зелень увеличила число растений и направлений, почти удвоила клетчатку, немного подняла белок и снизила калорийность относительно мясной версии.
Баллы клеток здесь не складываются: итоговая сумма смешала бы широту рациона, покрытие карты и сортировку продуктов внутри групп.
Во что превратились четыре строки
COUNT(DISTINCT food) отвечает на один узкий вопрос: сколько известных базовых продуктов было за неделю. Смешанное блюдо разбирается до сохранённых ингредиентов, а способы приготовления одного продукта объединяются. Несколько привычных форм одной капусты могут увеличить число; карта затем сжимает их в одну-две области.
Карта отвечает на второй вопрос: в каких из 14 направлений есть пробелы. Она специально сжимает петрушку, укроп и кинзу в одну клетку «зелень», хотя недельный счётчик сохраняет три растения.
Нутриентный балл отвечает на третий: какие конкретные продукты показать выше внутри пустующей группы. Он считается на 100 г и ограничивает вклад каждого показателя одной дневной нормой. Место в рейтинге сортирует варианты; съеденная тарелка рассчитывается по реальным граммам.
У сетки есть ещё одна цена: каждый продукт занимает одно основное место. Лосось находится в морской омеге, поэтому не заполняет одновременно жирорастворимые витамины и B-комплекс. Так карта остаётся читаемой, но часть связей теряется. Если подсвечивать все подходящие клетки, пробелы станут точнее, а сама карта — сложнее.
Я нашёл системы, считающие растения (30Veg и Plant Points), групповые популяционные метрики (MDD-W), исследовательский трекер, умеющий разбирать смешанные блюда (Diet DQ Tracker), и трекеры, которые закрывают нутриентные пробелы (Cronometer Oracle). Есть даже настоящая Periodic Table of Food Initiative — она картографирует молекулярный состав еды, а не личную неделю.
Я не нашёл готового решения, которое соединяло бы ингредиенты, недельное число разных продуктов, видимые пробелы и объяснимые варианты, чем их заполнить.
Если знаете работающую систему, статью или визуализацию, которую я пропустил, напишите в комментариях или в личку. Особенно интересны две идеи: какую суммарную массу базового продукта за неделю считать достаточной для зачёта и как показывать продукты вроде лосося, которые обоснованно относятся сразу к нескольким группам.