feat: import gosedo and media news sources
All checks were successful
CI/CD Pipeline / Code Quality Checks (push) Successful in 2m54s
CI/CD Pipeline / Run Tests (push) Successful in 5m15s
CI/CD Pipeline / Build and Push Dev Images (push) Successful in 31s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 34s

This commit is contained in:
2026-08-13 14:12:53 +02:00
parent 1662f49810
commit 46d0971320
11 changed files with 929 additions and 26 deletions

View File

@@ -20,6 +20,7 @@ from apps.external_data.models import (
ArbitrationCase,
BankruptcyProcedure,
DefenseUnreliableSupplier,
ElectronicDocumentExchangeEntry,
FinancialReport,
FinancialReportLine,
IndustrialCertificate,
@@ -27,6 +28,7 @@ from apps.external_data.models import (
InformationSecurityRegistryEntry,
LaborVacancy,
ManufacturerRegistryEntry,
MediaMention,
ProsecutorCheck,
PublicProcurement,
)
@@ -58,6 +60,18 @@ def build_exchange_archive(
schema_version: int = ExchangePackageImportService.SUPPORTED_SCHEMA_VERSION,
) -> SimpleUploadedFile:
"""Build encrypted exchange archive compatible with import service."""
provided_data = data or {}
normalized_data = {
section: provided_data.get(section, [])
for section in ExchangePackageImportService.SECTION_KEYS
}
normalized_data.update(
{
section: rows
for section, rows in provided_data.items()
if section not in normalized_data
}
)
payload = {
"format": ExchangePackageImportService.PAYLOAD_FORMAT,
"schema_version": schema_version,
@@ -66,9 +80,9 @@ def build_exchange_archive(
"source_system": "mostovik-dev",
"produced_at": "2026-04-07T12:00:00+00:00",
"schema_version": schema_version,
"sections": list((data or {}).keys()),
"sections": list(ExchangePackageImportService.SECTION_KEYS),
},
"data": data or {},
"data": normalized_data,
}
payload_bytes = json.dumps(
payload,
@@ -369,6 +383,41 @@ def build_exchange_payload() -> dict[str, list[dict[str, object]]]:
"source_url": "https://trudvsem.ru/vacancy/001",
}
],
"electronic_document_exchange": [
{
"organization_inn": "7707083893",
"external_id": "gosedo:001",
"record_type": "participant",
"title": "АО Альфа",
"status": "active",
"participant_status": "active",
"attestation_status": "attested",
"registration_type": "ogrn",
"registration_number": "1027700132195",
"source_version": "633",
"source_published_at": "2026-08-10",
"source_row_class": "pt-on",
"responsible_phones": [],
"responsible_emails": [],
"extra_fields": {},
"load_batch": 8,
}
],
"media_mentions": [
{
"organization_inn": "7707083893",
"external_id": "a" * 64,
"news_source": "Тестовое СМИ",
"title": "АО Альфа расширяет производство",
"sentiment": "positive",
"published_at": "2026-07-01",
"source_url": "https://example.test/news/1",
"excerpt_lines": ["СМИ", "Заголовок", "Лид", "Продолжение"],
"full_text": "СМИ\nЗаголовок\nЛид\nПродолжение\nПолный текст",
"okpo": "12345678",
"load_batch": 9,
}
],
}
@@ -481,6 +530,8 @@ class ExchangePackageApiTest(APITestCase):
self.assertEqual(DefenseUnreliableSupplier.objects.count(), 1)
self.assertEqual(InformationSecurityRegistryEntry.objects.count(), 1)
self.assertEqual(LaborVacancy.objects.count(), 1)
self.assertEqual(ElectronicDocumentExchangeEntry.objects.count(), 1)
self.assertEqual(MediaMention.objects.count(), 1)
self.assertEqual(
response.data["result"]["bankruptcy_procedures"]["created"],
1,
@@ -499,6 +550,11 @@ class ExchangePackageApiTest(APITestCase):
1,
)
self.assertEqual(response.data["result"]["labor_vacancies"]["created"], 1)
self.assertEqual(
response.data["result"]["electronic_document_exchange"]["created"],
1,
)
self.assertEqual(response.data["result"]["media_mentions"]["created"], 1)
organization = Organization.objects.get(inn="7707083893")
self.assertEqual(organization.name, "АО Альфа Обновленная")
@@ -634,7 +690,7 @@ class ExchangePackageApiTest(APITestCase):
def test_upload_rejects_legacy_schema_version(self):
archive = build_exchange_archive(
package_id="pkg-legacy-schema-version",
schema_version=2,
schema_version=3,
data=build_exchange_payload(),
)
@@ -646,7 +702,7 @@ class ExchangePackageApiTest(APITestCase):
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("schema_version 3", response.data["file"][0])
self.assertIn("schema_version 4", response.data["file"][0])
self.assertEqual(Organization.objects.count(), 0)
def test_upload_rejects_legacy_registry_memberships_section(self):
@@ -697,7 +753,7 @@ class ExchangePackageApiTest(APITestCase):
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("schema_version 3", response.data["file"][0])
self.assertIn("schema_version 4", response.data["file"][0])
self.assertEqual(Organization.objects.count(), 0)
def test_upload_rejects_external_rows_for_organization_absent_from_package(self):
@@ -769,6 +825,61 @@ class ExchangePackageApiTest(APITestCase):
duplicate_import = ExchangePackageImport.objects.order_by("-created_at").first()
self.assertIsNotNone(duplicate_import.duplicate_of)
def test_v4_gosedo_is_scoped_replacement_and_media_is_upsert_only(self):
first_archive = build_exchange_archive(
package_id="pkg-v4-first",
data=build_exchange_payload(),
)
first_response = self.client.post(
self.url,
{"file": first_archive},
format="multipart",
HTTP_X_EXCHANGE_TOKEN=TEST_TOKEN,
)
self.assertEqual(first_response.status_code, status.HTTP_201_CREATED)
self.assertEqual(ElectronicDocumentExchangeEntry.objects.count(), 1)
self.assertEqual(MediaMention.objects.count(), 1)
outside_scope = Organization.objects.create(
inn="7707083999",
name="АО Вне пакета",
)
ElectronicDocumentExchangeEntry.objects.create(
organization=outside_scope,
external_id="outside-scope",
record_type=ElectronicDocumentExchangeEntry.RecordType.PARTICIPANT,
title=outside_scope.name,
source_version="632",
source_published_at="2026-08-01",
)
second_payload = build_exchange_payload()
second_payload["electronic_document_exchange"] = []
second_payload["media_mentions"] = []
second_archive = build_exchange_archive(
package_id="pkg-v4-second",
data=second_payload,
)
second_response = self.client.post(
self.url,
{"file": second_archive},
format="multipart",
HTTP_X_EXCHANGE_TOKEN=TEST_TOKEN,
)
self.assertEqual(second_response.status_code, status.HTTP_201_CREATED)
self.assertEqual(
second_response.data["result"]["electronic_document_exchange"]["deleted"],
1,
)
self.assertEqual(ElectronicDocumentExchangeEntry.objects.count(), 1)
self.assertTrue(
ElectronicDocumentExchangeEntry.objects.filter(
organization=outside_scope,
external_id="outside-scope",
).exists()
)
self.assertEqual(MediaMention.objects.count(), 1)
def test_upload_replaces_stale_financial_report_lines(self):
first_payload = build_exchange_payload()
first_archive = build_exchange_archive(