- Hopyo_View basisOk(후보 근거, 줄 근거) — 앞 마디 같음은 근거 맞음 아님 · 엑셀 직종 없는 줄(기계)도 조항 같음 + 단위면 맞음 · basisPick · 고르기 창 꼬리표가 줄 근거를 넘김 - 시험 screen(앞 마디 · 보통인부뿐 · 직종 없음) · seam(북부청 실제 응답 1순위 근거 맞음 123 = 서버 기준 · 앞 마디 0) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
213 lines
9.4 KiB
Python
213 lines
9.4 KiB
Python
"""일위대가 실무자료 화면(PLAN 324-4) — 좌측 판 · 본 화면 · [로직 추가] 「실무 호표」 줄.
|
||
|
||
- 고르기 창 덧줄(Node · fetch 대역): 이 층 · 위 층 호표 줄(맞춤 n/m) · 종류 고르기 ·
|
||
고르면 문서 층에 만들기 길 → `lib.pick` 참조 줄(별칭 = 호표 품명) · 프로젝트 층엔 없음 ·
|
||
본 화면 글 도우미
|
||
- 고르기 창 근거(330-3): 꼬리표 · 엑셀 근거 줄 · [근거 맞음 모두 확정] 대상
|
||
- 엑셀 보기 창(329): 행 → 호표 · 호표 영역 A1 · 격자 길 · 여는 자리 넷 · 늦게 불러옴
|
||
- 소스: 좌측 판 자리(사전조합 아래 · 구조물 조합 층 · 쓰기 권한 없으면 올리기 꺼짐 ·
|
||
숨은 file 입력) · Labor_Detail 줄 수 그대로 · 새 파일 이름 · 700 줄 · 색 = 공용 토큰
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import re
|
||
import subprocess
|
||
from pathlib import Path
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
M02 = Path(HERE).parents[1] / "M02_MasterTemplete"
|
||
NEW = ("Hopyo_Api", "Hopyo_Pick", "Hopyo_Side", "Hopyo_View", "Hopyo_Sheet")
|
||
|
||
|
||
def _text(name: str) -> str:
|
||
return (M02 / name).read_text(encoding="utf-8")
|
||
|
||
|
||
def _run() -> dict:
|
||
row = {
|
||
"층": "system",
|
||
"주인": None,
|
||
"키": "HP-000001",
|
||
"파일": "북부청.xls",
|
||
"번": 3,
|
||
"이름": "임도표지석",
|
||
"규격": "910×600×150",
|
||
"단위": "개소",
|
||
"맞춤": [2, 4],
|
||
}
|
||
built = {
|
||
"참조": {"층": "company", "주인": "7", "키": "SC-000009"},
|
||
"이름": "임도표지석",
|
||
"다시씀": False,
|
||
"빠진줄": [{"차례": 0}],
|
||
"비교": {},
|
||
}
|
||
proc = subprocess.run(
|
||
["node", os.path.join(HERE, "helper_m02_hopyo_pick.cjs")],
|
||
input=json.dumps({"rows": [row], "built": built}, ensure_ascii=False),
|
||
capture_output=True,
|
||
text=True,
|
||
encoding="utf-8",
|
||
check=True,
|
||
)
|
||
return json.loads(proc.stdout)
|
||
|
||
|
||
def test_고르기_창_실무_호표_줄과_고름():
|
||
got = _run()
|
||
assert got["calls"][0] == {
|
||
"url": "/api/m02/layers/company/hopyo-picks?owner=7",
|
||
"method": "GET",
|
||
"body": None,
|
||
}
|
||
assert got["kinds"] == ["logic", "precombo", "hopyo"]
|
||
assert got["rows"] == [
|
||
{
|
||
"kind": "hopyo",
|
||
"key": "system||HP-000001|3",
|
||
"tag": "실무 호표",
|
||
"name": "임도표지석 910×600×150",
|
||
"sub": "북부청.xls · 제3호표 · 마스터",
|
||
"unit": "개소 · 맞춤 2/4",
|
||
},
|
||
{
|
||
"kind": "hopyo",
|
||
"key": "system||HP-000001|엑셀",
|
||
"tag": "실무 호표",
|
||
"name": "엑셀로 보기 · 여러 호표",
|
||
"sub": "북부청.xls · 마스터",
|
||
"unit": "호표 1",
|
||
},
|
||
]
|
||
build = got["calls"][1]
|
||
assert build["url"] == "/api/m02/layers/company/hopyo-build?owner=7" # 만드는 층 = 문서 층
|
||
assert build["body"] == {
|
||
"층": "system",
|
||
"주인": None,
|
||
"키": "HP-000001",
|
||
"번": 3,
|
||
"새로": False,
|
||
}
|
||
assert got["picked"] == [
|
||
{
|
||
"ref": {"층": "company", "주인": "7", "키": "SC-000009"},
|
||
"name": "임도표지석",
|
||
"alias": "임도표지석",
|
||
}
|
||
]
|
||
assert got["project"] is True # 프로젝트 층 = 덧줄 없음
|
||
|
||
|
||
def test_본_화면_글_도우미():
|
||
view = _run()["view"]
|
||
assert view["target"] == ["구조물터파기 [㎥]", "레미콘 25-21-120 [㎥]", "제46호표 [㎡]"]
|
||
assert view["inputs"] == "횟수=4회사용시, 지역=서울 · ⚠ 못 읽음: 보정작업"
|
||
assert view["memory"] == [
|
||
{"로직": "GF000101", "입력값": {"지역": "서울"}, "환산": 1},
|
||
{"자재": "MT1", "입력값": {}, "환산": 0.5},
|
||
None, # 환산 0 = 확정 못 함
|
||
None, # 대상 없음
|
||
]
|
||
assert view["word"] == ["만듦", "다시 씀", "새로 만들어 덮음"] # 알림 · 판 글
|
||
|
||
|
||
def test_줄_표_줄바꿈_없음과_고르기_창_머리():
|
||
css = _text("M02_MasterTemplete_Hopyo.css")
|
||
rule = css.split(".m02-hopyo__table.m02-hopyo__lines\n :is(th, td):is(")[1].split("}")[0]
|
||
cols = [int(n) for n in re.findall(r":nth-child\((\d+)\)", rule)]
|
||
assert cols == [1, 4, 5, 6, 7, 9, 10, 11] and "white-space: nowrap;" in rule
|
||
heads = ["갈래", "품명", "규격", "수량", "단위", "엑셀 단가", "맞춤", "대상", "입력값", "환산"]
|
||
view = _text("M02_MasterTemplete_Hopyo_View.ts")
|
||
flat = re.sub(r"\s+", "", view)
|
||
assert re.sub(r"\s+", "", ",".join(f'"{h}"' for h in heads)) in flat # 열 차례 그대로
|
||
top = css.split(".m02-hopyo__choose .ui-modal__top > .ui-btn {")[1].split("}")[0]
|
||
assert "flex-shrink: 0;" in top and "white-space: nowrap;" in top
|
||
assert '"가까운 것 없음"' in view and "builtWord(got, fresh)" in view
|
||
|
||
|
||
def test_좌측_판_자리와_권한():
|
||
side = _text("M02_MasterTemplete_UI_Side.ts")
|
||
assert 'import { buildHopyoSection } from "./M02_MasterTemplete_Hopyo_Side";' in side
|
||
host = side.split('className: "m02-side__lists ui-sidebar-panel"')[1]
|
||
assert host.index("precomboSec,") < host.index("section(hopyo.title, hopyo.body),")
|
||
assert host.index("section(hopyo.title, hopyo.body),") < host.index("extras ?")
|
||
assert "canWrite: canEdit(layer, opt.isAdmin)," in side # 구조물 조합 층 쓰기 권한
|
||
assert "hopyo.paint();" in side.split("function paint(): void {")[1]
|
||
names = set(re.findall(r"\b\w*[Pp]ractice\w*\b", side))
|
||
assert names <= {"buildPracticeSection", "practice", "M02_MasterTemplete_Practice"} # 옛 이름만
|
||
assert "LaborSection" not in side and "labor?." not in side and "picked" not in side
|
||
hopyo_side = _text("M02_MasterTemplete_Hopyo_Side.ts")
|
||
assert 'accept: ".xls,.xlsx"' in hopyo_side and "input.hidden = true;" in hopyo_side
|
||
assert "disabled: !opt.canWrite," in hopyo_side and "pageScope()" in hopyo_side
|
||
|
||
|
||
def test_로직_추가_한_곳과_파일_규칙():
|
||
detail = _text("M02_MasterTemplete_Labor_Detail.ts")
|
||
assert len(detail.splitlines()) == 693 # 줄 수 그대로(제자리 고침)
|
||
assert "withHopyo(library)," in detail
|
||
assert 'cur.담은로직.push({ 로직: "", 참조: ref, 별칭, 메모: null' in detail
|
||
pick = _text("M02_MasterTemplete_Labor_Pick.ts")
|
||
assert "more?: () => Promise<PickExtra[]>;" in pick and "...later" in pick
|
||
for name in NEW:
|
||
text = _text(f"M02_MasterTemplete_{name}.ts")
|
||
assert len(text.splitlines()) <= 700, name
|
||
css = _text("M02_MasterTemplete_Hopyo.css")
|
||
assert "var(--color-" in css and not re.search(r"#[0-9a-fA-F]{3,6}\b", css)
|
||
assert '"./M02_MasterTemplete_Hopyo.css"' in _text("M02_MasterTemplete_Hopyo_View.ts")
|
||
|
||
|
||
def test_엑셀_보기_창_행과_호표():
|
||
got = _run()
|
||
assert got["calls"][2] == {
|
||
"url": "/api/m02/layers/company/hopyo-grid/HP-000001?owner=7",
|
||
"method": "GET",
|
||
"body": None,
|
||
}
|
||
sheet = got["sheet"]
|
||
assert sheet["at"] == [None, 3, 3, 3, 4, 4, None] # 22행 → 제3호표 · 범위 밖 = 없음
|
||
assert sheet["span"] == "B20:O24" # 제3호표 영역 = B열 ~ 끝 열(열 폭 정보 O)
|
||
assert sheet["last"] == ["AA", "A"]
|
||
assert sheet["rows"] == ["4345:4345", "93:101"] # 단가산출 행 전체
|
||
|
||
|
||
def test_엑셀_보기_창_여는_자리와_늦게_불러옴():
|
||
lazy = 'import("./M02_MasterTemplete_Hopyo_Sheet")'
|
||
side = _text("M02_MasterTemplete_Hopyo_Side.ts")
|
||
assert lazy in side.split("await show(got);")[1].split("})")[0] # 올린 직후 자동
|
||
view = _text("M02_MasterTemplete_Hopyo_View.ts")
|
||
assert lazy in view and '"엑셀로 보기"' in view and '"엑셀에서 보기"' in view
|
||
assert "호표: no" in view # 호표 화면 = 그 호표 고른 채
|
||
pick = _text("M02_MasterTemplete_Hopyo_Pick.ts")
|
||
assert lazy in pick and "lib.pick(got.참조" in pick
|
||
static = r'^import (?!type )[^(;]*"\./M02_MasterTemplete_Hopyo_Sheet"'
|
||
for text in (side, view, pick):
|
||
assert not re.search(static, text, re.M) # 정적 import 없음(타입만)
|
||
sheet = _text("M02_MasterTemplete_Hopyo_Sheet.ts")
|
||
assert 'import("../A00_Common/spreadsheet/spreadsheet")' in sheet
|
||
assert not re.search(
|
||
r'^import \{[^}]*\} from "\.\./A00_Common/spreadsheet/spreadsheet"', sheet, re.M
|
||
)
|
||
assert "readOnly: true" in sheet and "wide: true" in sheet
|
||
|
||
|
||
def test_고르기_창_근거_꼬리표와_근거_맞음():
|
||
basis = _run()["basis"]
|
||
assert basis["ok"] == [True, False, False, False, False] # 조항 같음 · 직종 · 단위 모두
|
||
assert basis["plain"] == [True, True] # 줄 직종이 보통인부뿐 · 없음 = 직종 안 봄
|
||
assert basis["tag"] == [
|
||
"조항 건설 공통 3-2-1 · 직종 특별인부 · 단위 같음",
|
||
"조항 건설 공통 3-2-1 · 단위 같음",
|
||
"",
|
||
]
|
||
assert basis["source"] == [
|
||
"출처 건설표준품셈 3-2-1 · 직종 특별인부 · 단가산출 93–101행",
|
||
"단가산출 7행",
|
||
"",
|
||
]
|
||
# 지금 대상이 근거 안 맞으면 근거 맞는 후보 · 「후보」 줄만 · 근거 맞는 것 없으면 없음
|
||
assert basis["pick"] == ["GC000083", "GC000083", None, None]
|
||
view = _text("M02_MasterTemplete_Hopyo_View.ts")
|
||
assert "단산행: basis.행" in view and "근거 맞음 모두 확정" in view
|
||
assert "x.근거 || x.맞춤.후보?.some((c) => c.근거)" in view # 옛 파일 = 단추 없음
|