diff --git a/src/apps/parsers/clients/checko/client.py b/src/apps/parsers/clients/checko/client.py index fb8710e..3f25cf5 100644 --- a/src/apps/parsers/clients/checko/client.py +++ b/src/apps/parsers/clients/checko/client.py @@ -234,6 +234,14 @@ def _map_ru_keys(data: dict | None) -> dict | None: 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 class CheckoClient: """ @@ -471,7 +479,7 @@ class CheckoClient: def _parse_bankruptcy_message(self, data: dict) -> BankruptcyMessage: """Парсинг сообщения о банкротстве.""" return BankruptcyMessage( - type=data.get("type", ""), + type=_first_present(data, "type", "type_name", default=""), date=data.get("date", ""), case_number=data.get("case_number"), ) @@ -1183,37 +1191,54 @@ class CheckoClient: ogrn=data.get("ogrn"), inn=data.get("inn"), kpp=data.get("kpp"), - name=data.get("name"), + name=_first_present(data, "name", "short_name", "full_name"), region_code=data.get("region_code"), ) 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( - registry_number=data.get("registry_number", ""), - publish_date=data.get("publish_date"), - sign_date=data.get("sign_date"), - execution_date=data.get("execution_date"), - price=data.get("price"), + registry_number=_first_present( + data, "registry_number", "регномер", default="" + ), + publish_date=_first_present(data, "publish_date", "date"), + 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"), status=data.get("status"), - subject=data.get("subject"), + subject=subject, law=data.get("law"), purchase_number=data.get("purchase_number"), - customer=self._parse_contract_party(data.get("customer")), - suppliers=tuple( - self._parse_contract_party(s) for s in data.get("suppliers", []) if s + customer=self._parse_contract_party( + _first_present(data, "customer", "заказ") ), - 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: """Парсинг данных о контрактах.""" if not data: return None + records = _first_present(data, "contracts", "records", default=[]) return ContractsData( - contracts=tuple(self._parse_contract(c) for c in data.get("contracts", [])), - pagination=self._parse_pagination(data.get("pagination")), + contracts=tuple(self._parse_contract(c) for c in records), + pagination=self._parse_pagination(data.get("pagination") or data), total_sum=data.get("total_sum"), ) @@ -1278,32 +1303,52 @@ class CheckoClient: 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( - id=data.get("id"), - erp_id=data.get("erp_id"), - plan_date_from=data.get("plan_date_from"), - plan_date_to=data.get("plan_date_to"), + id=_first_present(data, "id", "number"), + erp_id=_first_present(data, "erp_id", "number"), + plan_date_from=_first_present(data, "plan_date_from", "start_date"), + plan_date_to=_first_present(data, "plan_date_to", "датаоконч"), actual_date_from=data.get("actual_date_from"), actual_date_to=data.get("actual_date_to"), - type=data.get("type"), - form=data.get("form"), + type=_first_present(data, "type", "типпров"), + form=_first_present(data, "form", "типрасп"), status=data.get("status"), - authority_name=data.get("authority_name"), - authority_ogrn=data.get("authority_ogrn"), - subject=data.get("subject"), - result=data.get("result"), - violations_found=data.get("violations_found", False), + authority_name=_first_present( + data, + "authority_name", + default=authority.get("name") if isinstance(authority, dict) else None, + ), + 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: """Парсинг данных о проверках.""" if not data: return None + records = _first_present(data, "inspections", "records", default=[]) return InspectionsData( - inspections=tuple( - self._parse_inspection(i) for i in data.get("inspections", []) - ), - pagination=self._parse_pagination(data.get("pagination")), + inspections=tuple(self._parse_inspection(i) for i in records), + pagination=self._parse_pagination(data.get("pagination") or data), ) def get_inspections(self, request: InspectionsRequest) -> InspectionsResponse: @@ -1465,20 +1510,26 @@ class CheckoClient: def _parse_legal_case(self, data: dict) -> LegalCase: """Парсинг арбитражного дела.""" return LegalCase( - case_number=data.get("case_number", ""), - court_name=data.get("court_name"), - type=data.get("type"), + case_number=_first_present(data, "case_number", "number", default=""), + court_name=_first_present(data, "court_name", "суд"), + type=_first_present(data, "type", "type_name"), category=data.get("category"), - status=data.get("status"), - filing_date=data.get("filing_date"), + status=_first_present( + data, + "status", + default="active" if data.get("практив") else None, + ), + filing_date=_first_present(data, "filing_date", "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"), 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( - 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( self._parse_case_party(t) for t in data.get("third_parties", []) @@ -1486,17 +1537,18 @@ class CheckoClient: instances=tuple( 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: """Парсинг данных о делах.""" if not data: return None + records = _first_present(data, "cases", "records", default=[]) return LegalCasesData( - cases=tuple(self._parse_legal_case(c) for c in data.get("cases", [])), - pagination=self._parse_pagination(data.get("pagination")), - total_claim_amount=data.get("total_claim_amount"), + cases=tuple(self._parse_legal_case(c) for c in records), + pagination=self._parse_pagination(data.get("pagination") or data), + total_claim_amount=_first_present(data, "total_claim_amount", "общсуммиск"), ) def get_legal_cases(self, request: LegalCasesRequest) -> LegalCasesResponse: diff --git a/tests/apps/parsers/test_checko.py b/tests/apps/parsers/test_checko.py index 46f9f48..9671f7a 100644 --- a/tests/apps/parsers/test_checko.py +++ b/tests/apps/parsers/test_checko.py @@ -436,6 +436,52 @@ class CheckoClientApiTest(SimpleTestCase): 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): inn = "".join(str(fake.random_int(0, 9)) for _ in range(10)) case_number = fake.bothify(text="A-####/####") @@ -465,6 +511,47 @@ class CheckoClientApiTest(SimpleTestCase): 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): with TestHTTPServer() as server: server.add_json( @@ -583,6 +670,49 @@ class CheckoClientExtraEndpointsTest(SimpleTestCase): 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): inn = _digits(10)