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