Merge remote-tracking branches 'origin/sub_laptop_3' and 'origin/sub_laptop_5' into sub_laptop_2

This commit is contained in:
2026-10-16 21:16:39 +09:00
10 changed files with 417 additions and 27 deletions
@@ -0,0 +1,13 @@
"""359-3 덤(요구 235-27) — B05 배수유역 버튼 글 명사형."""
from pathlib import Path
LOCALE = Path(__file__).resolve().parents[2] / "ui_template" / "ui_template_locale_b2.ts"
def test_drainage_buttons_are_nouns():
text = LOCALE.read_text(encoding="utf-8")
assert 'B05_Drainage_Btn_Analyze: ["유역 재분할",' in text
assert 'B05_Drainage_Btn_DeleteSelected: ["관 삭제",' in text
assert "유역 다시 나누기" not in text
assert "선택한 관 삭제" not in text
+23
View File
@@ -123,6 +123,29 @@ def test_로직_여럿_같은_별칭_감쌈_자재_몫_덮음(client):
assert sorted(p.name for p in (mine / "precombo").glob("*.json")) == files
def test_자재_세_칸_로직_자재_두_번_막음(client):
"""358-2 — 별도계상 줄 자재 = 세 칸(별도계상 · 관급) · 로직 자재와 같은 품목 = 400 아니고
사용자 자재에서 빼고 그 로직 `자재표시` 로 + 빠진 줄 까닭."""
assert _post(client).status_code == 200
apart = {"차례": 0, "대상": [{"자재": "MT004258", "배": 1}]} # 레미콘(25-21-120) 별도계상 줄
got = _build(client, 1, [apart])
mine = layers.personal_dir(7, 42)
mats = _doc(mine, got["참조"]["키"])["자재"]
assert [{k: m.get(k) for k in ("코드", "별도계상", "자재구분", "운반비")} for m in mats] == [
{"코드": "MT004258", "별도계상": "별도계상", "자재구분": "사급", "운반비": None}
] # 자재구분 = 고른 자재 이름(-사급) 먼저
twice = [
{"차례": 1, "대상": [{"로직": "GC000207", "배": 1}]}, # 철근 현장조립 = 로직 자재 결속선
{"차례": 0, "대상": [{"자재": "MT000209", "배": 1}]}, # 같은 결속선을 별도계상 줄에
]
got = _build(client, 1, twice)
doc = _doc(mine, got["참조"]["키"])
assert doc["자재"] == []
line = next(x for x in doc["담은로직"] if x.get("로직") == "GC000207")
assert {"별도계상": "별도계상", "자재구분": "관급"} in line["자재표시"].values()
assert any(m["까닭"] == hb.TWICE and m["차례"] == 0 for m in got["빠진줄"])
def test_몸_없음_재검사_400(client):
assert _post(client).status_code == 200
one = {"로직": "GF000101", "배": 1}
+2 -1
View File
@@ -82,7 +82,8 @@ def test_끝까지_확인한_줄만_별칭(client):
assert "굴착기 0.29 → 0.2909 ✓" in remicon["compare"]
assert "소량감률=50(블록)" in remicon["inputs"] and remicon["target"].endswith("[㎥]")
states = {x["상태"] for x in screen["lines"]}
assert {"맞음", "다른 점", "후보"} <= states
assert {"맞음", "다른 점"} <= states # 358-2 — 레미콘 · 면목 자재 줄도 맞음(후보 남지 않음)
assert seen["면목"]["상태"] == "맞음" and seen["면목"]["checked"]
body = screen["body"] # 화면이 만든 몸 = 서버 체크 제안 줄만
suggested = [x["차례"] for x in hopyo["줄"] if x["맞춤"]["체크"]]
assert [x["차례"] for x in body] == suggested and screen["leftOut"]["확인 안 함"] > 0
+62 -3
View File
@@ -115,9 +115,11 @@ def test_새_상태_체크_제안(client):
assert remicon["품"]["같음"]["콘크리트공"] and remicon["품"]["엑셀"]["인력"]["콘크리트공"]
plywood = first["합판거푸집"]
assert plywood["상태"] == "맞음" and set(plywood["갈리는칸"]) == {"측압", "폼타이규격"}
mat = first["레미콘(25-21-120)"]
assert mat["상태"] == "후보" and mat["체크"] is False
assert all(c["규격"] == "25-21-120" for c in mat["후보"][:3]) # 규격 같은 자재 먼저
mat = first["레미콘(25-21-120)"] # 358-2 — 「별도계상」 글 = 표시 · 지역 변형 = 첫째 + 꼬리표
assert mat["상태"] == "맞음" and mat["체크"] is True and "별도계상" in mat["꼬리표"]
assert mat["대상"][0]["규격"].endswith("25-21-120") and "레미콘" in mat["대상"][0]["이름"]
assert any(t.startswith("지역 · 관급/사급 못 가림") for t in mat["꼬리표"])
assert all(c["규격"].endswith("25-21-120") for c in mat["후보"]) # 변형 = 고를 수 있게
dig = _hopyo(client, 3)["구조물터파기(인력100%)"]
assert dig["상태"] == "다른 점" and dig["체크"] is False
assert dig["대상"][0]["로직"] == "GC000083" and len(dig["후보"]) == 5
@@ -268,3 +270,60 @@ def test_이름후보_뜻겹침_출처_표시(client):
assert [c["로직"] for c in dig["후보"]][0] == "GC000083" # 차례 · 판정 그대로(표시만)
mat = _hopyo(client, 1)["레미콘(25-21-120)"]
assert "이름후보" not in mat # 자재 줄은 로직 이름후보 없음
# ── 자재 · 별도계상 줄(358-2 · `M02_Hopyo_Material`) ─────────────────────────
MATERIAL_ANSWER = { # 북부청 임도 자재 · 별도계상 줄 → 우리 자재(정답 표 · 사람 확인)
("면목", "0.03×0.03×0.0424"): "MT007567", # 면 목 42, 30×30(0.03 m = 30 mm)
("자물쇠", "50m/m"): "MT008011",
("열연강판", "3.0mm"): "MT000884", # 3.0≤t<4.5 kg(같은 단위 먼저)
("원형돌망태(몸통)", "#8,45×45"): "MT006349",
("원형돌망태(뚜껑)", "#8,45×45"): "MT006350",
}
def test_자재_다듬기_규격_수():
from M02_MasterTemplete import M02_Hopyo_Material as hmat
line, apart = hmat.clean({"이름": "조약돌(별도계상)", "규격": "Ø100~200mm내외", "갈래": "자재"})
assert apart and line["이름"] == "조약돌"
assert hmat.clean({"이름": "레미콘(25-21-120)", "규격": "별도계상"})[0]["규격"] == ""
assert hmat.base("레미콘(관급)-강원도(홍천군(내면 제외))") == hmat.base("레미콘(서울)-사급")
assert (
hmat.base("와이어메쉬(용접철망)관급")
== hmat.base("와이어메쉬(용접철망),서울")
== "와이어메쉬"
)
row = {"spec": "", "nums": [42.0, 30.0, 30.0], "ranges": []}
assert hmat.spec_fit({"이름": "면목", "규격": "0.03×0.03×0.0424"}, row) == 1.0
rng = {"spec": "", "nums": [], "ranges": hmat._ranges("2.0≤T<3.0")}
assert hmat.spec_fit({"이름": "열연강판", "규격": "3.0mm"}, rng) == 0.0 # 끝 3.0 은 안 듦
got = hmat.marks(
{"이름": "레미콘", "규격": "별도계상", "갈래": "별도계상", "비고": "운반비 별도"}
)
assert got == {"별도계상": "별도계상", "자재구분": "관급", "운반비": "미포함"}
assert hmat.marks({"이름": "면목", "단가": {"재료비": 360}}, "면 목") == {
"별도계상": "직접계산",
"자재구분": "사급",
}
def test_북부청_자재_별도계상_맞음_늚(client):
_ready(client)
seen, sure = {}, 0
for no in range(1, 69):
res = client.get(f"{URL.format('personal')}/HP-000001/{no}")
if res.status_code != 200:
continue
for one in res.json()["호표"]["줄"]:
key = (one["이름"], one["규격"])
if one["갈래"] in ("자재", "별도계상") and key not in seen:
seen[key] = one["맞춤"]
sure = sum(m["상태"] == "맞음" for m in seen.values())
assert len(seen) == 30 and sure >= 8 # 옛 2 → 8(면목 · 레미콘 둘 · 와이어메쉬 · 열연강판 …)
for key, code in MATERIAL_ANSWER.items():
assert seen[key]["상태"] == "맞음" and seen[key]["대상"][0]["자재"] == code, key
pipe = seen[("탄소강관(백)", "D=125mm(외경139.8mm)")] # 이름 달라도 규격 수로 후보
assert pipe["상태"] == "후보" and "139.8" in pipe["후보"][0]["규격"]
mortar = seen[("드라이모르터", "조적용레미탈")] # 이름 오타 · 규격 같음 = 1위 후보
assert mortar["후보"][0]["자재"] == "MT004251"