fix(B09 통합 점검): 단가연결 낱말 별칭 · 줄 0 개 일위대가 = 빈 줄 · 단위 다름 알림 · 잊은 짝 짝 표 제안 제외 (통합 점검 흠 ① ③ ④ + 잊기)

- 기준 목록 별칭에 「터파기」→「파기」 낱말(측구파기 → 측구터파기) · 목록 모양 SHAPE 올림
- 줄 0 개 일위대가 = 빈 줄 하나 · 빈 줄 있는 대가를 이은 내역 줄 = 단가 모름(금액 0 아님) · py · ts 짝 + 거울 시험
- 단가연결 때 줄 단위 ≠ 일위대가 단위면 「단위 다름」 알림(연결은 됨)
- 잊은 짝은 짝 표 제안에서도 안 나옴(다시 이으면 풀림)
- 낡은 시험 여섯: 707-4 잇기 메뉴 묶음(UI_Link) 빠진 소스 목록 보충

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 18:43:32 +09:00
co-authored by Claude Sonnet 5.5
parent 8626b1a922
commit 51f6eeffb1
16 changed files with 94 additions and 18 deletions
+24 -1
View File
@@ -196,7 +196,7 @@ def test_새_산근_사본_뒤_번호_호표_탭_집계표(tmp_path):
def test_우클릭_메뉴_항목_추가_줄_삭제_연결_TS():
# 184-3 — 모든 줄 메뉴 · 셈 출력 줄 = 계약 꼴(parent · auto · auto_{칸}) · 첫 손질 = v2 로 굳힘
src = _ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit"])
src = _ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Link", "UI_Tabs_Bill_Edit"])
rows = [
{"kind": "title", "key": "sec:1", "parent": None, "auto": True},
{"kind": "title", "key": "sec:1.2", "parent": "sec:1", "auto": True},
@@ -243,3 +243,26 @@ console.log(JSON.stringify(out));
# 새 줄 지움 = 덮개에서 빠짐(gone 은 자동 줄만)
assert [e["key"] for e in out["left"]["rows"]] == ["sec:1", "sec:1.2", "earth:4"]
assert out["left"]["gone"] == []
def test_줄_0개_일위대가_이으면_빈_줄():
"""703-1 가져오기 흠 — 줄 0 개 대가(금액 0)를 이은 내역 줄 = 금액 0 이 아니라 빈 줄."""
empty = {"no": 4, "key": "DG:e", "labor": 0, "material": 0, "expense": 0, "sum": 0,
"blank": 1} # fmt: skip
books = {**BOOKS, "daega": [*BOOKS["daega"], empty]}
hand = {"key": "hand:e", "section": "1.1", "after": "earth:1", "name": "빈 대가", "spec": "",
"unit": "㎡", "qty": 3, "ref": {"tab": "dg", "key": "DG:e"}} # fmt: skip
user = {"bill": {"rows": [hand]}}
sheets = compute_estimation(INPUTS, user, None, books)
row = _rows(sheets)["hand:e"]
assert row["sum"] is None and row["note"] == "대가4"
ok = _rows(compute_estimation(INPUTS, {"bill": {"rows": [HAND[0]]}}, None, books))
assert ok["hand:a"]["sum"] == 12300 # 빈 줄 없는 대가는 그대로
calc = _ts(["Calc_Base", "Calc_Bill"])
body = (
f"\nconst i = {json.dumps(INPUTS, ensure_ascii=False)};"
f"\nconst u = {json.dumps(user, ensure_ascii=False)};"
f"\nconst b = {json.dumps(books, ensure_ascii=False)};"
"\nconsole.log(JSON.stringify(computeEstimation(i, u, null, b)));"
)
assert json.loads(_node(calc + body)) == sheets # 거울
+5 -3
View File
@@ -218,7 +218,9 @@ def test_우클릭_레벨_메뉴_TS():
body = MENU_JS.replace("__ROWS__", json.dumps(rows, ensure_ascii=False)).replace(
"__USER__", json.dumps(USER, ensure_ascii=False)
)
out = json.loads(_node(_ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit"]) + body))
out = json.loads(
_node(_ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Link", "UI_Tabs_Bill_Edit"]) + body)
)
level = {"묶음 줄로 바꾸기", "항목으로 바꾸기", "들여쓰기 →", "내어쓰기 ←"}
pick = lambda key: [x for x in out[key] if x in level] # noqa: E731
assert [x for x in out["g1"] if "추가" in x] == ["항목 추가"] # 246-1 더함 하나
@@ -312,7 +314,7 @@ def test_연결_단추_칸_메뉴_고르기_창_TS():
.replace("__BILL__", json.dumps(sheets["bill"], ensure_ascii=False))
.replace("__DAEGA__", json.dumps(BOOKS["daega"], ensure_ascii=False))
)
src = _ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"])
src = _ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"])
out = json.loads(_node(src + body))
assert out["columns"] == ["connect", "note"] # 비고 앞 「연결」 칸
# 184-3 모든 항목 줄 · 새 줄 연결됨 = 호표 번호 · 자동 줄 연결됨 = 「연결됨」 · 묶음 = 없음
@@ -357,7 +359,7 @@ console.log(JSON.stringify(out));
def test_글_칸_위_우클릭도_줄_메뉴_TS():
src = _ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"])
src = _ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"])
out = json.loads(_node(src + ROW_MENU_JS))
assert out["onInput"] is True and out["onCell"] is True
assert out["opened"] == [[5, 6, ["줄 7"]], [5, 6, ["줄 7"]]]
+1 -1
View File
@@ -95,7 +95,7 @@ def _ts(names: list[str]) -> str:
)
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit"]
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Link", "UI_Tabs_Bill_Edit"]
def _menu(user, labels=(), runs=(), edits=(), rows=ROWS):
@@ -124,7 +124,7 @@ out.keyAct = [
console.log(JSON.stringify(out));
"""
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill", "UI_Tabs_Bill_Drag",
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill", "UI_Tabs_Bill_Drag",
"UI_Tabs_Bill_Level"] # fmt: skip
IN, OUT, UP, DOWN = "들여쓰기 →", "내어쓰기 ←", "위로 ↑", "아래로 ↓"
+1 -1
View File
@@ -33,7 +33,7 @@ if str(ROOT) not in sys.path:
from B09_Estimation import B09_Estimation_Store as store # noqa: E402
from B09_Estimation.B09_Estimation_Calc_Bill import compute_estimation # noqa: E402
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill", "UI_Tabs_Bill_Drag",
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill", "UI_Tabs_Bill_Drag",
"UI_Tabs_Bill_Level"] # fmt: skip
@@ -26,7 +26,7 @@ from B09_Estimation.B09_Estimation_Calc_Bill import compute_estimation # noqa:
IMPORTS = re.compile(r"^import[^;]*;\r?\n", re.MULTILINE)
TABLE = IMPORTS.sub("", (ROOT / "ui_template" / "ui_template_data_table.ts").read_text("utf-8"))
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill", "UI_Tabs_Bill_Drag",
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill", "UI_Tabs_Bill_Drag",
"UI_Tabs_Bill_Level"] # fmt: skip
IN, OUT = "들여쓰기 →", "내어쓰기 ←"
+2 -2
View File
@@ -27,7 +27,7 @@ from B09_Estimation.B09_Estimation_Calc_Bill import compute_estimation # noqa:
IMPORTS = re.compile(r"^import[^;]*;\r?\n", re.MULTILINE)
TABLE = IMPORTS.sub("", (ROOT / "ui_template" / "ui_template_data_table.ts").read_text("utf-8"))
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"]
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"]
SUMS = ["sum", "labor", "material", "expense"]
FOLDER_SVG, LEAF_SVG = "M1.5 3.5h4", "M3.5 1.5h6"
@@ -303,7 +303,7 @@ def test_고른_줄_Enter_F2_입력_시작():
"bar = levelBar(root, table);", "bar = levelBar(root, table, (i) => edits.push(i));"
)
body = (setup + KEYS_JS).replace("__ROWS__", json.dumps(FAKE_ROWS, ensure_ascii=False))
names = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill",
names = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill",
"UI_Tabs_Bill_Drag", "UI_Tabs_Bill_Level"] # fmt: skip
out = json.loads(_node(_ts(names) + body))
at = [r["key"] for r in FAKE_ROWS].index("earth:5")
@@ -126,3 +126,14 @@ def test_화면_기준_목록_계산서_담기() -> None:
router = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Router_Combos.py").read_text("utf-8")
assert '@router.get("/logic-lib/standard", dependencies=[Depends(verify_session)])' in router
assert '@router.post("/logic-lib/sheet", dependencies=[Depends(verify_session)])' in router
def test_내역서_낱말_측구파기로_측구터파기를_찾음() -> None:
logic = next(
logic
for _, data in m01._logic_files(m01.loaded()[0])
for logic in data.get("줄") or []
if logic.get("키") == "GF000099" # 9-12-1 측구터파기 토사
)
row = {**logic, "별칭": ", ".join(st.words_of(logic))}
assert hit(row, "측구파기")
+14
View File
@@ -301,3 +301,17 @@ def test_잊은_짝_느슨한_기억에도_안_나옴(tmp_path, monkeypatch):
assert lk.recall(memory, third) is None # 잊은 짝 곁에서는 느슨하게 안 좁힘
route.keep(user, {lk.key_of(row): live}) # 다시 이음 = 풀림
assert lk.recall(route.recalled(user), row) == live
def test_잊은_짝_짝_표_제안에도_안_나옴(geo):
_user, rows = _load(geo)
now = {s["key"]: s for s in lk.suggest(rows, {})}
line = next(r for r in rows if (r["name"], r.get("spec")) == ("토사절취", "백호0.7㎥"))
assert now[line["key"]]["꼬리표"] == lk.TABLE
memory = {lk.key_of(line): {lk.FORGOT: True}}
after = {s["key"]: s for s in lk.suggest(rows, memory)}
assert line["key"] not in after # 짝 표 제안도 잊음
assert set(now) - set(after) == {line["key"]} # 다른 줄은 그대로
memory[lk.key_of(line)] = {"확정": "earth:4", "이름": "절토(토사)"} # 다시 이음 = 풀림
assert lk.suggest(rows, memory)[0] is not None
assert line["key"] in {s["key"] for s in lk.suggest(rows, memory)}