Дата проверки: 2025-12-21
Проверено: Claude Code (Sonnet 4.5)
Ветка: claude/review-code-quality-X326M
Общая оценка: ⭐⭐⭐⭐ (4/5)
Проект демонстрирует хорошую архитектуру и организацию кода, но имеет несколько критических проблем, требующих внимания.
- Хорошо структурированная модульная архитектура
- Комплексная система типов с использованием dataclasses
- Детальная система отслеживания уверенности и предупреждений
- Продакшн-готовое API с FastAPI
- Двухэтапная стратегия LLM-экстракции
- Безопасность: Небезопасная обработка путей и отсутствие валидации
- Тестирование: Минимальное покрытие тестами (~5%)
- Документация: Неполная информация в setup.py
- Конфигурация: Отсутствуют файлы линтинга (pyproject.toml, .flake8)
- Обработка ошибок: Неконсистентная в некоторых местах
Файл: api/main.py:27-28
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))Проблема: Манипуляция sys.path в продакшн коде - плохая практика. Это может привести к импорту неожиданных модулей.
Рекомендация: Использовать правильную установку пакета через pip install -e .
Файл: api/main.py:126-135
def image_to_temp_path(image_bytes: bytes) -> str:
import tempfile
img = Image.open(io.BytesIO(image_bytes))
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
img.save(f, format="PNG")
return f.nameПроблемы:
- Нет проверки формата изображения (может быть zip-бомба)
- Нет ограничения на размер распакованного изображения
delete=Falseсоздает временные файлы, которые могут не удалиться при сбое
Рекомендация:
# Добавить проверку размеров
MAX_IMAGE_PIXELS = 89478485 # PIL default
Image.MAX_IMAGE_PIXELS = MAX_IMAGE_PIXELS
# Проверить формат
if img.format not in ['PNG', 'JPEG', 'WEBP']:
raise ValueError(f"Unsupported format: {img.format}")
# Проверить размеры
if img.width * img.height > MAX_IMAGE_PIXELS:
raise ValueError("Image too large")Файл: chart2csv/core/llm_extraction.py:50-52
api_key = os.environ.get("MISTRAL_API_KEY")
if not api_key:
raise ValueError("MISTRAL_API_KEY not set")Проблема: Ошибка может логироваться с контекстом, раскрывая факт использования API ключа.
Рекомендация: Использовать более безопасное сообщение об ошибке.
Файл: api/main.py:108-114
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 🚨 Небезопасно!
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)Проблема: allow_origins=["*"] с allow_credentials=True - серьезная уязвимость безопасности.
Рекомендация:
ALLOWED_ORIGINS = os.environ.get("ALLOWED_ORIGINS", "").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS or ["https://kikuai-lab.github.io"],
allow_credentials=True,
allow_methods=["POST", "GET"],
allow_headers=["Content-Type", "Authorization"],
)Файл: api/main.py
Rate limiting есть (20 req/min), но нет:
- API ключей для аутентификации
- Логирования запросов
- Защиты от CSRF
Рекомендация: Добавить API keys или JWT токены для продакшн использования.
Файл: api/main.py:183-335, 338-419, 422-515
Проблема: Три endpoint'а (/extract, /extract/base64, /extract/calibrated) содержат дублирующуюся логику:
- Проверка rate limit (3 раза)
- Сохранение в temp файл (3 раза)
- Построение CSV (3 раза)
- Cleanup temp файлов (3 раза)
Рекомендация: Выделить общую логику в helper функции:
async def _process_extraction(
image_bytes: bytes,
mode: str,
chart_type: Optional[str],
calibration: Optional[dict] = None
) -> ExtractionResult:
# Общая логика обработки
passФайл: chart2csv/core/types.py:85-90
def overall(self) -> float:
return (
0.3 * self.crop + # Откуда эти веса?
0.25 * self.axis +
0.3 * self.ocr +
0.15 * self.extraction
)Проблема: Магические числа без объяснения.
Рекомендация:
# Константы с документацией
CONFIDENCE_WEIGHTS = {
'crop': 0.3, # Crop is critical - affects all downstream
'axis': 0.25, # Axis detection enables transformation
'ocr': 0.3, # OCR determines scale accuracy
'extraction': 0.15 # Extraction confidence is relative
}Файл: chart2csv/core/llm_extraction.py:154-157
except json.JSONDecodeError as e:
return {"error": f"JSON parse error: {e}"}, 0.0
except Exception as e:
return {"error": str(e)}, 0.0Проблема: Слишком широкий except Exception. Скрывает реальные ошибки.
Рекомендация: Перехватывать конкретные исключения.
Файл: chart2csv/core/pipeline.py:158-163
transform, fit_error = build_transform(
ticks=ticks,
x_scale=x_scale,
y_scale=y_scale
)
if fit_error > 0.1 and x_scale == Scale.LINEAR and y_scale == Scale.LINEAR:Проблема: Переменная fit_error используется, но не определена в случае calibration_points (строка 152-156).
Потенциальный баг: UnboundLocalError при определенных условиях.
Файл: chart2csv/tests/test_mistral.py:4
from unittest.mock import MagicMock, patchПравильно! Но в Python 3.3+ это стандарт. Никаких проблем.
- Type hints везде - отличная типизация с использованием dataclasses
- Enum для констант -
ChartType,Scale,WarningCode - Dataclasses - чистые структуры данных
- Docstrings - присутствуют в большинстве функций
- Модульная архитектура - четкое разделение ответственности
-
README.md - хорошо структурирован:
- Quick start
- Структура проекта
- API endpoints
- Установка
-
Модульные README - в
/api,/chart2csv,/scripts,/deploy -
Docstrings - детальные в core модулях:
pipeline.py:45-47- параметры функцийtypes.py:74-84- формула confidence с документациейllm_extraction.py:30-49- полное описание API
-
Комментарии в коде - объясняют сложные алгоритмы
Файл: setup.py:26-31
author="Your Name",
author_email="your.email@example.com",
url="https://github.com/yourusername/chart2csv",Проблема: Неактуальные данные автора и URL.
Рекомендация:
author="KikuAI",
author_email="contact@kikuai.dev",
url="https://github.com/KikuAI-Lab/Chart2CSV",Файл: setup.py:39
"License :: OSI Approved :: MIT License",Проблема: Проект использует AGPL-3.0 (см. README.md:11, коммит 70f92cd), но в setup.py указан MIT.
Рекомендация:
"License :: OSI Approved :: GNU Affero General Public License v3",Нет гайда для контрибьюторов:
- Как запустить тесты
- Как настроить dev окружение
- Code style guide
Нет истории изменений версий.
Файл: api/main.py
API endpoints имеют docstrings, но:
- Нет примеров curl команд с calibration_json
- Нет описания формата ответа при ошибках
- Нет информации о retry политике
Нет директории /examples с:
- Примерами Python скриптов
- Примерами изображений
- Jupyter notebook tutorial
Текущее покрытие: ~5% (1 тестовый файл, 88 строк кода)
Покрыто тестами:
- ✅
chart2csv/core/mistral_ocr.py- частично - ✅
chart2csv/core/ocr.py- частично (через mocks)
НЕ покрыто тестами:
- ❌
api/main.py(521 строка) - 0% - ❌
chart2csv/core/pipeline.py(279 строк) - 0% - ❌
chart2csv/core/extraction.py- 0% - ❌
chart2csv/core/detection.py- 0% - ❌
chart2csv/core/transform.py- 0% - ❌
chart2csv/core/llm_extraction.py- 0% - ❌ CLI модули - 0%
-
Unit тесты:
tests/ ├── test_pipeline.py ├── test_detection.py ├── test_transform.py ├── test_extraction.py └── test_types.py
-
Integration тесты:
tests/integration/ └── test_api.py # FastAPI TestClient
-
Fixture данные:
tests/fixtures/ ├── sample_line_chart.png ├── sample_scatter.png └── expected_results.json
-
CI/CD:
# .github/workflows/test.yml - pytest --cov=chart2csv --cov-report=xml - coverage report --fail-under=70
Нет современного pyproject.toml. Рекомендуется заменить setup.py:
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "chart2csv"
version = "0.1.0"
authors = [{name = "KikuAI", email = "contact@kikuai.dev"}]
license = {text = "AGPL-3.0"}
requires-python = ">=3.8"
[tool.black]
line-length = 100
target-version = ['py38', 'py39', 'py310', 'py311']
[tool.mypy]
python_version = "3.8"
strict = true
warn_return_any = true
[tool.ruff]
line-length = 100
select = ["E", "F", "I", "N", "W"]
[tool.pytest.ini_options]
testpaths = ["chart2csv/tests"]
python_files = "test_*.py"Проверим наличие:
# Должно быть в .gitignore:
__pycache__/
*.py[cod]
.env
.pytest_cache/
.mypy_cache/
*.egg-info/
dist/
build/
temp_*.pngОтсутствуют. Рекомендация: .pre-commit-config.yaml
repos:
- repo: https://github.com/psf/black
rev: 23.0.0
hooks:
- id: black
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.280
hooks:
- id: ruff
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.0
hooks:
- id: mypy-
Модульный pipeline:
Preprocess → Detect → OCR → Transform → Extract → ExportКаждый шаг независим и тестируем.
-
Двухпроходная LLM стратегия:
# Pass 1: Analyze # Pass 2: Extract point-by-point
Умная оптимизация для плотных графиков.
-
Fallback механизмы:
LLM → CV pipeline → Manual calibration
-
Confidence tracking:
- Поэлементные метрики (crop, axis, ocr, extraction)
- Взвешенный overall score
- Зоны уверенности (high/medium/low)
-
Warning система:
- 12 типов предупреждений
- Рекомендации по исправлению
- Enum коды для обработки
Проблема: Жесткая зависимость от одного провайдера.
Рекомендация: Абстракция LLM провайдера:
class LLMProvider(Protocol):
def extract_chart(self, image: np.ndarray) -> Dict[str, Any]: ...
class MistralProvider(LLMProvider): ...
class OpenAIProvider(LLMProvider): ... # Будущее расширениеФайл: везде
Проблема: Используется print() вместо logging:
api/main.py:93-95- print в lifespan- Нет structured logging
Рекомендация:
import logging
logger = logging.getLogger(__name__)
# В API
logger.info("Chart2CSV API starting...", extra={
"version": "1.0.0",
"env": os.environ.get("ENV", "production")
})Файл: chart2csv/core/cache.py
✅ Уже реализовано! Disk-based кэширование OCR результатов.
Потенциальное улучшение: Добавить TTL и размер кэша:
CACHE_MAX_SIZE = 1000 # Max entries
CACHE_TTL_DAYS = 7 # Auto-cleanup old entriesopencv-python>=4.8.0 # ✅ Актуальная версия
pytesseract>=0.3.10 # ✅ OK
Pillow>=10.0.0 # ✅ Современная версия
numpy>=1.24.0 # ✅ OK
scipy>=1.11.0 # ✅ OK
scikit-image>=0.21.0 # ✅ OK
pypdfium2>=4.0.0 # ⚠️ Не используется в коде?
click>=8.1.0 # ❌ НЕ используется! (было CLI на click?)
pytest>=7.4.0 # ✅ Dev dep
black>=23.0.0 # ✅ Dev dep
mypy>=1.5.0 # ✅ Dev dep
ruff>=0.0.280 # ✅ Dev dep
mistralai>=1.0.0 # ✅ Основная зависимость- click не используется - CLI построен на
argparse, неclick - Нет FastAPI в requirements.txt - но используется в API!
- Нет uvicorn - нужен для запуска API
- Нет pydantic - используется в API моделях
Разделить на:
# requirements.txt (core)
opencv-python>=4.8.0
pytesseract>=0.3.10
Pillow>=10.0.0
numpy>=1.24.0
scipy>=1.11.0
scikit-image>=0.21.0
mistralai>=1.0.0
# requirements-api.txt
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
pydantic>=2.0.0
# requirements-dev.txt
pytest>=7.4.0
pytest-cov>=4.1.0
black>=23.0.0
mypy>=1.5.0
ruff>=0.0.280
Файл: api/main.py:183-335
async def extract_data(...):
# ...
result = extract_chart(...) # 🚨 Блокирующий вызов в async!Проблема: CV pipeline блокирует event loop.
Рекомендация:
from fastapi import BackgroundTasks
import asyncio
async def extract_data(...):
result = await asyncio.to_thread(extract_chart, temp_path, ...)Файл: chart2csv/core/llm_extraction.py:65
client = Mistral(api_key=api_key) # Создается каждый раз!Рекомендация:
# Переиспользовать клиент
_mistral_client = None
def get_mistral_client():
global _mistral_client
if _mistral_client is None:
_mistral_client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
return _mistral_client- Безопасность CORS - исправить
allow_origins=["*"] - Лицензия в setup.py - изменить MIT → AGPL-3.0
- Добавить FastAPI в requirements - API не запустится без него
- Исправить sys.path в API - использовать правильную установку
- Валидация изображений - защита от zip-бомб
- Тестирование - поднять покрытие до 70%+
- setup.py metadata - исправить author/url
- Документация API - примеры с calibration
- Логирование - заменить print на logging
- Async в API - asyncio.to_thread для CV
- pyproject.toml - мигрировать с setup.py
- pre-commit hooks - автоматический линтинг
- CONTRIBUTING.md - гайд для разработчиков
- CHANGELOG.md - версионирование
- Examples/ - примеры использования
- CI/CD - GitHub Actions
- Абстракция LLM - поддержка других провайдеров
- Рефакторинг API - убрать дублирование
Несмотря на проблемы, проект демонстрирует:
- Профессиональную архитектуру - четкое разделение concerns
- Продуманную систему confidence - с детальным tracking
- Интеллектуальные fallback - LLM → CV → Manual
- Production-ready API - с rate limiting и error handling
- Хорошую типизацию - type hints везде
- Двухпроходную LLM стратегию - innovative подход
- Модульность - каждый компонент независим
Рекомендация: Проект готов к использованию, но требует:
- Исправления критических проблем безопасности
- Значительного расширения тестов
- Обновления документации и метаданных
Оценка готовности к production: 75%
После исправления критических проблем (1-5 из списка выше) можно считать production-ready.
Конец отчета