Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
67 lines
3.6 KiB
Python
67 lines
3.6 KiB
Python
"""Порядок строк ответа `GET …/statement` (P3-17; спека `arm-core` «Ответ `GET …/statement`»). Чистая функция.
|
||
|
||
Строки `ok` (в том числе достроенные шаблонные и созданные правкой) — по коду, затем `row_index` (`NULL` последними),
|
||
затем в порядке входа (вызывающий передаёт строки по `id`). За строкой `ok` источника идут её «вложения» по
|
||
`row_index`: строки `detail` с этим родителем и строки `unreadable` / `missing` / `detail` без (известного) родителя,
|
||
у которых она — ближайшая предыдущая по `row_index` строка `ok` источника. Вложения без такой строки — в начале.
|
||
"""
|
||
|
||
import bisect
|
||
from collections.abc import Hashable, Sequence
|
||
from dataclasses import dataclass
|
||
|
||
__all__ = ["LayoutLine", "nearest_ok_parents", "order_lines"]
|
||
|
||
OK = "ok"
|
||
DETAIL = "detail"
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class LayoutLine:
|
||
key: Hashable
|
||
row_index: int | None
|
||
line_status: str
|
||
line_code: str | None
|
||
parent_row_index: int | None
|
||
|
||
|
||
def order_lines(lines: Sequence[LayoutLine]) -> list[Hashable]:
|
||
"""Ключи строк в порядке ответа."""
|
||
ok_lines = [line for line in lines if line.line_status == OK]
|
||
others = [line for line in lines if line.line_status != OK]
|
||
ok_ordered = sorted(
|
||
ok_lines,
|
||
key=lambda line: (line.line_code is None, line.line_code or "", line.row_index is None, line.row_index or 0),
|
||
)
|
||
# строки `ok` источника по `row_index` — адресаты вложений
|
||
sources = sorted((line for line in ok_lines if line.row_index is not None), key=lambda line: line.row_index or 0)
|
||
source_rows = [line.row_index or 0 for line in sources]
|
||
by_row = {line.row_index: line.key for line in sources}
|
||
attached: dict[Hashable | None, list[LayoutLine]] = {}
|
||
for line in sorted((line for line in others), key=lambda item: (item.row_index is None, item.row_index or 0)):
|
||
owner: Hashable | None = None
|
||
if line.line_status == DETAIL and line.parent_row_index in by_row:
|
||
owner = by_row[line.parent_row_index]
|
||
elif line.row_index is not None:
|
||
position = bisect.bisect_left(source_rows, line.row_index) - 1
|
||
owner = sources[position].key if position >= 0 else None
|
||
attached.setdefault(owner, []).append(line)
|
||
result: list[Hashable] = [line.key for line in attached.get(None, [])]
|
||
for line in ok_ordered:
|
||
result.append(line.key)
|
||
result.extend(item.key for item in attached.get(line.key, []))
|
||
return result
|
||
|
||
|
||
def nearest_ok_parents(lines: Sequence[LayoutLine]) -> dict[Hashable, int | None]:
|
||
"""Родитель строк `detail`: `row_index` ближайшей строки `ok` выше по `row_index` (строки без `row_index` —
|
||
шаблонные — родителями не бывают); нет такой — `None`. Временная: в части Б заменяется `detail_parents` пакета."""
|
||
ok_rows = sorted(line.row_index for line in lines if line.line_status == OK and line.row_index is not None)
|
||
parents: dict[Hashable, int | None] = {}
|
||
for line in lines:
|
||
if line.line_status != DETAIL:
|
||
continue
|
||
position = bisect.bisect_left(ok_rows, line.row_index) - 1 if line.row_index is not None else -1
|
||
parents[line.key] = ok_rows[position] if position >= 0 else None
|
||
return parents
|