"""Порядок строк ответа `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