Step 2: copy compare/ services to app/services/ with fixed imports
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
"""Repository facade — Protocol поверх db/*.py.
|
||||
|
||||
План decoupling Ф3:
|
||||
- Repository (Protocol) — интерфейс доступа к данным
|
||||
- PgRepository — реальная БД (обёртка над db/*.py)
|
||||
- MemRepository — in-memory для юнит-тестов
|
||||
- SQL не переписываем
|
||||
"""
|
||||
from typing import Protocol, Optional
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
# ── Протокол ────────────────────────────────────────────────────
|
||||
|
||||
class Repository(Protocol):
|
||||
"""Фасад доступа к данным."""
|
||||
|
||||
def insert_document(self, filename: str, mime_type: str, original_bytes: str,
|
||||
batch_id: str = None, zip_source: str = None) -> dict:
|
||||
"""Вставить документ, вернуть row dict."""
|
||||
...
|
||||
|
||||
def set_document_parsed(self, doc_id: str, elements: list) -> None:
|
||||
"""Обновить elements_json + status='parsed'."""
|
||||
...
|
||||
|
||||
def set_document_error(self, doc_id: str, error: str) -> None:
|
||||
"""Обновить status='error'."""
|
||||
...
|
||||
|
||||
def set_classification(self, doc_id: str, doc_type: str, own_number: str = None,
|
||||
parent_number: str = None, doc_date: str = None,
|
||||
counterparty: str = None,
|
||||
classify_raw: str = None, classify_input: str = None) -> None:
|
||||
"""Сохранить результат классификации."""
|
||||
...
|
||||
|
||||
def set_classify_garbage(self, doc_id: str, reason: str = "") -> None:
|
||||
"""Пометить документ как мусор."""
|
||||
...
|
||||
|
||||
def set_classify_failed(self, doc_id: str, error: str) -> None:
|
||||
"""Пометить классификацию как failed."""
|
||||
...
|
||||
|
||||
def list_pending(self, batch_id: str) -> list[dict]:
|
||||
"""Документы, ожидающие классификации."""
|
||||
...
|
||||
|
||||
def insert_contract(self, number: str, client: str = "") -> str:
|
||||
"""Создать контракт, вернуть contract_id."""
|
||||
...
|
||||
|
||||
def insert_supplement(self, contract_id: str, doc_id: str, supp_type: str) -> None:
|
||||
"""Создать связь contract↔document."""
|
||||
...
|
||||
|
||||
def list_supplements(self, contract_id: str) -> list[dict]:
|
||||
"""Список дополнений контракта."""
|
||||
...
|
||||
|
||||
def get_spec_current(self, contract_id: str) -> list[dict]:
|
||||
"""Текущая спецификация контракта."""
|
||||
...
|
||||
|
||||
def get_document(self, doc_id: str) -> dict | None:
|
||||
"""Получить документ по id."""
|
||||
...
|
||||
|
||||
def list_by_batch(self, batch_id: str) -> list[dict]:
|
||||
"""Все документы батча с полями классификации."""
|
||||
...
|
||||
|
||||
def count_by_status(self, batch_id: str) -> dict[str, int]:
|
||||
"""Количество документов по classify_status."""
|
||||
...
|
||||
|
||||
def reset_classify_status(self, batch_id: str) -> None:
|
||||
"""Сбросить classify_status на 'pending'."""
|
||||
...
|
||||
|
||||
def set_classify_processing(self, doc_id: str) -> None:
|
||||
"""Пометить документ как обрабатываемый."""
|
||||
...
|
||||
|
||||
def delete_document(self, doc_id: str) -> None:
|
||||
"""Удалить документ."""
|
||||
...
|
||||
|
||||
|
||||
# ── Продакшен: обёртка над db/*.py ──────────────────────────────
|
||||
|
||||
class PgRepository:
|
||||
"""Реальный доступ к PostgreSQL через существующие db/*.py."""
|
||||
|
||||
def insert_document(self, filename, mime_type, original_bytes, batch_id=None, zip_source=None):
|
||||
from app.db import documents
|
||||
return documents.insert(filename, mime_type, original_bytes,
|
||||
batch_id=batch_id, zip_source=zip_source)
|
||||
|
||||
def set_document_parsed(self, doc_id, elements):
|
||||
from app.db import documents
|
||||
documents.set_parsed(doc_id, elements) # documents.set_parsed уже делает json.dumps
|
||||
|
||||
def set_document_error(self, doc_id, error):
|
||||
from app.db import documents
|
||||
documents.set_error(doc_id, error)
|
||||
|
||||
def set_classification(self, doc_id, doc_type, own_number=None, parent_number=None,
|
||||
doc_date=None, counterparty=None,
|
||||
classify_raw=None, classify_input=None):
|
||||
from app.db import documents
|
||||
documents.set_classification(doc_id, doc_type, own_number, parent_number,
|
||||
doc_date, counterparty,
|
||||
classify_raw=classify_raw, classify_input=classify_input)
|
||||
|
||||
def set_classify_garbage(self, doc_id, reason=""):
|
||||
from app.db import documents
|
||||
documents.set_classify_garbage(doc_id, reason)
|
||||
|
||||
def set_classify_failed(self, doc_id, error):
|
||||
from app.db import documents
|
||||
documents.set_classify_failed(doc_id, error)
|
||||
|
||||
def list_pending(self, batch_id):
|
||||
from app.db import documents
|
||||
return documents.list_pending(batch_id)
|
||||
|
||||
def insert_contract(self, number, client=""):
|
||||
from app.db import contracts
|
||||
c = contracts.insert(number, client)
|
||||
return c["id"] if c else ""
|
||||
|
||||
def insert_supplement(self, contract_id, doc_id, supp_type):
|
||||
from app.db import supplements
|
||||
supplements.insert(contract_id, doc_id, supp_type)
|
||||
|
||||
def list_supplements(self, contract_id):
|
||||
from app.db import supplements
|
||||
return supplements.list_by_contract(contract_id)
|
||||
|
||||
def get_spec_current(self, contract_id):
|
||||
from app.db import spec_current
|
||||
return spec_current.list_by_contract(contract_id)
|
||||
|
||||
def get_document(self, doc_id):
|
||||
from app.db import documents
|
||||
return documents.get(doc_id)
|
||||
|
||||
def list_by_batch(self, batch_id):
|
||||
from app.db import documents
|
||||
return documents.list_by_batch(batch_id)
|
||||
|
||||
def count_by_status(self, batch_id):
|
||||
from app.db import documents
|
||||
return documents.count_by_status(batch_id)
|
||||
|
||||
def reset_classify_status(self, batch_id):
|
||||
from app.db import documents
|
||||
documents.reset_classify_status(batch_id)
|
||||
|
||||
def set_classify_processing(self, doc_id):
|
||||
from app.db import documents
|
||||
documents.set_classify_processing(doc_id)
|
||||
|
||||
def delete_document(self, doc_id):
|
||||
from app.db import documents
|
||||
documents.delete(doc_id)
|
||||
|
||||
|
||||
# ── Тестовый: in-memory заглушка ────────────────────────────────
|
||||
|
||||
class MemRepository:
|
||||
"""In-memory хранилище для юнит-тестов."""
|
||||
|
||||
def __init__(self):
|
||||
self.documents: dict[str, dict] = {}
|
||||
self.contracts: dict[str, dict] = {}
|
||||
self.supplements: list[dict] = []
|
||||
self.spec_current: dict[str, list[dict]] = {}
|
||||
|
||||
def insert_document(self, filename, mime_type, original_bytes, batch_id=None, zip_source=None):
|
||||
doc_id = str(uuid.uuid4())
|
||||
self.documents[doc_id] = {
|
||||
"id": doc_id, "filename": filename, "mime_type": mime_type,
|
||||
"original_bytes": original_bytes, "status": "uploaded",
|
||||
"elements_json": None, "doc_type": None, "own_number": None,
|
||||
"parent_number": None, "doc_date": None, "counterparty": None,
|
||||
"classify_status": "pending", "batch_id": batch_id, "zip_source": zip_source,
|
||||
}
|
||||
return self.documents[doc_id]
|
||||
|
||||
def set_document_parsed(self, doc_id, elements):
|
||||
if doc_id in self.documents:
|
||||
self.documents[doc_id]["elements_json"] = elements
|
||||
self.documents[doc_id]["status"] = "parsed"
|
||||
|
||||
def set_document_error(self, doc_id, error):
|
||||
if doc_id in self.documents:
|
||||
self.documents[doc_id]["status"] = "error"
|
||||
self.documents[doc_id]["error_message"] = error
|
||||
|
||||
def set_classification(self, doc_id, doc_type, own_number=None, parent_number=None,
|
||||
doc_date=None, counterparty=None,
|
||||
classify_raw=None, classify_input=None):
|
||||
if doc_id in self.documents:
|
||||
d = self.documents[doc_id]
|
||||
d.update({"doc_type": doc_type, "own_number": own_number,
|
||||
"parent_number": parent_number, "doc_date": doc_date,
|
||||
"counterparty": counterparty, "classify_raw": classify_raw,
|
||||
"classify_input": classify_input, "classify_status": "classified"})
|
||||
|
||||
def set_classify_garbage(self, doc_id, reason=""):
|
||||
if doc_id in self.documents:
|
||||
self.documents[doc_id]["doc_type"] = "garbage"
|
||||
self.documents[doc_id]["classify_status"] = "garbage"
|
||||
|
||||
def set_classify_failed(self, doc_id, error):
|
||||
if doc_id in self.documents:
|
||||
self.documents[doc_id]["classify_status"] = "failed"
|
||||
self.documents[doc_id]["error_message"] = error
|
||||
|
||||
def list_pending(self, batch_id):
|
||||
return [d for d in self.documents.values()
|
||||
if d.get("batch_id") == batch_id and d.get("classify_status") == "pending"]
|
||||
|
||||
def insert_contract(self, number, client=""):
|
||||
cid = str(uuid.uuid4())
|
||||
self.contracts[cid] = {"id": cid, "number": number, "client": client}
|
||||
return cid
|
||||
|
||||
def insert_supplement(self, contract_id, doc_id, supp_type):
|
||||
self.supplements.append({
|
||||
"contract_id": contract_id, "document_id": doc_id, "type": supp_type,
|
||||
})
|
||||
|
||||
def list_supplements(self, contract_id):
|
||||
return [s for s in self.supplements if s["contract_id"] == contract_id]
|
||||
|
||||
def get_spec_current(self, contract_id):
|
||||
return self.spec_current.get(contract_id, [])
|
||||
|
||||
def get_document(self, doc_id):
|
||||
return self.documents.get(doc_id)
|
||||
|
||||
def list_by_batch(self, batch_id):
|
||||
return [d for d in self.documents.values() if d.get("batch_id") == batch_id]
|
||||
|
||||
def count_by_status(self, batch_id):
|
||||
counts = {}
|
||||
for d in self.documents.values():
|
||||
if d.get("batch_id") == batch_id:
|
||||
s = d.get("classify_status", "unknown")
|
||||
counts[s] = counts.get(s, 0) + 1
|
||||
return counts
|
||||
|
||||
def reset_classify_status(self, batch_id):
|
||||
for d in self.documents.values():
|
||||
if d.get("batch_id") == batch_id:
|
||||
d["classify_status"] = "pending"
|
||||
|
||||
def set_classify_processing(self, doc_id):
|
||||
if doc_id in self.documents:
|
||||
self.documents[doc_id]["classify_status"] = "processing"
|
||||
|
||||
def delete_document(self, doc_id):
|
||||
self.documents.pop(doc_id, None)
|
||||
Reference in New Issue
Block a user