Andrey Epifancev 6a2439239d perf: модалки на малом и выращенном листе (grow)
perfkit.gen.grow доращивает FORM_2 AHR_UTILITY/AHR_SECURITY до 500 и
FORM_4 CAP до 300 строк; у форм-модалок варианты @big.

Refs #202

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 01:04:06 +04:00

122 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Доращивание отдельных листов 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))