01Про проєкт
Карта тривог показує, де зараз повітряна тривога: по областях і по громадах, з рухом цілей на карті, лінією фронту, термоточками із супутників і стрічкою моніторингових каналів унизу. Її читають ті, кому відповідь потрібна за секунди, і читають з телефона, вночі, на поганому мобільному інтернеті.
При цьому сторінка — лише частина продукту. Ті самі дані виходять назовні відкритим JSON-API з ключами та лімітами і віджетом, який інші сайти ставлять до себе в рамку. Ми спроєктували й зробили все: збирачі, зберігання, API, карту та віджет.
02Контекст і проблема
Даних, які відповідають на питання «чи є тривога тут», не існує в одному місці. Офіційні тривоги надходять по областях. Локальні тривоги громад — з іншого сервісу. Де цілі, словами пишуть моніторингові канали. Лінію фронту публікує картографічний проєкт. Пожежі бачать супутники. П’ять джерел, п’ять форматів, п’ять різних уявлень про те, що таке «зараз».
Друга проблема — мовчання. Будь-яке з джерел може стати: канал замовк, API почав відвалюватися за таймаутом, — і чесна поведінка тут сказати про це, а не продовжувати малювати вчорашню картину. Карта, що виглядає спокійною, бо в неї помер джерело, гірша за відсутність карти.
03Завдання проєкту
- одна картина, зібрана з п’яти незалежних джерел, у кожного свій годинник оновлення;
- відповідь за секунди з телефона, на слабкому зв’язку, вночі;
- дані, які можуть виходити із сайту: відкритий API з ключами, лімітами й тарифами, і віджет для вбудовування;
- три мови, бо аудиторія не лише місцева;
- джерело, що замовкло, має бути видно як мовчання, а не як спокій;
- адреса самого сервера не має доходити до джерел, які він читає.
04П’ять джерел — одна картина
- Офіційні тривоги по областях — основа карти;
- локальні тривоги громад і районів, з окремого сервісу;
- моніторингові канали в Telegram — читаються і через публічні прев’ю, і, для закритих каналів, самим протоколом месенджера;
- лінія фронту — від картографічного проєкту, який її публікує;
- термоточки із супутників NASA — пожежі, видимі з орбіти.
Збирачі працюють двома довгоживучими воркерами під системним супервізором, кожен перезапускається сам по собі, і пишуть у локальне сховище. Сайт ніколи не ходить до джерела напряму: він читає те, що збирачі вже записали, — тому повільне джерело робить карту старішою, а не недоступною.
05Чотири файли замість однієї бази
Дані лежать у чотирьох окремих файлах SQLite: журнал тривог, підписки на пуші та їхній стан по регіонах, події карти зі стрічкою, ключі API з лічильниками лімітів. Спільної бази немає навмисно — відмова одного файлу не має забирати решту. Недоступні підписки — карта однаково малюється; зайнятий склад ключів — сторінка однаково відповідає.
У ті самі файли пише загальна панель керування групи проєктів — випуск ключа, скидання стану тривоги, — тому кожне з’єднання відкривається з очікуванням зайнятості. За замовчуванням SQLite на зайнятій базі здається миттєво, і це «миттєво» тут означало б сторінку з помилкою в момент, коли воркер лише дописував рядок.
06Троє годинників на одній сторінці
Різні дані старіють з різною швидкістю — і оновлюються з різною: тривоги раз на 15 секунд, рухомі цілі раз на 10, стрічка раз на 20. Один спільний інтервал був би неправильним одразу в обидва боки: надто повільним для цілей, марнотратним для стрічки й зайвим запитом кожні кілька секунд для тисяч відкритих вкладок.
У кожного блока є і своє «оновлено». Сторінка, що мовчки перемальовується, привчає довіряти їй саме тоді, коли довіряти не варто; зупинена позначка часу — найшвидший спосіб сказати «ця частина застаріла».
07Відкритий API з ключами й тарифами
Карта публікує те, що знає: поточні тривоги, події, аналітику з тепловою картою «область × кошик часу», ритм доби, типи цілей і напрямки. Анонім отримує скромний ліміт на хвилину; ключ піднімає його, а тарифи ростуть сходинками для тих, хто на цьому будує.
Ключі випускаються із загальної панелі керування й ніколи не набираються руками, а витрата рахується за ключем і за днями. Одна точка навмисно закрита для всіх, крім власника ключа, — та, що розповідає ключу його тариф, ліміт і витрату. Решта відкрита, бо суспільний сервіс, що ховає свої дані, — суспільний лише наполовину.
08Віджет, що живе на чужих сайтах
Віджет — та частина продукту, що йде з дому. Два фіксовані розміри — смуга стану і квадратна панель, три мови, дві теми й політика вкладення в рамку, написана спеціально так, щоб вбудовування було дозволено тут і ніде більше на сайті: карту й віджет у рамку брати можна, кабінет і сторінки підтримки — ні.
Раз він живе в рамці, у віджета немає власних кук і він не обирає покоління інтерфейсу: виглядає однаково в усіх. Це рішення, а не обмеження — віджет, що малюється по-різному залежно від колишніх виборів відвідувача, неможливо було б підтримувати на чужій сторінці.
09Пуші, що знають ваш регіон
Підписка прив’язана до регіону, а стан кожного регіону зберігається поряд із підписками. Саме це робить сповіщення подією на межі — коли тривога у вашій області почалася чи закінчилася, — а не на кожне оновлення джерела. У цій різниці весь продукт: одне — сигнал, інше — шум, який вимикають першого ж дня.
Поверх тих самих даних працює нативний застосунок, і ходить він у своє app-API, а не по сторінках сайту. Окрема адреса живості відповідає реєстру застосунків: перевірка живості не має коштувати дорожче за роботу, яку перевіряє.
10Три мови, включно з віджетом
Карта говорить українською, англійською та російською — і так само говорять довідник API, сторінки кабінету й віджет. Новий рядок інтерфейсу заводиться одразу трьома мовами, а перевірка рахує відсутні: їх має бути нуль. Наполовину перекладені інтерфейси не повідомляють про себе — вони тихо показують одну мову всередині іншої й виглядають нормально для того, хто їх писав.
Назви регіонів — окрема історія: вони надходять із джерел у власному написанні, тому карта тримає власний словник, а не довіряє тому, що приїхало в стрічці.
11Читати джерела, не показуючи свою адресу
Усе, що забирають збирачі, іде через SOCKS5-проксі — включно з протоколом месенджера для закритих каналів, де проксіювання зроблено кодом, без зайвої залежності. Адреса самого сервера не доходить до джерела, а джерело, яке вирішило когось заблокувати, блокує проксі, а не машину, що віддає карту.
Обмежувач частоти стоїть перед API з тієї самої причини, тільки навпаки: публічна точка з живими даними притягує скрейперів, і ліміт на ключ за хвилину — це те, що лишає суспільний сервіс придатним для тих, заради кого він написаний.
12Технології
- PHP 8.3 без фреймворка: маленький роутер, точки API та рендери;
- воркери на Python під системним супервізором — один лише на стандартній бібліотеці, другий говорить протоколом месенджера;
- SQLite, чотири окремі файли, у кожного своя причина існувати;
- Leaflet для карти, асети з хешем в імені, віджет фіксованих розмірів;
- вебпуші зі станом по регіонах, нативний застосунок на тих самих даних;
- SOCKS5 назовні для кожного вихідного запиту, ліміти на ключ перед API.
13Результат
Карта, що відповідає за секунди і продовжує відповідати, коли одне з п’яти джерел стало. Дані, які можна забрати до себе: відкритий API з ключами й тарифами та віджет трьома мовами у двох темах. Сповіщення, що спрацьовують на межі події, а не на кожне опитування. Нативний застосунок на тих самих даних і панель, з якої випускають ключі та скидають стани.
14Висновки
Три речі, які ми понесемо в будь-який проєкт із живими даними. Відокремлювати збирач від сторінки: сайт, що йде до джерела, поки відвідувач чекає, — це сайт, який падає разом із джерелом. Давати кожному блоку свій годинник і свою позначку часу: один інтервал неправильний одразу в обидва боки, а мовчазне перемальовування ховає застарівання. Казати, коли не знаєш: порожній блок, який у цьому зізнається, вартий більше за спокійну на вигляд карту з учорашніми даними.
І один продуктовий висновок: щойно дані можна вбудувати до себе, продуктом стає віджет. Йому потрібні фіксовані розміри, власна політика рамки та переклади — та сама турбота, що й головній сторінці, а не залишок від неї.
Якщо у вас є живі дані й потрібні карта, API та віджет, який зможуть узяти інші сайти, — це робота, яку ми робимо цілком.



