Rebuilt the capture and mapping pipeline after an audit found the simulator's data could not be trusted: * Hotspot coordinates never matched the screenshots. Capture now scrolls the page over CDP and pastes each frame at the measured scrollY, so image pixels and DOM coordinates share one grid by construction. * Metrics were synthesised (1200 + n*410) and presented as analytics. Numbers are now attached only when the catalog has a matching row; metrics.json carries a `source` label and the UI says "no data" instead of showing zeros. * Event interception hooked a connector bridge that never fires. The app posts to api.amplitude.com using the legacy form-urlencoded v1 API; the hook now reads event_type off the wire. 36 keys are verified as `observed`. * All device access moved into tools/telecom_cdp.py: dynamic WebView socket discovery (the PID was hardcoded), id-matched CDP, measured native geometry. * Editor edits can now be saved to disk; API failures no longer report success from a stale result file; screenId is no longer interpolated into a shell. Screens went from 7 (with fabricated markup) to 32, all verified: image height equals map height, no out-of-bounds hotspots, no dead links. The id_card screenshot has been manually redacted - it showed a national ID. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
33 KiB
📡 TelecomKz Analytics Mapper & Visual Simulator
Архитектура, принципы работы и результаты аудита
Ревизия документа: 2026-08-24. Проверено на реальном устройстве Samsung SM-S938B (Android 16, 1080×2340, WebView Chrome 151), приложение
kz.telecom.app, WebViewhttps://customer.telecom.kz/.
📑 Содержание
- О проекте
- Архитектура
- Система координат — главное, что нужно понять
- Достоверность данных
- Функционал
- Результаты аудита: что было сломано и как исправлено
- Структура проекта
- Запуск и типовые сценарии
🎯 1. О проекте
Визуальный инструмент для сквозного маппинга UI-элементов мобильного приложения Казахтелеком на события Amplitude и таблицы ClickHouse: наведение на любую кнопку показывает ключ события и продуктовые метрики, а разметка снимается прямо с подключённого по USB смартфона.
Приложение гибридное: нативная оболочка (тулбар сверху, таб-бар снизу) плюс Vue.js-WebView в середине. Из этого следует всё остальное — и способ съёмки, и способ получения координат, и способ перехвата событий.
🏗️ 2. Архитектура
flowchart TD
subgraph Device ["📱 Android (ADB)"]
Native["Нативная оболочка: тулбар + таб-бар"]
WV["WebView (Vue, customer.telecom.kz)"]
end
subgraph Core ["⚙️ tools/telecom_cdp.py — общее ядро"]
ADB["ADB: screencap, uiautomator, input"]
Sock["Поиск сокета webview_devtools_remote_<pid>"]
CDP["CDP-клиент с сопоставлением id ответов"]
Geom["probe_native_layout(): реальные границы WebView и таб-бара"]
end
subgraph Tools ["🐍 Инструменты"]
Cap["capture_screen.py — сшивка по смещению прокрутки"]
Live["live_auto_recorder.py — пересборка хотспотов"]
Mon["monitor_live_events.py — перехват событий"]
Crawl["crawl_and_map_all.py — автообход"]
CH["fetch_clickhouse.py — метрики"]
end
subgraph Server ["🚀 Vite dev server"]
A1["/api/capture-adb"]
A2["/api/live-sync-webview"]
A3["/api/save-app-map"]
A4["/api/device-status"]
end
subgraph UI ["💻 React 19 + TypeScript"]
Sim["PhoneSimulator"]
Tip["HotspotTooltip"]
Ed["GuiEditor"]
end
Native --> ADB
WV --> CDP
Sock --> CDP
ADB --> Geom
Core --> Tools
Tools --> Server
Server --> UI
Стек. React 19 + TypeScript + Vite; Python 3.10+ (Pillow, OpenCV, websockets);
ADB + uiautomator; Chrome DevTools Protocol через adb forward.
Ключевое архитектурное решение аудита: весь доступ к устройству идёт через
единственный модуль tools/telecom_cdp.py. Раньше каждый скрипт заново реализовывал
подключение — и каждый воспроизводил одни и те же три ошибки (см. §6).
📐 3. Система координат — главное, что нужно понять
Все прямоугольники хотспотов живут в пиксельной сетке сшитого скриншота:
строки 0 .. webviewTop нативный тулбар
webviewTop .. +contentHeight полный контент WebView
последние navHeight строк нативный таб-бар
Элемент DOM со смещением в документе (left, top + scrollY) попадает в
x = round(left * scale)
y = webviewTop + round((top + scrollY) * scale), где scale = ширина экрана / window.innerWidth
На проверенном устройстве: webviewTop = 255, webviewBottom = 2139,
scale = 1080 / 384 = 2.8125. Но эти числа измеряются на каждом запуске через
uiautomator (границы android.webkit.WebView и kz.telecom.app:id/bottomNavigation),
а не задаются константами.
Почему сшивка сделана именно так
Скриншот снимается покадрово: страница прокручивается командой window.scrollTo
через CDP, после каждого шага реальное значение window.scrollY читается обратно,
и кадр вставляется на строку round(scrollY * scale). Совпадение координат
получается по построению, без поиска совпадений и без накопления ошибки.
Два отвергнутых варианта:
- Template matching (старая реализация). Высота результата непредсказуема и никак не связана с высотой документа, из которой считались координаты хотспотов. При неудачном совпадении код молча подставлял смещение 750 px.
Page.captureScreenshotсcaptureBeyondViewport. Проверено на живом приложении: карусель во второй строке не отрисовывается, вместо неё дублируется верх страницы. Изображение 1080×2531 выглядит правдоподобно, но нижняя треть не соответствует DOM.
🔍 4. Достоверность данных
Инструмент показывает числа аналитику, поэтому источник каждого числа обозначен явно.
4.1 Источник метрик — public/data/metrics.json, поле source
clickhouse— выгруженоtools/fetch_clickhouse.py;demo— демонстрационные значения, не аналитика.
Если ключа события нет в каталоге, у хотспота нет блока metrics, и интерфейс
пишет «Нет данных». Ноль в этом месте означал бы «кнопку не нажимают» — совсем другое
утверждение.
4.2 Достоверность ключа — поле keyConfidence
observed › dom-attribute › manual › rule › guessed.
Только observed означает, что ключ действительно снят с приложения.
Важно. Ключи вида PAYMENTS_CLICK, SERVICES_CLICK, MENUCLICKED в поставляемой
разметке имеют статус rule — это соглашение проекта. Реальные ключи, снятые с живого
приложения 2026-08-24, выглядят иначе:
POST https://api.amplitude.com/
POST https://mc.yandex.ru/watch/96490559/1?page-url=goal://customer.telecom.kz/HOMEPAGEPAYMENTS
POST https://mc.yandex.ru/watch/96490559/1?page-url=goal://customer.telecom.kz/OPENWINDOWPAYMENT
То есть HOMEPAGEPAYMENTS, а не PAYMENTS_CLICK. Перевести ключи в observed:
py tools/monitor_live_events.py --seconds 120 --map main_dashboard
🔑 4.3 Реальные ключи событий, снятые с приложения
36 ключей подтверждено на устройстве. Каждый снят с провода
(amplitude — тело запроса к api.amplitude.com), ни один не выведен из подписи.
| Экран | Элемент | Ключ |
|---|---|---|
main_dashboard |
AITU Music Слушайте без ограничений | aitu_music_banner_clicked |
main_dashboard |
NEW TURBO Увеличьте скорость интернета | turbo_widget_clicked |
main_dashboard |
TV+ Смотрите все новинки | HOMEADDSERVICETVPLUS |
main_dashboard |
Telecom Shop Покупайте выгодно | OPENINGWINDOWTELECOMSHOP |
main_dashboard |
Заявки | HOMETAPORDERS |
main_dashboard |
Лицевой счет 11440366 г.Алматы, прс.До | SELECTACCOUNT |
main_dashboard |
Меню (правый навбар) | notautho_main |
main_dashboard |
Мои услуги | HOMETAPMYSERVICES |
main_dashboard |
Платежи | HOMEPAGEPAYMENTS |
main_dashboard |
Подключить интернет Скидки и бонусы | fd_auth_tariffs_opened |
main_dashboard |
Свободные средства 16 484.62 ₸ | detalization_widget_open |
main_dashboard |
Трафик | HOMEDETALIZATION |
main_dashboard |
Удв. лич. | ID_CARD_HOME_CLICK |
orders_screen |
Создать заявку | ORDERSOPENSCREENCREATEORDER |
payments_screen |
История платежей | PAYMENTTAPPAYMENTSTORIE |
payments_screen |
Мой автоплатеж | PAYMENTSTAPMYAUTOPAYMENT |
services_screen |
Подробнее | click_details_mp |
side_menu |
Заявки | ORDERS |
side_menu |
Мои услуги | MYSERVICES |
side_menu |
Оферта | IMPORTANTDOCUMENTS |
side_menu |
Платежи | PAYMENTS |
side_menu |
Помощь | HELP |
side_menu |
Профиль телеком | PROFILETELECOM |
side_menu |
Трафик | DETALIZATION |
tariff_bereket |
Подробнее о скидке | fd_auth_bereket_constructor_opened |
tariff_internet_200 |
Подробнее о скидке | fd_auth_internet200_constructor_opened |
tariff_internet_500 |
Подробнее о скидке | fd_auth_internet500_constructor_opened |
tariff_keremet_mobile |
Подробнее о скидке | fd_auth_keremet_mobile_constructor_opened |
tariff_keremet_tv |
Подробнее о скидке | fd_auth_keremet_tv_constructor_opened |
tariffs_full_digital |
Подробнее | constructor_opened |
tariffs_full_digital |
Подробнее | constructor_opened |
tariffs_full_digital |
Подробнее | constructor_opened |
tariffs_full_digital |
Подробнее | constructor_opened |
tariffs_full_digital |
Подробнее | constructor_opened |
traffic_screen |
Домашний интернет | button_click_home_internet |
traffic_screen |
Мобильная связь | DETALIZATIONMOBILEINTERNET |
Что из этого важно
-
Ни один ключ не совпал с тем, что предлагало правило по подписи. «Трафик» — это
HOMEDETALIZATION, «Заявки» —HOMETAPORDERS, «Свободные средства» —detalization_widget_open. -
Один и тот же раздел из разных точек входа даёт разные ключи. Плитка на главном экране и строка бокового меню ведут на один маршрут, но событие разное:
Раздел Плитка на главной Строка бокового меню Платежи HOMEPAGEPAYMENTSPAYMENTSМои услуги HOMETAPMYSERVICESMYSERVICESТрафик HOMEDETALIZATIONDETALIZATIONЗаявки HOMETAPORDERSORDERSЭто ровно та разница, ради которой инструмент и делался: по одному ключу «переход в Платежи» нельзя понять, откуда пришёл пользователь.
-
Именование в приложении несогласованное:
HOMETAPORDERSрядом сturbo_widget_clicked,button_click_home_internetиclick_details_mp. -
Не инструментированы: «Сервисы», «QR», «Мои бонусы» — переходят на свой маршрут, но не отправляют события.
🗺️ 4.4 Инвентарь экранов
| Экран | Маршрут / поверхность | Зон | Подтверждено |
|---|---|---|---|
main_dashboard |
/ | 24 | 13 |
services_screen |
/services | 7 | 1 |
traffic_screen |
/detalization | 10 | 2 |
payments_screen |
/payments | 10 | 2 |
orders_screen |
/appeals | 7 | 1 |
music_screen |
native | 20 | 0 |
side_menu |
/ | 15 | 7 |
payments_auto |
/payments/auto | 7 | 0 |
payments_history |
/payments/history | 7 | 0 |
detalization_internet |
/detalization/internet | 7 | 0 |
detalization_mobile |
/detalization/mobile | 7 | 0 |
appeals_create |
/appeals/create-appeal | 13 | 0 |
service_details |
/ont/3608039 | 8 | 0 |
profile_settings |
/settings | 10 | 0 |
balance_detail |
/detalization/balance/current | 12 | 0 |
bonuses_screen |
/bonuses | 7 | 0 |
qr_scan |
/scan-qr | 6 | 0 |
id_card |
/digital-document/id-card | 8 | 0 |
services_catalog |
/extra-services | 12 | 0 |
tariffs_full_digital |
/full-digital | 16 | 5 |
tv_plus |
/tv-plus | 14 | 0 |
turbo_landing |
/turbo | 7 | 0 |
help_screen |
/help | 10 | 0 |
important_docs |
/important-docs | 9 | 0 |
aitu_music |
/aitu/music | 12 | 0 |
tariff_bereket |
/full-digital/auth/tariff | 10 | 1 |
connect_service |
/full-digital/auth/connect-service | 10 | 0 |
tariff_keremet_mobile |
/full-digital/auth/tariff | 10 | 1 |
tariff_keremet_tv |
/full-digital/auth/tariff | 10 | 1 |
tariff_internet_500 |
/full-digital/auth/tariff | 10 | 1 |
tariff_internet_200 |
/full-digital/auth/tariff | 10 | 1 |
account_selector |
/ | 11 | 0 |
Названия маршрутов не совпадают с подписями: «Трафик» → /detalization,
«Заявки» → /appeals, «Подключить интернет» → /full-digital,
«Удв. лич.» → /digital-document/id-card.
Три режима съёмки
- обычный — сшивка по прокрутке для WebView-страниц;
--no-scroll— один экран без прокрутки; нужен для оверлеев: боковое меню закрывается приwindow.scrollTo;--native— экран без WebView (Музыка): разметка изuiautomator.
Заметки по подэкранам
- Один ключ на пять кнопок — это не ошибка. Все пять «Подробнее» в каталоге
тарифов отправляют
constructor_opened; различить тариф можно только по следующему событию —fd_auth_<тариф>_constructor_openedу «Подробнее о скидке». - Экраны с задержкой отрисовки. «Удв. лич.» и каталог тарифов рисуют скелетон
и наполняют его через 4-5 секунд. Съёмка ждёт, пока страница перестанет меняться
(
--wait, по умолчанию 20 с); в разметке остаётся флагrenderSettled. - Выбор лицевого счёта — это нижняя шторка на маршруте
/, а не отдельный маршрут. Снимается с--no-scroll. - Повторяющиеся подписи. На экране может быть пять кнопок «Подробнее»; обход адресует их по порядковому номеру, иначе все пять кликают в первую.
/full-digital/auth/tariff— один маршрут для всех тарифов, поэтому каждый снят под своим id (tariff_bereket,tariff_keremet_mobile, ...).
Ограничения перехвата
- Нативные вкладки (Кабинет / TV+ / Музыка / Чаты / Бизнес), аватар и уведомления отправляют события из нативного SDK. Через WebView их не видно.
- Экран выбора лицевого счёта отправляет
SELECTACCOUNT, но затем требует пин-код — снять его скриншот автоматически нельзя. - Цепочки событий. Одно нажатие порождает несколько событий; при частых нажатиях позднее событие приписывается следующему элементу. Пауза 5+ секунд и повтор в двух прогонах.
⚡ 5. Функционал
- Симулятор — мокап смартфона с прокруткой, переходами и историей «Назад». Переходы на несуществующие экраны не «проглатываются»: такая зона не кликабельна.
- Тултипы метрик — ключ события, русское название, число событий, уникальные пользователи, события на пользователя, доля кликов, целевой экран; плюс пометки «демо-значения» и «ключ не проверен».
- Тепловая карта — нормировка только по измеренным зонам; зоны без метрик серые.
- Редактор разметки — координаты, ключ события с автодополнением из каталога,
целевой экран. Сохранение на диск через
/api/save-app-map(с резервной копией). - Съёмка экрана с телефона — полная прокрутка, нативная разметка из
uiautomator, DOM-элементы из CDP, обрезка выходящих за экран каруселей, дедупликация. - Перехват событий —
fetch,XMLHttpRequest,sendBeaconи (как запасной источник) analytics-connector; плюс цель Яндекс.Метрики как подтверждение. - Автообход — переход по разделам через DOM, съёмка каждого, запись пойманных ключей.
- Индикатор устройства — различает «телефон не подключен», «нет WebView» и «сервер не отвечает».
🛠️ 6. Результаты аудита: что было сломано и как исправлено
6.1 Координаты хотспотов не совпадали с картинкой
Разметка считалась в координатах документа WebView
(docHeight = scrollHeight * scale + 455), а картинка бралась либо одиночным
скриншотом 2340 px, либо непредсказуемой сшивкой. На момент аудита
main_dashboard имел totalHeight: 3886 при изображении высотой 2340: нижнее меню
и часть кнопок оказывались за пределами картинки и были недоступны.
Исправлено: детерминированная сшивка по прочитанному scrollY (§3). Проверено:
изображение 1080×2989, все 24 зоны совпадают с элементами.
6.2 live_auto_recorder.py игнорировал аргумент
Скрипт читал sys.argv, но __main__ вызывал
sync_current_screen_from_webview("main_dashboard") жёстко. Любой запрос
/api/live-sync-webview?screenId=… перезаписывал главный экран. Воспроизведено:
запрос для services_screen изменил main_dashboard.
Исправлено: аргументы через argparse, валидация screenId.
6.3 Жёстко зашитый PID сокета WebView
webview_devtools_remote_18732 в двух скриптах. PID меняется при каждом перезапуске
приложения.
Исправлено: find_webview_socket() — pidof с проверкой по /proc/net/unix.
6.4 Выдуманные метрики выдавались за аналитику
totalEvents: 1200 + len(hotspots) * 410 в трёх скриптах; metrics.json целиком
синтетический; fetch_clickhouse.py указывал на http://100.x.x.x:8123.
Исправлено: генерация чисел удалена; attach_metrics() либо ставит реальную строку
каталога, либо не ставит ничего; каталог помечается source; коннектор ClickHouse
переписан на серверные параметры ({name:Type}) вместо подстановки в SQL.
6.5 Перехват событий не работал
Хук ставился на analyticsConnectorInstances['$default_instance'].eventBridge.
Проверено: хук цепляется (две инстанции, обе с setEventReceiver), но не срабатывает
ни разу — приложение отправляет события напрямую HTTP-запросом на
api.amplitude.com.
Исправлено: tools/amplitude_hook.py оборачивает fetch, XMLHttpRequest и
sendBeacon, разбирает events[].event_type из payload Amplitude HTTP V2 и
дополнительно снимает цель Яндекс.Метрики. Именно так были получены реальные ключи
из §4.2.
6.6 API возвращал успех после неудачи
/api/capture-adb читал ${screenId}_result.json от предыдущего запуска и возвращал
его даже при падении Python-скрипта — интерфейс показывал «✅ обновлено» со старыми
данными.
Исправлено: решение принимается по JSON-вердикту скрипта; ошибка отдаётся с 502.
6.7 Подстановка screenId в командную строку
exec(\py tools/capture_screen.py ${screenId} …`)` — параметр запроса попадал в
шелл.
Исправлено: execFile с массивом аргументов плюс проверка ^[A-Za-z0-9_-]{1,64}$
на обеих сторонах.
6.8 Гонка записи
Middleware запускал capture_screen.py и live_auto_recorder.py одновременно, оба
писали telecomkz_app_map.json.
Исправлено: очередь на сервере, атомарная запись через временный файл.
6.9 Правки редактора нельзя было сохранить
Существовал только экспорт файла в «Загрузки»; всё, что редактировалось, терялось при перезагрузке, а Live-синхронизация раз в 2 секунды затирала форму.
Исправлено: эндпойнт /api/save-app-map с резервной копией, кнопка «Сохранить
разметку на диск», индикатор несохранённых изменений; Live-синхронизация
приостанавливается в режиме редактора и при несохранённых правках.
6.10 Ошибки CDP-протокола во всех скриптах
await ws.recv() после Page.enable / Network.enable считался ответом на команду,
хотя CDP присылает события вперемешку с ответами; целевая страница выбиралась как
pages[0] без проверки типа.
Исправлено: CdpSession.send() сопоставляет ответы по id и буферизует события;
pick_page_target() фильтрует по type == "page" и предпочитает customer.telecom.kz.
6.11 Найдено при доснятии остальных экранов
- Повторная съёмка стирала подтверждённые ключи.
capture()полностью заменяет хотспоты экрана, поэтому каждый пересъём терял всеobserved-ключи. Добавлен перенос доверенных ключей (observed/manual/dom-attribute) по подписи элемента:telecom_cdp._merge_trusted_keys(). - Оверлей дублировался на скриншоте. Боковое меню —
position: fixedповерх документа, который всё ещё сообщает о прокрутке:scrollYменялся, пиксели — нет, и второй кадр вклеивался ниже, повторяя интерфейс. Теперь кадры сравниваются, и сшивка останавливается, как только картинка перестаёт меняться. - Скрытые за оверлеем элементы попадали в разметку. Добавлена проверка
document.elementFromPointпо центру видимой части элемента. Для прокручиваемых экранов она применяется только к полностью видимым элементам — иначе карточки под «липким» баннером на нулевой прокрутке отбрасывались бы, хотя ниже по сшитому скриншоту они видны. - Боковое меню отсутствовало в разметке. Его строки — обычные
div.nav-linkбезroleиhref, ни один селектор их не покрывал. - Совпадающие ключи. «Мой автоплатеж» и «История платежей» оба содержат
«платеж» и получали один
PAYMENTS_CLICK. Ключи на экране теперь уникальны. - Блокировка экрана портила данные. Заблокированный телефон продолжает отвечать
на
screencapиuiautomator, поэтому съёмка молча записывала экран блокировки поверх настоящего. Добавлена проверкаrequire_awake(). - Обрыв CDP терял собранное. Переход на экран, заменяющий WebView, рвал сокет, и
ConnectionClosedErrorуходил мимо обработчика — прогон падал вместе с уже пойманными событиями. Теперь этоDeviceError, и монитор завершается штатно.
6.12 Прочее
- Замороженный процесс приложения не отвечает по сокету — добавлена проверка переднего плана и вывод приложения на экран перед подключением.
build_smart_hotspots()игнорировала снятый дампuiautomatorи возвращала захардкоженную разметку главного экрана для любогоscreenId. Теперь нативные элементы берутся из дампа поresource-id, включая активную вкладку (clickable="false"у выбранного таба).- Кириллические ключи (
CLICK_ЧАТЫ) заменены транслитерацией. - Элементы горизонтальных каруселей выходили за правый край (x + width = 1347 при ширине 1080) — теперь обрезаются по канве.
- Порядок правил классификации: «Подключить интернет Скидки и бонусы» попадал в
BONUSES_CLICK; правила переупорядочены от частных к общим. - Пути в скриптах резолвятся от корня проекта, а не от текущего каталога.
- 11 дублирующих скриптов перенесены в
tools/legacy/, на их местах — заглушки, перенаправляющие на рабочие реализации.
📂 7. Структура проекта
telecomkz_scraper/
├── public/
│ ├── assets/screens/ скриншоты + *_result.json
│ └── data/
│ ├── telecomkz_app_map.json экраны, зоны, связи
│ └── metrics.json каталог метрик с полем source
├── src/
│ ├── components/
│ │ ├── PhoneSimulator.tsx мокап, оверлеи, тепловая карта
│ │ ├── HotspotTooltip.tsx карточка метрик + пометки достоверности
│ │ ├── GuiEditor.tsx редактор зон
│ │ └── Sidebar.tsx экраны, режимы, съёмка, сохранение
│ ├── types/simulator.ts типы + KeyConfidence / MetricsSource
│ └── App.tsx контроллер
├── tools/
│ ├── telecom_cdp.py ядро: ADB, CDP, геометрия, app map
│ ├── amplitude_hook.py перехват событий в странице
│ ├── capture_screen.py съёмка + разметка
│ ├── live_auto_recorder.py пересборка хотспотов экрана
│ ├── monitor_live_events.py живой монитор событий
│ ├── crawl_and_map_all.py автообход
│ ├── fetch_clickhouse.py метрики
│ ├── reclassify_hotspots.py пересчёт ключей без телефона
│ ├── inspect_page.py диагностика WebView
│ ├── device_status.py статус устройства
│ └── legacy/ старые версии, не используются
├── vite.config.ts dev-сервер + API-мост
└── .env.example настройки ClickHouse
🚀 8. Запуск и типовые сценарии
npm install
py -m pip install opencv-python numpy pillow websockets requests
npm run dev # http://localhost:5173
Проверить связь с телефоном
py tools/device_status.py
Снять текущий экран
py tools/capture_screen.py main_dashboard --name "Главный экран (Кабинет)"
Узнать настоящие ключи событий (главный сценарий для аналитика)
py tools/monitor_live_events.py --seconds 120 --map main_dashboard
Подтянуть метрики
cp .env.example .env # указать CLICKHOUSE_HOST
py tools/fetch_clickhouse.py --days 30
py tools/reclassify_hotspots.py
Диагностика WebView
py tools/inspect_page.py amplitude bridge vue
Известные ограничения
- Если приложение уходит в фон, Android замораживает процесс и отладочный сокет перестаёт отвечать. Инструменты сами выводят приложение на передний план.
- Ключи событий в поставляемой разметке имеют статус
ruleи требуют подтверждения черезmonitor_live_events.py— до этого join с ClickHouse не даст результата. metrics.jsonв репозитории помеченsource: "demo".