Уровень технического долга: Критический
- Хардкод секретов в
.env, backend/*.py и репозитории — Риск: ВЫСОКИЙ, Время: ~0.5ч, Выигрыш: защита аккаунтов OpenAI/Yandex/GigaChat и возможность развёртывать код публично. /api/reportпринимает произвольный HTML и сразу печатает PDF — Риск: ВЫСОКИЙ, Время: ~1ч, Выигрыш: защита от SSRF/XSS и изоляция PDF-пайплайна.gigachat_helperбросаетValueErrorпри импортe без переменнойGIGACHAT_CREDENTIALS— Риск: СРЕДНИЙ, Время: ~0.5ч, Выигрыш: сервер перестаёт падать и можно запускать без российских ключей.
- Удалить реальные ключи из
.envи переписать.gitignore(~20 мин) → выигрыш: безопасность + репозиторий снова можно шарить. - Выключить
debug=Trueперед production и добавить переменнуюFLASK_DEBUG(~15 мин) → выигрыш: нет удалённого дебаггера. - Заменить
console.logчувствительных данных вfrontend/src/api/index.tsнаdebugфлаг (~30 мин) → выигрыш: нет утечки данных в логах браузера.
Цель: закрыть уязвимости, чтобы код можно было запускать на сервере.
- Перенести секреты в .env.example + Config (CRITICAL-1) (~0.5ч)
- Санитайз HTML перед WeasyPrint и добавить allowlist (CRITICAL-2) (~1ч)
- Исправить аварийный импорт GigaChat + добавить graceful fallback (CRITICAL-3) (~0.5ч)
- Включить централизованные обработчики ошибок, убрать
debug=True, добавить healthcheck (CRITICAL-4) (~1.5ч)
Результат: безопасный сервер, который запускается без всех ключей, отдаёт понятные ошибки и не выполняет произвольный HTML.
Время: ~3.5–4 часа.
Цель: уменьшить нагрузку на LLM и RAM, упростить React-компоненты.
- Потоковая обработка CSV (chunked чтение и реальное использование
process_large_csv) (~3ч) - Кэширование результатов LLM по
dataset_hash+modelи debounce load-more запросов (~2ч) - Разделение
frontend/src/App.tsxиcomponents/AnalysisResult.tsxна контейнер + представление + сервисы (~6ч) - Вынести API baseURL в
.env+ axios instance, убратьfetch('http://localhost:5000')дубли (HIGH-4) (~2ч) - Добавить rate limiting / auth placeholder (например, simple API key) (~2ч)
Результат: меньше повторных обращений к LLM, UI не подвисает, код делится на слои.
Время: ~15 часов.
Цель: обеспечить возможность дальнейших изменений без страха.
- Type hints для backend, mypy в CI (~4ч)
- Покрыть Flask API unit/integration тестами (pytest + Flask client) и React компонентов (RTL) (~16ч)
- Включить TS strict + заменить
anyна конкретные интерфейсы (~4ч) - Очистить логи, вынести магические числа (page_size, required_fields) в константы (~2ч)
- Настроить eslint/prettier + black/flake8 + pre-commit (~4ч)
Результат: >60% покрытия тестами, строгая типизация, чистые логи.
Время: ~30 часов.
Цель: подготовить к production.
- Обновить документацию (
frontend/README.md,docs/*, добавить диаграммы) (~2ч) - Docker Compose (Flask + React + reverse proxy) (~3ч)
- Логирование и мониторинг (структурированные JSON-логи, Sentry или аналог) (~2ч)
- Обновить зависимости (npm audit/pip-audit) и настроить renovate (~1ч)
Результат: reproducible окружение и актуальные зависимости.
Время: ~8 часов.
Итого: ~57 часов ≈ 7-8 рабочих дней.
{
"project_info": {
"name": "Multi-LLM Analyzer",
"type": "Full-stack web app",
"status": "partially working",
"purpose": "Загрузка табличных данных, очистка пропусков и получение AI‑аналитики/PDF от OpenAI, YandexGPT и GigaChat",
"structure": "project/ -> backend/pdf_server.py (Flask API), backend/llm/* helpers, frontend/src/App.tsx + components (React), docs/*, sample CSV/XLSX, .env с ключами"
},
"tech_stack": {
"backend": {
"language": "Python 3.x",
"framework": "Flask 2.x",
"database": "None",
"orm": "None",
"async": "sync only",
"data_processing": ["pandas", "pdfplumber", "WeasyPrint"],
"file_storage": "temp files on disk",
"auth": "None"
},
"frontend": {
"language": "TypeScript 4.9",
"framework": "React 18 (CRA/Webpack)",
"state": "React useState/useEffect",
"bundler": "react-scripts 5",
"ui": "MUI 5",
"visualization": "Recharts"
},
"integrations": ["OpenAI", "YandexGPT", "GigaChat", "WeasyPrint"]
},
"metrics": {
"size": {
"total_files": 30,
"python_lines": 596,
"js_lines": 2434,
"largest_files": [
{"file": "frontend/src/components/AnalysisResult.tsx", "lines": 1110},
{"file": "frontend/src/App.tsx", "lines": 477},
{"file": "backend/pdf_server.py", "lines": 329},
{"file": "README.md", "lines": 233},
{"file": "frontend/src/api/index.ts", "lines": 144}
]
},
"quality": {
"overall_score": 4,
"readability": 4,
"structure": 3,
"documentation": 3,
"tests": 1,
"security": 2,
"performance": 4,
"test_coverage": 0,
"type_coverage_backend": 40,
"type_coverage_frontend": 60,
"lint_warnings": null,
"security_issues": 3
},
"complexity": {
"avg_function_lines": 33,
"avg_cyclomatic_complexity": 9,
"duplicated_code_percent": 20
},
"dependencies": {
"backend_packages": 13,
"frontend_packages": 21,
"outdated_packages": 10,
"vulnerable_packages": null
}
},
"critical_issues": [
{
"id": "SEC-1",
"type": "security",
"severity": "critical",
"category": "Hardcoded secrets",
"file": ".env:2-12",
"description": "YANDEX/OpenAI/GigaChat ключи лежат в репозитории и совпадают в backend/.env",
"code": "OPENAI_API_KEY=sk-proj-Try...; YANDEX_API_KEY=AQVN1OxX...",
"risk": "HIGH — компрометация аккаунтов и платных квот",
"estimated_fix_time": "30m"
},
{
"id": "SEC-2",
"type": "security",
"severity": "critical",
"category": "Unsanitized HTML",
"file": "backend/pdf_server.py:296-315",
"description": "report_html клиента вставляется в WeasyPrint без фильтрации",
"risk": "HIGH — SSRF/XSS через PDF генератор",
"estimated_fix_time": "1h"
},
{
"id": "BUG-1",
"type": "bug",
"severity": "critical",
"category": "Startup failure",
"file": "backend/llm/gigachat_helper.py:8-23",
"description": "Модуль бросает ValueError при отсутствии GIGACHAT_CREDENTIALS",
"risk": "HIGH — backend не запускается вне TEST_MODE",
"estimated_fix_time": "45m"
}
],
"high_priority_issues": [
{
"id": "ARCH-1",
"type": "architecture",
"severity": "high",
"category": "Monolithic component",
"file": "frontend/src/components/AnalysisResult.tsx:1-1110",
"description": "Один компонент содержит визуализацию, экспорт, анализ и вызовы API",
"impact": "Трудно тестировать и оптимизировать, дублирование HTML шаблонов",
"estimated_fix_time": "4h"
},
{
"id": "PERF-1",
"type": "performance",
"severity": "high",
"category": "Full-file reads",
"file": "backend/pdf_server.py:173-214",
"description": "pandas читает весь CSV/Excel перед пагинацией",
"impact": "Большие файлы кладут память и блокируют сервер",
"estimated_fix_time": "3h"
},
{
"id": "PERF-2",
"type": "performance",
"severity": "high",
"category": "Repeated full LLM calls",
"file": "frontend/src/App.tsx:213-244",
"description": "Каждое добавление строк повторно отправляет весь массив в /api/analyze",
"impact": "Латентность и квоты растут линейно от объёма данных",
"estimated_fix_time": "3h"
}
],
"medium_priority_issues": [
{
"id": "QUALITY-1",
"type": "code_quality",
"severity": "medium",
"category": "Loose typing/logging",
"file": "frontend/src/components/AnalysisResult.tsx",
"description": "Состояния объявлены как any и выводят полные данные в консоль",
"impact": "Повышенный риск регрессий и утечек",
"estimated_fix_time": "4h"
},
{
"id": "TEST-1",
"type": "testing",
"severity": "medium",
"category": "No automated tests",
"file": "frontend/src/App.test.tsx",
"description": "Остался шаблон CRA; тесты падают сразу",
"impact": "Невозможно внедрить CI/CD и безопасный рефакторинг",
"estimated_fix_time": "8h"
}
],
"low_priority_issues": [
{
"id": "DOC-1",
"type": "documentation",
"severity": "low",
"category": "Outdated README",
"file": "frontend/README.md",
"description": "Фронтовый README остаётся шаблоном Create React App и не описывает продукт",
"impact": "Новым разработчикам сложно понять сценарии запуска",
"estimated_fix_time": "1h"
}
],
"technical_debt": {
"level": "critical",
"estimated_hours": 80,
"blocks_development": true,
"main_blockers": [
"API ключи в репозитории — нельзя публиковать код",
"Отсутствие тестов/CI делает любые изменения рискованными",
"Монолитные компоненты App/AnalysisResult затрудняют расширение"
]
},
"constraints": {
"backward_compatibility": false,
"deadline": "none",
"team_size": 1,
"available_time_hours": 40,
"environment": "local dev only",
"expected_load": "до 1000 строк на страницу (100 МБ upload limit)"
}
}Категория: Безопасность Время: ~30 мин Риск: Низкий Зависимости: нет
Проблема: .env (и backend/.env) с реальными ключами лежат в Git, LLM helper'ы читают их напрямую и падают без них.
Выигрыш: секреты не утекут, можно отделить dev/prod.
- Создать
backend/config.py, подружить его сdotenv, добавитьConfig.validate()(пример кода уже в PRD). - Перенести чтение ключей из
os.getenv/строк в Config. - Обновить
.gitignore, оставить толькоenv.example.
# Было (backend/llm/openai_helper.py)
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# Стало
from backend.config import Config
client = OpenAI(api_key=Config.OPENAI_API_KEY)Команды:
cd backend
pip install python-dotenv
python -c "from backend.config import Config; Config.validate(); print('OK')"Откат: git checkout -- backend/config.py backend/llm/*.py .gitignore.
Категория: Безопасность Время: ~1 ч Риск: Средний Зависимости: CRITICAL-1 (Config для DEBUG флага)
Проблема: /api/report слепо принимает HTML и отдаёт WeasyPrint → XSS/SSRF/чтение локальных файлов.
Выигрыш: можно безопасно встраивать отчёты.
Шаги:
- Добавить белый список тегов + атрибутов (например,
bleach). - Ограничить длину HTML (
len(report_html) < MAX_REPORT_BYTES). - Включить sandbox WeasyPrint (disable network).
# Было
html = HTML(string=data['report_html'])
pdf = html.write_pdf()
# Стало
from bleach.sanitizer import Cleaner
cleaner = Cleaner(tags=['p','b','i','table','tr','td','img'], attributes={'img': ['src', 'alt']}, strip=True)
safe_html = cleaner.clean(data['report_html'])
html = HTML(string=safe_html, base_url=None, url_fetcher=lambda url: (_ for _ in ()).throw(ValueError("External refs disabled")))
pdf = html.write_pdf()Команды:
pip install bleach
pytest tests/test_report_endpoint.py -k sanitize -vРиск/откат: если отчёт ломается → добавить feature flag ALLOW_RAW_HTML.
Категория: Стабильность Время: ~30 мин Риск: Низкий
Проблема: модуль поднимает ValueError при импорте, если нет GIGACHAT_CREDENTIALS, даже если пользователь не выбирает провайдера giga.
Решение: лениво инициализировать клиента внутри get_giga_response, а в TEST_MODE возвращать мок.
# Было (верх файла)
if os.getenv("TEST_MODE", "false").lower() != "true":
credentials = os.getenv("GIGACHAT_CREDENTIALS")
if not credentials:
raise ValueError("GIGACHAT_CREDENTIALS ...")
# Стало
def _init_client():
credentials = Config.GIGACHAT_CREDENTIALS
if not credentials:
raise APIError("GigaChat credentials missing", status_code=500)
return GigaChat(...)
def get_giga_response(...):
if Config.TEST_MODE:
return "Mock"
client = _init_client()
...Команды: pytest tests/test_giga_fallback.py.
Категория: Стабильность Время: ~1.5ч
- Создать
backend/errors.pyсAPIError,ValidationError,register_error_handlers. - Заменить
return jsonify({'error': str(e)}), 500наraise FileProcessingError("..."). - В
pdf_server.pyиспользоватьapp.config['DEBUG']=Config.DEBUGи запуск черезflask run(а неapp.run(debug=True)).
# Было
except Exception as e:
logger.exception("Error during analysis")
return jsonify({'error': str(e)}), 500
# Стало
from backend.errors import LLMError
...
except Exception as e:
logger.exception("Error during analysis")
raise LLMError("Failed to get LLM response") from eКоманды: pytest tests/test_errors.py -v, FLASK_DEBUG=0 flask run.
Категория: Производительность Время: ~3ч
- Использовать
pandas.read_csv(..., chunksize=page_size)и отдавать нужный chunk. - Реально подключить
process_large_csv(сейчас мёртвый код). - Добавить тесты на 50k строк (заготовки
test_cars-1000.csv).
# Было
df = pd.read_csv(temp_file_name, encoding='utf-8')
df_page = df.iloc[start:end]
# Стало
chunks = pd.read_csv(temp_file_name, chunksize=page_size, iterator=True)
df_page = next(islice(chunks, page-1, page), pd.DataFrame())Команды: pytest tests/test_upload_large.py -k chunk.
Категория: Архитектура Время: ~6ч
- Вынести логику загрузки в
hooks/useDataset.ts. - Сохранить
datasetIdиpageCursor, чтобы отправлять только новые строки. - В
AnalysisResultсоздавать подкомпоненты:FilterPanel,Charts,ReportExporter,MissingDataPanel.
// Было (App.tsx)
const response = await analyzeLLM({ table_data: newAllData });
// Стало
const delta = newData.slice(lastAnalyzedRows);
const response = await analyzeLLM({ table_data_delta: delta, datasetId });Команды: npm run lint, npm test, npm run build.
Категория: Архитектура Время: ~2ч
- Создать
frontend/src/api/client.tsс axios instance, который используетprocess.env.REACT_APP_API_URL. - Переписать
handleAIFillStartи PDF экспорт на этот клиент, убратьhttp://localhost:5000.
// Было
const response = await fetch('http://localhost:5000/api/report', { ... });
// Стало
const client = axios.create({ baseURL: process.env.REACT_APP_API_URL });
const response = await client.post('/report', { report_html: htmlContent }, { responseType: 'blob' });Команды: REACT_APP_API_URL=https://api.example.com npm run build.
Категория: Качество Время: ~4ч
- Добавить
TypedDictдляTableRow,BasicAnalysis. - Вынести
REQUIRED_FIELDS,NUMERIC_COLUMNSвbackend/constants.py. - Проверить
mypy backend -p backend.
Категория: Тестирование Время: ~16ч
- Backend: pytest для
/api/upload,/api/analyze,/api/report,/api/fill-missing-ai. - Frontend: заменить CRA-тесты на React Testing Library (ререндер без load).
- Настроить GitHub Actions (pytest + npm test).
Категория: DX Время: ~3ч
frontend/README.md: описать реальные шаги (env vars, API URL).- Docker Compose с
services: backend, frontend, nginx. - Обновить
docs/DEPLOYMENT.mdи добавить screenshot.
| Метрика | До | Цель | Улучшение |
|---|---|---|---|
| Security issues | 3 critical | 0 | -100% |
| Backend OOM на 100MB CSV | Да | Нет (chunked) | стабильность |
Среднее время ответа /api/analyze |
>3s | <1s (кэш/дельта) | -66% |
| React bundle | ~850 KB | <450 KB (code splitting, убраны неиспользуемые пакеты) | -47% |
| Test coverage backend | 0% | ≥60% | +60pp |
| Test coverage frontend | 0% | ≥50% | +50pp |
| Type hints backend | 40% | 90% | +50pp |
| Type coverage frontend | 60% | 90% | +30pp |
- Создать ветку
refactor/security - Удалить секреты, обновить
.gitignore, прогнатьgit ls-files | grep '.env' - Интегрировать
bleach, протестировать/api/report - Реализовать центральный error handler,
pytest tests/test_errors.py - Включить chunked CSV и протестировать на
test_cars-1000.csv - Рефакторить React по шагам (hooks → компоненты → API client)
- Добавить тесты и CI
- Обновить документацию и Docker
# Backend
pip install -r backend/requirements.txt
black backend && isort backend
pytest tests -v --maxfail=1
FLASK_DEBUG=0 flask run
# Frontend
npm install
REACT_APP_API_URL=http://localhost:5000 npm start
npm run lint && npm test -- --watch=false
npm run build
# Security
pip-audit
npm audit && npm audit fix
# Docker (после настройки)
docker compose up --buildИтоговая оценка: 4/10 → после рефакторинга целимcя ≥7/10.
Рекомендация: Серьёзный рефакторинг (без переписывания с нуля).
- Сначала безопасность (секреты, HTML, error handling).
- Далее производительность (chunked CSV, уменьшение LLM нагрузок).
- Затем тесты + типизация (медиум).
- В конце документация и DevOps.
План рассчитан на одиночного разработчика на 7-8 рабочих дней. Выполняем по приоритету, коммитим атомарно:
git commit -m "[CRITICAL-2] Sanitize report HTML" и т.д.
Не пытайся чинить всё сразу. Закрой критические вещи — и только потом берись за глубокий рефакторинг UI/архитектуры. Это позволит повысить доверие к проекту и безопасно включить его в CI/CD.