feat: use authoritative organization directory
This commit is contained in:
@@ -20,6 +20,7 @@ from organizations.models import (
|
||||
OrganizationSourceRecord,
|
||||
)
|
||||
from organizations.name_normalization import normalize_organization_name
|
||||
from organizations.resolver import OrganizationDirectoryResolver
|
||||
from organizations.source_cache import invalidate_source_data_cache
|
||||
from organizations.source_groups import (
|
||||
SourceGroupDescriptor,
|
||||
@@ -72,12 +73,16 @@ class OrganizationSourceIngestionResult:
|
||||
created_financial_lines: int = 0
|
||||
updated_financial_lines: int = 0
|
||||
unresolved: int = 0
|
||||
skipped_unmatched: int = 0
|
||||
skipped_ambiguous: int = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _NormalizedRecordInput:
|
||||
index: int
|
||||
record: SourceRecordInput
|
||||
rn: str
|
||||
okpo: str
|
||||
inn: str
|
||||
kpp: str
|
||||
ogrn: str
|
||||
@@ -131,8 +136,9 @@ class OrganizationSourceIngestionService:
|
||||
normalized_records = cls._normalize_records(records)
|
||||
(
|
||||
organizations_by_index,
|
||||
created_organizations,
|
||||
) = cls._resolve_or_create_organizations(normalized_records)
|
||||
skipped_unmatched,
|
||||
skipped_ambiguous,
|
||||
) = cls._resolve_existing_organizations(normalized_records)
|
||||
del normalized_records
|
||||
unresolved = scanned - len(organizations_by_index)
|
||||
|
||||
@@ -174,7 +180,7 @@ class OrganizationSourceIngestionService:
|
||||
|
||||
return OrganizationSourceIngestionResult(
|
||||
scanned=scanned,
|
||||
created_organizations=created_organizations,
|
||||
created_organizations=0,
|
||||
created_extensions=created_extensions,
|
||||
updated_extensions=updated_extensions,
|
||||
created_records=created_records,
|
||||
@@ -182,6 +188,8 @@ class OrganizationSourceIngestionService:
|
||||
created_financial_lines=created_financial_lines,
|
||||
updated_financial_lines=updated_financial_lines,
|
||||
unresolved=unresolved,
|
||||
skipped_unmatched=skipped_unmatched,
|
||||
skipped_ambiguous=skipped_ambiguous,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -229,6 +237,16 @@ class OrganizationSourceIngestionService:
|
||||
_NormalizedRecordInput(
|
||||
index=index,
|
||||
record=record_input,
|
||||
rn=OrganizationDirectoryResolver._digits(
|
||||
(record_input.payload or {}).get("rn")
|
||||
or (record_input.payload or {}).get("organization_rn"),
|
||||
max_length=20,
|
||||
),
|
||||
okpo=OrganizationDirectoryResolver._digits(
|
||||
(record_input.payload or {}).get("okpo")
|
||||
or (record_input.payload or {}).get("mn_okpo"),
|
||||
max_length=32,
|
||||
),
|
||||
inn=inn,
|
||||
kpp=kpp,
|
||||
ogrn=ogrn,
|
||||
@@ -238,42 +256,46 @@ class OrganizationSourceIngestionService:
|
||||
)
|
||||
return normalized_records
|
||||
|
||||
@classmethod
|
||||
def _resolve_existing_organizations(
|
||||
cls,
|
||||
normalized_records: list[_NormalizedRecordInput],
|
||||
) -> tuple[dict[int, Organization], int, int]:
|
||||
organizations_by_index: dict[int, Organization] = {}
|
||||
skipped_unmatched = 0
|
||||
skipped_ambiguous = 0
|
||||
|
||||
for record in normalized_records:
|
||||
result = OrganizationDirectoryResolver.resolve(
|
||||
OrganizationDirectoryResolver.identity(
|
||||
rn=record.rn,
|
||||
okpo=record.okpo,
|
||||
inn=record.inn,
|
||||
kpp=record.kpp,
|
||||
ogrn=record.ogrn,
|
||||
ogrip=record.ogrip,
|
||||
)
|
||||
)
|
||||
if result.organization is not None:
|
||||
organizations_by_index[record.index] = result.organization
|
||||
continue
|
||||
if result.status == "ambiguous":
|
||||
skipped_ambiguous += 1
|
||||
else:
|
||||
skipped_unmatched += 1
|
||||
|
||||
return organizations_by_index, skipped_unmatched, skipped_ambiguous
|
||||
|
||||
@classmethod
|
||||
def _resolve_or_create_organizations(
|
||||
cls,
|
||||
normalized_records: list[_NormalizedRecordInput],
|
||||
) -> tuple[dict[int, Organization], int]:
|
||||
organizations_by_index: dict[int, Organization] = {}
|
||||
|
||||
cls._resolve_organizations_by_inn_kpp(
|
||||
normalized_records,
|
||||
organizations_by_index,
|
||||
"""Compatibility wrapper; parser ingestion must not create organizations."""
|
||||
organizations, _skipped_unmatched, _skipped_ambiguous = (
|
||||
cls._resolve_existing_organizations(normalized_records)
|
||||
)
|
||||
cls._resolve_organizations_by_ogrn_or_ogrip(
|
||||
normalized_records,
|
||||
organizations_by_index,
|
||||
)
|
||||
cls._resolve_organizations_by_unique_inn(
|
||||
normalized_records,
|
||||
organizations_by_index,
|
||||
)
|
||||
cls._resolve_organizations_by_exact_name(
|
||||
normalized_records,
|
||||
organizations_by_index,
|
||||
)
|
||||
|
||||
(
|
||||
organizations_by_index,
|
||||
created_organizations,
|
||||
) = cls._create_missing_organizations(
|
||||
normalized_records,
|
||||
organizations_by_index,
|
||||
)
|
||||
cls._update_resolved_organization_identities(
|
||||
normalized_records,
|
||||
organizations_by_index,
|
||||
)
|
||||
return organizations_by_index, created_organizations
|
||||
return organizations, 0
|
||||
|
||||
@classmethod
|
||||
def _resolve_organizations_by_inn_kpp(
|
||||
@@ -875,34 +897,16 @@ class OrganizationSourceIngestionService:
|
||||
ogrn=record_input.ogrn,
|
||||
ogrip=record_input.ogrip,
|
||||
)
|
||||
organization = cls._resolve_organization(
|
||||
inn=inn,
|
||||
kpp=kpp,
|
||||
ogrn=ogrn,
|
||||
ogrip=ogrip,
|
||||
organization_name=record_input.organization_name,
|
||||
)
|
||||
if organization is not None:
|
||||
return organization, False
|
||||
|
||||
name = (
|
||||
str(record_input.organization_name or "").strip()
|
||||
or str(record_input.title or "").strip()
|
||||
or str(record_input.external_id or "").strip()
|
||||
)
|
||||
if not name:
|
||||
return None, False
|
||||
|
||||
return (
|
||||
Organization.objects.create(
|
||||
name=name,
|
||||
result = OrganizationDirectoryResolver.resolve(
|
||||
OrganizationDirectoryResolver.from_payload(
|
||||
payload=record_input.payload,
|
||||
inn=inn,
|
||||
kpp=kpp,
|
||||
ogrn=ogrn,
|
||||
ogrip=ogrip,
|
||||
),
|
||||
True,
|
||||
)
|
||||
)
|
||||
return result.organization, False
|
||||
|
||||
@classmethod
|
||||
def _resolve_organization(
|
||||
@@ -914,23 +918,16 @@ class OrganizationSourceIngestionService:
|
||||
ogrip: str,
|
||||
organization_name: str,
|
||||
) -> Organization | None:
|
||||
for resolver in (
|
||||
cls._resolve_by_inn_kpp,
|
||||
cls._resolve_by_ogrn_or_ogrip,
|
||||
cls._resolve_by_ogrip,
|
||||
cls._resolve_by_unique_inn,
|
||||
cls._resolve_by_exact_normalized_name,
|
||||
):
|
||||
organization = resolver(
|
||||
result = OrganizationDirectoryResolver.resolve(
|
||||
OrganizationDirectoryResolver.identity(
|
||||
inn=inn,
|
||||
kpp=kpp,
|
||||
ogrn=ogrn,
|
||||
ogrip=ogrip,
|
||||
organization_name=organization_name,
|
||||
)
|
||||
if organization is not None:
|
||||
return organization
|
||||
return None
|
||||
)
|
||||
return result.organization
|
||||
|
||||
@staticmethod
|
||||
def _resolve_by_inn_kpp(
|
||||
|
||||
Reference in New Issue
Block a user