"""LLM infrastructure (#960) — ОПЦИОНАЛЬНЫЙ слой поверх детерминированного движка.

Foundational-пакет. Консьюмеры приедут позже: #956 граддок-extraction (sync Celery)
и #957 chat (async FastAPI). Этот пакет их НЕ реализует — только инфраструктуру.

═══════════════════════════════════════════════════════════════════════════════
КОНТРАКТ БЕЗОПАСНОСТИ — обязателен к прочтению авторами консьюмеров
═══════════════════════════════════════════════════════════════════════════════

1. Детерминированный fallback ВСЕГДА. ``complete`` НИКОГДА не бросает из-за LLM:
   при ``LLMResult.ok is False`` вы ОБЯЗАНЫ подставить свой детерминированный
   результат. Forecasting-движок остаётся детерминированным; LLM ничего не «ломает».

       res = complete(system_prompt=..., payload=safe)
       if not res.ok:
           return deterministic_result()   # обязательно
       use(res.content, res.tool_calls)

2. Allowlist-first (§19 data-residency). Провайдер ВНЕШНИЙ (OpenAI) → данные
   покидают РФ. Поэтому в ``complete`` нельзя передать сырые строки/DB-записи —
   только ЯВНО собранный ``SafePayload`` из проверенных не-чувствительных полей:

       payload = SafePayload(
           text=public_regulation_text,        # публичный/безопасный текст
           fields={"zone_index": "Ц-1", "floors": 16},  # агрегаты/коды
           is_confidential=insight.is_confidential,     # прокинуть флаг источника!
       )

   НЕ собирайте ``SafePayload`` из сырой строки insight/лида без проверки полей.

3. Hard-block конфиденциального. Если ``SafePayload.is_confidential is True`` —
   для внешнего провайдера ``complete`` НЕ отправит данные, вернёт fallback
   (reason="redaction_refused"). Всегда прокидывайте confidential-флаг источника.

4. Regex-scrub — вторичная защита (belt-and-suspenders): из ``text``/``fields``
   автоматически вырезаются RU PII (телефон/email/СНИЛС/ИНН/паспорт/ФИО) перед
   отправкой. Это НЕ заменяет пункт 2 — не полагайтесь на scrub как на гарантию.

5. ``llm_enabled`` default False. В проде (до настройки секретов) сеть НЕ дёргается —
   ``complete`` сразу возвращает fallback. Включение — осознанное действие.
"""

from __future__ import annotations

from .client import LLMResult, LLMUnavailableError, complete, complete_or_raise
from .prompts import PromptNotFoundError, render
from .provider import (
    LLMProvider,
    LLMProviderError,
    LLMRateLimitedError,
    LLMTimeoutError,
    OpenAIProvider,
    ToolCall,
)
from .redaction import RedactionRefusedError, SafePayload, scrub_safe_payload, scrub_text

# Сортировка isort-style (RUF022). Группировка по слоям — в docstring выше:
# orchestration (complete/complete_or_raise/LLMResult), redaction (SafePayload/...),
# provider abstraction (LLMProvider/OpenAIProvider/... — шов под RU-hosted), prompts.
__all__ = [
    "LLMProvider",
    "LLMProviderError",
    "LLMRateLimitedError",
    "LLMResult",
    "LLMTimeoutError",
    "LLMUnavailableError",
    "OpenAIProvider",
    "PromptNotFoundError",
    "RedactionRefusedError",
    "SafePayload",
    "ToolCall",
    "complete",
    "complete_or_raise",
    "render",
    "scrub_safe_payload",
    "scrub_text",
]
