optimize 11.8x
This commit is contained in:
parent
510d668ab8
commit
5935eceadf
@ -82,18 +82,42 @@ from .column_constants import (
|
|||||||
from .column_registry import COLUMNS, ColumnMeta
|
from .column_registry import COLUMNS, ColumnMeta
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class _CompiledCandidate:
|
||||||
|
raw: str
|
||||||
|
normalized: str
|
||||||
|
short: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class MappingRule:
|
class MappingRule:
|
||||||
column_index: int
|
column_index: int
|
||||||
column_id: str
|
column_id: str
|
||||||
column_name: str
|
column_name: str
|
||||||
candidate_keys: tuple[str, ...]
|
compiled_candidates: tuple[_CompiledCandidate, ...]
|
||||||
source: str = "any"
|
source: str = "any"
|
||||||
allow_direct_mapping: bool = True
|
allow_direct_mapping: bool = True
|
||||||
allow_short_lookup: bool = True
|
allow_short_lookup: bool = True
|
||||||
structured_value: bool = False
|
structured_value: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class _LookupEntry:
|
||||||
|
raw_path: str
|
||||||
|
normalized_path: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class _PayloadLookup:
|
||||||
|
values: dict[str, str]
|
||||||
|
entries: tuple[_LookupEntry, ...]
|
||||||
|
normalized_paths: tuple[str, ...]
|
||||||
|
normalized_exact_first: dict[str, str]
|
||||||
|
normalized_suffix_first: dict[str, str]
|
||||||
|
raw_short_first: dict[str, str]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class StructuredGroupRule:
|
class StructuredGroupRule:
|
||||||
structured_column: int
|
structured_column: int
|
||||||
@ -128,6 +152,17 @@ def _bank_bik_candidate_keys(bank_block: str) -> tuple[str, ...]:
|
|||||||
return tuple(candidates)
|
return tuple(candidates)
|
||||||
|
|
||||||
|
|
||||||
|
def _compile_candidates(candidates: tuple[str, ...]) -> tuple[_CompiledCandidate, ...]:
|
||||||
|
return tuple(
|
||||||
|
_CompiledCandidate(
|
||||||
|
raw=candidate,
|
||||||
|
normalized=_INDEXED_PATH_SEGMENT.sub("", candidate),
|
||||||
|
short=candidate.rsplit(".", maxsplit=1)[-1],
|
||||||
|
)
|
||||||
|
for candidate in candidates
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _build_rules(mapping_data: tuple[ColumnMeta, ...]) -> tuple[MappingRule, ...]:
|
def _build_rules(mapping_data: tuple[ColumnMeta, ...]) -> tuple[MappingRule, ...]:
|
||||||
rules: list[MappingRule] = []
|
rules: list[MappingRule] = []
|
||||||
for meta in mapping_data:
|
for meta in mapping_data:
|
||||||
@ -212,12 +247,13 @@ def _build_rules(mapping_data: tuple[ColumnMeta, ...]) -> tuple[MappingRule, ...
|
|||||||
direct_candidates = candidates
|
direct_candidates = candidates
|
||||||
if meta.structured_value and non_one_line_candidates:
|
if meta.structured_value and non_one_line_candidates:
|
||||||
direct_candidates = non_one_line_candidates
|
direct_candidates = non_one_line_candidates
|
||||||
|
candidate_keys = tuple(direct_candidates)
|
||||||
rules.append(
|
rules.append(
|
||||||
MappingRule(
|
MappingRule(
|
||||||
column_index=index,
|
column_index=index,
|
||||||
column_id=meta.column_id,
|
column_id=meta.column_id,
|
||||||
column_name=column,
|
column_name=column,
|
||||||
candidate_keys=tuple(direct_candidates),
|
compiled_candidates=_compile_candidates(candidate_keys),
|
||||||
source=meta.source_scope,
|
source=meta.source_scope,
|
||||||
allow_direct_mapping=meta.allow_direct_mapping,
|
allow_direct_mapping=meta.allow_direct_mapping,
|
||||||
allow_short_lookup=meta.allow_short_lookup,
|
allow_short_lookup=meta.allow_short_lookup,
|
||||||
@ -321,11 +357,16 @@ def build_fixed_row_by_index(
|
|||||||
COL_FILE_NAME: file_name,
|
COL_FILE_NAME: file_name,
|
||||||
COL_RECORD_ID: record_id,
|
COL_RECORD_ID: record_id,
|
||||||
}
|
}
|
||||||
merged = _merge_fields(operation_fields, participant_fields)
|
operation_lookup = _build_payload_lookup(operation_fields)
|
||||||
merged_suffix_index = _build_suffix_index(merged)
|
participant_lookup = _build_payload_lookup(participant_fields)
|
||||||
operation_suffix_index = _build_suffix_index(operation_fields)
|
participant_base_lookup, participant_eio_lookup = _partition_participant_lookup(
|
||||||
has_eio_block = _has_any_prefixed_key(
|
participant_lookup
|
||||||
participant_fields,
|
)
|
||||||
|
merged_lookup = _build_payload_lookup(
|
||||||
|
_merge_fields(operation_fields, participant_fields)
|
||||||
|
)
|
||||||
|
has_eio_block = _lookup_has_any_prefixed_key(
|
||||||
|
participant_lookup,
|
||||||
(
|
(
|
||||||
"УчастникЮЛ.СведЕИО.",
|
"УчастникЮЛ.СведЕИО.",
|
||||||
"УчастникЮЛ.БенефициарЮЛ.",
|
"УчастникЮЛ.БенефициарЮЛ.",
|
||||||
@ -338,8 +379,8 @@ def build_fixed_row_by_index(
|
|||||||
"БенефициарИНБОЮЛ.",
|
"БенефициарИНБОЮЛ.",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
has_cp_block = _has_any_prefixed_key(
|
has_cp_block = _lookup_has_any_prefixed_key(
|
||||||
operation_fields,
|
operation_lookup,
|
||||||
(
|
(
|
||||||
"СведЦП.",
|
"СведЦП.",
|
||||||
"СообщОперКО.ИнформЧасть.СведКО.Операция.СведЦП.",
|
"СообщОперКО.ИнформЧасть.СведКО.Операция.СведЦП.",
|
||||||
@ -352,34 +393,30 @@ def build_fixed_row_by_index(
|
|||||||
row[rule.column_index] = ""
|
row[rule.column_index] = ""
|
||||||
continue
|
continue
|
||||||
if rule.source == "participant":
|
if rule.source == "participant":
|
||||||
source_payload = _scope_participant_payload(
|
source_lookup = (
|
||||||
participant_fields,
|
participant_eio_lookup
|
||||||
eio_only=rule.column_index in BLOCK_EIO,
|
if rule.column_index in BLOCK_EIO
|
||||||
|
else participant_base_lookup
|
||||||
)
|
)
|
||||||
suffix_index = _build_suffix_index(source_payload)
|
|
||||||
elif rule.source == "operation":
|
elif rule.source == "operation":
|
||||||
source_payload = operation_fields
|
source_lookup = operation_lookup
|
||||||
suffix_index = operation_suffix_index
|
|
||||||
else:
|
else:
|
||||||
source_payload = merged
|
source_lookup = merged_lookup
|
||||||
suffix_index = merged_suffix_index
|
|
||||||
if rule.structured_value:
|
if rule.structured_value:
|
||||||
row[rule.column_index] = _pick_structured_value(
|
row[rule.column_index] = _pick_structured_value(
|
||||||
source_payload=source_payload,
|
lookup=source_lookup,
|
||||||
suffix_index=suffix_index,
|
candidates=rule.compiled_candidates,
|
||||||
candidates=rule.candidate_keys,
|
|
||||||
join_with_space="фио" in rule.column_name.lower(),
|
join_with_space="фио" in rule.column_name.lower(),
|
||||||
allow_short_lookup=rule.allow_short_lookup,
|
allow_short_lookup=rule.allow_short_lookup,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
row[rule.column_index] = _pick_value(
|
row[rule.column_index] = _pick_value(
|
||||||
source_payload,
|
source_lookup,
|
||||||
suffix_index,
|
rule.compiled_candidates,
|
||||||
rule.candidate_keys,
|
|
||||||
allow_short_lookup=rule.allow_short_lookup,
|
allow_short_lookup=rule.allow_short_lookup,
|
||||||
)
|
)
|
||||||
if not is_continuation:
|
if not is_continuation:
|
||||||
_apply_participant_identity_rules(row, participant_fields)
|
_apply_participant_identity_rules(row, participant_lookup)
|
||||||
# Сначала убираем запрещённые однострочные адреса, чтобы структурная
|
# Сначала убираем запрещённые однострочные адреса, чтобы структурная
|
||||||
# группа не очистила их компоненты до проверки резидентства.
|
# группа не очистила их компоненты до проверки резидентства.
|
||||||
_rule_address_one_line(row)
|
_rule_address_one_line(row)
|
||||||
@ -389,8 +426,8 @@ def build_fixed_row_by_index(
|
|||||||
row,
|
row,
|
||||||
has_eio_block=has_eio_block,
|
has_eio_block=has_eio_block,
|
||||||
has_cp_block=has_cp_block,
|
has_cp_block=has_cp_block,
|
||||||
operation_fields=operation_fields,
|
operation_lookup=operation_lookup,
|
||||||
participant_fields=participant_fields,
|
participant_lookup=participant_lookup,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_apply_continuation_validation_rules(row)
|
_apply_continuation_validation_rules(row)
|
||||||
@ -455,149 +492,172 @@ def _merge_fields(
|
|||||||
return merged
|
return merged
|
||||||
|
|
||||||
|
|
||||||
def _has_payload_block(payload: dict[str, str], block_name: str) -> bool:
|
def _normalize_indexed_path(path: str) -> str:
|
||||||
prefix = f"{block_name}."
|
"""Убирает индексы повторов, сохраняя логическую структуру XML-пути."""
|
||||||
nested_marker = f".{block_name}."
|
return _INDEXED_PATH_SEGMENT.sub("", path)
|
||||||
return any(
|
|
||||||
(normalized := _normalize_indexed_path(key)).startswith(prefix)
|
|
||||||
or nested_marker in normalized
|
def _build_payload_lookup(payload: dict[str, str]) -> _PayloadLookup:
|
||||||
for key in payload
|
entries = tuple(
|
||||||
|
_LookupEntry(
|
||||||
|
raw_path=key,
|
||||||
|
normalized_path=_normalize_indexed_path(key),
|
||||||
|
value=value,
|
||||||
|
)
|
||||||
|
for key, value in payload.items()
|
||||||
|
)
|
||||||
|
return _build_payload_lookup_from_entries(entries)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_payload_lookup_from_entries(
|
||||||
|
entries: tuple[_LookupEntry, ...],
|
||||||
|
) -> _PayloadLookup:
|
||||||
|
values = {entry.raw_path: entry.value for entry in entries}
|
||||||
|
normalized_exact_first: dict[str, str] = {}
|
||||||
|
normalized_suffix_first: dict[str, str] = {}
|
||||||
|
raw_short_first: dict[str, str] = {}
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
if not entry.value:
|
||||||
|
continue
|
||||||
|
normalized_exact_first.setdefault(entry.normalized_path, entry.value)
|
||||||
|
path_parts = entry.normalized_path.split(".")
|
||||||
|
for start_index in range(len(path_parts)):
|
||||||
|
suffix = ".".join(path_parts[start_index:])
|
||||||
|
normalized_suffix_first.setdefault(suffix, entry.value)
|
||||||
|
raw_short = entry.raw_path.rsplit(".", maxsplit=1)[-1]
|
||||||
|
raw_short_first.setdefault(raw_short, entry.value)
|
||||||
|
|
||||||
|
return _PayloadLookup(
|
||||||
|
values=values,
|
||||||
|
entries=entries,
|
||||||
|
normalized_paths=tuple(entry.normalized_path for entry in entries),
|
||||||
|
normalized_exact_first=normalized_exact_first,
|
||||||
|
normalized_suffix_first=normalized_suffix_first,
|
||||||
|
raw_short_first=raw_short_first,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _pick_scoped_payload_value(payload: dict[str, str], scoped_path: str) -> str:
|
def _is_normalized_eio_path(normalized_path: str) -> bool:
|
||||||
nested_suffix = f".{scoped_path}"
|
surrounded_path = f".{normalized_path}."
|
||||||
for key, value in payload.items():
|
return ".СведИНБОЮЛ.Учредитель." in surrounded_path or any(
|
||||||
normalized = _normalize_indexed_path(key)
|
f".{block_name}." in surrounded_path for block_name in _EIO_BLOCK_NAMES
|
||||||
if value and (normalized == scoped_path or normalized.endswith(nested_suffix)):
|
)
|
||||||
return str(value).strip()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def _pick_direct_payload_value(payload: dict[str, str], field_name: str) -> str:
|
def _partition_participant_lookup(
|
||||||
|
participant_lookup: _PayloadLookup,
|
||||||
|
) -> tuple[_PayloadLookup, _PayloadLookup]:
|
||||||
|
base_entries: list[_LookupEntry] = []
|
||||||
|
eio_entries: list[_LookupEntry] = []
|
||||||
|
for entry in participant_lookup.entries:
|
||||||
|
target = (
|
||||||
|
eio_entries
|
||||||
|
if _is_normalized_eio_path(entry.normalized_path)
|
||||||
|
else base_entries
|
||||||
|
)
|
||||||
|
target.append(entry)
|
||||||
|
return (
|
||||||
|
_build_payload_lookup_from_entries(tuple(base_entries)),
|
||||||
|
_build_payload_lookup_from_entries(tuple(eio_entries)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _lookup_has_payload_block(lookup: _PayloadLookup, block_name: str) -> bool:
|
||||||
|
prefix = f"{block_name}."
|
||||||
|
nested_marker = f".{block_name}."
|
||||||
|
return any(
|
||||||
|
normalized_path.startswith(prefix) or nested_marker in normalized_path
|
||||||
|
for normalized_path in lookup.normalized_paths
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _lookup_pick_scoped_value(lookup: _PayloadLookup, scoped_path: str) -> str:
|
||||||
|
value = lookup.normalized_suffix_first.get(scoped_path, "")
|
||||||
|
return str(value).strip() if value else ""
|
||||||
|
|
||||||
|
|
||||||
|
def _lookup_pick_direct_value(lookup: _PayloadLookup, field_name: str) -> str:
|
||||||
"""Возвращает только прямое поле текущего XML-блока без suffix-поиска."""
|
"""Возвращает только прямое поле текущего XML-блока без suffix-поиска."""
|
||||||
for key, value in payload.items():
|
value = lookup.normalized_exact_first.get(field_name, "")
|
||||||
if value and _normalize_indexed_path(key) == field_name:
|
return str(value).strip() if value else ""
|
||||||
return str(value).strip()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def _apply_participant_identity_rules(
|
def _apply_participant_identity_rules(
|
||||||
row: dict[int, str], participant_fields: dict[str, str]
|
row: dict[int, str], participant_lookup: _PayloadLookup
|
||||||
) -> None:
|
) -> None:
|
||||||
participant_type = _normalize_code(_get(row, COL_PARTICIPANT_TYPE))
|
participant_type = _normalize_code(_get(row, COL_PARTICIPANT_TYPE))
|
||||||
has_legal_entity = _has_payload_block(participant_fields, "УчастникЮЛ")
|
has_legal_entity = _lookup_has_payload_block(participant_lookup, "УчастникЮЛ")
|
||||||
has_physical_person = _has_payload_block(participant_fields, "УчастникФЛИП")
|
has_physical_person = _lookup_has_payload_block(participant_lookup, "УчастникФЛИП")
|
||||||
has_foreign_structure = _has_payload_block(participant_fields, "УчастникИНБОЮЛ")
|
has_foreign_structure = _lookup_has_payload_block(
|
||||||
|
participant_lookup, "УчастникИНБОЮЛ"
|
||||||
|
)
|
||||||
|
|
||||||
if has_physical_person and not has_legal_entity:
|
if has_physical_person and not has_legal_entity:
|
||||||
identification = _pick_scoped_payload_value(
|
identification = _lookup_pick_scoped_value(
|
||||||
participant_fields, "УчастникФЛИП.ИдентификацияФЛ"
|
participant_lookup, "УчастникФЛИП.ИдентификацияФЛ"
|
||||||
)
|
)
|
||||||
_set(row, COL_IDENT_FL, identification)
|
_set(row, COL_IDENT_FL, identification)
|
||||||
else:
|
else:
|
||||||
_set(row, COL_IDENT_FL, "")
|
_set(row, COL_IDENT_FL, "")
|
||||||
|
|
||||||
if has_legal_entity or participant_type == PARTICIPANT_TYPE_UL:
|
if has_legal_entity or participant_type == PARTICIPANT_TYPE_UL:
|
||||||
value = _pick_scoped_payload_value(
|
value = _lookup_pick_scoped_value(participant_lookup, "УчастникЮЛ.СведЮЛ.КППЮЛ")
|
||||||
participant_fields, "УчастникЮЛ.СведЮЛ.КППЮЛ"
|
|
||||||
)
|
|
||||||
elif has_foreign_structure:
|
elif has_foreign_structure:
|
||||||
value = _pick_scoped_payload_value(
|
value = _lookup_pick_scoped_value(
|
||||||
participant_fields,
|
participant_lookup,
|
||||||
"УчастникИНБОЮЛ.СведИНБОЮЛ.ПризнакОргФормаИНБОЮЛ",
|
"УчастникИНБОЮЛ.СведИНБОЮЛ.ПризнакОргФормаИНБОЮЛ",
|
||||||
)
|
)
|
||||||
elif has_physical_person:
|
elif has_physical_person:
|
||||||
value = _pick_scoped_payload_value(
|
value = _lookup_pick_scoped_value(
|
||||||
participant_fields, "УчастникФЛИП.ИдентификацияФЛ"
|
participant_lookup, "УчастникФЛИП.ИдентификацияФЛ"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
value = ""
|
value = ""
|
||||||
_set(row, COL_KPP, value)
|
_set(row, COL_KPP, value)
|
||||||
|
|
||||||
|
|
||||||
def _has_any_prefixed_key(payload: dict[str, str], prefixes: tuple[str, ...]) -> bool:
|
def _lookup_has_any_prefixed_key(
|
||||||
|
lookup: _PayloadLookup, prefixes: tuple[str, ...]
|
||||||
|
) -> bool:
|
||||||
return any(
|
return any(
|
||||||
any(_normalize_indexed_path(key).startswith(prefix) for prefix in prefixes)
|
any(normalized_path.startswith(prefix) for prefix in prefixes)
|
||||||
for key in payload
|
for normalized_path in lookup.normalized_paths
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _normalize_indexed_path(path: str) -> str:
|
|
||||||
"""Убирает индексы повторов, сохраняя логическую структуру XML-пути."""
|
|
||||||
return _INDEXED_PATH_SEGMENT.sub("", path)
|
|
||||||
|
|
||||||
|
|
||||||
def _is_eio_path(path: str) -> bool:
|
|
||||||
normalized = _normalize_indexed_path(path)
|
|
||||||
return ".СведИНБОЮЛ.Учредитель." in f".{normalized}." or any(
|
|
||||||
f".{block_name}." in f".{normalized}." for block_name in _EIO_BLOCK_NAMES
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _scope_participant_payload(
|
|
||||||
payload: dict[str, str],
|
|
||||||
*,
|
|
||||||
eio_only: bool,
|
|
||||||
) -> dict[str, str]:
|
|
||||||
"""Изолирует поля участника от вложенного блока ЕИО/бенефициара."""
|
|
||||||
return {
|
|
||||||
key: value for key, value in payload.items() if _is_eio_path(key) is eio_only
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _build_suffix_index(payload: dict[str, str]) -> dict[str, str]:
|
|
||||||
index: dict[str, str] = {}
|
|
||||||
for key, value in payload.items():
|
|
||||||
if not value:
|
|
||||||
continue
|
|
||||||
short = key.rsplit(".", maxsplit=1)[-1]
|
|
||||||
index.setdefault(short, value)
|
|
||||||
return index
|
|
||||||
|
|
||||||
|
|
||||||
def _pick_value(
|
def _pick_value(
|
||||||
payload: dict[str, str],
|
lookup: _PayloadLookup,
|
||||||
suffix_index: dict[str, str],
|
candidates: tuple[_CompiledCandidate, ...],
|
||||||
candidates: tuple[str, ...],
|
|
||||||
*,
|
*,
|
||||||
allow_short_lookup: bool = True,
|
allow_short_lookup: bool = True,
|
||||||
) -> str:
|
) -> str:
|
||||||
for key in candidates:
|
for candidate in candidates:
|
||||||
if value := payload.get(key):
|
if value := lookup.values.get(candidate.raw):
|
||||||
|
return value
|
||||||
|
if value := lookup.normalized_suffix_first.get(candidate.normalized):
|
||||||
return value
|
return value
|
||||||
normalized_key = _normalize_indexed_path(key)
|
|
||||||
for payload_key, value in payload.items():
|
|
||||||
normalized_payload_key = _normalize_indexed_path(payload_key)
|
|
||||||
if value and (
|
|
||||||
normalized_payload_key == normalized_key
|
|
||||||
or normalized_payload_key.endswith(f".{normalized_key}")
|
|
||||||
):
|
|
||||||
return value
|
|
||||||
if not allow_short_lookup:
|
if not allow_short_lookup:
|
||||||
continue
|
continue
|
||||||
short = key.rsplit(".", maxsplit=1)[-1]
|
if value := lookup.values.get(candidate.short):
|
||||||
if value := payload.get(short):
|
return value
|
||||||
|
if value := lookup.raw_short_first.get(candidate.short):
|
||||||
return value
|
return value
|
||||||
if short in suffix_index:
|
|
||||||
return suffix_index[short]
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _pick_structured_value(
|
def _pick_structured_value(
|
||||||
*,
|
*,
|
||||||
source_payload: dict[str, str],
|
lookup: _PayloadLookup,
|
||||||
suffix_index: dict[str, str],
|
candidates: tuple[_CompiledCandidate, ...],
|
||||||
candidates: tuple[str, ...],
|
|
||||||
join_with_space: bool,
|
join_with_space: bool,
|
||||||
allow_short_lookup: bool = True,
|
allow_short_lookup: bool = True,
|
||||||
) -> str:
|
) -> str:
|
||||||
values: list[str] = []
|
values: list[str] = []
|
||||||
for key in candidates:
|
for candidate in candidates:
|
||||||
value = _pick_value(
|
value = _pick_value(
|
||||||
source_payload,
|
lookup,
|
||||||
suffix_index,
|
(candidate,),
|
||||||
(key,),
|
|
||||||
allow_short_lookup=allow_short_lookup,
|
allow_short_lookup=allow_short_lookup,
|
||||||
)
|
)
|
||||||
if value and value not in values:
|
if value and value not in values:
|
||||||
@ -643,8 +703,8 @@ def _apply_business_rules(
|
|||||||
*,
|
*,
|
||||||
has_eio_block: bool,
|
has_eio_block: bool,
|
||||||
has_cp_block: bool,
|
has_cp_block: bool,
|
||||||
operation_fields: dict[str, str],
|
operation_lookup: _PayloadLookup,
|
||||||
participant_fields: dict[str, str],
|
participant_lookup: _PayloadLookup,
|
||||||
) -> None:
|
) -> None:
|
||||||
codes = _operation_codes(row)
|
codes = _operation_codes(row)
|
||||||
operation_sign = _get(row, COL_OPERATION_SIGN)
|
operation_sign = _get(row, COL_OPERATION_SIGN)
|
||||||
@ -658,7 +718,7 @@ def _apply_business_rules(
|
|||||||
_rule_unusual_codes(row, codes)
|
_rule_unusual_codes(row, codes)
|
||||||
_rule_digital_rights_currency(row, operation_sign)
|
_rule_digital_rights_currency(row, operation_sign)
|
||||||
_rule_sale_currency(row)
|
_rule_sale_currency(row)
|
||||||
_rule_extra_info(row, codes, operation_fields, participant_fields)
|
_rule_extra_info(row, codes, operation_lookup, participant_lookup)
|
||||||
_rule_suspicious_activity(row, codes)
|
_rule_suspicious_activity(row, codes)
|
||||||
_rule_metal_name(row)
|
_rule_metal_name(row)
|
||||||
_rule_item_type(row, codes)
|
_rule_item_type(row, codes)
|
||||||
@ -740,15 +800,15 @@ def _rule_sale_currency(row: dict[int, str]) -> None:
|
|||||||
def _rule_extra_info(
|
def _rule_extra_info(
|
||||||
row: dict[int, str],
|
row: dict[int, str],
|
||||||
codes: set[str],
|
codes: set[str],
|
||||||
operation_fields: dict[str, str],
|
operation_lookup: _PayloadLookup,
|
||||||
participant_fields: dict[str, str],
|
participant_lookup: _PayloadLookup,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Правило 34: объединяет допустимые комментарии операции и участника."""
|
"""Правило 34: объединяет допустимые комментарии операции и участника."""
|
||||||
operation_comment = _pick_direct_payload_value(operation_fields, "Коммент")
|
operation_comment = _lookup_pick_direct_value(operation_lookup, "Коммент")
|
||||||
participant_comment = ""
|
participant_comment = ""
|
||||||
if codes & EXPORT_SUBSIDIARY_COMMENT_CODES:
|
if codes & EXPORT_SUBSIDIARY_COMMENT_CODES:
|
||||||
participant_comment = _pick_direct_payload_value(
|
participant_comment = _lookup_pick_direct_value(
|
||||||
participant_fields, "КомментУчастник"
|
participant_lookup, "КомментУчастник"
|
||||||
)
|
)
|
||||||
|
|
||||||
values = list(
|
values = list(
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from app.pipeline import mapping
|
||||||
from app.pipeline.mapping import (
|
from app.pipeline.mapping import (
|
||||||
FIXED_REPORT_COLUMNS,
|
FIXED_REPORT_COLUMNS,
|
||||||
_extract_tag_candidate_keys,
|
_extract_tag_candidate_keys,
|
||||||
@ -154,6 +155,38 @@ def test_build_fixed_row_skips_empty_suffix_values_in_index() -> None:
|
|||||||
assert row[_column_with("ИНН")] == "222222222222"
|
assert row[_column_with("ИНН")] == "222222222222"
|
||||||
|
|
||||||
|
|
||||||
|
def test_mapping_normalizes_each_payload_path_only_constant_times(monkeypatch) -> None:
|
||||||
|
calls = 0
|
||||||
|
original = mapping._normalize_indexed_path
|
||||||
|
|
||||||
|
def counted(path: str) -> str:
|
||||||
|
nonlocal calls
|
||||||
|
calls += 1
|
||||||
|
return original(path)
|
||||||
|
|
||||||
|
monkeypatch.setattr(mapping, "_normalize_indexed_path", counted)
|
||||||
|
|
||||||
|
operation_fields = {
|
||||||
|
"НомерОперация": "OP1",
|
||||||
|
"КодОперации": "6001",
|
||||||
|
}
|
||||||
|
participant_fields = {
|
||||||
|
"УчастникЮЛ.СведЮЛ.НаимЮЛ": "АО СИРИУС",
|
||||||
|
"УчастникЮЛ.СведЮЛ.ИННЮЛ": "2820000210",
|
||||||
|
"УчастникЮЛ.СведЕИО[0].ФЛЕИО.ФИОФЛИП.Фам": "Музалёв",
|
||||||
|
"УчастникЮЛ.СведЕИО[0].ФЛЕИО.ИННФЛИП": "410116812922",
|
||||||
|
}
|
||||||
|
build_fixed_row_by_index(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields=operation_fields,
|
||||||
|
participant_fields=participant_fields,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert calls == 2 * (len(operation_fields) + len(participant_fields))
|
||||||
|
|
||||||
|
|
||||||
def test_structured_address_is_built_from_right_columns() -> None:
|
def test_structured_address_is_built_from_right_columns() -> None:
|
||||||
row = build_fixed_row(
|
row = build_fixed_row(
|
||||||
file_name="f.xml",
|
file_name="f.xml",
|
||||||
@ -870,6 +903,27 @@ def test_participant_and_beneficiary_inn_are_scoped_regardless_of_field_order()
|
|||||||
assert "111111111111" not in row[157]
|
assert "111111111111" not in row[157]
|
||||||
|
|
||||||
|
|
||||||
|
def test_legal_entity_and_eio_representative_inn_remain_strictly_scoped() -> None:
|
||||||
|
row = build_fixed_row_by_index(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields={
|
||||||
|
"УчастникЮЛ.СведЮЛ.НаимЮЛ": "АО СИРИУС",
|
||||||
|
"УчастникЮЛ.СведЮЛ.ИННЮЛ": "2820000210",
|
||||||
|
"УчастникЮЛ.СведЕИО[0].ФЛЕИО.ФИОФЛИП.Фам": "Музалёв",
|
||||||
|
"УчастникЮЛ.СведЕИО[0].ФЛЕИО.ФИОФЛИП.Имя": "Александр",
|
||||||
|
"УчастникЮЛ.СведЕИО[0].ФЛЕИО.ФИОФЛИП.Отч": "Сергеевич",
|
||||||
|
"УчастникЮЛ.СведЕИО[0].ФЛЕИО.ИННФЛИП": "410116812922",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert row[120] == "2820000210"
|
||||||
|
assert row[157] == "Музалёв Александр Сергеевич"
|
||||||
|
assert row[162] == "410116812922"
|
||||||
|
|
||||||
|
|
||||||
def test_flip_beneficiary_is_scoped_to_eio_columns() -> None:
|
def test_flip_beneficiary_is_scoped_to_eio_columns() -> None:
|
||||||
row = build_fixed_row_by_index(
|
row = build_fixed_row_by_index(
|
||||||
file_name="f.xml",
|
file_name="f.xml",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user