docs: add TRD for SheetDoc AI MVP

Derive technical requirements from PRD_SheetDoc_AI.md and the
telecom.quest deploy constraints in GITEA.md: stack (React/Vite +
FastAPI), stateless backend, four API endpoints, Traefik-labelled
docker-compose deploy, MVP limits (1000 rows x 30 cols), 6-day plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
galymzhan 2026-05-22 11:42:57 +05:00
parent 333ccca8a2
commit efd2323d05

380
TRD.md
View File

@ -0,0 +1,380 @@
# TRD: SheetDoc AI
Технические требования к реализации MVP сервиса генерации документов из Google Таблиц. Источники: `PRD_SheetDoc_AI.md`, `GITEA.md`.
---
## 1. Цель документа
Описать техническую реализацию MVP: стек, архитектуру, модули, API, обработку данных, AI-логику, безопасность и порядок развёртывания на инфраструктуре `telecom.quest` (Gitea + Traefik).
Документ покрывает только то, что нужно для MVP-сценария:
вставка ссылки → выбор листа → анализ → уточняющие вопросы → текстовая справка → копирование.
---
## 2. Технологический стек
### 2.1. Frontend
- Язык: TypeScript.
- Фреймворк: React 18 + Vite.
- UI: Tailwind CSS, минимум компонентов (shadcn/ui по необходимости).
- Состояние: React Query (серверное), Zustand (локальное состояние мастера шагов).
- Сборка: статический бандл (`dist/`).
### 2.2. Backend
- Язык: Python 3.11.
- Фреймворк: FastAPI + Uvicorn.
- HTTP-клиент к Google: `httpx` (без OAuth, только публичные CSV-экспорты Sheets).
- Валидация: Pydantic v2.
- LLM SDK: `anthropic` (Claude). Модель по умолчанию: `claude-sonnet-4-6`.
- Менеджер пакетов: `uv` или `pip` + `requirements.txt`.
### 2.3. Хранилище
- MVP: stateless. Никакой БД.
- Сессия мастера хранится на клиенте (Zustand + sessionStorage).
- Кэш ответа Sheets и анализа: in-memory LRU на backend (TTL 10 минут, ключ — `sheet_id:gid`).
### 2.4. Инфраструктура
- Docker, docker-compose.
- Reverse proxy: Traefik (уже поднят на сервере).
- Хост: `sheetdoc.telecom.quest` (или `<login>.telecom.quest/sheetdoc/` через Gitea Pages — только если фронт развязан от бэка).
---
## 3. Архитектура
### 3.1. Общая схема
```
[Browser SPA] ──HTTPS──> [Traefik] ──> [FastAPI backend] ──HTTPS──> [Google Sheets CSV export]
│
└──HTTPS──> [Anthropic API]
```
- SPA — статический бандл, отдаётся либо backend-ом (через FastAPI StaticFiles), либо отдельно с Gitea Pages.
- Backend — единый сервис, без отдельной БД.
- Внешние зависимости: Google Sheets публичный экспорт, Anthropic API.
### 3.2. Поток данных MVP
1. SPA шлёт `POST /api/sheets/resolve` со ссылкой.
2. Backend парсит `spreadsheetId`, проверяет доступность, возвращает список листов.
3. SPA шлёт `POST /api/sheets/load` с `spreadsheetId` + `gid`.
4. Backend качает CSV, парсит, определяет активный диапазон, типы колонок, возвращает превью.
5. SPA шлёт `POST /api/analyze/questions` — backend через LLM генерирует 3–7 уточняющих вопросов.
6. SPA шлёт `POST /api/generate` с ответами пользователя — backend через LLM формирует текстовую справку.
7. SPA рендерит результат, даёт кнопку «Копировать».
---
## 4. Модули backend
### 4.1. `sheets_client`
Задача: получить данные публичной Google Таблицы без OAuth.
- URL парсер: извлекает `spreadsheetId` из ссылок вида
`https://docs.google.com/spreadsheets/d/<id>/edit#gid=<gid>`.
- Список листов: HTML-страница `https://docs.google.com/spreadsheets/d/<id>/htmlview`, парсинг названий + gid (fallback). Альтернатива — `gviz/tq?tqx=out:json&sheet=<name>`.
- Загрузка данных: `https://docs.google.com/spreadsheets/d/<id>/export?format=csv&gid=<gid>`.
- Парсер CSV: `csv` stdlib, защита от больших файлов (лимит 5 MB на ответ).
- Ошибки: 401/403/404 от Google → доменные исключения (`SheetForbidden`, `SheetNotFound`).
### 4.2. `range_detector`
Задача: определить активный диапазон.
- Срезать полностью пустые строки сверху/снизу.
- Срезать полностью пустые столбцы слева/справа.
- Определить строку заголовков:
- кандидат — первая непустая строка, где все ячейки строковые и не выглядят как числа/даты;
- fallback — первая непустая строка.
- Учитывать внутренние пропуски (не обрывать диапазон на одной пустой строке).
### 4.3. `column_profiler`
Задача: профилировать колонки.
- Тип значения: `text | number | date | bool | empty`.
- Доля заполненности, число уникальных значений, top-3 значения.
- Эвристики для категориальных полей (статус, регион, тариф): доля уникальных < 20% и тип text.
- Эвристики для дат: pandas-стиль парсинг (`dateutil`), валидные >70%.
### 4.4. `question_generator`
Задача: сформировать 3–7 уточняющих вопросов через LLM.
- Вход: профиль таблицы (название листа, колонки, типы, top-значения, число строк).
- Промпт требует JSON-ответ: `{ questions: [{ id, text, why, optional, kind: "single|multi|text" , options? }] }`.
- Жёсткое ограничение: 3 ≤ N ≤ 7.
- Валидация Pydantic.
### 4.5. `document_generator`
Задача: сгенерировать текстовую справку.
- Вход: профиль таблицы + ответы пользователя + первые 50 строк данных (truncate, чтобы уложиться в контекст).
- Промпт фиксирует структуру: заголовок, описание источника, показатели, наблюдения, выводы, рекомендации (если запрошены), дата генерации.
- Опционально (необязательная функция MVP): структура презентации тем же endpoint-ом, отдельный режим.
### 4.6. `llm_client`
Обёртка над Anthropic SDK.
- Поддержка повторов (3 попытки, экспоненциальный backoff).
- Включён prompt caching для системных промптов.
- Логирование: токены, latency, без содержимого данных пользователя.
---
## 5. API
Базовый префикс: `/api`. Все ответы — JSON. Ошибки — единый формат:
```json
{ "error": { "code": "SHEET_FORBIDDEN", "what": "...", "why": "...", "next": "..." } }
```
Коды: `INVALID_URL`, `NOT_A_SHEET`, `SHEET_FORBIDDEN`, `SHEET_NOT_FOUND`, `SHEET_EMPTY`, `SHEET_TOO_LARGE`, `STRUCTURE_UNCLEAR`, `LLM_FAILED`, `VALIDATION_ERROR`.
### 5.1. `POST /api/sheets/resolve`
Вход: `{ url: string }`.
Выход: `{ spreadsheetId, title, sheets: [{ gid, name, isEmpty }] }`.
### 5.2. `POST /api/sheets/load`
Вход: `{ spreadsheetId, gid }`.
Выход:
```json
{
"rows": 240,
"cols": 8,
"headers": ["..."],
"columns": [{ "name": "...", "type": "text|number|date|bool", "fill": 0.97, "top": ["..."] }],
"preview": [["..."]]
}
```
Лимиты: 1 000 строк × 30 колонок. Превышение → `SHEET_TOO_LARGE` с предложением сузить диапазон.
### 5.3. `POST /api/analyze/questions`
Вход: `{ profile: <выход load> }`.
Выход: `{ questions: [...] }` (3–7 шт).
### 5.4. `POST /api/generate`
Вход:
```json
{
"profile": {...},
"answers": [{ "id": "q1", "value": "..." }],
"mode": "report" | "presentation_outline",
"dataSample": [[...]]
}
```
Выход: `{ document: "<markdown>", generatedAt: "ISO-8601", mode: "report" }`.
### 5.5. `GET /api/health`
Простой liveness-чек для Traefik.
---
## 6. Frontend: экраны и состояния
### 6.1. Шаги мастера
1. **Ссылка** — поле ввода + подсказка «откройте доступ по ссылке».
2. **Лист** — список листов, отметка пустых, автoselect если один.
3. **Превью** — карточка анализа: число строк/колонок, заголовки, типы, топ-значения.
4. **Вопросы** — динамическая форма по схеме из API. Опциональные помечены, есть «Пропустить».
5. **Результат** — markdown → отрендеренный текст, кнопки «Копировать», «Изменить ответы», «Сгенерировать заново».
### 6.2. Состояния загрузки и ошибок
- Глобальный индикатор для шагов > 1 секунды.
- Ошибки по схеме «что/почему/что делать» (см. п. 9.5 PRD).
- Кнопка повторного запроса на ошибках сети/LLM.
### 6.3. Копирование
- `navigator.clipboard.writeText` для markdown.
- Дополнительно — копирование plain text (без markdown-разметки) для вставки в Word/Docs.
---
## 7. Нефункциональные требования
### 7.1. Производительность
- Парсинг 1 000 × 30 — ≤ 2 секунд (без LLM).
- Генерация вопросов — ≤ 8 секунд p95.
- Генерация справки — ≤ 25 секунд p95.
- Стриминг LLM-ответа на фронт через `text/event-stream` (опционально для MVP).
### 7.2. Лимиты
- Размер CSV-ответа от Google: 5 MB.
- Строк после обрезки: 1 000.
- Активных колонок: 30.
- Размер ответа LLM: до 4 000 токенов на справку.
### 7.3. Безопасность
- CORS: разрешён только домен фронта.
- Никакой записи содержимого таблиц в логи или БД.
- Anthropic API ключ — через env var `ANTHROPIC_API_KEY`, не в репозитории.
- Rate limit: 30 запросов / минута / IP на `/api/generate` и `/api/analyze/questions`.
- HTTPS принудительно — терминируется на Traefik.
### 7.4. Приватность
- Данные таблицы существуют только в памяти процесса на время запроса.
- Кэш — только агрегированный профиль (без сырых строк) на 10 минут.
- На фронте — баннер «данные обрабатываются в памяти и не сохраняются».
### 7.5. Логирование и метрики
- Логи: structlog в JSON, поля `request_id`, `endpoint`, `latency_ms`, `status`.
- Без содержимого пользовательских данных.
- Метрики (опционально, после MVP): Prometheus `/metrics`.
---
## 8. Развёртывание
Согласно `GITEA.md`: статика — Gitea Pages, динамика — docker-compose за Traefik.
### 8.1. Вариант MVP (рекомендуемый)
Один сервис `sheetdoc-api`, который отдаёт и API, и собранный SPA как статику.
`docker-compose.yml` в корне репозитория:
```yaml
services:
sheetdoc:
build: .
image: sheetdoc:latest
restart: unless-stopped
environment:
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
ALLOWED_ORIGINS: "https://sheetdoc.telecom.quest"
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.sheetdoc.rule=Host(`sheetdoc.telecom.quest`)"
- "traefik.http.routers.sheetdoc.entrypoints=websecure"
- "traefik.http.routers.sheetdoc.tls=true"
- "traefik.http.services.sheetdoc.loadbalancer.server.port=8000"
networks:
traefik:
external: true
```
Правила из `GITEA.md`:
- Никаких `ports:` наружу — только лейблы Traefik.
- Сеть `traefik` подключается как `external: true`.
- Секреты — через `.env` файл рядом с compose (не коммитить).
### 8.2. Вариант с Gitea Pages (только если фронт живёт отдельно)
- Ветка `gitea-pages`, в корне — `index.html` собранного бандла.
- Топик репозитория — `gitea-pages-allowall`.
- Все пути ассетов — относительные (`./assets/...`).
- Backend — отдельный compose-стек с Traefik по адресу `api.sheetdoc.telecom.quest`.
- На фронте — env-переменная `VITE_API_BASE=https://api.sheetdoc.telecom.quest`.
Этот вариант сложнее (CORS, два домена) и для MVP не рекомендуется.
### 8.3. Dockerfile (контур)
Двухстейджевый:
1. `node:20-alpine` — `npm ci && npm run build` → `/app/dist`.
2. `python:3.11-slim` — установка backend, копирование `dist` в `app/static`, запуск `uvicorn`.
### 8.4. CI / автодеплой
- На push в `main`: Gitea Actions собирает образ, кладёт на сервер, `docker compose up -d --build`.
- Pages-ветка `gitea-pages` — только если выбран вариант 8.2.
---
## 9. Обработка ошибок (mapping PRD → реализация)
| PRD пункт | Backend код | UX-сообщение |
|---|---|---|
| 7.1 Неверная ссылка | `INVALID_URL` / `NOT_A_SHEET` | «Ссылка не похожа на Google Sheets» |
| 7.2 Закрытая таблица | `SHEET_FORBIDDEN` | «Откройте доступ по ссылке» |
| 7.3 Несколько листов | — | UI: выбор листа |
| 7.4 Пустой лист | `SHEET_EMPTY` | «Лист пуст, выберите другой» |
| 7.5 Плохая структура | `STRUCTURE_UNCLEAR` | Доп. вопросы о заголовках |
| 9.5.7 Слишком много данных | `SHEET_TOO_LARGE` | «Сузьте диапазон до 1 000 × 30» |
| 9.5.9 LLM упал | `LLM_FAILED` | «Повторить генерацию» |
---
## 10. Тестирование
### 10.1. Backend
- Unit: `range_detector`, `column_profiler`, парсер URL — pytest, без сети.
- Контрактные: моки Anthropic SDK, проверка валидности JSON-ответов.
- Integration: запись фикстур реальных публичных таблиц (CSV-снимки в `tests/fixtures/`).
### 10.2. Frontend
- Vitest + React Testing Library: рендер шагов мастера, ошибки, валидация формы.
- Playwright (опционально): e2e на staging домене с тестовой таблицей.
### 10.3. Smoke-чек MVP
Скрипт `scripts/smoke.sh`: дергает все четыре endpoint-а на одной зафиксированной публичной таблице, печатает результаты.
---
## 11. Этапы разработки
1. **День 1** — каркас FastAPI, парсер URL, CSV-загрузка, `range_detector`, `column_profiler`. Тесты на фикстурах.
2. **День 2** — LLM-клиент, `question_generator`, `document_generator`. Промпты + валидация JSON.
3. **День 3** — SPA: шаги 1–3 (ссылка, лист, превью), интеграция API.
4. **День 4** — SPA: шаги 4–5 (вопросы, результат, копирование). Обработка ошибок.
5. **День 5** — Dockerfile, compose, Traefik labels, деплой на `sheetdoc.telecom.quest`. Smoke-тесты.
6. **День 6** — полировка UX, тексты ошибок, README, демонстрация end-to-end сценария.
---
## 12. Что НЕ реализуется в MVP (фиксация из PRD)
- OAuth и приватные таблицы.
- Экспорт в `.docx`, `.pdf`, `.pptx`.
- Личный кабинет, история, БД.
- Графики, диаграммы.
- Excel-файлы, несколько листов одновременно.
- Корпоративные шаблоны и стили.
---
## 13. Открытые технические вопросы
1. Достаточно ли публичного CSV-экспорта, или нужен `gviz/tq` для получения форматирования?
2. Стримить ли ответ LLM на фронт (улучшает UX, усложняет CORS/Traefik)?
3. Хранить ли кратко (10 минут) сгенерированный документ, чтобы пользователь мог обновить страницу без потери результата?
4. Нужен ли rate limit per-IP уже в MVP или достаточно лимита от Anthropic?
5. Использовать ли Gitea Actions или ручной `docker compose up` для первой выкатки?
---
## 14. Следующий шаг
После согласования TRD — реализация по этапам из п. 11. Промпты для `question_generator` и `document_generator` оформить отдельным файлом `prompts/` в репозитории, чтобы версионировать независимо от кода.