chore: initial commit — recon artifacts + design spec + Phase 1 plan
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,305 @@
|
||||
# Driver App Recon — Design Doc
|
||||
|
||||
- **Дата:** 2026-05-11
|
||||
- **Статус:** Draft, ожидает review владельца
|
||||
- **Владелец:** vladtechno@gmail.com (PremiumPark)
|
||||
- **Родительский проект:** PremiumDriverApp
|
||||
- **Экосистема:** Premium CRM (TaxiDashboard)
|
||||
|
||||
---
|
||||
|
||||
## 1. Контекст и проблема
|
||||
|
||||
У PremiumPark есть Android-приложение для водителей проката, поставляемое подрядчиком 1С на условиях абонентской платы. Текущее приложение умеет:
|
||||
|
||||
- авторизовать водителя (логин/пароль),
|
||||
- показывать остаток баланса и список штрафов,
|
||||
- принимать платежи через приложение (детали неизвестны, выяснит разведка).
|
||||
|
||||
Все оплаты от водителей принимает юр.лицо PremiumPark. Подрядчик прекратил доработки приложения, оставаясь при этом получателем абонентской платы. Никаких функций эксплуатации авто (ТО, ремонты, фото повреждений, заявки в техслужбу) в приложении нет.
|
||||
|
||||
Стратегическая цель — построить собственное приложение, интегрированное с Premium CRM, и постепенно вытащить интеллектуальную собственность из 1С, сняв зависимость от подрядчика.
|
||||
|
||||
**Настоящий документ описывает только подпроект «Разведка»** — фазу изучения существующего приложения, предшествующую любой разработке. Подпроект «Новое приложение» имеет отдельный дизайн-док, который пишется после завершения разведки.
|
||||
|
||||
## 2. Цели и не-цели
|
||||
|
||||
### Цели
|
||||
|
||||
1. Получить полную карту HTTP/HTTPS-эндпоинтов, к которым ходит существующее приложение.
|
||||
2. Документировать поток аутентификации и lifecycle сессии/токенов.
|
||||
3. Документировать платёжный поток end-to-end: провайдер, инициация, токенизация, фискализация (54-ФЗ), связь с балансом в 1С.
|
||||
4. Документировать механизм обновлений (poll / push / FCM) — как водитель узнаёт о новом штрафе.
|
||||
5. Идентифицировать защитные механизмы (TLS pinning, обфускация, root detection, request signing, Play Integrity).
|
||||
6. Принять решение go/no-go по подпроекту «Новое приложение» на основе фактов.
|
||||
|
||||
### Не-цели
|
||||
|
||||
- Не разрабатываем код нового приложения, BFF или платёжного канала.
|
||||
- Не модифицируем APK и не публикуем «свой клиент».
|
||||
- Не подключаем mitmproxy к боевому трафику реальных водителей — работаем только на тестовом аккаунте, заведённом в 1С специально для разведки.
|
||||
- Не передаём APK, дампы, decompiled-исходники или отчёты третьим лицам.
|
||||
- Не реализуем платёжный канал в этом подпроекте.
|
||||
|
||||
## 3. Юридическая рамка
|
||||
|
||||
- Декомпиляция выполняется на собственном устройстве/эмуляторе пользователя, с собственным тестовым аккаунтом, для целей совместимости и переноса данных — попадает под ст. 1280 ГК РФ (декомпилирование программы для ЭВМ для interoperability).
|
||||
- PremiumPark является плательщиком абонентской платы за продукт и владельцем данных (балансы, штрафы, платежи водителей).
|
||||
- Артефакты разведки хранятся локально, в `recon/artifacts/`, не публикуются в git (см. `.gitignore`).
|
||||
- В случае официального запроса от подрядчика — разведка приостанавливается, артефакты архивируются.
|
||||
|
||||
## 4. Технический стенд
|
||||
|
||||
### 4.1 Хост
|
||||
|
||||
- Windows 11 (рабочая машина владельца)
|
||||
- Python 3.10+ (для mitmproxy и frida-tools)
|
||||
- Android Studio с Emulator и Platform-tools (`adb`)
|
||||
- Свободное место: ≥30 GB (эмулятор + APK + дампы + декомпил)
|
||||
|
||||
### 4.2 Эмулятор Android
|
||||
|
||||
- **AVD:** Pixel 6 или Pixel 4
|
||||
- **System Image:** Google APIs (НЕ Google Play) — это критично, иначе `adb root` недоступен и не получится положить mitmproxy CA в системные сертификаты
|
||||
- **API Level:** 33 или 34
|
||||
- **Архитектура:** x86_64 (для совместимости с frida-server)
|
||||
|
||||
При обнаружении сильной анти-эмуляторной защиты допустимо переключение на физическое Android-устройство с Magisk; это указано как fallback в плане исполнения.
|
||||
|
||||
### 4.3 Инструменты
|
||||
|
||||
| Инструмент | Назначение | Установка |
|
||||
|---|---|---|
|
||||
| `jadx-gui` | Декомпиляция APK в Java | github.com/skylot/jadx/releases |
|
||||
| `apktool` | Распаковка манифеста и ресурсов | scoop install apktool |
|
||||
| `mitmproxy` | Перехват HTTPS-трафика | `pip install mitmproxy` |
|
||||
| `frida-tools` | Динамические хуки | `pip install frida-tools` |
|
||||
| `objection` | Frida-обёртка с готовыми bypass'ами | `pip install objection` |
|
||||
| `adb` | Управление эмулятором | Android SDK Platform-tools |
|
||||
|
||||
Конкретные команды установки и шаги настройки идут в план исполнения (writing-plans), не в спеку.
|
||||
|
||||
### 4.4 Файловая структура проекта
|
||||
|
||||
```
|
||||
PremiumDriverApp/
|
||||
├── README.md
|
||||
├── .gitignore # игнорирует recon/artifacts/
|
||||
├── docs/superpowers/specs/
|
||||
│ └── 2026-05-11-driver-app-recon-design.md # этот документ
|
||||
└── recon/
|
||||
├── artifacts/ # НЕ в git
|
||||
│ ├── original.apk # вытащенный APK
|
||||
│ ├── decompiled/ # вывод jadx
|
||||
│ ├── unpacked/ # вывод apktool
|
||||
│ └── flows/ # mitmproxy дампы (*.mitm)
|
||||
├── scripts/
|
||||
│ ├── frida-bypass-pinning.js # шаблон обхода TLS pinning
|
||||
│ ├── mitm-filter.py # фильтр mitmproxy для шумовых хостов
|
||||
│ └── setup-emulator.ps1 # подготовка AVD
|
||||
└── findings/
|
||||
├── 00-final-report.md # финальный отчёт + go/no-go
|
||||
├── 01-static-analysis.md
|
||||
├── 02-network-capture.md
|
||||
├── 03-auth-flow.md
|
||||
├── 04-payment-flow.md
|
||||
├── 05-realtime.md
|
||||
└── 06-defenses.md
|
||||
```
|
||||
|
||||
## 5. Этапы разведки
|
||||
|
||||
### Этап 1. Статический анализ APK
|
||||
|
||||
**Цель:** понять, что в принципе делает приложение, найти константы (URL, ключи провайдеров, package id), оценить уровень защиты.
|
||||
|
||||
**Действия:**
|
||||
|
||||
1. Получить APK: `adb shell pm path <package>` → `adb pull <path> recon/artifacts/original.apk`
|
||||
2. Декомпиляция: `jadx-gui recon/artifacts/original.apk` → `recon/artifacts/decompiled/`
|
||||
3. Распаковка манифеста: `apktool d original.apk -o recon/artifacts/unpacked/`
|
||||
4. Извлечь из manifest и кода:
|
||||
- `package` name, version, target SDK, min SDK
|
||||
- permissions (особенно `INTERNET`, `RECEIVE_SMS`, `ACCESS_FINE_LOCATION`, payment-related)
|
||||
- `network_security_config.xml` (cleartext policy, pinning declarations)
|
||||
- встроенные base-URL — grep по `https://`
|
||||
- сторонние SDK — поиск пакетов `ru.yoomoney`, `ru.tinkoff`, `com.google.firebase`, `okhttp3`, `retrofit2`
|
||||
5. Оценить обфускацию: имена классов читаемые → R8/ProGuard не агрессивный; одно-/двухбуквенные имена → агрессивный.
|
||||
6. Найти root/emulator detection: grep по `RootBeer`, `isDebuggerConnected`, `build.fingerprint`, `Build.PRODUCT`.
|
||||
|
||||
**Deliverable:** `recon/findings/01-static-analysis.md` со следующими разделами:
|
||||
- Package metadata
|
||||
- Permissions
|
||||
- Network config
|
||||
- Встроенные URL-ы и хосты
|
||||
- Сторонние SDK
|
||||
- Уровень обфускации
|
||||
- Защитные механизмы (предварительный список)
|
||||
|
||||
### Этап 2. Перехват сетевого трафика
|
||||
|
||||
**Цель:** получить полный лог HTTP/HTTPS-запросов в обычных пользовательских сценариях.
|
||||
|
||||
**Действия:**
|
||||
|
||||
1. Подготовить эмулятор:
|
||||
- `emulator -avd Pixel_6_API_34 -writable-system -no-snapshot`
|
||||
- `adb root` → доступ к /system
|
||||
- Установить mitmproxy CA в `/system/etc/security/cacerts/` (имя файла — hash сертификата + `.0`)
|
||||
2. Прописать прокси: `Settings → Network → WiFi → Modify → Proxy: manual → 10.0.2.2:8080`
|
||||
3. Запустить mitmproxy на хосте: `mitmweb --listen-port 8080`
|
||||
4. Установить приложение: `adb install recon/artifacts/original.apk`
|
||||
5. Прогнать сценарии (каждый — отдельным mitmproxy save):
|
||||
- **S1. Вход:** запуск приложения → ввод логина/пароля → попадание на главный экран
|
||||
- **S2. Просмотр баланса:** открытие экрана баланса, ожидание 60 сек (захватить полл, если есть)
|
||||
- **S3. Просмотр штрафов:** открытие списка штрафов, открытие одного штрафа
|
||||
- **S4. Экран платежа (без оплаты):** перейти к оплате, выбрать позицию, дойти до выбора способа оплаты, остановиться
|
||||
- **S5. Инициация платежа:** довести до момента подтверждения, **но не платить реальной картой**
|
||||
- **S6. Выход:** logout
|
||||
6. Сохранить flow'ы: `recon/artifacts/flows/S1-login.mitm`, `S2-balance.mitm`, …
|
||||
|
||||
**При TLS pinning:**
|
||||
- Запустить frida-server в эмуляторе.
|
||||
- `objection --gadget <package> explore` → `android sslpinning disable`
|
||||
- Если не помогает — кастомный Frida-скрипт (шаблон в `recon/scripts/frida-bypass-pinning.js`)
|
||||
|
||||
**Deliverable:** `recon/findings/02-network-capture.md` с таблицей всех увиденных эндпоинтов:
|
||||
|
||||
| Endpoint | Method | Auth | Сценарий | Запрос (схема) | Ответ (схема) | Частота |
|
||||
|---|---|---|---|---|---|---|
|
||||
|
||||
И ссылки на исходные mitm-файлы для воспроизведения.
|
||||
|
||||
### Этап 3. Глубокое погружение в auth
|
||||
|
||||
**Цель:** понять формат и lifecycle токена, наличие подписей.
|
||||
|
||||
**Действия:**
|
||||
|
||||
1. Распарсить токен (jwt.io если JWT) — header, payload, claims, alg, iss, exp.
|
||||
2. Проверить, есть ли request signing (HMAC, подпись тела) — искать в декомпиле использование `Mac.getInstance` / `Signature`.
|
||||
3. Сэмплировать сессии:
|
||||
- повторный логин → тот же токен или новый?
|
||||
- есть ли refresh-токен и отдельный refresh-эндпоинт?
|
||||
- что происходит при 401?
|
||||
4. Построить sequence diagram (Mermaid).
|
||||
|
||||
**Deliverable:** `recon/findings/03-auth-flow.md`
|
||||
|
||||
### Этап 4. Платёжная подсистема (центральный этап)
|
||||
|
||||
**Цель:** разобрать поток оплаты end-to-end, выявить провайдера и механизм фискализации.
|
||||
|
||||
**Гипотезы, которые проверяем:**
|
||||
|
||||
- H1. Приложение использует SDK провайдера (YooKassa / Tinkoff Acquiring / СберPay) и общается напрямую с провайдером после получения init-токена от 1С.
|
||||
- H2. Приложение прокидывает данные в 1С, а 1С общается с провайдером (приложение видит только наши эндпоинты).
|
||||
- H3. Гибрид: init через 1С → провайдер через SDK → колбэк в 1С.
|
||||
|
||||
**Действия:**
|
||||
|
||||
1. В декомпиле найти SDK провайдера (по package name).
|
||||
2. В трафике (этап 2, сценарий S5) изолировать запросы к провайдерским доменам vs к 1С.
|
||||
3. Проследить токенизацию: куда уходят данные карты — в провайдер напрямую или в 1С (если последнее — большой ред-флаг по PCI DSS).
|
||||
4. Понять чек 54-ФЗ:
|
||||
- кто формирует — 1С / провайдер / ОФД напрямую?
|
||||
- где водитель видит чек (push, email, экран приложения)?
|
||||
5. Корреляция платёж ↔ баланс:
|
||||
- до платежа `GET /balance` → X
|
||||
- инициация → колбэк → когда `GET /balance` возвращает X + delta? (сразу/асинхронно/после явного pull)
|
||||
6. Sequence diagram (Mermaid) с участниками: App, 1С, Provider, ОФД.
|
||||
|
||||
**Безопасность разведки:** не доводить до фактического списания. Останавливаемся на моменте, когда приложение запрашивает у провайдера init-токен и формирует webview/redirect — этого достаточно для понимания флоу.
|
||||
|
||||
**Deliverable:** `recon/findings/04-payment-flow.md`
|
||||
|
||||
### Этап 5. Push / realtime
|
||||
|
||||
**Цель:** понять, как водитель узнаёт о новом штрафе/начислении.
|
||||
|
||||
**Действия:**
|
||||
|
||||
- Проверить наличие FCM в манифесте (`com.google.firebase.MESSAGING_EVENT`).
|
||||
- Если FCM — посмотреть payload (требует регистрации в FCM или симуляции).
|
||||
- Если нет FCM — проверить наличие long-polling эндпоинта в трафике.
|
||||
- Если возможно — попросить ответственного администратора 1С создать тестовый штраф для тестового водителя и засечь, как именно эта инфа приходит.
|
||||
|
||||
**Deliverable:** `recon/findings/05-realtime.md`
|
||||
|
||||
### Этап 6. Защитные механизмы (фоном)
|
||||
|
||||
В течение всех этапов фиксировать в `recon/findings/06-defenses.md`:
|
||||
|
||||
- TLS pinning — есть / нет / способ обхода
|
||||
- Root/emulator detection — есть / нет / срабатывает ли на нашем стенде
|
||||
- Request signing / HMAC — есть / нет / откуда берётся ключ
|
||||
- Play Integrity API — есть / нет
|
||||
- Антидебаг и анти-Frida хуки
|
||||
|
||||
### Этап 7. Финальный отчёт
|
||||
|
||||
**Deliverable:** `recon/findings/00-final-report.md`. Структура:
|
||||
|
||||
1. **Executive Summary** (1 страница, для бизнес-обсуждения)
|
||||
2. **Полная карта API** (агрегированная таблица из этапа 2)
|
||||
3. **Auth-flow** (Mermaid + текст)
|
||||
4. **Payment-flow** (Mermaid + текст, ключевая часть)
|
||||
5. **Realtime** (Mermaid + текст)
|
||||
6. **Защитные механизмы и их преодоление**
|
||||
7. **Оценка трудозатрат на «Новое приложение»** (грубая, в неделях разработчика)
|
||||
8. **Рекомендация go/no-go**
|
||||
9. **Открытые риски и неизвестные**
|
||||
|
||||
## 6. Критерии go / no-go
|
||||
|
||||
### Go — двигаемся в подпроект «Новое приложение»
|
||||
|
||||
- API эндпоинты и аутентификация поняты, стабильны (нет признаков частой смены контракта).
|
||||
- Платёжный поток разобран, и видна возможность построить параллельный канал (свой эквайринг → свой backend → свой чек).
|
||||
- Защитные механизмы преодолимы статически (т.е. мы понимаем, как мимикрировать или обойти без runtime-вмешательства).
|
||||
- В трафике нет признаков hardware-bound криптографии, доступной только подрядчику.
|
||||
|
||||
### No-go — пересмотр стратегии
|
||||
|
||||
- API использует interactive challenge, который нельзя автоматизировать (CAPTCHA, манипуляция UI и пр.).
|
||||
- Платёжный поток жёстко зависит от 1С на стороне сервера, и параллельный канал требует доступа к серверным обработкам.
|
||||
- Используется криптография, привязанная к серверному секрету подрядчика (например, server-issued tokens с проверкой подписи приложения).
|
||||
- Уровень защиты несоразмерен ценности — реверс займёт больше, чем оригинальная разработка с нуля.
|
||||
|
||||
### В случае no-go — альтернативы
|
||||
|
||||
1. **Прямые переговоры с подрядчиком** о публикации API (на руках уже будет отчёт разведки — рычаг в диалоге).
|
||||
2. **Companion App** — приложение **только с новыми функциями** (блок B: эксплуатация авто), старое приложение остаётся для балансов/платежей. Реверс не нужен.
|
||||
3. **Полная замена 1С** — крупный проект, не на этом цикле.
|
||||
|
||||
## 7. Open questions (на разведку)
|
||||
|
||||
Заполняются по ходу. Начальный список:
|
||||
|
||||
- Package name приложения — будет известно из APK на этапе 1.
|
||||
- Платёжный провайдер — выясняется на этапе 4.
|
||||
- Частота поллинга баланса — выясняется на этапе 2.
|
||||
- Используется ли FCM — выясняется на этапе 5.
|
||||
- Уровень обфускации — выясняется на этапе 1.
|
||||
|
||||
## 8. Следующий шаг
|
||||
|
||||
После утверждения этой спеки владельцем:
|
||||
|
||||
1. Создаём подробный план исполнения через `writing-plans` skill — пошагово, с чёткими DoD по каждому этапу.
|
||||
2. Скрипты-заготовки в `recon/scripts/`: `setup-emulator.ps1`, `frida-bypass-pinning.js`, `mitm-filter.py`.
|
||||
3. README с инструкцией «как развернуть стенд на пустой машине».
|
||||
4. Запускаем этап 1 (статический анализ).
|
||||
|
||||
---
|
||||
|
||||
## Приложение A. Контактные точки с экосистемой Premium CRM
|
||||
|
||||
Этот подпроект ничего не пишет в боевую инфраструктуру (PremiumCRM, PostgreSQL, n8n, ASR-стек) и ничего не читает оттуда. Разведка изолирована — все артефакты локальны.
|
||||
|
||||
Контактные точки появятся **только** в подпроекте «Новое приложение»:
|
||||
|
||||
- FastAPI backend PremiumCRM получит новые роуты `/api/v1/driver/*`.
|
||||
- PostgreSQL получит новые таблицы (maintenance, repairs, tickets, photos, driver_external_ids).
|
||||
- Дейв (user_id=26) может стать author'ом авто-комментариев в новых сущностях.
|
||||
- Telegram-бот @p_park_bot — потенциальный канал доставки OTP при логине нового приложения (TBD на этапе дизайна нового приложения).
|
||||
@@ -0,0 +1,397 @@
|
||||
# Premium Механик — PWA Design Doc
|
||||
|
||||
- **Дата:** 2026-05-16
|
||||
- **Статус:** Draft, ожидает review владельца
|
||||
- **Владелец:** vladtechno@gmail.com (PremiumPark)
|
||||
- **Родительский проект:** PremiumDriverApp
|
||||
- **Экосистема:** Premium CRM (TaxiDashboard) на crm.pptaxi.ru
|
||||
- **Предшественник:** реверс-разведка приложения «Механик» NaughtySoft (см. `01-static-analysis.md` + перехват трафика)
|
||||
|
||||
---
|
||||
|
||||
## 1. Контекст и цель
|
||||
|
||||
Сотрудники-механики PremiumPark сейчас работают через приложение «Механик» вендора NaughtySoft (`com.naughtysoft.ttc`, Xamarin/.NET). Все данные осмотров уходят в 1С вендора, доступ к ним из PremiumCRM невозможен без прохода через вендорские API.
|
||||
|
||||
Через разведку (см. `02-network-capture.md`) подтверждён формат backend-API вендора (REST на `api.ttcontrol.naughtysoft.ru`). Эндпоинты осмотров (`/api/vehicletechinspection/byvehicle`, `/api/vehiclestate/history`, `/api/image/<id>`, `/api/inspectionvideo`) подтверждают, что **модель данных осмотров с координатами повреждений и фото уже существует и нам полностью видна**.
|
||||
|
||||
**Цель проекта:** построить **собственное** приложение «Premium Механик» (PWA), которое:
|
||||
|
||||
1. Полностью заменяет вендорское приложение для сотрудников-механиков.
|
||||
2. Хранит данные осмотров **в нашей PostgreSQL** в PremiumCRM, не отправляя их к вендору.
|
||||
3. Поддерживает основные кейсы: съёмка фото авто, обводка повреждений на фото, метки повреждений с координатами, сравнение с прошлым осмотром при приёмке.
|
||||
4. Развёртывается без RuStore / App Store — деплой = `git push`.
|
||||
|
||||
**Долгосрочная цель:** отвязать парк от 1С вендора, начав с самой ценной модели данных (осмотры с фото).
|
||||
|
||||
## 2. Цели и не-цели
|
||||
|
||||
### Цели
|
||||
|
||||
1. Делать осмотр авто: 4-8 фото по сторонам, метки и обводки повреждений на каждой фото.
|
||||
2. При приёмке машины обратно от водителя — показать прошлые повреждения для сравнения «было / стало».
|
||||
3. Все данные осмотра хранятся в PostgreSQL PremiumCRM (новая БД-схема), без участия вендора.
|
||||
4. Доступ для авторизованных сотрудников-механиков (через существующий auth PremiumCRM).
|
||||
5. Работает на Android-планшетах механиков, на iPad/iPhone, на десктопе — без раздельных сборок.
|
||||
|
||||
### Не-цели
|
||||
|
||||
- Не делаем приложение для водителя в рамках этого подпроекта (отдельная фаза).
|
||||
- Не интегрируем фактическую интеграцию обратно в 1С вендора (наоборот — уходим от неё).
|
||||
- Не делаем нативные APK / IPA в первой версии. Только PWA. Возможен Capacitor-fallback в фазе 3 при необходимости.
|
||||
- Не делаем offline-first в MVP (механик работает с WiFi в парке). Service Worker — задел на будущее.
|
||||
- Не делаем real-time чат / уведомления через push на iOS (сложно, неприоритетно).
|
||||
- Не импортируем существующие осмотры из вендора в первой версии (отдельная задача миграции данных).
|
||||
|
||||
## 3. Технологический стек
|
||||
|
||||
### Frontend
|
||||
|
||||
| Слой | Выбор | Обоснование |
|
||||
|---|---|---|
|
||||
| Build/Dev | **Vite + TypeScript** | Быстрая итерация, типизация. Стек, уже знакомый команде. |
|
||||
| UI Framework | **React 18** | Команда уже работает с React (SuperLanding на Astro + React islands). |
|
||||
| UI Library | **shadcn/ui + Tailwind CSS** | Совпадает с design preferences (cream palette, Inter only, без жёлтого, без emoji). |
|
||||
| State Client | **TanStack Query (React Query)** | Кэширование fetches, optimistic updates, retries. |
|
||||
| Local State | **Zustand** | Минималистичный store без boilerplate. |
|
||||
| Routing | **React Router 7** | Standard в React экосистеме. |
|
||||
| Forms | **React Hook Form + Zod** | Типизированная валидация, лёгкий контроль. |
|
||||
| Canvas/Annotation | **Konva.js + react-konva** | Лучший выбор для photo annotations: layers, touch, gestures, undo/redo. |
|
||||
| HTTP | **Native fetch + ky или axios** | TBD на старте. |
|
||||
| PWA | **vite-plugin-pwa** | Service Worker, web manifest, install prompt — из коробки. |
|
||||
| QR-сканер | **html5-qrcode** | Для сканирования госномера-стикера машины. |
|
||||
|
||||
### Backend
|
||||
|
||||
| Слой | Выбор |
|
||||
|---|---|
|
||||
| Расширение | Новый модуль `app/mechanic/` в FastAPI PremiumCRM |
|
||||
| Базовый URL | `/api/v1/mechanic/*` |
|
||||
| ORM | SQLAlchemy (используется в TaxiDashboard) |
|
||||
| Миграции | Alembic |
|
||||
| Auth | Существующий PremiumCRM (JWT cookie + bcrypt PIN) — переиспользуем |
|
||||
| Permissions | Существующая dept-based система permissions (см. memory: `project_taxidashboard_permissions`) |
|
||||
| Файловое хранилище | **MinIO** на 100.64.0.12 (контейнер `taxi-minio`). Bucket **`pp-inspections`** (уже создан). Endpoint `s3.pptaxi.ru` (HTTPS), admin UI `s3-admin.pptaxi.ru`. Креды в `/opt/sites/taxi-dashboard/minio.env`. |
|
||||
| S3 client | **`aiobotocore`** — переиспользуем существующую интеграцию из TaxiDashboard (она же используется для фото осмотров уже). |
|
||||
| Upload-схема | **Pre-signed POST** — фронтенд льёт фото напрямую в MinIO, минуя FastAPI. Backend выдаёт presigned URL + поля. |
|
||||
| Read-схема | Существующий nginx-proxy `/media/<key>` на VDS (уже работает для других фото TaxiDashboard) ИЛИ pre-signed GET с TTL 1 час. Решим на этапе implementation. |
|
||||
| Image-processing | Pillow для thumbnails и EXIF strip — запускается celery-task'ом после `confirm` upload'а. |
|
||||
| Дополнительно | **Только MinIO**, без локального дубля на FS (решение владельца). Расхождение с существующим dual-write-паттерном в TaxiDashboard оправдано тяжестью медиа осмотров (10-20 фото на осмотр) — забивать диск VDS дублями нецелесообразно. |
|
||||
|
||||
### Развёртывание
|
||||
|
||||
| Хост | Назначение |
|
||||
|---|---|
|
||||
| 100.64.0.12 (PremiumCRM VDS) | Backend + DB + статика фото |
|
||||
| Caddy | Reverse proxy + TLS + raw уролы для фото |
|
||||
| Поддомен | `mechanic.pptaxi.ru` либо `crm.pptaxi.ru/mechanic/` (TBD, см. open question) |
|
||||
|
||||
## 4. Модель данных
|
||||
|
||||
Новые таблицы в существующей `taxi_dashboard` PostgreSQL:
|
||||
|
||||
```sql
|
||||
-- Осмотр
|
||||
CREATE TABLE inspections (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
vehicle_id BIGINT NOT NULL REFERENCES vehicles(id),
|
||||
type VARCHAR(32) NOT NULL, -- 'handover'|'return'|'periodic'|'ad-hoc'
|
||||
performed_by INTEGER NOT NULL REFERENCES users(id),
|
||||
driver_id INTEGER REFERENCES users(id),
|
||||
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
finished_at TIMESTAMPTZ,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'in_progress', -- 'in_progress'|'completed'|'cancelled'
|
||||
notes TEXT,
|
||||
prev_inspection_id BIGINT REFERENCES inspections(id), -- ссылка на предыдущий осмотр того же авто
|
||||
meta JSONB DEFAULT '{}'::jsonb
|
||||
);
|
||||
|
||||
CREATE INDEX idx_inspections_vehicle_started ON inspections(vehicle_id, started_at DESC);
|
||||
|
||||
-- Фото осмотра
|
||||
CREATE TABLE inspection_photos (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
inspection_id BIGINT NOT NULL REFERENCES inspections(id) ON DELETE CASCADE,
|
||||
side VARCHAR(16) NOT NULL, -- 'front'|'rear'|'left'|'right'|'interior'|'vin'|'odometer'|'wheel-fl'|'wheel-fr'|'wheel-rl'|'wheel-rr'|'free'
|
||||
slot_index INT DEFAULT 0,
|
||||
storage_key TEXT NOT NULL, -- путь относительно /opt/premiumcrm/uploads/
|
||||
annotated_key TEXT, -- путь к фото с canvas-обводкой (опц.)
|
||||
thumb_key TEXT,
|
||||
width INT,
|
||||
height INT,
|
||||
bytes BIGINT,
|
||||
taken_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
display_order INT DEFAULT 0
|
||||
);
|
||||
|
||||
-- Метка повреждения (на фото и/или на схеме авто)
|
||||
CREATE TABLE damage_markers (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
inspection_id BIGINT NOT NULL REFERENCES inspections(id) ON DELETE CASCADE,
|
||||
photo_id BIGINT REFERENCES inspection_photos(id), -- NULL если только на схеме
|
||||
side VARCHAR(16), -- сторона авто
|
||||
x DECIMAL(6,4), -- normalized 0..1 в координатах фото или схемы
|
||||
y DECIMAL(6,4),
|
||||
polygon JSONB, -- опционально: координаты обведённого контура [{x,y}, ...]
|
||||
damage_type VARCHAR(32), -- 'scratch'|'dent'|'paint'|'crack'|'missing'|'rust'|'glass'
|
||||
severity VARCHAR(16), -- 'cosmetic'|'minor'|'moderate'|'severe'
|
||||
description TEXT,
|
||||
carried_over_from_id BIGINT REFERENCES damage_markers(id), -- если унаследовано от прошлого осмотра
|
||||
resolved BOOLEAN DEFAULT FALSE, -- было ли устранено
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_damage_markers_inspection ON damage_markers(inspection_id);
|
||||
```
|
||||
|
||||
**Ключевая идея для дифа «было/стало»:**
|
||||
|
||||
- При создании return-inspection backend копирует все `damage_markers` из `prev_inspection_id` со ссылкой `carried_over_from_id`. Механик может либо подтвердить «есть» (оставить), либо отметить «устранено» (`resolved=true`), либо добавить новые маркеры. На фронте отображается две группы: «было раньше» (полупрозрачные) + «новые» (яркие).
|
||||
|
||||
## 5. API контракт
|
||||
|
||||
Все endpoints под `/api/v1/mechanic/`. Auth — через существующий JWT cookie PremiumCRM. Permission gate: `mechanic:inspect` (новое право в матрице, добавляется в модуль permissions).
|
||||
|
||||
### Машины
|
||||
|
||||
```
|
||||
GET /vehicles?q=<гос-номер|VIN>&limit=20
|
||||
→ [{id, license_plate, vin, make, model, year, current_driver, last_inspection_at}]
|
||||
|
||||
GET /vehicles/{id}
|
||||
→ детали авто + список последних осмотров
|
||||
|
||||
GET /vehicles/{id}/last-inspection
|
||||
→ последний завершённый осмотр (для дифа при приёмке)
|
||||
```
|
||||
|
||||
### Осмотры
|
||||
|
||||
```
|
||||
POST /inspections
|
||||
body: {vehicle_id, type, driver_id?}
|
||||
→ {id, ...} создан в статусе in_progress; если type='return', копирует markers из prev
|
||||
|
||||
GET /inspections/{id}
|
||||
→ полная карточка с photos + markers + meta
|
||||
|
||||
PATCH /inspections/{id}
|
||||
body: {status?, notes?, finished_at?}
|
||||
|
||||
POST /inspections/{id}/photos/upload-url
|
||||
body: {side, slot_index, content_type, size}
|
||||
→ {photo_id, presigned_url, fields, storage_key, expires_at}
|
||||
# фронт льёт сам файл напрямую в MinIO через presigned_url
|
||||
|
||||
POST /photos/{photo_id}/confirm
|
||||
body: {actual_size?, width?, height?}
|
||||
→ {id, storage_key, thumb_key, status: 'processing'|'ready'}
|
||||
# после confirm backend ставит celery-task на thumbnail и EXIF strip
|
||||
|
||||
POST /inspections/{id}/markers
|
||||
body: {photo_id?, side, x, y, polygon?, damage_type, severity, description}
|
||||
→ {id, ...}
|
||||
|
||||
PATCH /markers/{id}
|
||||
body: {damage_type?, severity?, description?, polygon?, resolved?}
|
||||
|
||||
DELETE /markers/{id}
|
||||
|
||||
POST /photos/{id}/annotated/upload-url
|
||||
body: {content_type, size}
|
||||
→ {presigned_url, fields, storage_key, expires_at}
|
||||
# аналогично: фронт льёт annotated-версию напрямую в MinIO
|
||||
|
||||
POST /photos/{id}/annotated/confirm
|
||||
→ {id, annotated_key, status: 'ready'}
|
||||
```
|
||||
|
||||
### Чтение фото
|
||||
|
||||
```
|
||||
GET /photos/{id} → 302 Redirect на pre-signed S3 URL (TTL 1 час)
|
||||
GET /photos/{id}/thumb → 302 Redirect на pre-signed S3 URL thumbnail
|
||||
GET /photos/{id}/annotated → 302 Redirect на pre-signed S3 URL annotated
|
||||
```
|
||||
|
||||
### Меня (текущий механик)
|
||||
|
||||
```
|
||||
GET /me
|
||||
→ {id, name, dept, permissions: [...], avatar_url}
|
||||
```
|
||||
|
||||
## 6. UI экраны и поток
|
||||
|
||||
### Экран 1. Login
|
||||
PIN-вход в PremiumCRM (как уже работает в TaxiDashboard).
|
||||
|
||||
### Экран 2. Главная — выбор машины
|
||||
- Поле поиска по госномеру/VIN
|
||||
- Кнопка «Сканировать QR» (стикер на лобовом стекле машины с госномером)
|
||||
- Список последних 10 машин, с которыми работал этот механик
|
||||
- На каждой карточке: гос-номер, модель, водитель, дата последнего осмотра
|
||||
|
||||
### Экран 3. Карточка авто
|
||||
- Резюме (гос-номер, VIN, модель, цвет, год)
|
||||
- Текущий водитель (если в аренде)
|
||||
- **Кнопки:** «Передача водителю» (handover) | «Приёмка от водителя» (return) | «Плановый осмотр» (periodic) | «Свободный осмотр» (ad-hoc)
|
||||
- **Прошлые осмотры** — лента (превью фото + метки)
|
||||
|
||||
### Экран 4. Новый осмотр — список «слотов» фото
|
||||
Карточка-grid: 4 обязательных + 4 опциональных:
|
||||
- Перед, Зад, Левый, Правый (обязательные)
|
||||
- VIN, Одометр, Салон, Свободное фото
|
||||
- Каждый слот: пустой → камера UI; заполнен → миниатюра + индикатор количества меток
|
||||
- В режиме `return` каждая карточка слота показывает «было N меток, добавь новые / подтверди / отметь устранённые»
|
||||
|
||||
### Экран 5. Камера
|
||||
- Полноэкранный preview (через `getUserMedia()` если поддерживается, иначе `<input type="file" capture="environment">`)
|
||||
- Overlay-направляющая под текущий слот (силуэт стороны авто для guidance)
|
||||
- Снять → preview → принять/перенять
|
||||
|
||||
### Экран 6a. Vehicle Scheme (zone-навигация)
|
||||
|
||||
- Интерактивная SVG-схема авто с тремя проекциями: top-down (вид сверху), боковая (left/right), интерьер
|
||||
- Каждая зона (бампер, капот, дверь, крыша, диск, лобовое и т.д.) — кликабельный hot-spot SVG `<path>`
|
||||
- При тапе на зону → переход в Экран 5 (Камера) с предзаполненным `side`/`slot_index`
|
||||
- Поверх схемы — полупрозрачные метки прошлых повреждений (для return-режима): красные точки на тех зонах, где были повреждения
|
||||
- Прогресс осмотра: подсветка зон, у которых уже есть фото (зелёный outline)
|
||||
|
||||
### Экран 6b. Annotation Editor (главная фишка)
|
||||
- Фото на весь экран, поверх — Konva canvas
|
||||
- Tool panel:
|
||||
- 🔘 **Маркер** — тап создаёт точку-метку
|
||||
- ✏️ **Обводка** — рисуем замкнутый контур (полигон) вокруг повреждения
|
||||
- ↩️ Undo / 🗑️ Erase
|
||||
- 🎨 Тип/severity выбор (4 типа × 4 severity)
|
||||
- При тапе на маркер — modal с типом, severity, описанием
|
||||
- В `return`-режиме старые маркеры показываются полупрозрачными; тап → «есть/устранено»
|
||||
|
||||
### Экран 7. Обзор осмотра
|
||||
- Карточка-grid всех фото + список всех маркеров + общие notes
|
||||
- Кнопка «Завершить осмотр» → finished_at, status=completed
|
||||
|
||||
### Экран 8. Подтверждение (опц.)
|
||||
- Подпись водителя (canvas) — опционально
|
||||
- Сохранение, переход на главную
|
||||
|
||||
## 7. Архитектура frontend
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/
|
||||
│ ├── client.ts # настройка fetch/axios + auth interceptor
|
||||
│ ├── inspections.ts # endpoints через TanStack Query
|
||||
│ ├── vehicles.ts
|
||||
│ └── photos.ts
|
||||
├── components/
|
||||
│ ├── ui/ # shadcn-сгенерированные
|
||||
│ ├── camera/
|
||||
│ │ ├── CameraCapture.tsx
|
||||
│ │ └── PhotoSlot.tsx
|
||||
│ ├── annotation/
|
||||
│ │ ├── AnnotationCanvas.tsx # Konva-based
|
||||
│ │ ├── ToolPalette.tsx
|
||||
│ │ └── MarkerModal.tsx
|
||||
│ ├── vehicle-scheme/
|
||||
│ │ ├── VehicleScheme.tsx # SVG interactive scheme
|
||||
│ │ ├── schemes/ # SVG-файлы по типу кузова (sedan, hatch, suv)
|
||||
│ │ │ ├── sedan-top.svg
|
||||
│ │ │ ├── sedan-side.svg
|
||||
│ │ │ └── interior.svg
|
||||
│ │ └── HotSpot.tsx # переиспользуемая зона-кнопка
|
||||
│ └── shared/
|
||||
├── pages/
|
||||
│ ├── Login.tsx
|
||||
│ ├── Home.tsx
|
||||
│ ├── VehicleCard.tsx
|
||||
│ ├── InspectionStart.tsx
|
||||
│ ├── InspectionEditor.tsx
|
||||
│ └── InspectionReview.tsx
|
||||
├── store/ # Zustand store(s)
|
||||
│ ├── inspectionDraft.ts
|
||||
│ └── auth.ts
|
||||
├── lib/
|
||||
│ ├── pwa.ts # SW registration
|
||||
│ └── coords.ts # norm/denorm coords
|
||||
├── App.tsx
|
||||
└── main.tsx
|
||||
```
|
||||
|
||||
## 8. Архитектура backend (модуль в PremiumCRM)
|
||||
|
||||
```
|
||||
app/mechanic/
|
||||
├── __init__.py
|
||||
├── router.py # /api/v1/mechanic/ роуты
|
||||
├── models.py # SQLAlchemy: Inspection, InspectionPhoto, DamageMarker
|
||||
├── schemas.py # Pydantic
|
||||
├── service.py # бизнес-логика (start_inspection, finalize, copy_markers, ...)
|
||||
├── storage.py # сохранение файлов на VDS + thumbnails
|
||||
├── deps.py # auth + permission deps
|
||||
└── migrations/
|
||||
└── 20260516_mechanic_init.py # Alembic
|
||||
```
|
||||
|
||||
## 9. Фазы реализации
|
||||
|
||||
### Фаза 1. MVP — Skeleton (3-4 нед)
|
||||
Только основной flow без annotation, для первичного теста UX:
|
||||
|
||||
- Backend: модуль `mechanic`, миграции, endpoints вэхикулов и осмотров, фото upload (без annotation)
|
||||
- Frontend: Login → Home → Vehicle → Inspection (создать, выбрать тип, добавить фото в 4 слота, добавить просто маркер-точку без сложной обводки) → Review → Complete
|
||||
- Дифф с прошлым: список маркеров прошлого осмотра, чекбоксы «есть/устранено»
|
||||
|
||||
### Фаза 2. Annotation Editor (+2-3 нед)
|
||||
- Konva-based canvas-overlay с инструментами обводки (polygon)
|
||||
- Сохранение annotated-фото отдельным URL
|
||||
- Старые маркеры/обводки полупрозрачно поверх новой фотографии для сравнения
|
||||
|
||||
### Фаза 3. Polish (+1-2 нед)
|
||||
- Service Worker + offline queue (фото снимаются на устройство, загружаются по WiFi)
|
||||
- QR-сканер стикеров
|
||||
- Push-уведомления для админа (через web push с FCM-фолбэком или Telegram-бота)
|
||||
- Дашборд статистики в админ-части PremiumCRM
|
||||
|
||||
### Фаза 4 (опционально). Capacitor wrapper
|
||||
- Если в production столкнёмся с ограничениями iOS Safari camera или offline
|
||||
- Тот же codebase → native APK + IPA
|
||||
- ~1-2 недели
|
||||
|
||||
## 10. Связь с разведкой
|
||||
|
||||
- Endpoints, обнаруженные у вендора (`api.ttcontrol.naughtysoft.ru/api/vehicletechinspection/byvehicle` и пр.), могут быть использованы **отдельно** для **миграции данных** — одноразовый импорт прошлых осмотров вендора в нашу базу. Это отдельный sub-project (не входит в MVP).
|
||||
- В первой версии работаем только с НОВЫМИ осмотрами, создаваемыми в PremiumMechanic — старые остаются в системе вендора и используются для справки до момента миграции.
|
||||
|
||||
## 11. Решения и открытые вопросы
|
||||
|
||||
### Решения (по итогам ревью владельца)
|
||||
|
||||
| # | Решение |
|
||||
|---|---|
|
||||
| Поддомен | **`mechanic.pptaxi.ru`** (отдельный поддомен). DNS уже настроен владельцем. На VDS 100.64.0.12 в Caddyfile добавляется блок с TLS через ACME. |
|
||||
| Подпись водителя | **Не нужна** в MVP. Из спеки исключена. |
|
||||
| 2D-схема авто | **Нужна обязательно с MVP.** Делаем SVG-схему сами: top-down + боковая проекция + интерьер. Не привязываемся к ассетам вендора (в APK schemes как раздельных файлов нет, у них всё через WebView + либо embedded в Xamarin DLL, либо отдельная фотография крупным планом). У нас SVG-первый подход с zone-based hot-spots. |
|
||||
|
||||
### Замечания по разведке вендорского редактора
|
||||
|
||||
- Layout `vehicleinspectiondetaildrawinglayout.xml` подтверждает: вендор использует `<android.webkit.WebView>` для редактора рисования повреждений. То есть **наша PWA-стратегия = технически тот же подход, что у вендора**, только без обёртки Xamarin.
|
||||
- Заголовок WebView в layout = «Левая дверь» — у вендора **zone-based** детализация (кнопка → крупный план зоны → редактирование).
|
||||
- ObjectID формата `660e7abfd13231f1dd6e8a4d` в `vehicleId` доказывает, что у вендора на бэке **MongoDB**, а не 1С. Имя `taksi.0nalog.com:1703` (виденное в Driver-приложении) — отдельный 1С-узел, не основной storage. Это означает: имитировать «1С-структуру» нам не надо — у вендора и так нет 1С на ключевом пути данных осмотров.
|
||||
|
||||
### Остающиеся open questions
|
||||
|
||||
1. **Permissions:** добавить новое право `mechanic:inspect` или ограничиться существующим `dept=Тех. служба`? — выбор на этапе implementation.
|
||||
2. **Storage:** решено использовать **MinIO**, **только** MinIO без локального FS-дубля (разрешено отклониться от dual-write паттерна для тяжёлых медиа). Параметры: bucket `pp-inspections`, endpoint `s3.pptaxi.ru` HTTPS, креды в `/opt/sites/taxi-dashboard/minio.env`. Существующая `aiobotocore`-интеграция переиспользуется. Остаётся **операционно**: настроить CORS на bucket для allowed origins (`https://mechanic.pptaxi.ru`, `https://crm.pptaxi.ru`, dev `http://localhost:5173`) — задача в плане реализации.
|
||||
3. **Связь с CheckAuto:** опционально показывать в карточке машины автоматические проверки водителя — после MVP.
|
||||
4. **Импорт истории осмотров от вендора:** через VendorBridge sub-project — после MVP.
|
||||
|
||||
## 12. Следующий шаг
|
||||
|
||||
После утверждения этой спеки:
|
||||
|
||||
1. Создать подробный план реализации Фазы 1 через `writing-plans` skill.
|
||||
2. Сгенерировать миграции БД + scaffold бэкенд-модуля.
|
||||
3. Скаффолд React-проекта в `mechanic-pwa/frontend/` с базовой страницей логина.
|
||||
4. На итерациях по 2-3 экрана.
|
||||
Reference in New Issue
Block a user