Files
telegram-helper-bot/helper_bot/services/scoring/base.py
2026-02-02 00:13:33 +03:00

160 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Базовые классы и протоколы для сервисов скоринга.
"""
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, Optional, Protocol
@dataclass
class ScoringResult:
"""
Результат оценки поста от одного сервиса.
Attributes:
score: Оценка от 0.0 до 1.0 (вероятность публикации)
source: Источник оценки ("deepseek", "rag", etc.)
model: Название используемой модели
confidence: Уверенность в оценке (опционально)
timestamp: Время получения оценки
metadata: Дополнительные данные
"""
score: float
source: str
model: str
confidence: Optional[float] = None
timestamp: int = field(default_factory=lambda: int(datetime.now().timestamp()))
metadata: Dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
"""Валидация score в диапазоне [0.0, 1.0]."""
if not 0.0 <= self.score <= 1.0:
raise ValueError(
f"Score должен быть в диапазоне [0.0, 1.0], получено: {self.score}"
)
def to_dict(self) -> Dict[str, Any]:
"""Преобразует результат в словарь для сохранения в JSON."""
result = {
"score": round(self.score, 4),
"model": self.model,
"ts": self.timestamp,
}
if self.confidence is not None:
result["confidence"] = round(self.confidence, 4)
if self.metadata:
result["metadata"] = self.metadata
return result
@classmethod
def from_dict(cls, source: str, data: Dict[str, Any]) -> "ScoringResult":
"""Создает ScoringResult из словаря."""
return cls(
score=data["score"],
source=source,
model=data.get("model", "unknown"),
confidence=data.get("confidence"),
timestamp=data.get("ts", int(datetime.now().timestamp())),
metadata=data.get("metadata", {}),
)
@dataclass
class CombinedScore:
"""
Объединенный результат от всех сервисов скоринга.
Attributes:
deepseek: Результат от DeepSeek API (None если отключен/ошибка)
rag: Результат от RAG сервиса (None если отключен/ошибка)
errors: Словарь с ошибками по источникам
"""
deepseek: Optional[ScoringResult] = None
rag: Optional[ScoringResult] = None
errors: Dict[str, str] = field(default_factory=dict)
@property
def deepseek_score(self) -> Optional[float]:
"""Возвращает только числовой скор от DeepSeek."""
return self.deepseek.score if self.deepseek else None
@property
def rag_score(self) -> Optional[float]:
"""Возвращает только числовой скор от RAG."""
return self.rag.score if self.rag else None
def to_json_dict(self) -> Dict[str, Any]:
"""
Преобразует в словарь для сохранения в ml_scores колонку.
Формат:
{
"deepseek": {"score": 0.75, "model": "...", "ts": ...},
"rag": {"score": 0.90, "model": "...", "ts": ...}
}
"""
result = {}
if self.deepseek:
result["deepseek"] = self.deepseek.to_dict()
if self.rag:
result["rag"] = self.rag.to_dict()
return result
def has_any_score(self) -> bool:
"""Проверяет, есть ли хотя бы один успешный скор."""
return self.deepseek is not None or self.rag is not None
class ScoringServiceProtocol(Protocol):
"""
Протокол для сервисов скоринга.
Любой сервис скоринга должен реализовывать эти методы.
"""
@property
def source_name(self) -> str:
"""Возвращает имя источника ("deepseek", "rag", etc.)."""
...
@property
def is_enabled(self) -> bool:
"""Проверяет, включен ли сервис."""
...
async def calculate_score(self, text: str) -> ScoringResult:
"""
Рассчитывает скор для текста поста.
Args:
text: Текст поста для оценки
Returns:
ScoringResult с оценкой
Raises:
ScoringError: При ошибке расчета
"""
...
async def add_positive_example(self, text: str) -> None:
"""
Добавляет текст как положительный пример (опубликованный пост).
Args:
text: Текст опубликованного поста
"""
...
async def add_negative_example(self, text: str) -> None:
"""
Добавляет текст как отрицательный пример (отклоненный пост).
Args:
text: Текст отклоненного поста
"""
...