- M01 단가산출 기초 · M02 단가산출 상세 보기 = 수식(줄 쌓기 · 기본) · 단가산출 상세 둘 - 줄 쌓기에 M02 사본 [단가 갱신] · [재생성](미리 셈 → 알림 → 저장 · 고침 있으면 막음) · 판 바뀜(409) 문서 그대로면 새 판으로 다시 저장 옮김 - 옛 수식 문서 화면 걷음(formula_doc_screen · defs · edit · lines · math · math_model · rows · sub · formula_doc.css) · 이미 안 부르던 796-2 수식 나무 화면(LogicLab_Equation · Defs · Var · css · M01_Logic_Equation_Model · sheet_formula_cell · ui_template_equation_defs · edit) 걷음 — 18 파일 4,860 줄 - formula_doc_render = 줄 쌓기가 쓰는 reach 만 · 옵션 가로 CSS 는 line_stack.css 로 - 남김 = 계산기 common_util_formula_doc* · 서버 길 · 옵션 · 사본 표지 · 합계 상자 · KaTeX · 시험 페이지 tools/line-stack-test - 시험: test_796_2 지움(걷은 화면 원본 검사뿐) · 799_1 · 800_2 걷은 화면 줄만 뺌 · 799_2 · 802_4b2 · 804_2 보기 · 화면 파일 대상 바꿈 · test_804_6 새로(걷은 파일 · 부르는 곳 없음 · 첫 탭 · 사본 할일) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 lines
5.4 KiB
Python
128 lines
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""PLAN 800-2 · 800-3 — 수식 탭 변수 정의 영역 · 바로 고침 · 새 기호 연결 (서버 몫 · 임시 쓰기 없음)."""
|
||
|
||
import json
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
sys.path[:0] = [str(ROOT), str(ROOT / "resources/master_data/scripts")]
|
||
|
||
from M01_MasterData import M01_MasterData_Equation as eq # noqa: E402
|
||
from M01_MasterData import M01_MasterData_Store as store # noqa: E402
|
||
|
||
KEY = "GC000211"
|
||
OFF = {"폼타이": "아님", "측압": "3t/㎡"}
|
||
ON = {"폼타이": "사용", "측압": "4t/㎡"}
|
||
|
||
|
||
def _defs(inputs):
|
||
return eq.read(KEY, inputs)["수식"]["정의"]
|
||
|
||
|
||
def _text(defs):
|
||
return json.dumps(defs, ensure_ascii=False)
|
||
|
||
|
||
def test_defs_merge_and_clean():
|
||
defs = _defs(OFF)
|
||
signs = [(d["기호"], d["위"], d["아래"]) for d in defs]
|
||
assert len(signs) == len(set(signs)), "같은 변수는 한 번만"
|
||
assert not re.search(r"[A-Z]{2}\d{6}", _text(defs)), "DB 열쇠 글 0"
|
||
assert all(
|
||
set(d) <= {"열쇠", "기호", "위", "아래", "이름", "갈래", "연결", "고침", "안씀"}
|
||
for d in defs
|
||
)
|
||
by = {
|
||
(d["이름"], d["갈래"]): d for d in defs
|
||
} # 800-1 기호가 생겨 중간 · 표 찾기는 다른 기호 = 다른 정의
|
||
sign = lambda d: (d["기호"], d["위"], d["아래"])
|
||
assert sign(by[("보통인부", "중간")]) != sign(by[("보통인부", "찾기")])
|
||
assert by[("사용횟수", "입력")]["연결"] == ["입력 — 옵션에서 고름"]
|
||
|
||
|
||
def test_unpicked_branches_hidden():
|
||
off, on = _text(_defs(OFF)), _text(_defs(ON))
|
||
for col in ("4t/㎡", "5t/㎡", "6t/㎡", "폼타이 소요량"):
|
||
assert col not in off
|
||
assert "칸 4t/㎡" in on and "칸 3t/㎡" not in on and "칸 5t/㎡" not in on
|
||
assert "측압" in on and "측압" not in off # 폼타이 아님이면 측압 갈래도 안 탐
|
||
|
||
|
||
def test_live_edit_preview_changes_total():
|
||
spot = {"할일": "식", "자리": "호표:0", "식": "형틀목공 × 2"}
|
||
got = eq.preview(KEY, [spot], OFF)
|
||
assert got["되나"] and got["후"]["계"] > got["전"]["계"]
|
||
|
||
|
||
def test_unknown_symbol_asks_link_then_links():
|
||
spot = {"할일": "식", "자리": "호표:0", "식": "형틀목공 × 2 + Z"}
|
||
got = eq.preview(KEY, [spot], OFF)
|
||
assert not got["되나"] and "모르는 기호 「Z」" in got["까닭"][0]
|
||
for link in (
|
||
{"갈래": "상수", "값": "0"},
|
||
{"갈래": "입력", "기본값": "0"},
|
||
{"갈래": "표", "표": "QC000431", "칸": "형틀목공", "조건": {"유형": "유형"}},
|
||
{"갈래": "요소", "요소": "LB000007"},
|
||
):
|
||
add = {"할일": "연결", "열쇠": "Z", "이름": "새값", **link}
|
||
got = eq.preview(KEY, [add, spot], OFF)
|
||
assert got["되나"], (link, got["까닭"])
|
||
assert any(d["기호"] == "Z" for d in got["수식"]["정의"])
|
||
|
||
|
||
def test_link_bad_input_blocked():
|
||
bad = [
|
||
{"갈래": "표", "표": "QC000431", "칸": "없는칸", "조건": {"유형": "유형"}},
|
||
{"갈래": "표", "표": "QC000431", "칸": "형틀목공", "조건": {}},
|
||
{"갈래": "요소", "요소": "GC000211"},
|
||
{"갈래": "상수", "값": "abc"},
|
||
]
|
||
for link in bad:
|
||
got = eq.preview(KEY, [{"할일": "연결", "열쇠": "Z", **link}], OFF)
|
||
assert not got["되나"], link
|
||
|
||
|
||
def test_drop_guard_and_unused_input():
|
||
got = eq.preview(KEY, [{"할일": "지움", "열쇠": "사용횟수"}], OFF)
|
||
assert not got["되나"] and "쓰는 곳" in got["까닭"][0]
|
||
add = {"할일": "연결", "열쇠": "Y", "갈래": "입력"}
|
||
got = eq.preview(KEY, [add], OFF)
|
||
unused = [d for d in got["수식"]["정의"] if d["기호"] == "Y"]
|
||
assert unused and unused[0].get("안씀")
|
||
assert eq.preview(KEY, [add, {"할일": "지움", "열쇠": "Y"}], OFF)["되나"]
|
||
|
||
|
||
def test_candidates():
|
||
whole = store.loaded()[1]
|
||
got = eq.links({"갈래": "표", "검색": "합판거푸집 폼타이", "분류": ""})
|
||
assert got["후보"] and "조건" in got["후보"][0]
|
||
assert eq.links({"갈래": "요소", "검색": "형틀목공", "분류": "인력"})["후보"]
|
||
assert eq.links({"갈래": "로직", "검색": "합판거푸집", "분류": ""})["후보"]
|
||
assert whole is not None
|
||
|
||
|
||
def test_groups_add_up_to_total():
|
||
"""800-4 — 묶음(블록 · 자원 · 덧줄) 소계 합 + 끝수 = 합계(비목마다) · 한 묶음 소계 = 노 + 재 + 경."""
|
||
for key in ("GC001148", "GC000211"):
|
||
eqn = eq.read(key, {})["수식"]
|
||
sub, total = eqn["소계"], eqn["합계"]
|
||
for k in ("노무비", "재료비", "경비"):
|
||
got = sum(float(r[k]) for r in sub["줄"]) + float(sub["끝수"][k])
|
||
assert abs(got - float(total[k])) < 1e-6, (key, k)
|
||
assert all(
|
||
abs(float(r["계"]) - sum(float(r[k]) for k in ("노무비", "재료비", "경비"))) < 1e-6
|
||
for r in sub["줄"]
|
||
)
|
||
|
||
|
||
def test_screen_source_groups_and_inline_edit():
|
||
ui = ROOT / "ui_template"
|
||
sum_ts = (ui / "ui_template_equation_sum.ts").read_text(encoding="utf-8")
|
||
line_ts = (ui / "ui_template_equation_line.ts").read_text(encoding="utf-8")
|
||
assert "ORDER" in sum_ts and "ui-eq__grp" in sum_ts # 가 · 나 · 다 묶음
|
||
assert "o.after?.(line)" in line_ts # 블록 식 줄 바로 아래
|
||
assert "block-key" not in line_ts # 줄 곁 DB 열쇠 글 없음
|
||
assert "ui-eq__solve" not in line_ts and "ui-eq__step" not in line_ts # 풀이 · 줄 번호 걷음
|