.. _feed: Feed ==== Feed - це механізм синхронізації даних, який надає доступ до переліку основних сутностей системи Prozorro (тендери, контракти, плани, фреймворки тощо). Feed є основним інструментом для інтеграції з системою Prozorro, дозволяючи отримувати актуальні дані та відстежувати зміни в режимі реального часу. Доступні Feed ендпоінти ----------------------- У системі Prozorro доступні наступні feed ендпоінти: - ``/tenders`` - перелік тендерів - ``/contracts`` - перелік контрактів - ``/plans`` - перелік планів закупівель - ``/frameworks`` - перелік фреймворків - ``/submissions`` - перелік подань - ``/qualifications`` - перелік кваліфікацій - ``/agreements`` - перелік угод Приклад запиту: .. sourcecode:: http GET /tenders?descending=1&limit=2 HTTP/1.1 Структура відповіді ------------------- Кожен feed ендпоінт повертає JSON відповідь наступної структури: .. code-block:: json { "data": [ { "id": "82caff5150214ad2b43aa583f7fabcae", "dateModified": "2026-01-10T12:52:00.068344+02:00" }, { "id": "8e57141320ec463abf6d82c1497702fc", "dateModified": "2026-01-09T16:40:51.220780+02:00" } ], "next_page": { "offset": "1767969651.246.1.4325efbb2409185921e764a90ea1cb93", "path": "/api/2.5/tenders?limit=2&descending=1&offset=1767969651.246.1.4325efbb2409185921e764a90ea1cb93", "uri": "https://lb-api-sandbox-2.prozorro.gov.ua/api/2.5/tenders?limit=2&descending=1&offset=1767969651.246.1.4325efbb2409185921e764a90ea1cb93" }, "prev_page": { "offset": "1768042320.082.1.5142ecd8a327509ce0580c5133be68f6", "path": "/api/2.5/tenders?limit=2&offset=1768042320.082.1.5142ecd8a327509ce0580c5133be68f6", "uri": "https://lb-api-sandbox-2.prozorro.gov.ua/api/2.5/tenders?limit=2&offset=1768042320.082.1.5142ecd8a327509ce0580c5133be68f6" } } Поля відповіді: - **data**: масив об'єктів з мінімальною інформацією (за замовчуванням ``id`` та ``dateModified``) - **next_page**: інформація про наступну сторінку - **prev_page**: інформація про попередню сторінку Сортування за часом модифікації ------------------------------- Feed ендпоінти автоматично сортувати об'єкти за часом модифікації, використовуючи внутрішнє поле ``public_modified``. Це поле містить Unix timestamp (в секундах) і автоматично оновлюється при будь-якій зміні об'єкта. **Важливо**: Сортування відбувається за полем ``public_modified``, а не за ``dateModified``. Це забезпечує стабільність пагінації та коректну обробку об'єктів з однаковим часом модифікації. За замовчуванням (без параметра ``descending``) об'єкти сортируються від старіших до новіших (за зростанням ``public_modified``). Параметри запиту ---------------- offset ~~~~~~ Курсор для пагінації. Використовується для отримання наступної або попередньої сторінки результатів. Формат: ``{timestamp}.{skip_len}.{skip_hash}`` - **timestamp**: Unix timestamp - час модифікації останнього об'єкта з попередньої сторінки - **skip_len**: кількість об'єктів з однаковим timestamp, які вже були показані - **skip_hash**: MD5 хеш від посортованих ID об'єктів з однаковим timestamp Приклад: ``1696888800.0.2.c9e589b703dacf9b5ed4833357465084`` Для отримання наступної сторінки використовуйте значення ``offset`` з поля ``next_page.offset`` або просто перейдіть за посиланням ``next_page.uri`` що містить значення ``offset``. limit ~~~~~ Кількість об'єктів на сторінці. - **За замовчуванням**: 100 - **Максимум**: 1000 - **Мінімум**: 1 - Якщо вказано більше 1000, автоматично обмежується до 1000 Приклад: ``/tenders?limit=500`` descending ~~~~~~~~~~ Сортування в зворотному порядку (від новіших до старіших). - Якщо параметр присутній (будь-яке значення) - сортування від новіших до старіших - Якщо параметр відсутній - сортування від старіших до новіших (за замовчуванням) Приклад: ``/tenders?descending=1`` mode ~~~~ Фільтр за режимом об'єктів (тестові або реальні). - **test**: тільки тестові об'єкти - **_all_**: всі об'єкти - Якщо не вказано: тільки не тестові об'єкти Приклади: - ``/tenders?mode=test`` - тільки тестові тендери - ``/tenders?mode=_all_`` - всі тендери - ``/tenders`` - тільки не тестові тендери (за замовчуванням) opt_fields ~~~~~~~~~~ Додаткові поля, які потрібно включити в відповідь. За замовчуванням feed ендпоінти повертають тільки ``id`` та ``dateModified``. Параметр ``opt_fields`` дозволяє додати додаткові поля до відповіді. Детальний перелік доступних полів для feed та детальних ендпоінтів дивіться в :ref:`opt_fields`. Дозволені поля залежать від типу ресурсу, але завжди доступні: - ``public_modified`` - Unix timestamp часу модифікації (в секундах) Поля розділяються комою: ``opt_fields=public_modified,dateCreated`` Приклади: - ``/tenders?opt_fields=status`` - ``/tenders?opt_fields=dateCreated,procuringEntity`` - ``/tenders?opt_fields=public_modified`` **Важливо про opt_fields=public_modified**: Поле ``public_modified`` містить Unix timestamp в секундах і використовується системою для внутрішнього сортування. Воно може бути корисним для: - Точного визначення часу модифікації об'єкта - Реалізації власної логіки синхронізації - Порівняння часу модифікації різних об'єктів - Збереження точки синхронізації для подальшого використання Приклад відповіді з ``opt_fields=public_modified``: .. code-block:: json { "data": [ { "id": "82caff5150214ad2b43aa583f7fabcae", "dateModified": "2023-10-10T01:00:00+03:00", "public_modified": 1696888800.123 } ], "next_page": { "offset": "1696888800.123.0.abc...", "uri": "..." } } Поле ``public_modified`` також можна отримати в детальних ендпоінтах через параметр ``opt_fields``. Приклад: .. sourcecode:: http GET /tenders/82caff5150214ad2b43aa583f7fabcae?opt_fields=public_modified HTTP/1.1 Пагінація --------- Feed ендпоінти використовують курсорну пагінацію на основі поля ``public_modified``. Це забезпечує стабільність пагінації навіть при одночасних змінах об'єктів у системі. Як працює пагінація: 1. **Перший запит**: Отримайте першу сторінку без параметра ``offset`` 2. **Наступні сторінки**: Перейдіть за посиланням ``next_page.uri`` 3. **Попередні сторінки**: Перейдіть за посиланням ``prev_page.uri`` **Важливо про напрямок пагінації**: - **next_page**: рухає вас у напрямку поточного сортування - **prev_page**: рухає вас у зворотному напрямку від поточного сортування, але **змінює напрямок сортування** Наприклад: - Якщо ви використовуєте ``descending=1`` (від новіших до старіших), то: - ``next_page`` (з ``descending=1``) дасть вам старіші об'єкти - ``prev_page`` (без ``descending=1``) - це "розворот", дасть новіші об'єкти, і тепер наступні запити треба робити з ``next_page`` (без descending) для отримання новіших об'єктів - Якщо ви не використовуєте ``descending`` (від старіших до новіших), то: - ``next_page`` (без descending) дасть вам новіші об'єкти - ``prev_page`` (з ``descending=1``) - це "розворот", дасть старіші об'єкти, і тепер наступні запити треба робити з ``next_page`` (з descending) для отримання старіших об'єктів Стратегії синхронізації ----------------------- Forward Sync: Синхронізація з початку (старіші об'єкти) до кінця (новіші об'єкти) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Для синхронізації всіх даних з початку до поточного моменту: **Крок 1: Ініціалізація** - Виконайте перший запит без параметрів (сортування від старіших до новіших) - Отримайте першу сторінку результатів **Крок 2: Ітерація по сторінках** - Перейдіть за посиланням ``next_page.uri`` для отримання наступної сторінки - Кожна наступна сторінка містить новіші об'єкти - Повторюйте до отримання порожнього списку в ``data`` **Крок 3: Визначення завершення** - Якщо відповідь містить порожній список в ``data``, це означає, що всі існуючі об'єкти були прочитані **Крок 4: Подальші дії** - Зупиніть синхронізацію, або - Продовжуйте читати оновлення в режимі реального часу, очікуючи появи нових об'єктів - При читанні оновлень в режимі реального часу рекомендується додати затримку між запитами при отриманні порожніх відповідей Backward Sync: Синхронізація від кінця (найновіші об'єкти) до початку (старіші об'єкти) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Для отримання найновіших об'єктів першими: **Крок 1: Ініціалізація** - Виконайте перший запит з ``descending=1`` (сортування від новіших до старіших) - Отримайте першу сторінку результатів з найновішими об'єктами **Крок 2: Ітерація по сторінках** - Перейдіть за посиланням ``next_page.uri`` для отримання наступної сторінки - Кожна наступна сторінка містить старіші об'єкти - Повторюйте до зникнення поля ``next_page`` **Крок 3: Визначення завершення** - Якщо у відповіді відсутнє поле ``next_page``, це означає, що всі об'єкти були прочитані **Крок 4: Завершення** - Зупиніть синхронізацію Forward/Backward Sync: Двонаправлена синхронізація ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Для синхронізації в обох напрямках (вперед і назад) та моніторингу нових змін: **Крок 1: Ініціалізація** - Виконайте перший запит з ``descending=1`` (сортування від новіших до старіших) - Отримайте першу сторінку результатів **Крок 2: Backward Sync: Рух назад (читання старих даних від новіших до старіших)** - Перейдіть за посиланням ``next_page.uri`` для отримання наступної сторінки - Кожна наступна сторінка містить старіші об'єкти - Повторюйте до зникнення поля ``next_page`` - Якщо у відповіді відсутнє поле ``next_page``, це означає, що всі об'єкти були прочитані - Зупиніть синхронізацію **Крок 3: Forward Sync: Рух вперед (читання нових даних в режимі реального часу)** - Перейдіть за посиланням ``prev_page.uri`` для "розвороту" (він автоматично знімає ``descending=1``) - Перейдіть за посиланням ``next_page.uri`` для отримання наступної сторінки - Кожна наступна сторінка містить новіші об'єкти - Якщо відповідь містить порожній список в ``data``, це означає, що всі існуючі об'єкти були прочитані - Продовжуйте читати оновлення в режимі реального часу, очікуючи появи нових об'єктів - При читанні оновлень в режимі реального часу рекомендується додати затримку між запитами при отриманні порожніх відповідей Отримання повної інформації про об'єкт -------------------------------------- Feed ендпоінти повертають тільки мінімальну інформацію про об'єкти. Для отримання повної інформації про конкретний об'єкт використовуйте детальний ендпоінт: .. sourcecode:: http GET /tenders/64e93250be76435397e8c992ed4214d1 HTTP/1.1 Приклад робочого процесу: 1. Отримайте список об'єктів через feed ендпоінт 2. Витягніть ``id`` з кожного об'єкта 3. Використайте ``id`` для отримання повної інформації через детальний ендпоінт Затримка появи нових об'єктів у фіді (watermark) ------------------------------------------------- Об'єкти у фіді з'являються з **невеликою затримкою** після їхнього збереження в базі даних. Це навмисна поведінка, що захищає від race condition: при паралельних записах MongoDB фіксує час операції на її початку, а не в момент коміту. Через це два об'єкти, збережені майже одночасно, можуть з'явитися в БД у неочікуваному порядку. Без затримки crawler міг би пропустити деякі об'єкти назавжди. **Поточне значення затримки: ~1 секунда.** Практичний ефект: - **Forward feed** (без ``descending``): об'єкти з'являються з затримкою ~1 секунда. Якщо ваш crawler досяг кінця і отримав порожню відповідь — нові об'єкти з'являться за ~1 секунду. При типовій паузі між запитами затримка непомітна. - **Descending без offset** (перша сторінка): також застосовується затримка ~1 секунда. Це критично для коректної двонаправленої синхронізації (backward→forward): offset з першої descending-сторінки гарантовано старіший за 1 секунду, тому використання його як стартової точки для подальшого forward sync не призведе до пропуску об'єктів. - **Descending з offset** (пагінація по历史nich даних): затримка **не застосовується** — читаються вже закомічені старі записи. .. warning:: **Backward→forward sync**: якщо ви реалізуєте двонаправлену синхронізацію (спочатку ``descending``, потім ``forward``), використовуйте offset з першої descending-сторінки як стартову точку для forward sync **без жодних коректувань**. Затримка на першій descending-сторінці вже гарантує, що жоден об'єкт не буде пропущений при переході на forward. .. seealso:: :ref:`cdb_feed_ordering` — детальний технічний опис race condition, реалізації watermark та альтернативних підходів (для розробників). Обмеження та рекомендації -------------------------- Обмеження швидкості запитів ~~~~~~~~~~~~~~~~~~~~~~~~~~~ На публічних ендпоінтах діє обмеження швидкості запитів: **140 запитів на секунду (r/s)**. Максимальний ліміт сторінки ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Максимальна кількість об'єктів на сторінку: **1000**. **Рекомендація**: Використовуйте значення ``limit`` від 100 до 500 для оптимального балансу між кількістю запитів та обсягом даних. Фільтрація об'єктів ~~~~~~~~~~~~~~~~~~~ Автоматично застосовуються наступні фільтри: - Тільки публічні об'єкти (статус не ``draft``) - Фільтр за ``mode`` (тестові/реальні об'єкти) Об'єкти зі статусами ``draft`` не відображаються в feed, навіть якщо вони публічні.