"""Манифест стенда: что есть в БД и какие URL/пользователей использовать в сценариях. Путь: perf/results/manifest-s{scale}.json. Сегмент {direction} в URL листа: web/src/components/common/TableList/TableList.jsx строит путь как `${table?.direction}`, поэтому при direction == null (JSON null из API) в URL попадает литерал "null" (маршрут в web/src/app/Routes.jsx: /table/form/:formId/form-type/:formType/:sheetName/:direction/:year). Task 3 заполняет `svods` и `modal_pages` по маршрутам Routes.jsx (/svod, /svod/2, /svods-navigator, /projects/summaries и т.д.) — это просто списки Page(name, url). Лист свода выбирается вкладкой внутри страницы, не URL: у Page свода url = путь + '#' + имя листа (`/svod#FORM_1`, `/svod/2#RF`); хеш на маршрутизацию не влияет, сценарий по нему выбирает вкладку. """ from __future__ import annotations import json from dataclasses import dataclass, field, asdict from pathlib import Path from . import RESULTS_DIR NO_DIRECTION = "null" # см. docstring модуля USER_KEYS = ("admin", "rf", "rf_coauthor", "dfip") @dataclass class Sheet: form_id: int form_type: str sheet: str direction: str | None year: int url: str rows: int # чьей оргединице принадлежит форма: "rf" — РФ пользователя `rf` (ему доступна), "ssp" — ССП # (доступна admin/dfip, но не `rf`). Поле добавлено Task 3, по умолчанию "rf". owner: str = "rf" @staticmethod def build_url(form_id, form_type, sheet, direction, year) -> str: d = NO_DIRECTION if direction is None else direction return f"/table/form/{form_id}/form-type/{form_type}/{sheet}/{d}/{year}" @dataclass class ProjectReport: project_id: int year: int report_type: str url: str rows: int @dataclass class Page: name: str url: str @dataclass class User: username: str password: str role_id: int @dataclass class Manifest: scale: int users: dict[str, User] = field(default_factory=dict) sheets: list[Sheet] = field(default_factory=list) project_reports: list[ProjectReport] = field(default_factory=list) svods: list[Page] = field(default_factory=list) modal_pages: list[Page] = field(default_factory=list) def manifest_path(scale: int, results_dir: Path | None = None) -> Path: return Path(results_dir or RESULTS_DIR) / f"manifest-s{scale}.json" def dump(m: Manifest, path) -> None: path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(asdict(m), ensure_ascii=False, indent=2), encoding="utf-8") def load(path) -> Manifest: d = json.loads(Path(path).read_text(encoding="utf-8")) return Manifest( scale=d["scale"], users={k: User(**v) for k, v in d.get("users", {}).items()}, sheets=[Sheet(**x) for x in d.get("sheets", [])], project_reports=[ProjectReport(**x) for x in d.get("project_reports", [])], svods=[Page(**x) for x in d.get("svods", [])], modal_pages=[Page(**x) for x in d.get("modal_pages", [])], )