- 표지 = 수식 문서 머리 · 옛 로직 줄 칸 「부품」 — 기계경비식 9 · 작업량 틀 60(기종 = 기계표 상세구분 · 규격 열쇠) · 계수식 4 · 변환기가 옮김 · 틀 문서 9 장 - 지움 = 작업량당 기계경비 6(GC001145 · 1148 · 1151 · 1156 · 1159 · 1166 · 부르는 곳 0 · 요구 734-4 · 735-4) — 수식 문서 · 옛 로직 · 키대장 폐기 칸 · 빌더 · 변수표 - 이름 꼬리 4 = 원문 제목(지게운반 · 인력운반(기계설비) · 벽돌운반 · 수중펌프 설치 및 해체) · 키 폐기 0 - 서버 = `M01_MasterData_Parts` — M01 `/logics` · M02 `/logic-lib/logics` 목록에서 부품 뺌(1,440) · 키로 열기 · 부르기 그대로 · `/m01/parts`(기종 계산식 · 틀 · 계수식) · `/m02/logic-lib/machine-parts` · 연결 찾기 과녁에서 부품 뺌(1,444 → 1,435 · 판정 코드 그대로) - 금액 전후 = 문서 1,513 · 셈 9,626(기본 + 옵션 표본) 모두 같음 · 791_1 지문 남은 1,513 같음 → 지문 다시 뜸 · 802_2 변환 대조 1,513 · 목록 xlsx 다시 씀(요약 수) - 낡은 시험 = 1,519 · 1,518 기대 → 1,513 · GC001148 견본 → 같은 꼴 GF000089 토사깍기(기계)(블록 Q 39.293 · 합계 2,190) · 지운 6 을 셈하던 줄 걷음 · 목록 수 1,440 · 묶음 328 · 부르는 수 507 - 새 지킴 시험 test_813_2_parts 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 ("GF000089", "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 # 풀이 · 줄 번호 걷음
|