Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_2', 'origin/sub_laptop_3' and 'origin/sub_laptop_4' into main_laptop_1

This commit is contained in:
2026-09-25 12:16:18 +09:00
18 changed files with 595 additions and 132 deletions
+28 -1
View File
@@ -92,10 +92,37 @@ def test_펼침_열은_설계에_쓰인_값만_차례대로(project: Path) -> No
assert "rv" not in ids and "ps" in ids # 펼침 마스터 칸은 빠지고 고정 열은 남음
# 관 유입·유출 기슭막이는 레지스트리 기본값(찰 · 메 · 높이 없음)으로 열이 섬 · id 는 설계 값
assert "rv|돌쌓기(찰)|-" in ids and "rv|돌쌓기(메)|-" in ids
assert _col(doc, "rv|돌쌓기(찰)|-")["머리"] == ["돌기슭막이", "찰쌓기", "H=-"]
assert _col(doc, "rv|돌쌓기(찰)|-")["머리"] == ["돌기슭막이", "찰쌓기", "높이 미입력"]
assert _col(doc, "fb|1000|-")["머리"] == ["세월교", "Φ1000×련수 미입력", None]
assert not any(i.startswith("pg_in|") for i in ids) # 날개벽·집수정 형식 없음 → 관보호공 없음
def test_묶음_열은_값마다_한데(project: Path) -> None:
ids = [column["id"] for column in _filled(project)["열"]]
assert [i for i in ids if i.startswith("pp_")] == [
"pp_len|흄관|1000",
"pp_cp|흄관|1000",
"pp_len|파형강관|1000",
"pp_cp|파형강관|1000",
]
assert [i for i in ids if i.startswith("pv_")] == [
f"pv_{k}|20" for k in ("b", "l", "w", "a", "jt")
]
def test_계산_열은_설계값_딱지_없음(project: Path) -> None:
master = _master()
assert not [c["id"] for c in master["열"] if c.get("식") and c.get("바인딩")]
doc = _filled(project)
for col_id in ("pv_a|20", "pv_jt|20", "pp_cp|파형강관|1000", "bm_area|메쌓기|40~60|2.5"):
column = _col(doc, col_id)
assert "바인딩" not in column and column["펼침틀"]["원열"]["id"] == col_id.split("|")[0]
assert _col(doc, "pp_cp|파형강관|1000")["머리"] == ["관공", "파형강관 Φ1000", "커플링밴드"]
# 다시 저장 · 채워도 같은 열(계산 열도 원열로 되돌림)
again = fill.fill_table(project, fill.strip_design(doc), {85.0: 14.0})
assert [c["id"] for c in again["열"]] == [c["id"] for c in doc["열"]]
def test_계산_열_식은_같은_묶음_열로_바뀌고_빈_변수면_안_셈(project: Path) -> None:
doc = _filled(project)
assert _col(doc, "pv_a|20")["식"] == 'IF([pv_l|20]>0,[pv_b|20]*[pv_l|20]+[pv_w|20],"")'
@@ -295,3 +295,44 @@ def test_프로젝트_표_저장은_서버가_설계값을_걸러_씀(world: dic
stored = layers.read_template(_root(world, P1) / "templates", "table", "구조물집계표")["문서"]
assert stored["줄"][1] == {"id": "s40.00", "값": {"sta": "NO.2", "h_intake": 2}}
assert "알림" not in stored and all("|" not in c["id"] for c in stored["열"])
def test_옛_표_틀은_새_판으로_손_값은_지킴(world: dict[str, Any]) -> None:
from M02_MasterTemplete import M02_Template_Migrate as migrate
new = {
"판": 2,
"열": [{"id": "a", "식": "1", "펼침틀": {"묶음": "g"}}],
"줄": [],
"변수": {"k": 1},
}
old = {
"판": 1,
"열": [{"id": "a", "식": "1", "바인딩": {"종류": "pipe", "값": "식"}}, {"id": "memo"}],
"줄": [{"id": "s10.00", "값": {"sta": "NO.0+10", "memo": "손"}}],
"변수": {"k": 5},
}
layers.write_template(layers.system_dir(), "table", "구조물집계표", new)
root2, root1 = _root(world, P2, user=43), _root(world, P1)
for folder in (root1 / "templates", root1 / "templates/_initial", root2 / "templates"):
layers.write_template(folder, "table", "구조물집계표", old)
layers.write_template(
root2 / "templates", "table", "구조물집계표", {**old, "줄": [], "변수": {"k": 1}}
)
assert migrate.refresh_tables(root1, dry_run=True) == [
"templates/table/구조물집계표",
"_initial/table/구조물집계표",
]
migrate.refresh_tables(root1)
work = layers.read_template(root1 / "templates", "table", "구조물집계표")["문서"]
assert work["열"] == new["열"] and work["변수"] == {"k": 5} # 틀만 새 판 · 변수 · 손 값 지킴
assert work["줄"] == old["줄"]
system_version = layers.version_of(layers.system_dir() / "table/구조물집계표.json")
initial = root1 / "templates/_initial"
assert layers.version_of(initial / "table/구조물집계표.json") != system_version # 변수 다름
assert layers.read_manifest(root1 / "templates")["table/구조물집계표"]["판"] == system_version
assert migrate.refresh_tables(root1) == [] # 다시 돌려도 그대로
# 손댄 것 없는 작업본은 시스템 파일 그대로(판이 같음)
migrate.refresh_tables(root2)
assert layers.version_of(root2 / "templates/table/구조물집계표.json") == system_version
+81
View File
@@ -0,0 +1,81 @@
"""표 쪽 나눔 — `ui_template_sheet_ops.ts` `pagePlan` 을 Node 로 바로 돌려 봄.
격자(`_render`)가 쪽을 이 계획대로 그림.
줄 120 문서 → project 쪽 3(50 · 50 · 20) · 쪽마다 머리(격자가 쪽마다 thead) · 합계는 마지막 쪽만 ·
「전구간」 줄은 맨 위 · master 는 한 쪽.
"""
from __future__ import annotations
import json
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
OPS = ROOT / "ui_template" / "sheet" / "ui_template_sheet_ops.ts"
pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
SCRIPT = """
const { pagePlan } = await import(process.argv[1]);
const doc = JSON.parse(process.argv[2]);
const out = {};
for (const mode of ["project", "master"]) {
out[mode] = pagePlan(doc, mode).map((p) => ({
ids: p.rows.map((r) => r.id), first: p.first, totals: p.totals,
}));
}
console.log(JSON.stringify(out));
"""
def _plan(doc: dict) -> dict:
result = subprocess.run(
[
"node",
"--experimental-strip-types",
"--no-warnings",
"--input-type=module",
"-e",
SCRIPT,
OPS.as_uri(),
json.dumps(doc, ensure_ascii=False),
],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
def _doc(n: int, **head) -> dict:
rows = [{"id": f"p{i}", "값": {}} for i in range(1, n)]
rows.insert(7, {"id": "all", "값": {}, "고정": "전구간"}) # 문서 가운데 있어도 맨 위로
return {"양식": "시험", "종류": "표", "판": 1, "열": [], "줄": rows, **head}
def test_120_rows_make_three_pages_totals_last():
plan = _plan(_doc(120))["project"]
assert [len(p["ids"]) for p in plan] == [50, 50, 20]
assert [p["first"] for p in plan] == [1, 51, 101]
assert [p["totals"] for p in plan] == [False, False, True]
assert plan[0]["ids"][0] == "all"
assert sum((p["ids"] for p in plan), []).count("all") == 1
def test_page_rows_from_doc_and_master_single_page():
got = _plan(_doc(55, 쪽줄=20))
assert [len(p["ids"]) for p in got["project"]] == [20, 20, 15]
assert [len(p["ids"]) for p in got["master"]] == [55]
assert got["master"][0]["totals"] is True
def test_empty_doc_still_one_page_with_totals():
plan = _plan({"양식": "시험", "종류": "표", "판": 1, "열": [], "줄": []})["project"]
assert plan == [{"ids": [], "first": 1, "totals": True}]