Копии api/contracts/ побайтно равны docs/contracts/ (тест идентичности плана 0): схемы v2.0 основные, .v1 рядом, examples/ — v2.0, examples/v1/ — v1.0. Модели result_v2.py и RecognitionResultMessageV2, versions.py выбирает модель по MAJOR. Приём конвертов принимает оба MAJOR; metrics.warnings в recognition_job пишется структурами (строки 1.x оборачиваются в LEGACY_WARNING). JobBrief.warnings отдаёт структуры, мусор пропускается, БД не переписывается. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
52 lines
2.1 KiB
Python
52 lines
2.1 KiB
Python
"""Выбор модели по MAJOR `schema_version` на окне перехода v1 → v2 (ядро принимает оба MAJOR).
|
||
|
||
`schema_version` — `MAJOR.MINOR`. Известны MAJOR 1 и 2; иной MAJOR, неверный формат или отсутствие поля —
|
||
`pydantic.ValidationError` (разбор отдан модели v1, чей шаблон `^1\\.\\d+$` такое не принимает).
|
||
"""
|
||
|
||
import json
|
||
import re
|
||
from typing import Any
|
||
|
||
from src.contract.message import RecognitionResultMessage, RecognitionResultMessageV2
|
||
from src.contract.result import RecognitionResult
|
||
from src.contract.result_v2 import RecognitionResultV2
|
||
|
||
_VERSION = re.compile(r"(\d+)\.\d+", re.ASCII)
|
||
MAJOR_V2 = 2
|
||
|
||
__all__ = ["major", "parse_message", "parse_result"]
|
||
|
||
|
||
def major(schema_version: str) -> int:
|
||
"""MAJOR из `MAJOR.MINOR`; иная запись — `ValueError`."""
|
||
match = _VERSION.fullmatch(schema_version)
|
||
if match is None:
|
||
raise ValueError(f"schema_version {schema_version!r}: ожидается MAJOR.MINOR")
|
||
return int(match.group(1))
|
||
|
||
|
||
def _is_v2(data: Any) -> bool:
|
||
version = data.get("schema_version") if isinstance(data, dict) else None
|
||
if not isinstance(version, str):
|
||
return False
|
||
try:
|
||
return major(version) == MAJOR_V2
|
||
except ValueError:
|
||
return False
|
||
|
||
|
||
def parse_message(data: dict[str, Any]) -> RecognitionResultMessage | RecognitionResultMessageV2:
|
||
"""Конверт `recognition-result-message`: MAJOR 1 → модель v1, MAJOR 2 → модель v2, иное — `ValidationError`."""
|
||
if _is_v2(data):
|
||
return RecognitionResultMessageV2.model_validate(data)
|
||
return RecognitionResultMessage.model_validate(data)
|
||
|
||
|
||
def parse_result(content: bytes) -> RecognitionResult | RecognitionResultV2:
|
||
"""Файл результата: те же правила MAJOR; нечитаемый JSON — `ValueError` (`json.JSONDecodeError`)."""
|
||
data = json.loads(content)
|
||
if _is_v2(data):
|
||
return RecognitionResultV2.model_validate(data)
|
||
return RecognitionResult.model_validate(data)
|