"""Доращивание отдельных листов rf до «большого» размера (для замеров модалок поверх большого листа). cd perf && .venv/bin/python -m perfkit.gen.grow --scale 1 [--vsp-rows 500] [--form4-rows 300] Базовая генерация (run.py) даёт на листах, где существуют форм-модалки, 31 (FORM_2 AHR_RENT/UTILITY/ SECURITY) и 110 (FORM_4 AHR/CAP/OPER) строк. Здесь строки донабираются через тот же API (POST .../line под admin, как в run.py), идемпотентно: если строк уже >= цели - ничего не делается. Растут: FORM_2 AHR_UTILITY и AHR_SECURITY (до --vsp-rows) и FORM_4 CAP (до --form4-rows) формы rf. AHR_RENT и FORM_4 AHR остаются малыми: это пара «малый лист» для тех же модалок, а скролл-сценарии по AHR не меняются. Порядок запуска поэтому не влияет на малые замеры; на скролл-сценарии по UTILITY/SECURITY/CAP grow влияет - запускать после них. Цели одинаковы на масштабах 1 и 2 (add_line на FORM_4 стоит O(n)). Манифест обновляется (Sheet.rows). """ from __future__ import annotations import argparse import asyncio import json import random import time from collections import Counter from datetime import datetime, timezone from .. import RESULTS_DIR from ..manifest import dump, load, manifest_path from .client import Api from .run import CONCURRENCY, FormInfo, YEAR, add_lines, count_rows, leaf_items, log, sheet_params VSP_ROWS = 500 FORM4_ROWS = 300 # (form_type, лист, вид) TARGETS = [("FORM_2", "AHR_UTILITY", "vsp"), ("FORM_2", "AHR_SECURITY", "vsp"), ("FORM_4", "CAP", "expense")] def rows_to_add(current: int, target: int) -> int: """Сколько строк донабрать, чтобы на листе стало >= target (current уже включает шаблонные и META-строки).""" return max(0, target - current) def _target_for(kind: str, vsp_rows: int, form4_rows: int) -> int: return vsp_rows if kind == "vsp" else form4_rows async def grow_vsp_sheet(api: Api, form_id: int, sheet: str, need: int, org_index: int) -> int: path = f"/form/{form_id}/sheet/{sheet}" rows = (await api.get(path))["result"] vsp_ids = sorted({r["data"]["vsp_id"] for r in rows if r.get("row_type") == "INPUT" and r["data"].get("vsp_id")}) if not vsp_ids: raise RuntimeError(f"{sheet}: нет ВСП в существующих строках") async def one(n: int) -> None: body = { "vsp_id": vsp_ids[n % len(vsp_ids)], "contract_number": f"ДГ-{org_index:02d}/{sheet[-4:]}/G{n:04d}", "contract_end_date": datetime(YEAR + 1 + n % 3, 1 + n % 12, 1 + n % 27, tzinfo=timezone.utc).isoformat(), } await api.post(path + "/line", json=body) await asyncio.gather(*(one(n) for n in range(need))) return need async def grow_form4_sheet(api: Api, form_id: int, sheet: str, need: int) -> int: path = f"/form/{form_id}/sheet/{sheet}" rows = (await api.get(path))["result"] vsp_ids = sorted({r["data"]["header"]["vsp_id"] for r in rows if r.get("row_type") == "INPUT" and r["data"]["header"].get("vsp_id")}) project_ids = sorted({r["data"]["header"]["project_id"] for r in rows if r.get("row_type") == "ITEM"}) if not project_ids: raise RuntimeError(f"{sheet}: нет проектов") leaves = await leaf_items(api, "FORM_4", sheet) form = FormInfo(form_id, "FORM_4", 0, "rf", 1, vsp_ids) stats: Counter = Counter() ids = await add_lines(api, form, sheet, None, need, leaves, random.Random(f"grow-{form_id}-{sheet}"), stats, project_ids) if stats: log(f" статистика {dict(stats)}") return len(ids) async def main(scale: int, vsp_rows: int, form4_rows: int) -> None: path = manifest_path(scale, RESULTS_DIR) m = load(path) api = Api("admin", "admin", asyncio.Semaphore(CONCURRENCY)) report = [] try: await api.login() for ftype, sheet, kind in TARGETS: sh = next((s for s in m.sheets if s.owner == "rf" and s.form_type == ftype and s.sheet == sheet and s.direction is None), None) if sh is None: raise RuntimeError(f"лист {ftype}/{sheet} (rf) не найден в манифесте") cur = await count_rows(api, f"/form/{sh.form_id}/sheet/{sheet}", sheet_params(None)) target = _target_for(kind, vsp_rows, form4_rows) need = rows_to_add(cur, target) t0 = time.time() if need: log(f"{ftype}/{sheet} form {sh.form_id}: {cur} -> {target} (+{need})") if kind == "vsp": await grow_vsp_sheet(api, sh.form_id, sheet, need, 1) else: await grow_form4_sheet(api, sh.form_id, sheet, need) else: log(f"{ftype}/{sheet} form {sh.form_id}: {cur} >= {target}, ничего не делаю") after = await count_rows(api, f"/form/{sh.form_id}/sheet/{sheet}", sheet_params(None)) sh.rows = after dt = time.time() - t0 report.append({"sheet": f"{ftype}/{sheet}", "form_id": sh.form_id, "before": cur, "after": after, "added": need, "seconds": round(dt, 1)}) log(f" итого {after} строк за {dt:.0f} c") dump(m, path) out = RESULTS_DIR / f"grow-s{scale}.json" out.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8") log(f"манифест обновлён: {path}; отчёт {out}") finally: await api.aclose() if __name__ == "__main__": ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("--scale", type=int, required=True) ap.add_argument("--vsp-rows", type=int, default=VSP_ROWS) ap.add_argument("--form4-rows", type=int, default=FORM4_ROWS) a = ap.parse_args() asyncio.run(main(a.scale, a.vsp_rows, a.form4_rows))