chore: initial commit — recon artifacts + design spec + Phase 1 plan

This commit is contained in:
2026-05-16 21:58:10 +10:00
commit d6eeb0cc50
20 changed files with 338613 additions and 0 deletions
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 экрана.