90 lines
3.5 KiB
Python
90 lines
3.5 KiB
Python
"""Контракты стадий пайплайна — frozen dataclass на выходах, dict на входах."""
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
from datetime import date
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ParseResult:
|
|
"""Выход parse.py — результат парсинга документа."""
|
|
status: str # 'parsed' | 'error'
|
|
element_count: int # количество извлечённых элементов
|
|
elements: list # [{type, text/rows}, ...]
|
|
error: Optional[str] = None # сообщение об ошибке (если status='error')
|
|
|
|
@classmethod
|
|
def from_row(cls, row: dict):
|
|
"""Из строки БД (documents)."""
|
|
elements = row.get("elements_json", [])
|
|
return cls(
|
|
status=row.get("status", "error"),
|
|
element_count=len(elements) if isinstance(elements, list) else 0,
|
|
elements=elements,
|
|
error=row.get("error_message"),
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ClassifyResult:
|
|
"""Выход classify.py — результат классификации документа."""
|
|
doc_type: str # 'contract' | 'supplement' | 'specification' | 'garbage' | 'other'
|
|
own_number: Optional[str] = None
|
|
parent_number: Optional[str] = None
|
|
doc_date: Optional[str] = None
|
|
counterparty: Optional[str] = None
|
|
|
|
@classmethod
|
|
def from_llm(cls, raw: dict):
|
|
"""Из сырого ответа LLM."""
|
|
return cls(
|
|
doc_type=raw.get("doc_type", "other"),
|
|
own_number=raw.get("own_number") or None,
|
|
parent_number=raw.get("parent_number") or None,
|
|
doc_date=raw.get("doc_date") or None,
|
|
counterparty=raw.get("counterparty") or None,
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class BatchGroupingResult:
|
|
"""Выход grouping.py — результат группировки всего батча."""
|
|
groups: list = field(default_factory=list) # list[GroupingResult]
|
|
unresolved: list = field(default_factory=list) # документы без группы
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class GroupingResult:
|
|
"""Выход grouping.py — результат группировки по контрактам."""
|
|
contract_number: str # нормализованный номер договора
|
|
counterparty: Optional[str] = None
|
|
documents: list = field(default_factory=list) # [{doc_id, filename, doc_type, ...}]
|
|
|
|
@classmethod
|
|
def from_row(cls, row: dict):
|
|
return cls(
|
|
contract_number=row.get("contract_number", ""),
|
|
counterparty=row.get("counterparty"),
|
|
documents=row.get("documents", []),
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CompareOp:
|
|
"""Одна операция сравнения (ADD/UPDATE/DELETE/UNRESOLVED)."""
|
|
action: str # 'ADD' | 'UPDATE' | 'DELETE' | 'UNRESOLVED'
|
|
new_row: Optional[dict] = None
|
|
new_values: Optional[dict] = None
|
|
target_hash: Optional[str] = None
|
|
comment: str = ""
|
|
|
|
@classmethod
|
|
def from_llm(cls, raw: dict):
|
|
"""Из сырого ответа LLM."""
|
|
return cls(
|
|
action=raw.get("action", "UNRESOLVED"),
|
|
new_row=raw.get("new_row") or None,
|
|
new_values=raw.get("new_values") or None,
|
|
target_hash=raw.get("target_hash") or raw.get("target_id"),
|
|
comment=raw.get("comment", ""),
|
|
)
|