feat: migrate parser data to source records
Some checks failed
CI/CD Pipeline / Quality Gate (push) Failing after 14s
CI/CD Pipeline / Build and Push Images (push) Has been skipped
CI/CD Pipeline / Deploy Dev in Dokploy (push) Has been skipped
CI/CD Pipeline / Internal Notify (push) Successful in 0s

This commit is contained in:
2026-05-19 20:21:31 +02:00
parent 1c7c7238be
commit b8a18d6da4
46 changed files with 2689 additions and 6179 deletions

View File

@@ -14,14 +14,23 @@ from organizations.name_normalization import normalize_organization_name
class SourceGroup(models.TextChoices):
"""Product-level organization source groups."""
FINANCIAL_INDICATORS = "financial_indicators", _("Финансово-экономические показатели")
FINANCIAL_INDICATORS = (
"financial_indicators",
_("Финансово-экономические показатели"),
)
GOVERNMENT_PROCUREMENTS = "government_procurements", _("Государственные закупки")
INDUSTRIAL_PRODUCTION = "industrial_production", _("Производители и продукция России")
INDUSTRIAL_PRODUCTION = (
"industrial_production",
_("Производители и продукция России"),
)
PLANNED_INSPECTIONS = "planned_inspections", _("Плановые проверки")
BANKRUPTCY = "bankruptcy", _("Сведения о процедурах банкротства")
DEFENSE_SUPPLIERS = "defense_suppliers", _("Недобросовестные поставщики ГОЗ")
ARBITRATION = "arbitration", _("Арбитражные дела")
SECURITY_REGISTRIES = "security_registries", _("Реестры по информационной безопасности")
SECURITY_REGISTRIES = (
"security_registries",
_("Реестры по информационной безопасности"),
)
VACANCIES = "vacancies", _("Вакансии")
@@ -472,6 +481,100 @@ class OrganizationSourceRecord(models.Model):
def __str__(self) -> str:
return self.title or self.external_id or str(self.uid)
@property
def id(self):
"""Compatibility alias for legacy parser services that exposed integer id."""
return self.pk
@property
def lines(self):
"""Compatibility alias for financial reports stored as source records."""
return self.financial_lines
@property
def inn(self) -> str:
"""Return the canonical organization INN or source payload INN."""
organization = self.extension.organization
return organization.inn or str((self.payload or {}).get("inn") or "")
@property
def kpp(self) -> str:
"""Return the canonical organization KPP or source payload KPP."""
organization = self.extension.organization
return organization.kpp or str((self.payload or {}).get("kpp") or "")
@property
def ogrn(self) -> str:
"""Return the canonical organization OGRN or source payload OGRN."""
organization = self.extension.organization
return organization.ogrn or str((self.payload or {}).get("ogrn") or "")
@property
def ogrip(self) -> str:
"""Return the canonical organization OGRIP or source payload OGRIP."""
organization = self.extension.organization
return organization.ogrip or str((self.payload or {}).get("ogrip") or "")
@property
def registry_organization(self):
"""Best-effort active registry organization matched by canonical identity."""
from django.db.models import CharField
from django.db.models.functions import Cast
from registers.models import Organization as RegistryOrganization
from registers.models import RegistryMembershipPeriod
def _registry_numeric_values(value: str) -> list[str]:
stripped = str(value or "").lstrip("0")
return [value, stripped] if stripped and stripped != value else [value]
identity_filter = Q()
inn = self.inn
ogrn = self.ogrn
ogrip = self.ogrip
if inn:
identity_filter |= Q(registry_inn_text__in=_registry_numeric_values(inn))
if ogrn:
identity_filter |= Q(registry_ogrn_text__in=_registry_numeric_values(ogrn))
if ogrip:
identity_filter |= Q(registry_ogrn_text__in=_registry_numeric_values(ogrip))
if not identity_filter:
return None
membership = (
RegistryMembershipPeriod.objects.filter(ended_at__isnull=True)
.select_related("organization")
.annotate(
registry_inn_text=Cast(
"organization__mn_inn",
output_field=CharField(),
),
registry_ogrn_text=Cast(
"organization__mn_ogrn",
output_field=CharField(),
),
)
.filter(identity_filter)
.order_by("organization__pn_name", "organization_id")
.first()
)
if membership is not None:
return membership.organization
return (
RegistryOrganization.objects.annotate(
registry_inn_text=Cast("mn_inn", output_field=CharField()),
registry_ogrn_text=Cast("mn_ogrn", output_field=CharField()),
)
.filter(identity_filter)
.order_by("pn_name", "id")
.first()
)
@property
def registry_organization_id(self):
registry_organization = self.registry_organization
return registry_organization.id if registry_organization is not None else None
class OrganizationSourceFinancialLine(models.Model):
"""Structured financial report line under a source record."""