Metadata-Version: 2.4
Name: kufar-finder-core
Version: 1.2.0
Summary: Общее ядро для независимых приложений поиска объявлений Kufar
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: beautifulsoup4<5,>=4.12
Requires-Dist: google-genai<2,>=1.0
Requires-Dist: pydantic<3,>=2.7
Requires-Dist: python-dotenv<2,>=1.0
Requires-Dist: requests<3,>=2.31
Provides-Extra: dev
Requires-Dist: pytest<10,>=8; extra == "dev"
Requires-Dist: pytest-cov<8,>=5; extra == "dev"

# Kufar Finder Core

Независимая Python-библиотека с общей инфраструктурой для приложений поиска товаров на Kufar.

## Что входит

- `KufarClient` — сбор объявлений по переданным категориям, описаний и фотографий;
- `GeminiEngine` — workers, повторные запросы, structured JSON и загрузка изображений;
- `GeminiEngine.analyze_and_extract_specs` — общий текстовый запрос для отбора
  объявления и извлечения характеристик;
- `process_streaming` — обработка пачек параллельно с продолжением сбора;
- `load_items` / `save_items` — хранение JSON;
- `GeminiConfig` / `KufarConfig` — настройки из `.env`.

## Требования

- Python 3.11 или новее;
- Windows 10, Linux или другая система с поддерживаемой версией Python;
- Gemini API key для реальных AI-запросов.

## Установка

```powershell
python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"
Copy-Item .env.example .env
```

## Использование Kufar

Категории задаёт приложение:

```python
from kufar_finder_core import KufarClient, KufarConfig

config = KufarConfig.from_env(categories=("CATEGORY_ID",))
with KufarClient(config) as client:
    ads = client.fetch_ads(min_price=20, max_price=100)
```

Границы цены указываются в BYN и включаются в результат. Чтобы искать без
верхней границы, передайте `max_price=None`:

```python
with KufarClient(config) as client:
    ads = client.fetch_ads(min_price=123, max_price=None)
```

Значение `min_price` по умолчанию равно `0`, а `max_price` — `100`. Отрицательные границы
и диапазон, где минимум больше максимума, вызывают `ValueError`.

Для передачи объявлений в следующий этап без ожидания полного сбора используйте
`iter_ads()` вместе с потоковой обработкой:

```python
from kufar_finder_core import process_streaming

with KufarClient(config) as client:
    result = process_streaming(
        client.iter_ads(min_price=20, max_price=100),
        batch_size=30,
        max_workers=2,
        processor=process_batch,
    )

raw_items = result.raw_items
processed_items = result.processed_items
```

## Один запрос для анализа и характеристик

Чтобы не расходовать два Gemini-запроса на одно объявление, используйте единую
Pydantic-модель с полями результата отбора и характеристик:

```python
from pydantic import BaseModel

from kufar_finder_core import GeminiConfig, GeminiEngine


class CombinedResult(BaseModel):
    link: str
    is_match: bool
    reason: str
    cpu: str | None = None
    ram_gb: int | None = None


config = GeminiConfig.from_env()
with GeminiEngine(config) as engine:
    results = engine.analyze_and_extract_specs(
        ads,
        instruction=(
            "Определи, подходит ли объявление для поиска сервера. "
            "Одновременно извлеки процессор и объём оперативной памяти. "
            "Если данных нет, верни null и не угадывай."
        ),
        response_model=CombinedResult,
    )
```

Метод использует `GEMINI_ANALYSIS_MODEL`, `GEMINI_CHUNK_SIZE` и
`GEMINI_MAX_CHUNK_CHARS`. Старые настройки `GEMINI_SPECS_*` сохранены для
обратной совместимости с приложениями, которые оставляют этапы раздельными.

## Тесты

```powershell
pytest
```
