Документ фіксує технічні рішення щодо проєкту до початку написання ТЗ і коду: що ми використовуємо, чому саме це, які навантаження очікуються і як розгортається оточення. Він передує технічному завданню — ТЗ описує, що система робить, ця записка описує, на чому вона зроблена і як живе.
Ключові рішення проєкту зведені в реєстрі Д-1…Д-13 (розділ 14).
Система складається з двох вебзастосунків:
| Параметр | Рішення |
|---|---|
| Призначення | Платформа польового збору даних із контролем доброчесності інтерв'юерів: очні опитування із записом аудіо та геолокації, автоматична перевірка голосів, вибірковий людський аудит, індекс доброчесності |
| Модель | Обласна мережа: міські вузли (збір) + обласний хаб (аналіз, діаризація, аудит, контроль координаторів). Два вебзастосунки, спільний протокол обміну |
| Розміщення | 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/сесії браузера; вхідний дзвінок або згортання перериває запис — інтерв'ю починається заново, кількість збоїв фіксується |
| Облік «до оплати» | Лише кількісний: скільки інтерв'ю і за який період підлягають оплаті (прийняті мінус відхилені, з коригуванням заднім числом). Без ставок, сум і виплат |
| Показник | Міський вузол (Полтава) | Обласний хаб | Коментар |
|---|---|---|---|
| Інтерв'юерів | 400 | 700 | по 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 млн рядків/рік на вузлі |
| Процес | Тригер і час | Обсяг (Полтава) | Що відбувається і навіщо |
|---|---|---|---|
| Прийом інтерв'ю | синхронізація 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 доби |
| Процес | Тригер і час | Обсяг (область) | Що відбувається і навіщо |
|---|---|---|---|
| Прийом маніфестів | пуші вузлів; протягом дня | ~700/день | Реєстрація пакетів до перевірки, черга діаризації з пріоритетом за попередніми сигналами вузла |
| Діаризація | черга; рівномірно | 8 600 записів ~5 год GPU | Початкові 30–90 с (адаптивно — до ~10 с чистої речі другого голосу): скільки голосів; другий ≠ інтерв'юер; відбиток другого голосу. Аудіо не зберігається. Повний файл — лише для флагованих |
| Звірка відбитків | після діаризації | 8 600 порівнянь | Відбиток «респондента» проти: відбитка інтерв'юера (чи не грає обох), його «респондентів» за 30 днів (запис по колу знайомих), бази області (та сама група відповідає різним інтерв'юерам). pgvector, мілісекунди |
| Маркери і індекс | після звірки | 8 600 розрахунків | Сигнали вузла + сигнали голосу зводяться за вагами в бал інтерв'ю та індекс доброчесності інтерв'юера (склад — розд. 4.4). Розбір зберігається — індекс пояснюваний |
| Черга аудиту | робочі години | вибірка з маркерами | Аудитори замовника: картка з маркерами, сигналами, картою; аудіо — стрімом із вузла (вікно 3 доби). Вердикт → перерахунок індексу → пуш у вузол |
| Нагляд за координаторами | щоденний звіт + маркери | 10–20 міст | Хаб бачить дії координаторів: кому і на якій підставі вимкнули аудіо-скринінг (Д-12), як швидко місто розбирає чергу, аномалії вердиктів. Вимкнення скринінгу без статистики або з поганою статистикою — маркер «підозра на змову». Місто, що не справляється, — маркер координатору (Д-10), щоб хаб міг втрутитися |
| Пуш результатів | подія «вердикт/маркер» | ~8 600/день | Вердикти, маркери, рейтинги — назад у вузли. Ретраї при недоступності вузла |
| Аналітика | запити панелі + нічні агрегати | дашборди області | Зведення по містах, динаміка індексів, топ підозрілих, статистика скринінгу. Матеріалізовані представлення |
| Бекап | щоночі + WAL | дамп БД + відбитки | Рейтинги, вердикти, відбитки, журнали. Офсайт-копія обов'язкова |
| Група | Сигнали | Що ловить |
|---|---|---|
| Геолокація | Старт і фініш інтерв'ю в радіусі завдання; час між стартом і фінішем; швидкість переміщення між сусідніми інтерв'ю; кластеризація точок | Роботу поза районом; інтерв'ю «з дивана»; фізично неможливі переміщення; серію анкет з однієї лавки |
| Темп роботи | Інтерв'ю за годину та за зміну проти порогів | 10–20 за годину — фабрикація анкет (маркер шахрайства); 1–2 за день — неефективність (показник продуктивності, не звинувачення) |
| Діаризація | Кількість голосів у префіксі; другий голос ≠ голос інтерв'юера; повторюваність «респондента» за 30 днів у розрізі інтерв'юера, міста, області | Самозапис «на два голоси»; запис по колу знайомих; групу, що «відповідає» різним інтерв'юерам |
| Дешеві аудіо-сигнали | Тривалість, частка тиші, VAD-частка мовлення, гучність, кліпінг, аудіо-фінгерпринт | Пусті й «тихі» записи; той самий файл, зданий двічі; запис із телевізора |
| Акаунт і сесії | Одна активна сесія; спроби паралельного входу; вхід із нової точки/пристрою; паралельні інтерв'ю з різних точок | Передачу акаунта («розмноження» одного логіна на бригаду); роботу під чужим обліковим записом |
| Стабільність запису | Кількість перерваних записів (дзвінки, згортання), частка незавершених інтерв'ю | Статистика збоїв PWA; аномальна частота переривань в одного інтерв'юера — сигнал маніпуляції записом |
| Вердикти аудиту | Частка відхилених, історія апеляцій | Підсумковий людський фактор поверх автоматики |
Ваги і пороги — налаштовувані, зберігаються з історією змін. Кожне значення індексу має розбір «з чого склалося» — індекс пояснюваний і для координатора, і для інтерв'юера в його кабінеті.
| Показник | Значення | Розрахунок |
|---|---|---|
| Аудіо на вузлі, норма | ~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): два однакові диски, корисний обсяг одного, автоматичне переживання відмови будь-якого з них.
| Шар | Вибір | Чому саме так |
|---|---|---|
| Обидва застосунки | 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 на цій самій карті |
| Сигнали рівня 0 | Python-воркер (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 доби |
Пілот розгортається у бойовій топології: вузол і хаб — окремі VPS, обмін через WireGuard. Об'єднання «все на одній машині» лишається тільки для локальної розробки.
| VPS | Конфігурація (орієнтир) | Роль |
|---|---|---|
| node-pilot | 4 vCPU · 8 ГБ RAM · 100 ГБ NVMe | Міський вузол пілотного міста: PWA, прийом інтерв'ю, сигнали рівня 0, гео. 20–50 інтерв'юерів, сотні інтерв'ю/день |
| hub-pilot | 8 vCPU · 16 ГБ RAM · 100 ГБ NVMe | Обласний хаб: панель замовника, діаризація в CPU-режимі (на пілотних обсягах GPU не потрібна), звірка відбитків, аудит |
| backup-pilot | Storage VPS · 250 ГБ HDD | Офсайт-приймач бекапів баз обох VPS (restic по SSH). Гаряче аудіо сюди не виноситься — воно живе на NVMe вузла у 3-денному вікні |
Що валідує пілот: капчур PWA (запис, Wake Lock, офлайн-синк, поведінка при дзвінках), точність діаризації і збігів відбитків на реальних вуличних записах UK/RU, пороги маркерів, воркфлоу аудиту, протокол вузол↔хаб, звіт нагляду за координатором. Перехід у бій — ті самі compose-файли на фізичних серверах, змінюється лише .env.
| Сервер | Конфігурація | Примітки |
|---|---|---|
| Обласний хаб (фізичний) | 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 ТБ |
Монорепозиторій: два застосунки і спільні пакети. Протокол обласного обміну — окремий пакет із версіонуванням: вузли оновлюються не одночасно, хаб мусить розуміти 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 · макети
| Рішення | Обґрунтування |
|---|---|
| Зміна — окрема сутність | Закриття зміни — головна подія обміну: формує маніфест, запускає перевірку. Пов'язує інтерв'юера, завдання, інтерв'ю і часові межі |
| Версіонування анкет | Анкета правиться під час хвилі; відповіді прив'язані до версії, інакше вивантаження перестають сходитися |
| Сигнали — окрема таблиця | Склад сигналів (розд. 4.4) зростатиме; кожен зберігає значення, вагу і внесок — індекс має бути пояснюваним |
| Сесії і пристрої — реєстр | Одна активна сесія на акаунт; кожна спроба паралельного входу — подія з гео і пристроєм. Живить сигнали передачі акаунта |
| Скринінг — стан з історією | Вимкнення/увімкнення аудіо: хто, коли, кому, на якій підставі. Джерело звіту нагляду за координаторами (Д-12) |
| Відбитки — вектори з TTL | pgvector на хабі; необоротні, TTL 90 днів (Д-11), індекси за інтерв'юером, містом, областю |
| Статуси синхронізації | Кожне інтерв'ю знає стан: прийнято вузлом → у маніфесті → діаризовано → маркери → вердикт → закрито. Розриви каналу не гублять записи |
| Облік «до оплати»: незмінні періоди | Закритий період не переписується; пізні відхилення — коригувальний рядок у наступному. Лише кількість, без сум |
| Журнал доступу до записів | Хто, коли, який запис слухав чи завантажив — включно з користувачами замовника на хабі. Вимога закону про захист персональних даних |
| Оточення | Де | Склад і відмінності |
|---|---|---|
| dev | машина розробника | dev.yml: вузол + хаб + Postgres + Redis у контейнерах на одній машині. Діаризація — мок або CPU на фікстурах. Дані синтетичні |
| пілот (MVP) | два VPS у HostPro | node.yml + hub.yml — бойова топологія в мініатюрі, WireGuard між VPS, діаризація CPU. Реальне місто, 20–50 інтерв'юерів |
| prod | хаб + вузли по містах | Ті самі compose-файли на фізичних серверах; секрети поза репозиторієм, адмінки за Cloudflare Access |
Різниця між оточеннями — лише у змінних оточення. Жодних if (env === 'production') у коді: розгортання нового міського вузла — процедура, а не проєкт.
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
hub.yml, міграції, панель замовника, моніторинг, бекапи. На фізичному — GPU-драйвериprovision-node.sh: ОС, node.yml, міграції, реєстрація на хабі, Cloudflare на публічний вхід| Ризик | Закриття |
|---|---|
| Хаб недоступний | Вузли автономні: збір, сигнали, гео — локальні. Маніфести й аудіо чекають, діаризація наздоганяє. 3-денний відлік стартує від діаризації — вікно аудиту не згорає |
| Вузол недоступний для хаба | Ретраї із затримкою; решта міст не блокується. Пристрої інтерв'юерів тримають офлайн-чергу і досилають |
| Аудитори не встигли за 3 доби | Флаговані записи притримуються до вердикта (Д-10); місту, що систематично не встигає, — маркер координатору, який бачить хаб. Глибина черги — ключовий алерт |
| Змова координатора з інтерв'юерами | Вимкнення скринінгу видно хабу зі статистичною підставою (Д-12); вимкнення «без статистики» — маркер підозри. Контрольні повторні увімкнення скринінгу вибірково |
| Передача/розмноження акаунта | Одна активна сесія; паралельний вхід — блок і фіксація; паралельні інтерв'ю з різних точок — жорсткий маркер в індекс |
| Хибні спрацювання діаризації вуличний шум, телевізор | Маркер — сигнал і пріоритет черги, не автосанкція. Пороги калібруються на пілоті; випадкова добавка у вибірку аудиту дає базову лінію |
| Замало мовлення респондента у префіксі | Адаптивне вікно до ~10 с чистої речі другого голосу, стеля 90 с; інакше — маркер «другий голос не підтверджено» |
| Обробка біометрії | Необоротні вектори, TTL 90 днів (Д-11), пункт у згоді, все в контурі замовника |
| Обмеження веб-клієнта без натива — свідомо (Д-13) | Дзвінок/згортання → чесний рестарт інтерв'ю + лічильник збоїв (аномалії — сигнал). Тимчасові дані в localStorage переживають перезавантаження вкладки. GPS-спуфінг у браузері компенсується перехресними сигналами: темп, кластеризація, сесії, діаризація |
| Розсинхрон версій протоколу | Пакет protocol версіонується; хаб приймає N і N−1. Реєстр вузлів знає версію кожного міста |
| № | Питання | Рішення |
|---|---|---|
| Д-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 | Флаговані записи і відстаючі міста | ✅ Флаговані не видаляються до вердикта; місту, що не справляється, — маркер координатору, видимий хабу |
| Д-11 | TTL голосових відбитків | ✅ 90 днів |
| Д-12 | Вимкнення аудіо-скринінгу довіреним | ✅ Керує координатор міста; хаб бачить звіт із підставами, контрольні вибірки, маркер «підозра на змову» |
| Д-13 | Мобільний застосунок | ✅ Відмова свідома. Лише веб-PWA; переваги натива компенсуються localStorage, рестартом інтерв'ю при дзвінку і статистикою збоїв |