Ассоциация производителей КРС голштинской породы

Эволюция продукта

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

Часть I. Введение и обзор

1. О системе#

1.1. Какую задачу решает#

«Племенная книга» — информационная система Ассоциации производителей КРС голштинской породы. Она ведёт учёт племенных животных, хранит происхождение и продуктивность, считает племенную ценность, выпускает племенные документы и — это главное — отвечает на вопрос, кто ручается за каждую цифру.

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

1.2. Кому адресован этот документ#

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

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

Отметки у разделов читаются так: работает — есть в коде и работает; спроектировано — контракт продуман и записан, реализации нет; план — направление известно, сроки нет.

1.3. Принципы#

  • Владелец распоряжается своими данными. Публичность записи включает хозяйство, а не Ассоциация. Ассоциация ручается за данные перед другими, но не открывает их за владельца.
  • У цифры есть происхождение. Уровень достоверности, автор, время правки и предыдущее значение хранятся вместе с записью, а не выводятся из журналов постфактум.
  • Правила предметной области живут в базе. Возраст, доли, знаки, запрет быть себе родителем — ограничения, которые нельзя обойти ни импортом, ни служебным скриптом.
  • Прошлое не редактируется. Документы отзываются, а не удаляются; правки дописываются в журнал; типы событий выводятся из обращения, а не стираются.
  • Стандарт вместо собственного формата. Обмен проектируется на ICAR ADE, документы — по форме Регламента (ЕС) 2016/1012. Свой формат пришлось бы объяснять каждому партнёру отдельно.
  • Отказ объясняется построчно. Импорт называет номер строки, идентификатор и причину. «Часть данных не принята» без указания какой — не сообщение, а тупик.

1.4. Текущее состояние и что это значит#

Версия 0.16.0-alpha. Работающий прототип: интерфейс, модель данных и расчёты действуют на объёме 280 тысяч животных и 42 хозяйств, но данные синтетические — построены по реальным распределениям, и ни одно хозяйство пока не ведёт здесь настоящий учёт.

Для интегратора это значит следующее. Модель данных и контракты чтения можно изучать и на них ориентироваться, но обещания обратной совместимости нет: ноль в начале номера версии именно об этом. Автоматической сборки, тестов и мониторинга нет — служебные ревизии запускаются вручную. Полный список ограничений — в приложении Б.

2. Быстрый старт#

2.1. Требования к окружению#

Node.js 22.12 и новее, PostgreSQL 16, около 2 ГБ свободной памяти на сборку. Внешних сервисов при локальном запуске не требуется: почта, хранилище файлов и очереди в текущей версии не используются.

СлойЧто используется
ПриложениеNext.js 16.3 — App Router, серверные компоненты и действия
Данные и админкаPayload CMS 3.85 поверх PostgreSQL через Drizzle
ИнтерфейсReact 19.2, Tailwind CSS 4
СборкаTurbopack; выпуск в режиме standalone

2.2. Запуск#

npm install
cp .env.example .env          # DATABASE_URI и PAYLOAD_SECRET
npm run db:sync               # схема и миграции в согласованное состояние
SEED_CONFIRM=1 npm run seed   # демонстрационные данные
npm run dev                   # http://localhost:3000

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

Отдельно о npm run db:sync: он приводит в порядок частый случай, когда схема в базе уже изменена разработкой, а журнал миграций об этом не знает. Разбор — в главе 15.

2.3. Роли#

РольЧто видит и что может
farm — хозяйствосвоё стадо целиком, публичные записи чужих; ведёт данные, управляет публичностью, отвечает на запросы доступа, подаёт заявки на верификацию
expert — эксперт Ассоциациичитает данные всех хозяйств, разбирает пакеты и заявки, оставляет замечания; править чужие данные не может
admin — администратор Ассоциациивсё перечисленное плюс справочники НСИ, членство и удаление записей
анонимпубличный список и открытые карточки; может запросить доступ

Проверки собраны в одном месте — src/access/index.ts. Разделение чтения и записи сделано намеренно: эксперту нужно видеть всё, чтобы проверять, и не нужно ничего менять — иначе исчезает смысл независимой проверки.

Часть II. Модель данных и предметная логика

3. Сущности и связи#

3.1. Граф сущностей#

В центре — животное. Всё остальное либо описывает его состояние в момент времени, либо связывает с организацией, документом или другим животным.

ГруппаКоллекцииЧто хранит
Ядроanimals, organizations, herdsживотное, хозяйство, стадо
Фенотипmilk-tests, calvings, inseminations, health-events, animal-exteriorsконтрольные дойки, отёлы, осеменения, здоровье, оценка экстерьера
Оценкаanimal-evaluations, index-values, index-bases, index-profilesистория оценок, рассчитанный индекс, база сравнения, профили весов
Оборот данныхdata-submissions, animal-revisions, verification-requests, access-requestsпакеты загрузки, журнал правок, заявки на верификацию и на доступ
Документы и НСИdocuments, events, media, справочникивыданные документы, лента событий, файлы, породы и линии

3.2. Животное и его идентификаторы#

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

ПолеЧто это
uuidGUID записи. Присваивается при создании, не меняется никогда, не зависит от номеров хозяйства. Устойчивый ключ сопоставления при обмене
identNumberиндивидуальный номер, основной для человека; формат проверяется правилами ID_RULES
idFormatкакому правилу подчинён номер: rf, icar, usa, can, deu, internal
altIds.isoIdномер средства маркирования по ISO 11784/11785
altIds.internationalIdмеждународный номер для обмена оценками (Interbull)
altIds.earTagномер ушной бирки — видимая метка на животном
altIds.gpkMarkмарка и номер тома государственной племенной книги

Часть полей вычисляется хуками записи, а не приходит извне: транслитерация клички по ГОСТ 7.79-2000, сумма жира и белка, служебные ранги сортировки, автор и время последней правки, флаг инбридинга выше 25%. Коэффициент инбридинга при записи не пересчитывается — он считается на лету при разборе родословной и при выпуске документа.

3.3. Фенотип и продуктивность#

Первичные наблюдения и сводка по ним разведены. Контрольные дойки, отёлы, осеменения и события здоровья лежат отдельными записями со своими датами; в карточке животного хранится сводка (summary) и разбор по лактациям. Смешивать их нельзя: сводка пересчитываема, наблюдение — нет.

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

3.4. Служебные ранги — важно при чтении через API#

Сортировка sort=-ipc формально работает, но первыми придут животные без оценки: PostgreSQL при ORDER BY … DESC ставит NULL в начало. Само приложение сортирует по -ipcRank и -summary.milkRank, где пустое значение заменено на −1 000 000. Внешнему клиенту, которому нужно «сначала лучшие», следует использовать те же поля.

Второе неочевидное место — архив. Служебные записи предков (archived = true) исключаются только на страницах приложения; REST и GraphQL возвращают их наравне с остальными. Условие приходится задавать явно.

4. Достоверность и происхождение данных#

4.1. Уровни достоверности#

У каждой записи есть уровень (trustLevel, от −1 до 3). Он не украшение интерфейса: от него зависит, можно ли выпустить племенной документ и попадёт ли животное в общую книгу.

УровеньКто ручаетсяЧем именно
−1 · Отклоненоэксперт Ассоциациинашёл расхождение, документы не выпускаются
0 · Черновикниктовнесено, но не заявлено готовым
1 · Заявлено хозяйствомхозяйствосогласилось с разбором пакета и разрешило показ
2 · Подтверждено лабораториейлабораторияв книге лежит её протокол по этому животному
3 · Верифицировано ассоциациейэксперт Ассоциацииразобрал записи по заявке на верификацию

Поле закрыто на прямую запись всем без исключения: до этого руководитель хозяйства мог поставить себе третий уровень обычным запросом к API. Первую ступень даёт публикация пакета — это заявление хозяйства о собственных данных, и подписью Ассоциации она не притворяется. Вторая выводится из протокола лаборатории и снимается, если протокол отозвали. Третью ставит только разбор заявки на верификацию.

Третья ступень означает две вещи сразу, и вторую до недавнего времени не проверяло ничто: «расхождений нет» и «передано всё необходимое». Запись без единого отёла и без единой дойки не противоречит ничему — противоречить нечему, — и знак Ассоциации вставал на пустую карточку. Теперь заявку держат два заслона: находки проверок (verification-gate.ts) и полнота (completeness.ts).

ТребуетсяЗачем
дата рожденияот неё считаются возраст первого отёла, номера лактаций и проверки родословной
породабез породы запись не сравнить со сверстницами и не включить в отчёты
отец: запись или номеркнига подтверждает происхождение; без отца подтверждать нечего
мать: запись или номербез матери рвётся материнская линия и не считается инбридинг потомка
хотя бы один отёлкорова без отёла — это либо не корова, либо потерянная история
контрольные дойки (минимум 6, если заявлен удой за 305 дней)удой за 305 дней без замеров — число, взятое неизвестно откуда

Состав назван хозяйству до подачи — на странице «Стадо → Верификация», — и берётся там из того же правила. Требование, о котором узнают из отказа, читается как придирка; названное заранее — как условие.

4.2. Журнал правок#

Каждая ручная правка карточки пишется в animal-revisions: поле, было, стало, кто и когда. Описано около пятидесяти полей — для связей сохраняется не идентификатор, а название на момент правки, иначе через год журнал превращается в столбец чисел.

Импорт и служебные скрипты журнал не засоряют: у хука есть признак context.skipJournal. Смысл журнала — ответить на вопрос «дата рождения такой была или её поправили», а массовая загрузка на него не отвечает.

4.3. Пакеты загрузки#

Данные приходят не записями, а пакетом: data-submissions хранит файл, разбор, историю состояний, назначенного эксперта и список замечаний. Замечание указывает на конкретное животное и имеет степень — от «обратить внимание» до «исправить», последняя исключает запись из приёмки.

Важное отличие текущей логики от спроектированного приёма по ADE: сейчас пакет принимается или отклоняется целиком, а в контракте ADE предусмотрен частичный приём — непрошедшие строки отклоняются поштучно.

5. Видимость и доступ#

5.1. Две ступени публичности#

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

ПолеЧто открывает
publicVisibleстроку списка: номер, кличка, владелец, пол, возрастная группа, состояние, удой, жир, белок, ИПЦ
publicDetailsкарточку целиком: оценку, экстерьер, фенотип, происхождение, события, документы

Проверяются они по очереди: сначала правило чтения коллекции решает, отдавать ли запись вообще, и только потом снимается или не снимается замок с подробностей. Отсюда следствие: пока publicVisible выключен, значение publicDetails ни на что не влияет — постороннему записи не существует, по прямой ссылке он получит 404.

5.2. Чужая карточка выглядит иначе#

Открытая карточка чужого хозяйства оформляется другим фоном и другой шапкой с надписью «Чужое хозяйство». Причина практическая: зоотехник открывает карточки вперемешку, и при одинаковом виде чужие данные принимают за свои и пытаются править. Гостю пишется «Открытые данные», а не «Чужое хозяйство»: сравнивать не с чем — он вполне может быть сотрудником этого самого хозяйства.

5.3. Запрос доступа#

Увидев закрытую запись, посторонний отправляет запрос с целью и текстом. Решение принимает владелец, а не Ассоциация: Ассоциация ручается за данные, но не распоряжается ими. Ответ приходит в ленту уведомлений заявителя.

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

5.4. Точечный доступ#

Работает. Доступ с границами по трём измерениям: к чему — вся карточка, только происхождение, только продуктивность или только оценка; на какой срок; с правом отозвать в любой момент. Обращения пишутся в журнал (access-views): хозяйство должно видеть, что доступ используется по назначению, иначе оно перестанет его выдавать.

Область гранта уважают не только карточка, но и все связанные коллекции: открыв «только происхождение», доступа к контрольным дойкам не получают ни через страницу, ни через API. Это одно правило — scopedRead в src/access/index.ts, — и за ним следит ревизия npm run audit:grants.

Тому, у кого нет учётной записи, выдаётся ссылка на просмотр (share-links, страница /share/[token]) — с тем же сроком и той же областью.

Техническая сложность здесь была в том, что проверка «есть ли действующий грант» попадает на горячий путь — страницу книги и карточку. Условие на связанную таблицу превращается в соединение, и на 280 тысячах записей это стоило секунд. Отсюда переключатель PLEMKNIGA_GRANTS_OFF: он снимает соединение целиком, если однажды окажется, что цена выросла.

6. Целостность#

6.1. Ограничения в базе#

Тридцать проверок предметной области вынесены в саму базу: диапазоны дат и возраста, неотрицательность удоя, доли в пределах ноль—единица, процентиль от 0 до 100, запрет животному быть собственным родителем. Приложение проверяет то же самое раньше и с внятным сообщением, но последний рубеж — база: её не обойдёт ни импорт, ни разовый скрипт, ни ошибка в новом коде.

Список собран в src/lib/db-constraints.ts и сверяется ревизией. Для интегратора это значит, что данные, пришедшие извне, будут отклонены на тех же условиях, что и введённые руками, — отдельного «мягкого режима» для API нет.

6.2. Архив предков#

Разбор родословной вглубь заводит записи предков, которых нет ни в одном хозяйстве: они нужны для расчёта инбридинга, но не являются частью стада. Такие записи помечаются archived и исключаются из списков приложения, оставаясь доступными по прямой ссылке и в дереве происхождения.

7. Глоссарий: что путают чаще всего#

Шесть пар терминов, которые звучат похоже и означают разное. Цена ошибки здесь высокая — от неверно прочитанного отчёта до неверно выпущенного документа.

7.1. Пары, которые нужно развести#

ОдноДругоеЧем различаются
Уровень достоверности записи trustLevel, −1…3Достоверность оценки reliabilityLevel, 1…5первое — кем проверены сами данные, второе — насколько надёжен прогноз племенной ценности; шкалы и источники разные, меняются независимо
Достоверность оценки, 1…5R, %первое — ступень готовности оценки для человека, второе — статистическая величина: доля дисперсии истинной племенной ценности, объяснённая оценкой
ИПЦ ipcПИ production.productionIndexИПЦ — свод по всем группам признаков; ПИ — свод только по продуктивным, одна строка внутри блока продуктивности
Прогноз forecastФакт summary, lactations[]прогноз — наследуемая часть, которую животное передаёт потомству; факт — то, что животное надоило само
Инбридинг животного (COI)Коэффициент родстваCOI — свойство одного животного; коэффициент родства — свойство пары, и он вдвое больше, чем COI их будущего потомка
Линия lineСемейство familyлиния ведётся по отцам от родоначальника-быка, семейство — по матерям; поля разные, справочник за ними один

Часть III. Бизнес-процессы и сквозные сценарии

8. Жизненный цикл данных#

8.1. Поступление#

Три пути, и они не равнозначны по назначению:

  • Импорт CSV — основной для регулярного потока. Разбирает файл, заводит пакет, обновляет существующих животных по индивидуальному номеру и объясняет каждую непринятую строку.
  • Ручной ввод — для того, что файлом не приходит: купленное животное, расхождение с бумажным свидетельством, событие по ходу дела. Пишется в журнал правок.
  • APIPOST /api/animals и остальные коллекции. Работают те же серверные проверки, что и в интерфейсе.

8.2. Проверка#

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

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

8.3. Публикация#

После разбора пакет публикуется: уровень достоверности вошедших животных поднимается. Показывать ли животных в общей книге — отдельное решение владельца, и оно от проверки не зависит. Ассоциация ручается за данные; открывает их хозяйство.

9. Процессы Ассоциации#

9.1. Верификация хозяйства#

Хозяйство подаёт заявку с целью — доверие, документ или членство — и списком животных. Заявка получает номер вида В-2026-001 и проходит состояния «новая → на проверке → одобрена / отклонена». Замечание со степенью «исправить» исключает конкретное животное из одобрения, не блокируя остальные.

9.2. Членство#

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

9.3. Выпуск документов#

Племенное свидетельство и зоотехнический сертификат выпускаются по форме Регламента (ЕС) 2016/1012. Выпуск требует уровня достоверности «верифицировано», полной готовности карточки и отсутствия действующего документа того же вида. Номера сквозные: ПС-2026-0001, ЗС-2026-0001.

Третье условие — не педантизм: два непогашенных свидетельства на одно животное это ровно тот случай, когда в спорной ситуации предъявляют то, которое выгоднее.

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

9.4. Качество книги#

Сводка по всей книге: полнота происхождения, доля подтверждённых записей, противоречия в данных. Каждый показатель ведёт в список конкретных записей — цифра без возможности перейти к причине бесполезна. Расчёт сделан тремя независимыми запросами с ограничением по времени: медленный показатель не должен ронять всю страницу. 660 мс на 280 тысячах записей.

10. Перенос данных#

10.1. Импорт и экспорт#

Импорт CSV сопоставляет строки с существующими животными по индивидуальному номеру: совпало — обновление, не совпало — создание. Каждая непринятая строка попадает в протокол с номером, идентификатором и причиной.

Выгрузка — GET /account/export?format=xlsx|csv|txt|xml|json, до 20 000 записей своей организации. Ограничение осознанное: выгрузка на 280 тысяч строк держит соединение минутами и всё равно заканчивается таймаутом посредника.

10.2. Перенос между контурами#

Порядок для нового кода всегда один: git push → деплой или payload migrate вручную → наполнение данными. Обратный порядок даёт ошибку вида column animals.for_sale does not exist — код знает про поля последнего коммита, база знает только про применённые миграции.

payload migrate идемпотентна: применённые пропускаются, применяются только новые.

Часть IV. Техническая архитектура и реализация

11. Технологическая основа#

11.1. Стек и почему он такой#

Payload CMS даёт из описания коллекций сразу три вещи: схему PostgreSQL, административный интерфейс и REST с GraphQL с теми же правами доступа, что и в приложении. Для системы, где предметная модель большая, а бюджет на инфраструктуру маленький, это решающий фактор — правила доступа пишутся один раз и действуют везде.

Next.js используется серверными компонентами: страницы ходят в базу напрямую через локальный интерфейс Payload, без обращения к собственному HTTP-слою. Формы работают через серверные действия. Клиентского JavaScript в приложении мало по замыслу, а не по недосмотру.

11.2. Слои#

КаталогОтветственность
src/collectionsпредметная модель, хуки записи, права
src/accessвсе правила доступа в одном месте
src/libрасчёты и запросы: родословная, индекс, проверки
src/actionsсерверные действия форм
src/app/(frontend)страницы
src/scriptsслужебные ревизии и обслуживание базы
src/migrationsмиграции схемы

Расчёты вынесены в src/lib отдельно от страниц и от коллекций намеренно: их нужно вызывать и из интерфейса, и из скриптов проверки на живой базе, а проверка, повторяющая логику вместо того, чтобы вызывать её же, проверяет саму себя.

11.3. Состояние в адресной строке#

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

12. Модули и алгоритмы#

12.1. Родословная и инбридинг#

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

Расчёт выполняется на лету (analyzeAncestry) при открытии вкладки происхождения и при выпуске документа. Значение поля inbreeding в карточке — это то, что пришло с данными, и оно может расходиться с расчётом по дереву; флаг «требует согласования» ставится по полю, а не по расчёту.

12.2. Индекс племенной ценности#

Признаки приводятся к стандартизованным значениям по собственной базе сравнения, взвешиваются, корректируются на достоверность и переводятся в процентиль внутри группы сверстников. Веса задаются двумя способами: экономически (рубли на единицу признака) или селекционно (проценты влияния).

Рассчитанное хранится: индекс лежит строкой в index-values вместе с процентилем. Считать при каждом открытии страницы книги на 280 тысячах записей невозможно — это проверено на практике, а не предположено.

12.3. Профили весов и коррелированный отклик#

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

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

12.4. Автоматические проверки данных#

Пятьдесят три правила, собранные реестром src/lib/checks-registry.ts: согласованность дат рождения и отёлов, правдоподобность величин (удой 500…25 000 кг за лактацию и подобные границы), разрывы и противоречия в происхождении, несовместимые состояния. Правила намеренно отделены от ограничений базы: ограничение запрещает невозможное, правило указывает на подозрительное — второе не должно блокировать запись, но должно попадать на глаза эксперту.

12.5. Геномный конвейер#

Из геномного конвейера в коде есть две вещи, и обе не про маркеры: расчёт инбридинга по родословной и механика пакетной загрузки с протоколом ошибок. Тип загрузки genomics в перечне заведён, обработчика для него нет.

Не реализовано ничего из работы с самими маркерами: приём файлов генотипирования, нормализация аллелей, контроль качества, импутация, матрицы родства, ssGBLUP. Это сказано прямо, потому что «геномная оценка» в описании системы обычно означает совсем другое.

Одна особенность хранения, которую стоит знать заранее: у продуктивных признаков и признаков здоровья есть пара «прогноз + достоверность», у ИПЦ к ней добавлен процентиль, а признаки экстерьера хранятся одиночными числами без прогноза и без R. Когда оценка начнёт считаться внутри системы, экстерьеру понадобится та же пара — иначе достоверность экстерьерной части индекса негде будет показать.

13. API и интеграции#

13.1. REST и GraphQL#

Payload поднимает REST и GraphQL поверх модели автоматически. Аутентификация — POST /api/users/login, дальше cookie или заголовок Authorization: JWT ….

GET /api/animals?where[kind][equals]=bull
                   &where[ipc][greater_than]=1000
                   &sort=-ipcRank&limit=25&page=1&depth=1

Поддерживаются операторы equals, not_equals, greater_than, less_than, in, contains, exists, логические and и or, а также depth — глубина разворачивания связей. Ответ содержит docs, totalDocs, page, totalPages, hasNextPage.

13.2. Права на чтение#

Правило одно и то же на всех уровнях: видно то, что видно у животного. Запись фенотипа или события не имеет собственной видимости — она наследует её у карточки, к которой относится. Так устроено scopedRead в src/access/index.ts: правило повторяет условие животного через связь и заодно учитывает области выданных доступов.

КоллекцияПравило чтения
animalsаноним — только публичные; пользователь — своя организация плюс публичные плюс открытые ему точечно; администратор — всё
organizations, herds, справочникичитает кто угодно, включая анонима
milk-tests, calvings, inseminations, health-events, eventsто же, что у животного: область «продуктивность»
animal-evaluations, animal-exteriorsто же, что у животного: область «оценка»
documentsвладелец, Ассоциация, тот, кому открыт доступ
mediaпубличный файл — кто угодно; остальные — владелец и Ассоциация
access-requestsзаявитель, владелец животного, администратор
usersсам себя либо администратор

До версии 0.13 здесь была дыра, и этот раздел о ней предупреждал: ограничение по организации стояло только у животных, а через /api/milk-tests авторизованный получал первичные данные чужого хозяйства. Предупреждение снято не потому, что стало неудобным, а потому что дыра закрыта: правила переписаны, и за ними следят ревизии npm run check:security и npm run audit:tenancy — обе ходят от лица настоящего пользователя и пробуют достать чужое.

13.3. Собственные маршруты#

МаршрутНазначение
GET /animals/:id/certificate/:kindпечатная форма: pedigree — племенное свидетельство, zootechnical — зоотехнический сертификат
GET /account/export?format=xlsx|csv|txt|xml|jsonвыгрузка своего стада, до 20 000 записей: XLSX, CSV, TXT, XML, JSON
GET /healthzсостояние базы и окружения, всегда HTTP 200
GET /healthz/liveпроба живости для контейнера, базы не касается

13.4. Обмен в форме ICAR ADE#

Целевой контракт интеграционного слоя — открытый стандарт ICAR ADE (OpenAPI 3.1, JSON Schema 2020-12). Он уже реализован рядом вендоров, и Section 15 Guidelines прямо отсылает к нему; собственный формат пришлось бы объяснять каждому партнёру отдельно.

POST /api/ade/milk-recording   результаты контрольной дойки
POST /api/ade/reproduction     осеменения, стельности, отёлы
POST /api/ade/animal-events    перемещения, выбытия
GET  /api/ade/animals          выдача животных наружу
  • идемпотентность — повторная отправка того же события не создаёт дубль; ключ: животное + тип события + дата;
  • частичный приём — непрошедшие строки отклоняются поштучно;
  • прослеживаемость — каждая принятая запись хранит систему-отправителя, идентификатор пакета и время приёма.

13.5. Приём генотипов#

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

POST /api/genotypes/upload      file, chip, assembly, alleleCoding, laboratory
GET  /api/genotypes/jobs/:id    статус, принято/отклонено, причины, протокол

13.6. Государственные реестры и системы управления стадом#

ВетИС «Хорриот» — реализуемо: шлюз ВетИС.API, доступ по официальному письму, тестовый контур, апробация не менее десяти рабочих дней. Сроки предсказуемы.

ФГИАС ПР — заблокировано отсутствием спецификаций. Регистрация обязательна с 01.03.2026, но публичного описания форматов обмена в открытом доступе нет. Ставить срок в план, пока спецификации не получены, нельзя.

У систем управления стадом публичного REST нет — это не интеграция по API, а набор адаптеров. Внутренний контракт адаптера один: вернуть события в форме ADE и указать источник. Тогда добавление новой системы не затрагивает ядро.

СистемаМеханизм забораЧто учесть
DairyComp 305периодический прогон командной строки и разбор выгрузокпередача данных третьей стороне оформляется соглашением, подписывает ферма
DelProпартнёрская интеграция либо чтение резервных копийсхема БД официально не опубликована
UNIFORM-AgriICAR ADEсамый прямой путь: стандарт реализован вендором
AfiFarmпо договорённости с вендоромпубличной спецификации нет

Часть V. Развёртывание и эксплуатация

14. Окружение#

14.1. Переменные окружения#

ПеременнаяНазначение
DATABASE_URIстрока подключения к PostgreSQL
PAYLOAD_SECRETподпись токенов; смена разлогинивает всех
SEED_CONFIRMпредохранитель массовых записывающих скриптов
PAYLOAD_DB_PUSHпринудительно выключает прямое изменение схемы

Параметр sslmode вырезается из строки подключения и превращается в настройку TLS по правилам libpq: драйвер понимает не все значения, которые понимает psql, и расхождение проявлялось отказом подключения уже на боевом контуре.

14.2. Контейнер#

Сборка в режиме standalone, в контейнере запускается node server.js, а не npm start: лишний процесс-посредник мешает корректной остановке по сигналу. HEALTHCHECK ходит в /healthz/live, который не касается базы, — иначе неверная строка подключения превращается в «Deploy failed» вместо диагностируемой ошибки.

15. Управление схемой#

15.1. Push в разработке, миграции на бою#

В разработке Payload изменяет схему напрямую по описанию коллекций; на бою работают только миграции. Разграничение сделано по строке подключения (isLocalDatabase), а не по NODE_ENV: скрипт, запущенный с рабочей машины против боевой базы, формально не «production» — и однажды этого оказалось достаточно, чтобы схема изменилась там, где не должна была.

15.2. Рассинхронизация и её лечение#

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

npm run db:sync      # снимает служебную отметку, доводит журнал, применяет миграции
npm run doctor       # семь проверок окружения и базы, только чтение
npm run db:precheck  # что произойдёт до того, как оно произойдёт

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

16. Мониторинг и диагностика#

16.1. Две пробы состояния#

/healthz отвечает HTTP 200 всегда, а результат выражает полем status в теле. Это сделано намеренно: проба нужнее всего именно тогда, когда база недоступна, а панели и платформы нередко прячут тело ответа с кодом 5xx за собственной заглушкой. Для автоматической проверки живости есть отдельный /healthz/live.

16.2. Служебные ревизии#

КомандаЧто проверяет
npm run doctorокружение, схема, долгие транзакции, распухание таблиц
npm run audit:tenancyне отдаёт ли какой-нибудь список чужие записи
npm run audit:pedigreeпротиворечия в происхождении
npm run audit:indexesиндексы базы: недостающие и ни разу не пригодившиеся
npm run smokeобход всех страниц живого сервера
npm run check:allвсе двадцать пять подряд, с записью результата на вкладку «Статус»

Полный перечень проверок с признаками — src/lib/check-registry.ts. Три признака решают всё: пишет ли она в базу (десять из двадцати пяти заводят записи и потом удаляют — на боевой книге такое гонять нельзя), нужен ли ей живой сервер (три ходят по страницам снаружи) и умеет ли её прогнать само приложение (четыре — они и попадают в ночной прогон).

Прогон на развёрнутой системе. GET /checks?token=…&label=Прод гоняет четыре пробы внутри работающего приложения и кладёт результат в коллекцию check-runs. Ключ — CHECKS_TOKEN, не короче шестнадцати знаков; без него маршрут отвечает несуществующей страницей. Код ответа говорит об исходе: 200 — сошлось, 409 — есть находки, так что ночному действию не нужно разбирать тело. На боевой машине прогон занимает около трети секунды и годится после каждой выкладки.

Прогон по боевой базе. Пятнадцать читающих проверок: DATABASE_URI=… BASE=… npm run check:all -- --readonly --label Прод. Десять пишущих туда не попадают и не попадут: обрыв посреди прогона оставил бы в книге записи, неотличимые от настоящих.

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

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

Часть VI. Приложения

17. Приложения#

А. Стандарты#

СтандартЧто из него взятоСостояние
ICAR Section 2 — Cattle Milk Recordingсхемы контрольных доек, модели лактационных кривыхспроектировано
ICAR Section 4 — DNA Technologyтребования к генотипированию и контролю качестваспроектировано
ISO 11784/11785, ICAR Section 10средства маркирования животныхспроектировано
ICAR ADE (Section 15)формат обмена данными между системамиспроектировано
Регламент (ЕС) 2016/1012форма зоотехнического сертификата и племенного свидетельстваработает
ГОСТ 7.79-2000 (ISO-9)транслитерация кличекработает
ВетИС «Хорриот»маркирование и учёт животныхплан
ФГИАС ПРгосударственный учёт племенных ресурсовплан

Б. Ограничения — честный список#

То, что нужно знать до того, как строить планы на систему. Список не сокращённый.

  • Данные синтетические. 280 тысяч животных построены по реальным распределениям, но ни одно хозяйство не ведёт здесь настоящий учёт.
  • Проверки данных смотрят одно хозяйство. Отчёты и сверки берут самое большое из заведённых; расхождение, которое возникает только у другого, ночным прогоном не найдётся.
  • Работы с маркерами нет. Ни приёма файлов генотипирования, ни нормализации аллелей, ни QC, ни импутации, ни ssGBLUP.
  • Нет сборки по коммиту и модульных тестов. Прогон проверок ставится на расписание и на выкладку (раздел 16.2), но код между выкладками не проверяется ничем.
  • Битые внешние ссылки не проверяет ничто. Обход страниц знает только свои адреса; ссылка на чужой сайт, который закрылся, останется незамеченной. Расхождение этой документации с кодом — тоже.
  • Интеграционный слой не реализован. Приём по ADE и приём генотипов спроектированы, эндпоинтов пока нет.
  • Приём пакета — целиком. Частичный приём предусмотрен контрактом ADE, но не текущей логикой пакетов.
  • Экстерьер хранится без достоверности. 18 линейных признаков и 3 композита — одиночные числа без прогноза и R.
  • Обещания совместимости нет. Версия 0.16.0-alpha: структура данных может измениться.

В. Где что лежит#

ЗадачаФайл или каталог
Правила доступаsrc/access/index.ts
Родословная и инбридингsrc/lib/ancestry.ts
Индекс племенной ценностиsrc/lib/breeding-index.ts
Автоматические проверкиsrc/lib/data-checks.ts
Ограничения базыsrc/lib/db-constraints.ts
Журнал правокsrc/lib/animal-journal.ts
Видимость и замокsrc/lib/visibility.ts
Качество книгиsrc/lib/book-quality.ts
Выпуск документовsrc/actions/documents.ts
Служебные скриптыsrc/scripts/

Г. Что нужно интегратору#

Если вы поставщик системы управления стадом, лаборатория или сервисная организация, порядок разговора такой.

  • Определить сторону обмена. Вы отдаёте данные в книгу, забираете из неё или и то и другое. От этого зависит, нужен ли вам контракт приёма (ADE) или достаточно чтения по REST.
  • Договориться об идентификаторе. Индивидуальный номер хозяйства уникален внутри хозяйства, а не глобально. Устойчивый ключ сопоставления — uuid плюс средство маркирования.
  • Проверить свою модель на ограничениях. Данные извне отклоняются по тем же 28 правилам, что и введённые руками; мягкого режима для API нет.
  • Учесть правовую сторону. Передача данных третьей стороне у части вендоров оформляется отдельным соглашением, и подписывает его хозяйство, а не разработчик.

Вопросы по интеграции — через Ассоциацию: контакты в подвале страницы.

Д. Журнал решений#

Спорные развилки записываются отдельным документом репозитория docs/reshenya.md — с разбором отвергнутых вариантов и причины отказа. Сейчас там 191 запись. Это не история изменений, а объяснение, почему сделано так: следующему человеку не нужно тратить вечер на ту же мысль.

Вернуться к началу документа