fix: parse Checko API response fields
This commit is contained in:
@@ -234,6 +234,14 @@ def _map_ru_keys(data: dict | None) -> dict | None:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _first_present(data: dict, *keys: str, default: Any = None) -> Any:
|
||||||
|
"""Return the first explicitly present value from normalized Checko data."""
|
||||||
|
for key in keys:
|
||||||
|
if key in data:
|
||||||
|
return data[key]
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CheckoClient:
|
class CheckoClient:
|
||||||
"""
|
"""
|
||||||
@@ -471,7 +479,7 @@ class CheckoClient:
|
|||||||
def _parse_bankruptcy_message(self, data: dict) -> BankruptcyMessage:
|
def _parse_bankruptcy_message(self, data: dict) -> BankruptcyMessage:
|
||||||
"""Парсинг сообщения о банкротстве."""
|
"""Парсинг сообщения о банкротстве."""
|
||||||
return BankruptcyMessage(
|
return BankruptcyMessage(
|
||||||
type=data.get("type", ""),
|
type=_first_present(data, "type", "type_name", default=""),
|
||||||
date=data.get("date", ""),
|
date=data.get("date", ""),
|
||||||
case_number=data.get("case_number"),
|
case_number=data.get("case_number"),
|
||||||
)
|
)
|
||||||
@@ -1183,37 +1191,54 @@ class CheckoClient:
|
|||||||
ogrn=data.get("ogrn"),
|
ogrn=data.get("ogrn"),
|
||||||
inn=data.get("inn"),
|
inn=data.get("inn"),
|
||||||
kpp=data.get("kpp"),
|
kpp=data.get("kpp"),
|
||||||
name=data.get("name"),
|
name=_first_present(data, "name", "short_name", "full_name"),
|
||||||
region_code=data.get("region_code"),
|
region_code=data.get("region_code"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _parse_contract(self, data: dict) -> Contract:
|
def _parse_contract(self, data: dict) -> Contract:
|
||||||
"""Парсинг контракта."""
|
"""Парсинг контракта."""
|
||||||
|
suppliers = _first_present(data, "suppliers", "постав", default=[])
|
||||||
|
if isinstance(suppliers, dict):
|
||||||
|
suppliers = [suppliers]
|
||||||
|
objects = _first_present(data, "objects", "объекты", default=[])
|
||||||
|
subject = data.get("subject")
|
||||||
|
if not subject and isinstance(objects, list):
|
||||||
|
subject = (
|
||||||
|
"; ".join(
|
||||||
|
str(item.get("name") or item.get("наим"))
|
||||||
|
for item in objects
|
||||||
|
if isinstance(item, dict) and (item.get("name") or item.get("наим"))
|
||||||
|
)
|
||||||
|
or None
|
||||||
|
)
|
||||||
return Contract(
|
return Contract(
|
||||||
registry_number=data.get("registry_number", ""),
|
registry_number=_first_present(
|
||||||
publish_date=data.get("publish_date"),
|
data, "registry_number", "регномер", default=""
|
||||||
sign_date=data.get("sign_date"),
|
),
|
||||||
execution_date=data.get("execution_date"),
|
publish_date=_first_present(data, "publish_date", "date"),
|
||||||
price=data.get("price"),
|
sign_date=_first_present(data, "sign_date", "date"),
|
||||||
|
execution_date=_first_present(data, "execution_date", "датаисп"),
|
||||||
|
price=_first_present(data, "price", "цена"),
|
||||||
currency_code=data.get("currency_code"),
|
currency_code=data.get("currency_code"),
|
||||||
status=data.get("status"),
|
status=data.get("status"),
|
||||||
subject=data.get("subject"),
|
subject=subject,
|
||||||
law=data.get("law"),
|
law=data.get("law"),
|
||||||
purchase_number=data.get("purchase_number"),
|
purchase_number=data.get("purchase_number"),
|
||||||
customer=self._parse_contract_party(data.get("customer")),
|
customer=self._parse_contract_party(
|
||||||
suppliers=tuple(
|
_first_present(data, "customer", "заказ")
|
||||||
self._parse_contract_party(s) for s in data.get("suppliers", []) if s
|
|
||||||
),
|
),
|
||||||
url=data.get("url"),
|
suppliers=tuple(self._parse_contract_party(s) for s in suppliers if s),
|
||||||
|
url=_first_present(data, "url", "стреис"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _parse_contracts_data(self, data: dict | None) -> ContractsData | None:
|
def _parse_contracts_data(self, data: dict | None) -> ContractsData | None:
|
||||||
"""Парсинг данных о контрактах."""
|
"""Парсинг данных о контрактах."""
|
||||||
if not data:
|
if not data:
|
||||||
return None
|
return None
|
||||||
|
records = _first_present(data, "contracts", "records", default=[])
|
||||||
return ContractsData(
|
return ContractsData(
|
||||||
contracts=tuple(self._parse_contract(c) for c in data.get("contracts", [])),
|
contracts=tuple(self._parse_contract(c) for c in records),
|
||||||
pagination=self._parse_pagination(data.get("pagination")),
|
pagination=self._parse_pagination(data.get("pagination") or data),
|
||||||
total_sum=data.get("total_sum"),
|
total_sum=data.get("total_sum"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1278,32 +1303,52 @@ class CheckoClient:
|
|||||||
|
|
||||||
def _parse_inspection(self, data: dict) -> Inspection:
|
def _parse_inspection(self, data: dict) -> Inspection:
|
||||||
"""Парсинг проверки."""
|
"""Парсинг проверки."""
|
||||||
|
authority = _first_present(data, "authority", "оргконтр", default={})
|
||||||
|
objects = _first_present(data, "objects", "объекты", default=[])
|
||||||
|
results = [
|
||||||
|
str(item.get("результ"))
|
||||||
|
for item in objects
|
||||||
|
if isinstance(item, dict) and item.get("результ")
|
||||||
|
]
|
||||||
return Inspection(
|
return Inspection(
|
||||||
id=data.get("id"),
|
id=_first_present(data, "id", "number"),
|
||||||
erp_id=data.get("erp_id"),
|
erp_id=_first_present(data, "erp_id", "number"),
|
||||||
plan_date_from=data.get("plan_date_from"),
|
plan_date_from=_first_present(data, "plan_date_from", "start_date"),
|
||||||
plan_date_to=data.get("plan_date_to"),
|
plan_date_to=_first_present(data, "plan_date_to", "датаоконч"),
|
||||||
actual_date_from=data.get("actual_date_from"),
|
actual_date_from=data.get("actual_date_from"),
|
||||||
actual_date_to=data.get("actual_date_to"),
|
actual_date_to=data.get("actual_date_to"),
|
||||||
type=data.get("type"),
|
type=_first_present(data, "type", "типпров"),
|
||||||
form=data.get("form"),
|
form=_first_present(data, "form", "типрасп"),
|
||||||
status=data.get("status"),
|
status=data.get("status"),
|
||||||
authority_name=data.get("authority_name"),
|
authority_name=_first_present(
|
||||||
authority_ogrn=data.get("authority_ogrn"),
|
data,
|
||||||
subject=data.get("subject"),
|
"authority_name",
|
||||||
result=data.get("result"),
|
default=authority.get("name") if isinstance(authority, dict) else None,
|
||||||
violations_found=data.get("violations_found", False),
|
),
|
||||||
|
authority_ogrn=_first_present(
|
||||||
|
data,
|
||||||
|
"authority_ogrn",
|
||||||
|
default=authority.get("ogrn") if isinstance(authority, dict) else None,
|
||||||
|
),
|
||||||
|
subject=_first_present(data, "subject", "цель"),
|
||||||
|
result=_first_present(
|
||||||
|
data,
|
||||||
|
"result",
|
||||||
|
default="; ".join(results) or None,
|
||||||
|
),
|
||||||
|
violations_found=bool(
|
||||||
|
_first_present(data, "violations_found", "наруш", default=False)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _parse_inspections_data(self, data: dict | None) -> InspectionsData | None:
|
def _parse_inspections_data(self, data: dict | None) -> InspectionsData | None:
|
||||||
"""Парсинг данных о проверках."""
|
"""Парсинг данных о проверках."""
|
||||||
if not data:
|
if not data:
|
||||||
return None
|
return None
|
||||||
|
records = _first_present(data, "inspections", "records", default=[])
|
||||||
return InspectionsData(
|
return InspectionsData(
|
||||||
inspections=tuple(
|
inspections=tuple(self._parse_inspection(i) for i in records),
|
||||||
self._parse_inspection(i) for i in data.get("inspections", [])
|
pagination=self._parse_pagination(data.get("pagination") or data),
|
||||||
),
|
|
||||||
pagination=self._parse_pagination(data.get("pagination")),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_inspections(self, request: InspectionsRequest) -> InspectionsResponse:
|
def get_inspections(self, request: InspectionsRequest) -> InspectionsResponse:
|
||||||
@@ -1465,20 +1510,26 @@ class CheckoClient:
|
|||||||
def _parse_legal_case(self, data: dict) -> LegalCase:
|
def _parse_legal_case(self, data: dict) -> LegalCase:
|
||||||
"""Парсинг арбитражного дела."""
|
"""Парсинг арбитражного дела."""
|
||||||
return LegalCase(
|
return LegalCase(
|
||||||
case_number=data.get("case_number", ""),
|
case_number=_first_present(data, "case_number", "number", default=""),
|
||||||
court_name=data.get("court_name"),
|
court_name=_first_present(data, "court_name", "суд"),
|
||||||
type=data.get("type"),
|
type=_first_present(data, "type", "type_name"),
|
||||||
category=data.get("category"),
|
category=data.get("category"),
|
||||||
status=data.get("status"),
|
status=_first_present(
|
||||||
filing_date=data.get("filing_date"),
|
data,
|
||||||
|
"status",
|
||||||
|
default="active" if data.get("практив") else None,
|
||||||
|
),
|
||||||
|
filing_date=_first_present(data, "filing_date", "date"),
|
||||||
result_date=data.get("result_date"),
|
result_date=data.get("result_date"),
|
||||||
claim_amount=data.get("claim_amount"),
|
claim_amount=_first_present(data, "claim_amount", "суммиск"),
|
||||||
awarded_amount=data.get("awarded_amount"),
|
awarded_amount=data.get("awarded_amount"),
|
||||||
plaintiffs=tuple(
|
plaintiffs=tuple(
|
||||||
self._parse_case_party(p) for p in data.get("plaintiffs", [])
|
self._parse_case_party(p)
|
||||||
|
for p in _first_present(data, "plaintiffs", "ист", default=[])
|
||||||
),
|
),
|
||||||
defendants=tuple(
|
defendants=tuple(
|
||||||
self._parse_case_party(d) for d in data.get("defendants", [])
|
self._parse_case_party(d)
|
||||||
|
for d in _first_present(data, "defendants", "ответ", default=[])
|
||||||
),
|
),
|
||||||
third_parties=tuple(
|
third_parties=tuple(
|
||||||
self._parse_case_party(t) for t in data.get("third_parties", [])
|
self._parse_case_party(t) for t in data.get("third_parties", [])
|
||||||
@@ -1486,17 +1537,18 @@ class CheckoClient:
|
|||||||
instances=tuple(
|
instances=tuple(
|
||||||
self._parse_case_instance(i) for i in data.get("instances", [])
|
self._parse_case_instance(i) for i in data.get("instances", [])
|
||||||
),
|
),
|
||||||
url=data.get("url"),
|
url=_first_present(data, "url", "стркад"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _parse_legal_cases_data(self, data: dict | None) -> LegalCasesData | None:
|
def _parse_legal_cases_data(self, data: dict | None) -> LegalCasesData | None:
|
||||||
"""Парсинг данных о делах."""
|
"""Парсинг данных о делах."""
|
||||||
if not data:
|
if not data:
|
||||||
return None
|
return None
|
||||||
|
records = _first_present(data, "cases", "records", default=[])
|
||||||
return LegalCasesData(
|
return LegalCasesData(
|
||||||
cases=tuple(self._parse_legal_case(c) for c in data.get("cases", [])),
|
cases=tuple(self._parse_legal_case(c) for c in records),
|
||||||
pagination=self._parse_pagination(data.get("pagination")),
|
pagination=self._parse_pagination(data.get("pagination") or data),
|
||||||
total_claim_amount=data.get("total_claim_amount"),
|
total_claim_amount=_first_present(data, "total_claim_amount", "общсуммиск"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_legal_cases(self, request: LegalCasesRequest) -> LegalCasesResponse:
|
def get_legal_cases(self, request: LegalCasesRequest) -> LegalCasesResponse:
|
||||||
|
|||||||
@@ -436,6 +436,52 @@ class CheckoClientApiTest(SimpleTestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data.contracts[0].registry_number, contract_number)
|
self.assertEqual(response.data.contracts[0].registry_number, contract_number)
|
||||||
|
|
||||||
|
def test_get_contracts_supports_checko_russian_response(self):
|
||||||
|
inn = _digits(10)
|
||||||
|
contract_number = _digits(12)
|
||||||
|
|
||||||
|
with TestHTTPServer() as server:
|
||||||
|
server.add_json(
|
||||||
|
"/v2/contracts",
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"ЗапВсего": 1,
|
||||||
|
"СтрВсего": 1,
|
||||||
|
"СтрТекущ": 1,
|
||||||
|
"Записи": [
|
||||||
|
{
|
||||||
|
"РегНомер": contract_number,
|
||||||
|
"СтрЕИС": "https://zakupki.gov.ru/contract/1",
|
||||||
|
"Дата": "2026-01-10",
|
||||||
|
"ДатаИсп": "2026-12-31",
|
||||||
|
"Цена": 125000,
|
||||||
|
"Заказ": {
|
||||||
|
"ИНН": inn,
|
||||||
|
"НаимПолн": "Тестовый заказчик",
|
||||||
|
},
|
||||||
|
"Постав": {
|
||||||
|
"ИНН": _digits(10),
|
||||||
|
"НаимСокр": "Тестовый поставщик",
|
||||||
|
},
|
||||||
|
"Объекты": [{"Наим": "Поставка оборудования"}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"meta": _meta_ok(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
client = _client_for(server)
|
||||||
|
response = client.get_contracts(
|
||||||
|
ContractsRequest(inn=inn, law=ContractLaw.FZ44)
|
||||||
|
)
|
||||||
|
|
||||||
|
contract = response.data.contracts[0]
|
||||||
|
self.assertEqual(contract.registry_number, contract_number)
|
||||||
|
self.assertEqual(contract.subject, "Поставка оборудования")
|
||||||
|
self.assertEqual(contract.customer.inn, inn)
|
||||||
|
self.assertEqual(len(contract.suppliers), 1)
|
||||||
|
self.assertEqual(response.data.pagination.total_records, 1)
|
||||||
|
|
||||||
def test_get_legal_cases(self):
|
def test_get_legal_cases(self):
|
||||||
inn = "".join(str(fake.random_int(0, 9)) for _ in range(10))
|
inn = "".join(str(fake.random_int(0, 9)) for _ in range(10))
|
||||||
case_number = fake.bothify(text="A-####/####")
|
case_number = fake.bothify(text="A-####/####")
|
||||||
@@ -465,6 +511,47 @@ class CheckoClientApiTest(SimpleTestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data.cases[0].case_number, case_number)
|
self.assertEqual(response.data.cases[0].case_number, case_number)
|
||||||
|
|
||||||
|
def test_get_legal_cases_supports_checko_russian_response(self):
|
||||||
|
inn = _digits(10)
|
||||||
|
case_number = "А40-12345/2026"
|
||||||
|
|
||||||
|
with TestHTTPServer() as server:
|
||||||
|
server.add_json(
|
||||||
|
"/v2/legal-cases",
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"ЗапВсего": 1,
|
||||||
|
"СтрВсего": 1,
|
||||||
|
"СтрТекущ": 1,
|
||||||
|
"ОбщСуммИск": 250000,
|
||||||
|
"Записи": [
|
||||||
|
{
|
||||||
|
"Номер": case_number,
|
||||||
|
"СтрКАД": "https://kad.arbitr.ru/Card/1",
|
||||||
|
"Дата": "2026-02-03",
|
||||||
|
"Тип": "Гражданское",
|
||||||
|
"ПрАктив": True,
|
||||||
|
"Суд": "Арбитражный суд города Москвы",
|
||||||
|
"Ист": [{"ИНН": inn, "Наим": "Истец"}],
|
||||||
|
"Ответ": [{"ИНН": _digits(10), "Наим": "Ответчик"}],
|
||||||
|
"СуммИск": 250000,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"meta": _meta_ok(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
client = _client_for(server)
|
||||||
|
response = client.get_legal_cases(LegalCasesRequest(inn=inn))
|
||||||
|
|
||||||
|
legal_case = response.data.cases[0]
|
||||||
|
self.assertEqual(legal_case.case_number, case_number)
|
||||||
|
self.assertEqual(legal_case.claim_amount, 250000)
|
||||||
|
self.assertEqual(legal_case.plaintiffs[0].inn, inn)
|
||||||
|
self.assertEqual(legal_case.status, "active")
|
||||||
|
self.assertEqual(response.data.total_claim_amount, 250000)
|
||||||
|
self.assertEqual(response.data.pagination.total_records, 1)
|
||||||
|
|
||||||
def test_api_error_handling(self):
|
def test_api_error_handling(self):
|
||||||
with TestHTTPServer() as server:
|
with TestHTTPServer() as server:
|
||||||
server.add_json(
|
server.add_json(
|
||||||
@@ -583,6 +670,49 @@ class CheckoClientExtraEndpointsTest(SimpleTestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data.inspections[0].id, inspection_id)
|
self.assertEqual(response.data.inspections[0].id, inspection_id)
|
||||||
|
|
||||||
|
def test_get_inspections_supports_checko_russian_response(self):
|
||||||
|
inn = _digits(10)
|
||||||
|
inspection_number = "77260041000101234567"
|
||||||
|
|
||||||
|
with TestHTTPServer() as server:
|
||||||
|
server.add_json(
|
||||||
|
"/v2/inspections",
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"ЗапВсего": 1,
|
||||||
|
"СтрВсего": 1,
|
||||||
|
"СтрТекущ": 1,
|
||||||
|
"Записи": [
|
||||||
|
{
|
||||||
|
"Номер": inspection_number,
|
||||||
|
"Статус": "Завершено",
|
||||||
|
"Наруш": True,
|
||||||
|
"ТипРасп": "Внеплановая",
|
||||||
|
"ТипПров": "Выездная",
|
||||||
|
"ДатаНач": "2026-03-01",
|
||||||
|
"ДатаОконч": "2026-03-05",
|
||||||
|
"Цель": "Контроль обязательных требований",
|
||||||
|
"ОргКонтр": {
|
||||||
|
"ОГРН": _digits(13),
|
||||||
|
"Наим": "Контрольный орган",
|
||||||
|
},
|
||||||
|
"Объекты": [{"Результ": "Выявлены нарушения"}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"meta": _meta_ok(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
client = _client_for(server)
|
||||||
|
response = client.get_inspections(InspectionsRequest(inn=inn))
|
||||||
|
|
||||||
|
inspection = response.data.inspections[0]
|
||||||
|
self.assertEqual(inspection.id, inspection_number)
|
||||||
|
self.assertEqual(inspection.authority_name, "Контрольный орган")
|
||||||
|
self.assertEqual(inspection.result, "Выявлены нарушения")
|
||||||
|
self.assertTrue(inspection.violations_found)
|
||||||
|
self.assertEqual(response.data.pagination.total_records, 1)
|
||||||
|
|
||||||
def test_iter_inspections_pagination(self):
|
def test_iter_inspections_pagination(self):
|
||||||
inn = _digits(10)
|
inn = _digits(10)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user