OTACKРозробка цифрових продуктів
№ СЗ-081
Внутрішній документ
Затверджено · в роботу
Службова записка

Архітектура проєкту та оточення
«Система контролю інтерв'юерів»

Обласна модель «міські вузли + обласний хаб» · технологічний стек · навантаження і процеси · порядок розгортання

1 Призначення документа

Документ фіксує технічні рішення щодо проєкту до початку написання ТЗ і коду: що ми використовуємо, чому саме це, які навантаження очікуються і як розгортається оточення. Він передує технічному завданню — ТЗ описує, що система робить, ця записка описує, на чому вона зроблена і як живе.

Ключові рішення проєкту зведені в реєстрі Д-1…Д-13 (розділ 14).

2 Обласна модель та схема взаємодії

Система складається з двох вебзастосунків:

Схема взаємодії · приклад: Полтавська область — обласний хаб + 3 міські вузли
Обласний хаб · панель замовника
аналітика по містах · черга аудиту · діаризація · рейтинги · маркери · контроль координаторів
700інтерв'юерів ~8 600інтерв'ю/день ~5 годGPU/день
laravel-hubfilament+vuehorizonpostgres+pgvectorredisdiar-worker · GPU ×1voiceprint-matcherwireguardprometheus+grafana
01 · Маніфествузол повідомляє хаб: «зміна/день закриті: 200 інтерв'юерів, 2 000 інтерв'ю до перевірки» — метадані, без аудіо
02 · Аудіо потокомхаб тягне записи по одному прямо в діаризацію; на диску хаба аудіо не осідає — лишаються зведення, відбитки, маркери
03 · Результатихаб повертає вузлу вердикти, маркери і рейтинги; вузол показує їх координатору і рахує кількісний облік «до оплати»
Полтава
400 інтерв'юерів · ~5 000 інтерв'ю/день · ~12,5 ГБ/день · на диску ~50 ГБ
laravel-nodepwapostgres+postgisredis+horizonnginxаудіо: диск, 3 доби
Кременчук
250 інтерв'юерів · ~3 000 інтерв'ю/день · ~7,5 ГБ/день · на диску ~30 ГБ
laravel-nodepwapostgres+postgisredis+horizonnginxаудіо: диск, 3 доби
Глобине
50 інтерв'юерів · ~600 інтерв'ю/день · ~1,5 ГБ/день · на диску ~6 ГБ
laravel-nodepwapostgres+postgisredis+horizonnginxаудіо: диск, 3 доби
службовий трафік хаб ↔ вузли — через WireGuard-тунелі · кожен вузол автономний: якщо хаб недоступний, збір даних у місті не зупиняється · нові міста додаються реєстрацією вузла на хабі

2.1 Як проходить доба на прикладі Полтави

Протягом дня
400 інтерв'юерів проводять інтерв'ю; PWA пише аудіо і GPS (точка на старті + точка на фініші), синхронізує на вузол Полтави. Вузол рахує дешеві сигнали (тиша, VAD, дублі) та гео-перевірки локально. Якщо запис перерваний дзвінком — інтерв'ю анулюється з повідомленням «почніть заново», збій фіксується у статистиці
Закриття зміни
Інтерв'юер закриває зміну — вузол додає його записи в маніфест. Маніфести йдуть на хаб протягом дня, а не одним пакетом опівночі: черга діаризації наповнюється рівномірно
Хаб · безперервно
Тягне записи по одному через WireGuard прямо в діаризацію (початкові 30–90 с запису): скільки голосів, чи другий голос ≠ голос інтерв'юера, чи не повторюється «респондент» в інших інтерв'ю цього інтерв'юера, міста й усієї області. Аудіо відкидається одразу, лишаються зведення + відбиток
Наступний ранок
Аудитори замовника бачать чергу з маркерами по всій області, слухають підозріле стрімом із міських вузлів (вікно — 3 доби), виносять вердикти. Вердикти повертаються у вузли: координатор Полтави бачить статуси, рахується кількісний облік «до оплати»
Доба + 3
Вузол видаляє аудіо, якому виповнилося 3 доби після діаризації. Флаговані записи без вердикта — притримуються до вердикта (Д-10)
✓ Чому обласна мережа, а не один великий сервер
Трафік аудіо залишається в місті: 12 ГБ/день Полтави не їдуть у центр — на хаб іде лише потік для діаризації, і той не зберігається. Вузли автономні (падіння каналу чи хаба не зупиняє збір), масштабування — додаванням вузлів. А централізована база голосових відбитків на хабі ловить те, що жоден окремий вузол не побачить: той самий «респондент», який відповідає інтерв'юерам у Полтаві та Кременчуці одночасно

3 Що будуємо — зафіксовані вихідні дані

ПараметрРішення
ПризначенняПлатформа польового збору даних із контролем доброчесності інтерв'юерів: очні опитування із записом аудіо та геолокації, автоматична перевірка голосів, вибірковий людський аудит, індекс доброчесності
МодельОбласна мережа: міські вузли (збір) + обласний хаб (аналіз, діаризація, аудит, контроль координаторів). Два вебзастосунки, спільний протокол обміну
РозміщенняMVP/пілот — два VPS у HostPro (вузол і хаб окремо — та сама топологія, що в бою). Бойовий контур — фізичні сервери: хаб з однією GPU + вузли по містах. Жодних хмарних сервісів обробки — все self-hosted
Перевірка аудіо100 % інтерв'ю: діаризація початкових 30–90 с + порівняння голосових відбитків, self-hosted на GPU хаба. Транскрипція не виконується; за майбутньої потреби — self-hosted Whisper на тій самій GPU
Аудіо-скринінг вимикаєтьсяКоординатор міста може вимкнути запис аудіо довіреним інтерв'юерам (окремо чи вибіркою). Хаб бачить кожне вимкнення у звіті й контролює обґрунтованість (Д-12)
Зберігання аудіо3 доби після діаризації, далі видалення. Флаговані без вердикта — притримуються (Д-10). Закріплення записів замовником не передбачене
Доступ замовникаПанель хаба: аналітика, маркери, рейтинги, зведення; прослуховування аудіо — стрімом із вузлів у межах 3-денного вікна. Кожне прослуховування пишеться в журнал
Згода респондентаПозначка (галочка) на початку інтерв'ю в анкеті
АвтентифікаціяЛогін, пароль, TOTP. Сесія 30 днів із прив'язкою до пристрою. Одна активна сесія на акаунт; паралельний вхід з іншої точки — блок і сигнал в індекс (розд. 4.4)
Клієнт інтерв'юераЛише веб-PWA — свідомо, без мобільного застосунку. Тимчасові дані — у localStorage/сесії браузера; вхідний дзвінок або згортання перериває запис — інтерв'ю починається заново, кількість збоїв фіксується
Облік «до оплати»Лише кількісний: скільки інтерв'ю і за який період підлягають оплаті (прийняті мінус відхилені, з коригуванням заднім числом). Без ставок, сум і виплат

4 Навантаження та процеси

4.1 Базові показники (область-приклад)

ПоказникМіський вузол (Полтава)Обласний хабКоментар
Інтерв'юерів400700по 8–16 інтерв'ю за зміну
Інтерв'ю на день~5 000~8 600
Вхідний аудіо-трафік~12,5 ГБ/день~21,5 ГБ/день транзитомна хабі не осідає; зменшується з кожним вимкненим скринінгом
Піковий канал~20 Мбіт/с~30 Мбіт/свечірня хвиля синхронізації
Аудіо на диску~50 ГБ≈ 0вікно 3 доби + буфер до діаризації
Діаризація (початкові 30–90 с)~140–210 год/день~5 год роботи однієї GPU
Голосові відбитки~9 МБ/день~3 ГБ/рік, pgvector, TTL 90 днів
Запитів до API~5 rps · пік 25~5 rps
GPS-точок2 точки на інтерв'юагрегатистарт + фініш; ~3,7 млн рядків/рік на вузлі
Межа пропускної здатності однієї GPU
Префікс ~60 с ÷ 30× realtime ≈ 2 с GPU на інтерв'ю → ~1 800 інтерв'ю на GPU-годину → за 12 корисних годин на добу одна карта класу RTX 4060 Ti закриває ~20 000–25 000 інтерв'ю/день (≈ 1 500–2 500 інтерв'юерів). Область-приклад (8 600/день) займає ~5 GPU-годин. Якщо область із 10–20 міст перевищує ~20 тис. інтерв'ю/день — у хаб ставиться друга карта; ще більше — окремий хаб. Точна цифра пропускної здатності заміряється на пілоті

4.2 Процеси міського вузла

ПроцесТригер і часОбсяг (Полтава)Що відбувається і навіщо
Прийом інтерв'юсинхронізація PWA; безперервно, пік увечері5 000 пакетів
12,5 ГБ
Анкета, гео (старт+фініш), метадані — в API; аудіо — на локальний диск через nginx. Контроль цілісності за хешем, офлайн-черга в браузері з дозавантаженням. Перерваний запис (дзвінок, згортання) — інтерв'ю анулюється, збій рахується. Єдиний пік вузла — мережевий
Сигнали рівня 0подія «файл прийнято»5 000 файлів
~2 ядро-год
Тривалість, частка тиші, VAD, гучність, кліпінг, фінгерпринт (дублі та повторно здані записи). Секунди на файл, CPU вузла
Гео-перевіркиподія «інтерв'ю прийнято»5 000 перевірокСтарт і фініш у радіусі завдання; швидкість переміщення між сусідніми інтерв'ю; кластеризація (кілька інтерв'ю з однієї точки). PostGIS, мілісекунди
Контроль темпуковзний розрахунок за зміну400 інтерв'юерів10–20 інтерв'ю за годину — підозра на шахрайство (маркер); 1–2 за день — неефективність (окремий показник продуктивності). Пороги налаштовуються
Контроль сесійкожен запит APIОдна активна сесія на акаунт; вхід із другого пристрою/точки — блок старої сесії, фіксація спроби, сигнал в індекс. Паралельні інтерв'ю з різних точок — жорсткий маркер
Маніфести на хабзакриття зміни~400 подій/деньПакет «зміна закрита: N інтерв'ю, метадані, попередні сигнали». Аудіо не пересилається — хаб забере сам
Віддача аудіо хабузапити хаба; рівномірно5 000 стрімів/деньПідписані службові URL через WireGuard. Після успішної діаризації — позначка «оброблено», з неї стартує 3-денний відлік
Прийом результатівпуш із хаба5 000 оновленьВердикти, маркери, рейтинги — у локальну базу. Координатор бачить статуси, інтерв'юер у кабінеті — свої результати
Retention-очисткащоночі~5 000 файлів
~12,5 ГБ
Видалення аудіо, старшого за 3 доби після діаризації, крім флагованих без вердикта. Кожне видалення — в журнал
Облік «до оплати»закриття періоду400 інтерв'юерівКількісний підсумок: скільки інтерв'ю прийнято за період і підлягає оплаті. Пізні відхилення — коригувальний рядок у наступному періоді. Без ставок і сум
Бекапщоночі + WAL щогодинидамп БД: одиниці ГББаза — критична. Аудіо в бекап не входить свідомо: строк життя 3 доби

4.3 Процеси обласного хаба

ПроцесТригер і часОбсяг (область)Що відбувається і навіщо
Прийом маніфестівпуші вузлів; протягом дня~700/деньРеєстрація пакетів до перевірки, черга діаризації з пріоритетом за попередніми сигналами вузла
Діаризаціячерга; рівномірно8 600 записів
~5 год GPU
Початкові 30–90 с (адаптивно — до ~10 с чистої речі другого голосу): скільки голосів; другий ≠ інтерв'юер; відбиток другого голосу. Аудіо не зберігається. Повний файл — лише для флагованих
Звірка відбитківпісля діаризації8 600 порівняньВідбиток «респондента» проти: відбитка інтерв'юера (чи не грає обох), його «респондентів» за 30 днів (запис по колу знайомих), бази області (та сама група відповідає різним інтерв'юерам). pgvector, мілісекунди
Маркери і індекспісля звірки8 600 розрахунківСигнали вузла + сигнали голосу зводяться за вагами в бал інтерв'ю та індекс доброчесності інтерв'юера (склад — розд. 4.4). Розбір зберігається — індекс пояснюваний
Черга аудитуробочі годинивибірка з маркерамиАудитори замовника: картка з маркерами, сигналами, картою; аудіо — стрімом із вузла (вікно 3 доби). Вердикт → перерахунок індексу → пуш у вузол
Нагляд за координаторамищоденний звіт + маркери10–20 містХаб бачить дії координаторів: кому і на якій підставі вимкнули аудіо-скринінг (Д-12), як швидко місто розбирає чергу, аномалії вердиктів. Вимкнення скринінгу без статистики або з поганою статистикою — маркер «підозра на змову». Місто, що не справляється, — маркер координатору (Д-10), щоб хаб міг втрутитися
Пуш результатівподія «вердикт/маркер»~8 600/деньВердикти, маркери, рейтинги — назад у вузли. Ретраї при недоступності вузла
Аналітиказапити панелі + нічні агрегатидашборди областіЗведення по містах, динаміка індексів, топ підозрілих, статистика скринінгу. Матеріалізовані представлення
Бекапщоночі + WALдамп БД + відбиткиРейтинги, вердикти, відбитки, журнали. Офсайт-копія обов'язкова

4.4 Індекс доброчесності — склад сигналів

ГрупаСигналиЩо ловить
ГеолокаціяСтарт і фініш інтерв'ю в радіусі завдання; час між стартом і фінішем; швидкість переміщення між сусідніми інтерв'ю; кластеризація точокРоботу поза районом; інтерв'ю «з дивана»; фізично неможливі переміщення; серію анкет з однієї лавки
Темп роботиІнтерв'ю за годину та за зміну проти порогів10–20 за годину — фабрикація анкет (маркер шахрайства); 1–2 за день — неефективність (показник продуктивності, не звинувачення)
ДіаризаціяКількість голосів у префіксі; другий голос ≠ голос інтерв'юера; повторюваність «респондента» за 30 днів у розрізі інтерв'юера, міста, областіСамозапис «на два голоси»; запис по колу знайомих; групу, що «відповідає» різним інтерв'юерам
Дешеві аудіо-сигналиТривалість, частка тиші, VAD-частка мовлення, гучність, кліпінг, аудіо-фінгерпринтПусті й «тихі» записи; той самий файл, зданий двічі; запис із телевізора
Акаунт і сесіїОдна активна сесія; спроби паралельного входу; вхід із нової точки/пристрою; паралельні інтерв'ю з різних точокПередачу акаунта («розмноження» одного логіна на бригаду); роботу під чужим обліковим записом
Стабільність записуКількість перерваних записів (дзвінки, згортання), частка незавершених інтерв'юСтатистика збоїв PWA; аномальна частота переривань в одного інтерв'юера — сигнал маніпуляції записом
Вердикти аудитуЧастка відхилених, історія апеляційПідсумковий людський фактор поверх автоматики

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

Рішення Д-12 · Вимкнення аудіо-скринінгу для довірених (затверджено)
Координатор міста може вимкнути запис аудіо окремому інтерв'юеру або масовою вибіркою: довірений за маркерами — аудіо більше не пишеться, решта перевірок (гео, темп, сесії) працюють. Економія диска і трафіку пропорційна частці довірених.

Контроль хабом: кожне вимкнення видно у звіті області з підставою: «місто N: 200 інтерв'юерів; 100 — довірені, скринінг вимкнено обґрунтовано; 20 — відсторонені; 10 — скринінг вимкнено без статистики чи з поганою статистикою — підозра на змову з координатором». Періодична контрольна вибірка: у частини довірених скринінг тимчасово вмикається знову

4.5 Диски та мережа

ПоказникЗначенняРозрахунок
Аудіо на вузлі, норма~4 × добовий обсягбуфер до діаризації (≤1 доба) + 3 доби вікна. Полтава ≈ 50 ГБ, Глобине ≈ 6 ГБ
Найгірший випадокаудіо за ~10 дібхаб недоступний тиждень: Полтава ≈ 140 ГБ, Глобине ≈ 16 ГБ
Диск вузла (велике місто)2 × 480 ГБ NVMe, дзеркалоаудіо (норма 50, пік 140 ГБ) + БД (~20 ГБ/рік) + ОС. Корисний обсяг = один диск, другий — жива копія на випадок відмови
Диск вузла (мале місто)2 × 240 ГБ NVMe, дзеркалотой самий розрахунок на менший потік
Диск хаба2 × 480 ГБ NVMe, дзеркалоБД (метадані+маркери ~10 ГБ/рік), відбитки (~3 ГБ/рік), журнали, транзитний буфер діаризації
Офсайт-бекапStorage VPS HostPro
250 ГБ HDD
приймач restic-дампів баз усіх вузлів і хаба (розд. 7). HDD і слабкий CPU тут не вада — це холодний приймач. Гаряче аудіо туди не виноситься: воно живе на NVMe вузла у 3-денному вікні. Масштабується до 6 ТБ
Канал вузла≥ 100 Мбіт/спік синхронізації ~20 Мбіт/с + віддача стрімів хабу
Канал хаба≥ 100 Мбіт/странзит діаризації ~30 Мбіт/с рівномірно

«2 × N» усюди означає дзеркало (RAID1): два однакові диски, корисний обсяг одного, автоматичне переживання відмови будь-якого з них.

5 Платформа застосунку та візуальна мова

Рішення Д-1 · Ядро — Laravel, візуальна мова адмінок — дизайн-система Otack CMS (затверджено)
Обидва застосунки — міський вузол і обласний хаб — будуються на Laravel зі спільними пакетами (протокол обміну, моделі сигналів): черги з ретраями, міграції, composer-екосистема, токенна авторизація для PWA і міжсерверна — для обласного обміну.

Сама Otack CMS у проєкті не використовується — від неї береться візуальний інтерфейс адмінок: токени, сітка, сайдбар, тулбар, картки, таблиці, пагінація, шрифти e-Ukraine. Обидві панелі виглядають як одна система.
Рішення Д-2 · Гібридні адмінки (затверджено)
Filament як каркас і CRUD-рушій, тема на токенах Otack CMS. Кастомні Vue-сторінки — там, де вирішується цінність продукту: на хабі — картка аудиту (плеєр-хвиля, маркери, карта), дашборд області, звіт нагляду за координаторами; на вузлі — карта завдань міста, конструктор анкет.

6 Технологічний стек

ШарВибірЧому саме так
Обидва застосункиLaravel 12 (PHP 8.3)Спільна кодова база і навички команди; навантаження скромне, Octane не потрібен
АдмінкиFilament 4 + Vue 3
тема на токенах Otack CMS
Див. Д-2
Кабінет інтерв'юераVue 3 + Vite + PWA
без мобільного застосунку
MediaRecorder + Wake Lock; тимчасові дані в localStorage/сесії; офлайн-черга IndexedDB із дозавантаженням. Дзвінок/згортання → запис зупиняється, інтерв'ю починається заново, збій фіксується. Відмова від натива — свідома (Д-13)
База данихPostgreSQL 17
вузол: +PostGIS · хаб: +pgvector
PostGIS — гео-перевірки на вузлі; pgvector — пошук збігів голосових відбитків на хабі
ЧергиRedis + HorizonНа вузлі — сигнали, синк, retention; на хабі — діаризація, звірка, пуші. Ретраї і спостережуваність із коробки
Сховище аудіоЛокальний диск вузла
через Laravel Storage
3-денне вікно робить об'єктне сховище зайвим: Полтава тримає ~50 ГБ. Абстракція Storage лишає перехід на S3-сумісне рішення конфігурацією
Діаризаціяpyannote.audio 3.1 + ECAPA
self-hosted, GPU хаба
MIT-ліцензія, без ліцензійних платежів. Кількість голосів + відбитки + звірка по області. Python-воркер, протокол задач через Redis. Транскрипція не виконується; за майбутньої потреби — self-hosted Whisper на цій самій карті
Сигнали рівня 0Python-воркер (CPU вузла)VAD, тиша, гучність, фінгерпринт — на 100 % записів локально, без залежності від хаба
Обласний обмінREST + підписані service-токени
поверх WireGuard
Маніфести, стріми аудіо, пуші результатів — усередині тунелів; жоден службовий порт не дивиться в інтернет
Периметрnginx; Cloudflare — на публічні входиPWA і панелі — за Cloudflare (WAF, Zero Trust на адмінки); службовий трафік — лише WireGuard
КонтейнеризаціяDocker ComposeОдин compose на вузол, один на хаб. Нове місто — скрипт + реєстрація на хабі
МоніторингPrometheus + Grafana + LokiНа хабі; метрики всіх вузлів через WireGuard. Черги, диски, канал, GPU, глибина черги аудиту
Бекапиpg_dump + WAL + restic
офсайт: Storage VPS HostPro
Бази вузлів і хаба → Storage VPS (250 ГБ HDD) по SSH. Аудіо не бекапиться — строк життя 3 доби

7 Обладнання: MVP і бойовий контур

7.1 MVP / пілот — два VPS у HostPro

Пілот розгортається у бойовій топології: вузол і хаб — окремі VPS, обмін через WireGuard. Об'єднання «все на одній машині» лишається тільки для локальної розробки.

VPSКонфігурація (орієнтир)Роль
node-pilot4 vCPU · 8 ГБ RAM · 100 ГБ NVMeМіський вузол пілотного міста: PWA, прийом інтерв'ю, сигнали рівня 0, гео. 20–50 інтерв'юерів, сотні інтерв'ю/день
hub-pilot8 vCPU · 16 ГБ RAM · 100 ГБ NVMeОбласний хаб: панель замовника, діаризація в CPU-режимі (на пілотних обсягах GPU не потрібна), звірка відбитків, аудит
backup-pilotStorage VPS · 250 ГБ HDDОфсайт-приймач бекапів баз обох VPS (restic по SSH). Гаряче аудіо сюди не виноситься — воно живе на NVMe вузла у 3-денному вікні

Що валідує пілот: капчур PWA (запис, Wake Lock, офлайн-синк, поведінка при дзвінках), точність діаризації і збігів відбитків на реальних вуличних записах UK/RU, пороги маркерів, воркфлоу аудиту, протокол вузол↔хаб, звіт нагляду за координатором. Перехід у бій — ті самі compose-файли на фізичних серверах, змінюється лише .env.

7.2 Бойовий контур

СерверКонфігураціяПримітки
Обласний хаб (фізичний)12–16 ядер · 64 ГБ ECC · GPU 16 ГБ (RTX 4060 Ti кл.) · 2 × 480 ГБ NVMe (дзеркало)Тягне ~20–25 тис. інтерв'ю/день; більший потік — друга карта. Споживання ~0,35 кВт: ДБЖ 1,5–2 кВА + зарядна станція ~1 кВт на довгі відключення. Звичайна кімната з кондиціонером
Вузол великого міста6–8 ядер · 32 ГБ · 2 × 480 ГБ NVMe (дзеркало)Фізичний сервер у місті або VPS — за наявністю площадки; ДБЖ обов'язковий
Вузол малого міста4 ядра · 16 ГБ · 2 × 240 ГБ NVMe (дзеркало)Мінімальна коробка або дешевий VPS
Резерв (офсайт)Storage VPS у HostPro: 250 ГБ HDDПриймач restic/rsync-бекапів баз усіх вузлів і хаба. HDD і слабкий CPU тут не вада — це холодний приймач дампів. За потреби масштабується до 6 ТБ
✓ Живлення й охолодження — побутового класу
Сумарне споживання хаба ~0,35 кВт, вузла ~0,1 кВт. Жодних генераторів і промислового охолодження: ДБЖ + зарядна станція закривають добові відключення. Діаризація — відкладена робота: при знеструмленні хаба черга чекає, вузли продовжують збір автономно

8 Потоки даних

8.1 Збір у місті

Потік 1 · пристрій інтерв'юера → міський вузол
01
PWA
згода-позначка → анкета + аудіо + гео старт/фініш; офлайн-черга; дзвінок = рестарт інтерв'ю
02
Cloudflare
WAF, TLS публічного входу
03
API вузла
валідація, радіус-чек, сесія-контроль, прийом аудіо на диск
04
Локально
сигнали рівня 0, гео, темп — попередній бал

8.2 Обласна перевірка

Потік 2 · закриття зміни → діаризація на хабі → результати у вузол
01
Зміна закрита
вузол шле маніфест: N інтерв'ю, метадані, сигнали
02
Хаб тягне аудіо
по одному, WireGuard, прямо в діаризацію
03
GPU-аналіз
голоси, відбитки, звірка по області; аудіо відкидається
04
Маркери + індекс
зведення в БД хаба, черга аудиту
05
Пуш у вузол
вердикти, індекси; старт 3-денного відліку

8.3 Аудит і облік

Потік 3 · від маркера до кількісного обліку «до оплати»
01
Черга аудиту
маркери області, пріоритет за підозрілістю
02
Аудитор
картка + стрім аудіо з вузла (вікно 3 доби)
03
Вердикт
прийнято / відхилено + апеляція
04
Вузол
статуси, індекс у кабінеті інтерв'юера
05
Період
скільки інтерв'ю до оплати; коригування заднім числом

9 Структура репозиторію

Монорепозиторій: два застосунки і спільні пакети. Протокол обласного обміну — окремий пакет із версіонуванням: вузли оновлюються не одночасно, хаб мусить розуміти N і N−1 версії.

fieldcontrol/
├── node/                       # застосунок міського вузла (Laravel)
│   ├── app/Domain/
│   │   ├── Assignment/         # завдання, райони (PostGIS), зміни
│   │   ├── Interview/          # інтерв'ю, відповіді, гео старт/фініш, згода
│   │   ├── Media/              # прийом аудіо, цілісність, retention 3 доби
│   │   ├── Signals/            # рівень 0 + темп + збої запису
│   │   ├── Sessions/           # одна активна сесія, пристрої, спроби передачі
│   │   ├── Screening/          # вимкнення аудіо-скринінгу, підстави, історія
│   │   ├── Sync/               # маніфести, віддача аудіо, прийом результатів
│   │   ├── Payable/            # кількісний облік «до оплати», періоди, коригування
│   │   └── Access/             # ролі, журнал доступу
│   ├── app/Filament/           # панель координатора міста
│   └── database/migrations/
│
├── hub/                        # застосунок обласного хаба (Laravel)
│   ├── app/Domain/
│   │   ├── Fleet/              # реєстр вузлів, ключі, версії протоколу
│   │   ├── Ingest/             # маніфести, черга забирання аудіо
│   │   ├── Voice/              # діаризація, відбитки, pgvector-звірка
│   │   ├── Scoring/            # сигнали, ваги, маркери, індекс доброчесності
│   │   ├── Audit/              # черга, вердикти, апеляції
│   │   ├── Oversight/          # нагляд за координаторами міст, звіти скринінгу
│   │   └── Analytics/          # зведення по області, дашборди
│   ├── app/Filament/           # панель замовника
│   └── database/migrations/
│
├── pwa/                        # Vue 3 + Vite — кабінет інтерв'юера
│   └── src/  capture/  offline/  views/  api/
│
├── workers/                    # Python
│   ├── diarization/            # pyannote + ECAPA — GPU хаба
│   ├── signals/                # рівень 0 — CPU вузла
│   └── shared/                 # протокол задач через Redis
│
├── packages/
│   └── protocol/               # спільний PHP-пакет обласного обміну: DTO, підписи, версії
│
├── deploy/
│   ├── compose/  node.yml  hub.yml  dev.yml   # dev = усе на одній машині
│   ├── env/                    # .env.example: node, hub, dev
│   ├── wireguard/  nginx/  monitoring/
│   └── bootstrap/              # provision-node.sh — нове місто за годину
│
└── docs/   СЗ (архітектура) · ТЗ з додатками · runbook.md · макети

10 Модель даних — опорні рішення

РішенняОбґрунтування
Зміна — окрема сутністьЗакриття зміни — головна подія обміну: формує маніфест, запускає перевірку. Пов'язує інтерв'юера, завдання, інтерв'ю і часові межі
Версіонування анкетАнкета правиться під час хвилі; відповіді прив'язані до версії, інакше вивантаження перестають сходитися
Сигнали — окрема таблицяСклад сигналів (розд. 4.4) зростатиме; кожен зберігає значення, вагу і внесок — індекс має бути пояснюваним
Сесії і пристрої — реєстрОдна активна сесія на акаунт; кожна спроба паралельного входу — подія з гео і пристроєм. Живить сигнали передачі акаунта
Скринінг — стан з історієюВимкнення/увімкнення аудіо: хто, коли, кому, на якій підставі. Джерело звіту нагляду за координаторами (Д-12)
Відбитки — вектори з TTLpgvector на хабі; необоротні, TTL 90 днів (Д-11), індекси за інтерв'юером, містом, областю
Статуси синхронізаціїКожне інтерв'ю знає стан: прийнято вузлом → у маніфесті → діаризовано → маркери → вердикт → закрито. Розриви каналу не гублять записи
Облік «до оплати»: незмінні періодиЗакритий період не переписується; пізні відхилення — коригувальний рядок у наступному. Лише кількість, без сум
Журнал доступу до записівХто, коли, який запис слухав чи завантажив — включно з користувачами замовника на хабі. Вимога закону про захист персональних даних

11 Оточення

ОточенняДеСклад і відмінності
devмашина розробникаdev.yml: вузол + хаб + Postgres + Redis у контейнерах на одній машині. Діаризація — мок або CPU на фікстурах. Дані синтетичні
пілот (MVP)два VPS у HostPronode.yml + hub.yml — бойова топологія в мініатюрі, WireGuard між VPS, діаризація CPU. Реальне місто, 20–50 інтерв'юерів
prodхаб + вузли по містахТі самі compose-файли на фізичних серверах; секрети поза репозиторієм, адмінки за Cloudflare Access

Різниця між оточеннями — лише у змінних оточення. Жодних if (env === 'production') у коді: розгортання нового міського вузла — процедура, а не проєкт.

12 Порядок розгортання

12.1 Локальна розробка

git clone <repo> fieldcontrol && cd fieldcontrol
cp deploy/env/.env.dev.example .env
docker compose -f deploy/compose/dev.yml up -d
docker compose exec node php artisan migrate --seed
docker compose exec hub  php artisan migrate --seed
cd pwa && npm install && npm run dev
# діаризація в dev: DIAR_DEVICE=cpu або DIAR_DRIVER=fake

12.2 Пілот на HostPro і бойовий контур

Крок 1 · Хаб
hub-pilot (VPS) або фізичний сервер: ОС, hub.yml, міграції, панель замовника, моніторинг, бекапи. На фізичному — GPU-драйвери
Крок 2 · WireGuard
Мережа області: хаб — центр, вузли — піри. Ключі генеруються при реєстрації вузла
Крок 3 · Перший вузол
provision-node.sh: ОС, node.yml, міграції, реєстрація на хабі, Cloudflare на публічний вхід
Крок 4 · Калібрування
Синтетика в обсязі добового піку міста + реальні записи UK/RU: пороги діаризації, збігів відбитків, темпу
Крок 5 · Приймання
Навчання: відключення живлення, розрив каналу вузол↔хаб (збір триває, черга наздоганяє), відновлення БД із бекапа
Далі · Нові міста
Повторення кроку 3. Ціль: нове місто — за годину роботи інженера

13 Ризики і як їх закрито

РизикЗакриття
Хаб недоступнийВузли автономні: збір, сигнали, гео — локальні. Маніфести й аудіо чекають, діаризація наздоганяє. 3-денний відлік стартує від діаризації — вікно аудиту не згорає
Вузол недоступний для хабаРетраї із затримкою; решта міст не блокується. Пристрої інтерв'юерів тримають офлайн-чергу і досилають
Аудитори не встигли за 3 добиФлаговані записи притримуються до вердикта (Д-10); місту, що систематично не встигає, — маркер координатору, який бачить хаб. Глибина черги — ключовий алерт
Змова координатора з інтерв'юерамиВимкнення скринінгу видно хабу зі статистичною підставою (Д-12); вимкнення «без статистики» — маркер підозри. Контрольні повторні увімкнення скринінгу вибірково
Передача/розмноження акаунтаОдна активна сесія; паралельний вхід — блок і фіксація; паралельні інтерв'ю з різних точок — жорсткий маркер в індекс
Хибні спрацювання діаризації
вуличний шум, телевізор
Маркер — сигнал і пріоритет черги, не автосанкція. Пороги калібруються на пілоті; випадкова добавка у вибірку аудиту дає базову лінію
Замало мовлення респондента у префіксіАдаптивне вікно до ~10 с чистої речі другого голосу, стеля 90 с; інакше — маркер «другий голос не підтверджено»
Обробка біометріїНеоборотні вектори, TTL 90 днів (Д-11), пункт у згоді, все в контурі замовника
Обмеження веб-клієнта
без натива — свідомо (Д-13)
Дзвінок/згортання → чесний рестарт інтерв'ю + лічильник збоїв (аномалії — сигнал). Тимчасові дані в localStorage переживають перезавантаження вкладки. GPS-спуфінг у браузері компенсується перехресними сигналами: темп, кластеризація, сесії, діаризація
Розсинхрон версій протоколуПакет protocol версіонується; хаб приймає N і N−1. Реєстр вузлів знає версію кожного міста

14 Реєстр рішень (затверджено)

ПитанняРішення
Д-1Платформа ядраLaravel для обох застосунків; від Otack CMS — візуальний інтерфейс адмінок
Д-2Технологія адмінокГібрид: Filament + Vue-сторінки (картка аудиту, дашборд області, карта завдань, конструктор анкет)
Д-3Сховище аудіо✅ Локальний диск вузла через Laravel Storage
Д-4Периметр✅ Cloudflare на публічні входи; службовий обмін — лише WireGuard
Д-5Фото і відео✅ В анкетах — ні. Фото лише в профілі інтерв'юера
Д-6Мова✅ Уся документація, ТЗ та інтерфейси — українською
Д-7Середня ланка✅ Координатор міста — роль панелі вузла; нагляд за координаторами — на хабі
Д-8ТранскрипціяНе робимо. За майбутньої потреби — self-hosted Whisper на GPU хаба, без хмарних сервісів
Д-9Покриття перевіркою голосів✅ 100 % інтерв'ю, self-hosted діаризація на GPU хаба
Д-10Флаговані записи і відстаючі міста✅ Флаговані не видаляються до вердикта; місту, що не справляється, — маркер координатору, видимий хабу
Д-11TTL голосових відбитків✅ 90 днів
Д-12Вимкнення аудіо-скринінгу довіреним✅ Керує координатор міста; хаб бачить звіт із підставами, контрольні вибірки, маркер «підозра на змову»
Д-13Мобільний застосунокВідмова свідома. Лише веб-PWA; переваги натива компенсуються localStorage, рестартом інтерв'ю при дзвінку і статистикою збоїв
OTACK · розробка цифрових продуктів
Рішення Д-1…Д-13 затверджено. Документ — основа для ТЗ і макетів.
Наступний крок:
технічне завдання · макети обох панелей