Files
elmer/doc/opus-fix-plan.md
T

145 lines
6.7 KiB
Markdown

# План правок по отчётам Опуса
> 31 мая 2026 · ветка `opus-fixes` · порядок: по критичности + зависимостям
---
## Этап 1. Сервер (`elmer/`) — 4 правки
### 1.1 🔴 `api/db.py` — WAL + закрытие соединений + request_id
**Файл**: `api/db.py`
**Строки**: класс `Database`, методы `_init_schema()`, `save_session()`
- [x] Добавить `PRAGMA journal_mode=WAL` и `busy_timeout=30000`
- [x] Добавить колонку `request_id TEXT UNIQUE` в `sessions`
- [x] Метод `close()` и контекстный менеджер (`__enter__`/`__exit__`)
- [x] `save_session()` — проверять `request_id` на дубликат, возвращать кэшированный диагноз
- [x] Индекс `idx_sessions_request_id`
### 1.2 🔴 `api/routes.py` — идемпотентность upload + /ping-llm без LLM
**Файл**: `api/routes.py`
**Строки**: `upload_session()`, `ping_llm()`
- [x] `upload_session()` — принимать `request_id` из JSON, возвращать кэш при дубликате
- [x] `upload_session()` — закрывать `db` через контекстный менеджер
- [x] `upload_session()` — не отдавать `str(e)` наружу, логировать, клиенту — обобщённый текст
- [x] `/ping-llm` — кэшировать результат на 60с, не вызывать LLM на каждый GET
### 1.3 🔴 `obd/protocol.py` — сброс буфера + не затирать ERROR
**Файл**: `obd/protocol.py`
**Строки**: `_write()`, `send()`
- [x] `_write()``self._ser.reset_input_buffer()` перед записью
- [x] `send()``if self._state == State.BUSY: self._state = State.READY` (не безусловно)
### 1.4 🟡 `brain/client.py` — таймаут из конфига + модель
**Файл**: `brain/client.py`
**Строки**: `Diagnoser.__init__()`, `Diagnoser.ask()`
- [x] `DEFAULT_MODEL``"gpt-oss-120b"`
- [x] `timeout` — параметр конструктора (по умолчанию 180)
- [x] Комментарии: убрать «DeepSeek»
---
## Этап 2. Сервер (`elmer/`) — улучшения (без 🔴 но важные)
### 2.1 🟡 `api/routes.py` — импорты наверх + кэш конфига
**Файл**: `api/routes.py`, `api/config.py`
- [x] Поднять импорты (`from brain.client import Diagnoser` и др.) на уровень модуля
- [x] `api/config.py``@lru_cache(maxsize=1)` на `load()`
### 2.2 🟡 `api/routes.py` — история /chat через роли
**Файл**: `api/routes.py`
**Строки**: `chat()`
- [x] Передавать историю как массив `messages` с ролями, а не строкой «Водитель:/Автоэксперт:»
### 2.3 🟡 `brain/client.py` — обработка ошибок LLM
**Файл**: `brain/client.py`
- [x] Различать `Timeout`, `HTTPError(429)`, `HTTPError(5xx)`, `HTTPError(4xx)`
- [x] Не отдавать детали исключения наружу
---
## Этап 3. Android (`elmer-android/`) — 6 правок
### 3.1 🔴 `ServerClient.kt` — request_id + идемпотентность
**Файл**: `app/src/main/java/ru/elmer/client/server/ServerClient.kt`
**Строки**: `uploadSession()`
- [ ] Генерировать `UUID` один раз до цикла ретраев
- [ ] Добавить `"request_id"` в JSON-тело
- [ ] Добавить заголовок `Idempotency-Key`
### 3.2 🔴 `ServerClient.kt` + `build.gradle.kts` — X-Api-Key
**Файлы**: `ServerClient.kt`, `app/build.gradle.kts`
- [ ] `build.gradle.kts``buildConfigField("String", "API_KEY", ...)`
- [ ] `ServerClient` — добавлять `X-Api-Key` во все запросы
- [ ] `MainActivity.sendToLlm()` и `startTest()` — тоже `X-Api-Key`
### 3.3 🔴 `ElmProtocol.kt` — не затирать ERROR + дренаж буфера
**Файл**: `app/src/main/java/ru/elmer/client/elm/ElmProtocol.kt`
**Строки**: `sendCommand()`, `write()`
- [ ] `sendCommand()``if (state == State.BUSY) state = State.READY`
- [ ] `write()``while (input.available() > 0) input.read()` перед записью
### 3.4 🔴 `SessionDb.kt` — безопасная миграция + индекс
**Файл**: `app/src/main/java/ru/elmer/client/db/SessionDb.kt`
**Строки**: `onUpgrade()`, `onCreate()`
- [ ] `onUpgrade()``ALTER TABLE` вместо `DROP TABLE`
- [ ] Индекс `idx_resp_session ON responses(session_id)`
### 3.5 🔴 `MainActivity.kt` — /ping-llm без LLM + двойной receiver + chatHistory
**Файл**: `app/src/main/java/ru/elmer/client/ui/MainActivity.kt`
**Строки**: `startTest()`, `sendToLlm()`, receiver-регистрация
- [ ] `startTest()` — троттлить `/ping-llm` (не чаще раза в 60с), предупреждать
- [ ] Убрать дублирующий receiver `statusReceiver` (оставить `scriptStatusReceiver`)
- [ ] `chatHistory` сохранять в `onSaveInstanceState` (JSON)
### 3.6 🔴 `ScriptRunnerService.kt` — null intent + мёртвый paused + try/finally
**Файл**: `app/src/main/java/ru/elmer/client/script/ScriptRunnerService.kt`
**Строки**: `onStartCommand()`, `executeScript()`
- [ ] `onStartCommand()``if (intent == null) { stopSelf(); return START_NOT_STICKY }`
- [ ] Убрать мёртвый флаг `paused` и `ACTION_RESUME` (или доделать паузу)
- [ ] `executeScript()``try/finally` вокруг `progress.stop()`
---
## Этап 4. Android (`elmer-android/`) — улучшения
### 4.1 🟡 `ServerClient.kt` — exponential backoff
**Файл**: `ServerClient.kt`
- [ ] `(1 shl (attempt-1)) * 1000 + Random.nextLong(0, 500)` вместо фиксированных 2000
### 4.2 🟡 `MainActivity.kt` — единый HTTP-клиент
**Файл**: `MainActivity.kt`
- [ ] `sendToLlm()` и `startTest()` перевести на OkHttp (через `ServerClient`)
### 4.3 🟡 `ScriptRunnerService.kt` — вынести хост в константу
**Файл**: `ScriptRunnerService.kt`, `MainActivity.kt`
- [ ] `obdai.ru``BuildConfig.SERVER_HOST` или константа
---
## Порядок выполнения
```
Этап 1 (сервер 🔴) → коммит
Этап 2 (сервер 🟡) → коммит
Этап 3 (Android 🔴) → коммит
Этап 4 (Android 🟡) → коммит
```
После каждого этапа — проверка: `python run.py` (сервер), сборка APK (Android).