У частині 1 ми обрали обсяг, зафіксували чотиришарову архітектуру та спроєктували канонічну схему треків. Частина 2 будує рушій, який перетворює сенсорні рапорти на достовірні треки: адаптери, що заводять джерела всередину; алгоритми кореляції, які вирішують, які рапорти стосуються одного й того самого фізичного об'єкта; управління життєвим циклом, що повідомляє СОК, коли трек став застарілим; і track store, який усе це утримує. Наприкінці частини 2 платформа продукує операційно корисні треки — їй лише нема де їх відобразити.

Концептуальний орієнтир для всього в частині 2 — Повний посібник зі злиття оборонних даних, який оглядає цю дисципліну загалом. Тут ми ухвалюємо конкретні рішення для нашої прикладної платформи.

Крок 1: патерн адаптера, без компромісів

Кожен датчик видає дані у власному форматі. Радари говорять ASTERIX; БПЛА — STANAG 4586; AIS-приймачі видають NMEA 0183; ATAK-клієнти говорять CoT; цивільні ADS-B-канали емітують інший бінарний протокол; рапорти спостерігачів, введені вручну, приходять через вебформу. Завдання шару адаптерів — перекласти все це у канонічну схему треків, визначену в частині 1.

Правило жорстке і варте того, щоб вивчити його напам'ять: жодне специфічне для датчика поняття не просочується за межі адаптера. Якщо код вашого рушія злиття посилається на ASTERIX-категорії — у вас протікаюча архітектура. Якщо у вашому track store є колонка під типи AIS-повідомлень — у вас протікаюча архітектура. Адаптери — це односторонні конвертери даних із суворою ізоляцією; угору вони експонують лише канонічні треки.

Прагматична структура адаптера для кожного джерела:

  • Transport — конектор до джерела (UDP-сокет, MQTT-підписка, HTTP-вебхук, file watcher). Стійкий до збоїв на боці джерела: перепідключення, backoff, облік втрачених повідомлень.
  • Parser — перекладає wire-формат у строго типізовану внутрішньопроцесну структуру. Валідує за специфікацією формату. Гучно відхиляє некоректний ввід, а не мовчки ковтає його.
  • Normalizer — мапить специфічні для джерела поля на канонічні. Перетворення системи координат (зазвичай у WGS84). Нормалізація міток часу (UTC, з дисципліною трьох часових міток із частини 1).
  • Emitter — публікує канонічне оновлення треку на шину повідомлень (message bus). Маркує ідентифікатором джерела, класифікацією джерела, releasability.

Кожен адаптер — окремий сервіс або процес. Вони спільно використовують кодогенеровану клієнтську бібліотеку для канонічної схеми, але жодних інших шляхів коду. Додати новий тип датчика — це написати новий адаптер, не торкаючись жодного іншого компонента. Детальні патерни інтеграції типових джерел — у Інтеграція AIS та ADS-B у військову картину, а сторона CoT — у Cursor on Target (CoT): XML-стандарт за тактичними застосунками.

Крок 2: підключіть шину повідомлень

Адаптери публікують у довговічний, упорядкований, партиціонований лог. Сервіси злиття з нього споживають. Як і сервіс аудиту, сервіс історичного відтворення (historical replay) та будь-яка спадна аналітика. Шина повідомлень — це спинний мозок платформи.

Для нашого прикладу ми використовуємо Kafka з топіком на тип джерела та додатковими топіками під вихід злиття. Адаптери публікують у raw.source-type; рушій злиття їх споживає та публікує в tracks.updates і tracks.lifecycle. Аудит підписаний на все. Патерн шини, разом із компромісами пропускної здатності й довговічності, — у Черги повідомлень для оборонних data-pipeline.

Архітектурне рішення, варте окремої згадки: не викликайте HTTP між компонентами злиття. Синхронне request-response-зчеплення робить конвеєр злиття крихким. Пік сенсорного навантаження, що блокує одного споживача, не повинен блокувати кожного вищерозташованого продюсера. Шина з backpressure — це структурне рішення; HTTP між компонентами злиття — рецидивне джерело збоїв.

Крок 3: кореляція треків (track-to-track)

Ядро рушія злиття — алгоритм, що вирішує, чи вхідний рапорт є оновленням до існуючого треку, чи народженням нового. Помилитеся — і оператор побачить «трек-суп» (тисяча символів там, де мала б бути сотня) або ghost-треки (дублікати, які мали б злитися). Зробите правильно — і СОК стає надійною.

Прагматичний патерн використовує двостадійний фільтр.

Стадія 1: правило-орієнтований gating. Для кожного вхідного рапорту обчислюємо множину кандидатних існуючих треків у межах кінематичної досяжності — позиційно-часовий gate, який каже: «трек, що рухається зі швидкістю не більше V м/с, міг би пройти від його останньої відомої позиції до позиції цього рапорту за цей часовий інтервал». Identity-пріори фільтрують далі: рапорт із тегом «vessel» (судно) не може бути зіставлений з треком «aircraft» (повітряне судно). Фільтри сумісності джерела: рапорт із наземного радара не може бути зіставлений з повітряним треком підводної платформи. Правило-орієнтована стадія дешево й однозначно обробляє 90% входів.

Стадія 2: ймовірнісна асоціація. Для спірних випадків — кілька кандидатів усередині gate, неоднозначна ідентичність, щільні сценарії з перетинаючимися траєкторіями — викликаємо probabilistic data association. Joint Probabilistic Data Association (JPDA) для помірної щільності; Multiple Hypothesis Tracking (MHT) для найскладніших випадків. Обидва обчислюють правдоподібність того, що вхідний рапорт належить до кожного кандидатного треку, і оновлюють треки, зважуючи за цією правдоподібністю.

Повна теоретична модель з інженерними наслідками — у Модель злиття даних JDL: практичний інженерний довідник. Інженерні нюанси того, коли застосовується кожна техніка, та необхідне налаштування — у Військове злиття даних, пояснене на пальцях.

Конкретна пастка, варта підкреслення: MHT без pruning генерує експоненційну кількість гіпотез. Політика pruning — скільки гіпотез утримувати, коли зливати, коли видаляти — важливіша за сам базовий алгоритм. За замовчуванням — агресивний pruning; нарощуйте лише тоді, коли цього вимагає картина загроз.

Крок 4: управління життєвим циклом треку

Трек — це не статичний запис. Він народжується, підтверджується, старіє, згасає і вмирає. Рушій злиття керує життєвим циклом явно; СОК відображає стан життєвого циклу, щоб оператори знали, яким трекам можна довіряти.

Стан-машина для нашого прикладу:

  • Tentative (попередній) — перше спостереження; ще не показується в операційній СОК, якщо не запитано явно. Згасає до видалення, якщо немає продовження в межах конфігурованого вікна.
  • Confirmed (підтверджений) — два чи більше скорельованих рапортів, кінематична сумісність витримана. Промотовано з tentative. Це стан за замовчуванням для відображуваних треків.
  • Mature (зрілий) — підтверджений і стабільний щонайменше N хвилин з узгодженими оновленнями. Використовується спадною аналітикою, якій потрібна стабільна ідентичність.
  • Fading (згасаючий) — немає оновлення в межах очікуваного інтервалу повторного відвідування (revisit interval). На дисплеї помічений як застарілий. Конфігурується за класом джерела (30-секундний морський трек — нормально; 30-секундний повітряний — fading).
  • Lost (втрачений) — немає оновлення тривалий час. Видаляється з активного дисплея, але зберігається в track store для аудиту та історичного аналізу.

Кожен перехід стану логується. Сервіс аудиту споживає потік переходів і записує незмінні записи — тема статті Event sourcing для оборонних аудит-трейлів. Переходи також експонуються на шині, щоб СОК могла рендерити стан життєвого циклу без polling.

Ключовий інсайт: оператори толерують відсутній трек. Вони не толерують упевнено відображений застарілий трек. Управління життєвим циклом — це шар, що робить різницю. Будуйте його ще до того, як рушій злиття повністю налаштований — це дешево і повертає кожного разу, коли падає сенсорний канал.

Крок 5: авторитативний track store

Злиття видає потік оновлень треків і переходів життєвого циклу. Track store — це матеріалізоване представлення (materialized view): поточний стан кожного активного треку, доступний для запитів від СОК та аналітики. Архітектурне рішення, яке варто ухвалити рано: track store — це read-модель, а не авторитативне джерело. Авторитативним джерелом є лог подій на шині повідомлень. Track store перебудовується з лога на вимогу.

Цей патерн — event-sourced стан з проєкціями read-моделей — має три операційні переваги. Сховище можна стерти й перебудувати без втрати даних. Можуть співіснувати кілька read-моделей різної форми (одна для СОК, одна для аналітики, одна для зовнішнього API). Часові запити (time-travel) стають тривіальними: відтворіть лог до обраного моменту, щоб реконструювати, у що платформа вірила тоді.

Саме сховище — PostgreSQL із PostGIS для геопросторового індексування. «Гарячі» треки живуть у пам'яті або в Redis-шарі перед PostgreSQL для субмілісекундних читань; реляційне сховище обслуговує запити «довгого хвоста» та гарантії персистентності. Детальний інженерний погляд — у PostGIS для оборонних геопросторових даних.

Опирайтеся спокусі додати графову базу «під зв'язки». Зв'язки між треками — виявлення конвоїв, розпізнавання шикувань, контактні мережі — це JDL Level 2 fusion, окрема турбота від Level 1 (обслуговування треків). Збудуйте спершу Level 1, експлуатуйте його рік в операціях, тоді поверніться до Level 2 з операційними доказами в руках.

Крок 6: тестуйте на реалістичних входах

Рушій злиття, протестований лише іграшковим навантаженням, проходить інтеграційний тест і провалюється в операціях. Дисципліни, які виловлюють проблеми до розгортання:

Replay-каркаси тестів. Захоплюйте справжні сенсорні траси (traces) у розробці й відтворюйте їх на повній швидкості проти рушія злиття. Траси слугують регресійним тестовим набором: нова версія алгоритму чи зміна схеми мусить давати еквівалентний або кращий результат на існуючих трасах, а не лише на синтетичному навантаженні.

Адверсаріальні входи. Підроблені AIS-повідомлення з неправдоподібною кінематикою. Некоректний CoT-XML. Радарні відмітки, що порушують фізику (наземні треки на Mach 5). Рушій злиття мусить відхиляти чи помічати їх — не панікувати, не падати, не продукувати «впевнено-але-неправильні» треки. Дисципліна та сама, що в ширшій тестовій дисципліні з Тестування mission-critical C2-систем.

Виявлення pattern-of-life. Щойно базове злиття працює, нашаровуйте PoL-аналітику — див. Аналіз pattern-of-life у військовій розвідці. PoL-сервіс споживає ті самі топіки шини; він видає збагачені анотації стану треку, а не конкурує з рушієм злиття.

Крок 7: цільові показники продуктивності та запас

Затримка злиття має операційні наслідки. Цілі для нашого прикладу: 95-й перцентиль наскрізної затримки злиття — нижче 500 мс (від прийому сенсорного рапорту до повідомлення про оновлення треку на шині); 99-й перцентиль — нижче 1,5 с; підтримувана пропускна здатність — 10 000 рапортів на секунду з однозначним відсотковим запасом CPU.

Це цілі тактично-бригадного рівня. Стратегічні платформи мають вільніші допуски на затримку та вищі стелі пропускної здатності. Цілі диктують архітектурні рішення: уникайте синхронних міжсервісних викликів на «гарячому» шляху; попередньо виділяйте стан гарячих треків; батчуйте лише там, де це дозволяє шина; інструментуйте кожну стадію конвеєра, щоб регресії затримки спливали в CI, а не в операціях.

Що далі

Частина 2 побудувала рушій. Сенсорні адаптери конвертують у канонічні треки; шина повідомлень переносить події; рушій злиття корелює рапорти в треки; управління життєвим циклом тримає операторів чесними щодо свіжості; track store експонує поточний стан. Платформа тепер продукує надійні дані треків. Їй лише бракує поверхні для оператора.

Частина 3 будує спільну оперативну картину — фронтенд, який перетворює треки на мапу, якою оператор насправді користується. Символіка, оновлення в реальному часі, фільтрація за роллю та інженерні рішення, що визначають, чи платформу приймуть на озброєння.