From d7e6fe5cf86d4c513dc60b4d33650a4b86dcaa4b Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 7 Nov 2026 18:00:30 +0900 Subject: [PATCH] =?UTF-8?q?feat(801-5):=20M01=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=EA=B8=80=20=EA=B1=B4=EC=84=A4=ED=92=88?= =?UTF-8?q?=EC=85=88=20=EC=9B=90=EB=AC=B8=20=EA=BC=B4=20=E2=80=94=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EB=8B=B9=20=E3=8E=A5=20=C2=B7=20=E3=8E=A1=20?= =?UTF-8?q?=E2=86=92=20hr=20=C2=B7=20=EC=B4=88=20=E2=86=92=20sec=20=C2=B7?= =?UTF-8?q?=20mm=20=C2=B7=20cm=20=C2=B7=20kg=20=E2=86=92=20=E3=8E=9C=20?= =?UTF-8?q?=C2=B7=20=E3=8E=9D=20=C2=B7=20=E3=8E=8F=20=C2=B7=20TON=20=C2=B7?= =?UTF-8?q?=20t=20=E2=86=92=20ton=20=C2=B7=20km/hr=20=E2=86=92=20=E3=8E=9E?= =?UTF-8?q?/hr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 대응표 master_unit_text.RULES(규칙마다 원문 근거 절) · 빌더 build_단위표기_801_5(--check · --list · 셈 지문 전후 같을 때만 씀) - M01 로직 53 개 · 칸 79 개 글만 바꿈(변수 입력 · 중간 단위 · 결과단위) · 계수 · 소요량 표 값칸 · 호표 줄 단위 · 원문이 두 꼴을 다 쓰는 단위(㎞ · km · 분 · min)는 안 건드림 - M02 사본 새로 씀 도구 M02_Copy_Units(M02_Copy_Symbols 의 fill 만 바꿔 끼움) — 사본 16 문서 28 칸 · 금액 전후 같음 - 검토 목록 docs/raw/요구_1104/_목록_801-5_단위표기.xlsx - 옛 시험 기대 고침: test_796_3 굴착기 cm 단위 초 → sec(원문 표 머리 「싸이클시간(Sec)」) - 새 test_801_5_units 6 Co-Authored-By: Claude Sonnet 5.5 --- M02_MasterTemplete/M02_Copy_Symbols.py | 10 +- M02_MasterTemplete/M02_Copy_Units.py | 55 +++++++ docs/raw/요구_1104/_목록_801-5_단위표기.xlsx | Bin 0 -> 9934 bytes .../scripts/build_단위표기_801_5.py | 142 ++++++++++++++++++ .../master_data/scripts/master_unit_text.py | 89 +++++++++++ .../로직_건설품셈_01장_적용기준.json | 6 +- .../로직_건설품셈_01장_철골공사.json | 12 +- .../로직_건설품셈_02장_가설공사.json | 2 +- .../로직_건설품셈_05장_기초공사.json | 4 +- .../로직_건설품셈_07장_항만공사.json | 8 +- .../로직_건설품셈_08장_건설기계.json | 24 +-- .../로직_건설품셈_08장_지반조사.json | 16 +- .../로직_건설품셈_10장_창호_및_유리공사.json | 2 +- .../로직_건설품셈_13장_플랜트설비공사.json | 2 +- .../로직_산림품셈_03장_작업장_관리.json | 2 +- .../로직_산림품셈_05장_식재_및_녹화.json | 6 +- .../로직_산림품셈_06장_생육보조.json | 2 +- .../master_data/로직_산림품셈_09장_토공.json | 46 +++--- .../로직_산림품셈_10장_자재․장비_운반.json | 10 +- .../master_data/로직_산림품셈_11장_가설.json | 4 +- .../로직_산림품셈_13장_구조물.json | 6 +- resources/master_data/로직_자체.json | 4 +- resources/master_template/labor/item1.json | 6 +- resources/master_template/labor/item1_2.json | 6 +- resources/master_template/labor/item3.json | 4 +- resources/master_template/labor/item3_2.json | 6 +- resources/master_template/labor/item3_3.json | 6 +- resources/tester/test_796_3_var_names.py | 2 +- resources/tester/test_801_5_units.py | 138 +++++++++++++++++ 29 files changed, 522 insertions(+), 98 deletions(-) create mode 100644 M02_MasterTemplete/M02_Copy_Units.py create mode 100644 docs/raw/요구_1104/_목록_801-5_단위표기.xlsx create mode 100644 resources/master_data/scripts/build_단위표기_801_5.py create mode 100644 resources/master_data/scripts/master_unit_text.py create mode 100644 resources/tester/test_801_5_units.py diff --git a/M02_MasterTemplete/M02_Copy_Symbols.py b/M02_MasterTemplete/M02_Copy_Symbols.py index d93d7b995..3a1c6b46d 100644 --- a/M02_MasterTemplete/M02_Copy_Symbols.py +++ b/M02_MasterTemplete/M02_Copy_Symbols.py @@ -59,8 +59,8 @@ def fill(copy: dict, whole: Any) -> tuple[dict, int, int]: return {**copy, "로직": rows, "판": skc._fp(rows)}, done, lost -def check(kind: str, path: Path) -> dict[str, Any]: - """문서 하나 — {상태: 있음 | 채울것없음 | 같음 | 다름 | 못셈, 채움, 못닿음, 사본?}.""" +def check(kind: str, path: Path, fill_fn=None) -> dict[str, Any]: + """문서 하나 — {상태: 있음 | 채울것없음 | 같음 | 다름 | 못셈, 채움, 못닿음, 사본?} · `fill_fn` = 다른 고침(801-5 단위 글).""" from M02_MasterTemplete import M02_MasterTemplete_Store as store doc = json.loads(path.read_bytes()) @@ -69,7 +69,7 @@ def check(kind: str, path: Path) -> dict[str, Any]: try: with store.at(path.parent.parent): live = skc.whole_() - new, done, lost = fill(doc[skc.KEY], live) + new, done, lost = (fill_fn or fill)(doc[skc.KEY], live) if not done: return {"상태": "채울것없음", "못닿음": lost} clean = mig._clean(kind, doc, path.stem) @@ -81,7 +81,7 @@ def check(kind: str, path: Path) -> dict[str, Any]: return {**row, "사본": new} if before == after else {**row, "전": before, "후": after} -def run(apply: bool = False, backup: Path | None = None) -> list[dict[str, Any]]: +def run(apply: bool = False, backup: Path | None = None, fill_fn=None) -> list[dict[str, Any]]: """마스터 · 회사 · 개인 층 문서마다 {어디, 종류, 키, 상태, 씀, …} · `apply` = 같음만 씀.""" if apply and backup is None: raise ValueError("--apply 는 --backup 폴더가 있어야 함") @@ -89,7 +89,7 @@ def run(apply: bool = False, backup: Path | None = None) -> list[dict[str, Any]] for where_, kind, path, project in mig._files(): if project: continue - got = check(kind, path) + got = check(kind, path, fill_fn) copy = got.pop("사본", None) row = {"어디": where_, "종류": kind, "키": path.stem, **got, "씀": False} if apply and copy: diff --git a/M02_MasterTemplete/M02_Copy_Units.py b/M02_MasterTemplete/M02_Copy_Units.py new file mode 100644 index 000000000..0710552b8 --- /dev/null +++ b/M02_MasterTemplete/M02_Copy_Units.py @@ -0,0 +1,55 @@ +"""옛 로직 사본 단위 글 새로 쓰기 — 한 번 돌리는 도구(PLAN 801-5 · 요구 663-2). + +M01 로직 단위 글을 건설품셈 원문 꼴(`master_unit_text`)로 바꾼 뒤, 사본 줄의 입력 · 중간 `단위` · `결과단위` 를 +같은 규칙으로 바꿈(`M02_Copy_Symbols` 와 같은 길 — 금액 전후 같을 때만 씀 · 쓰기 전 `--backup` 사본 · +사본 `판` 다시 셈 · 프로젝트 층 문서는 안 건드림). + + ./venv/Scripts/python.exe -m M02_MasterTemplete.M02_Copy_Units # 보기만 + ./venv/Scripts/python.exe -m M02_MasterTemplete.M02_Copy_Units --apply --backup tmp/801_5 +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path +from typing import Any + +from M02_MasterTemplete import M02_Copy_Symbols as cs +from M02_MasterTemplete import M02_Sangeun_Copy as skc + + +def fill(copy: dict, whole: Any) -> tuple[dict, int, int]: + """(새 사본, 바꾼 칸 수, 0) — 줄 단위 글만 고치고 `판` 다시 셈.""" + import master_unit_text as ut # M01 이 길을 둔 뒤 + + done, rows = 0, [] + for row in copy["로직"]: + new = {**row} + for part in ("입력", "중간"): + specs = [] + for spec in row.get(part) or []: + now = ut.canon(spec.get("단위")) + done += now != (spec.get("단위") or "") + specs.append({**spec, "단위": now} if now != (spec.get("단위") or "") else spec) + if row.get(part): + new[part] = specs + if row.get("결과단위"): + now = ut.canon(row["결과단위"]) + done += now != row["결과단위"] + new["결과단위"] = now + rows.append(new) + return {**copy, "로직": rows, "판": skc._fp(rows)}, done, 0 + + +if __name__ == "__main__": # 브레인 지시 뒤만 --apply + args = sys.argv[1:] + at = Path(args[args.index("--backup") + 1]) if "--backup" in args else None + rows = cs.run("--apply" in args, at, fill) + for one in rows: + if one["상태"] not in ("사본없음", "채울것없음", "같음"): + print(json.dumps(one, ensure_ascii=False, default=str)) + count: dict[str, int] = {} + for one in rows: + count[one["상태"]] = count.get(one["상태"], 0) + 1 + print(json.dumps(count, ensure_ascii=False), "바꿈", sum(r.get("채움", 0) for r in rows)) diff --git a/docs/raw/요구_1104/_목록_801-5_단위표기.xlsx b/docs/raw/요구_1104/_목록_801-5_단위표기.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8734267747c5bee54c8e7b471e994a7168815f10 GIT binary patch literal 9934 zcmZ{K1yo$ivi9IEgG+EvaCZoUy9Spa!QI^s!_9h5mXO+Zri4*xETVeYCS< zbhEaSi?lQ9C1LA)U!vAM}*u;*2#0xdcCYsdUCH@vkyRHenBG^ia5R zg!B~p00$)ibx7jfpy^_K2`U0}4PxOj_Xx}%rahFEg#Azk*s^S6>5oJ|YkBwHv~XgHCR<)c2hX>V)Y;yBglUhzhf(|&zs3@h7Kg8V4nqI{!2iv!k*$Nt zZ-xuNathtd$RRI737$tai6NhxbV9iOgL){@>uJyHKJS9Ul$w1NwChlzxuVY>ZmY^_ zcoB&(1Pjnvz`ZR5I(yX<_TAB98;r`e4~VIVI^AFzB5C(1G*-QQ8XAmaZ$) z(P4dM6K*JN=L(_Skr%N0*o$<2fLY#y23yknLLzUCzmg~!e5W+_ZXGJ z*ig(DcT|(lCx}yuWe+ekZ3DS$rBGNgh2S$oQY;kTK;|Q`B;|zM*7TJE*D<~8+u#S* zqCVf0*ro}n=V(YR2Hm2I6KrNU?mA9dJ-`)+PhP9FEaxR_U_&J1t+9XOpzob?OYAr= z%it%%#28ILf_)#7Oi7JV%;O^`iTV7lVDUL)TKFu1iSk|=9AjQhQRqZHy3aUuRz&WT1u7 zH|ZunRRes~Eaiw7G#L0f3U(fw1ET`t^tfKX9gBzswiAbNOQ#*@+G{{RW$ATGb?yfY ze|l>037KWHqL03jn8B6aMx!niI$&jo%S+QlXbt*ck^L={U9gCl6%ppVkyvoY!wr!u zsjo$`Jirs;Wpa_LtK>GIgU6TAd%WPZ@s~i(R6Q4kw`u2eg!ZbqjdssNIm_ZaJ0q=; zy2I!|ziLH1-2zW`$ImA-!L0M+`yZ4+6=|9b6gaUm{?+d(8y+Ku&ffMQ>>F!SAq=Zd zn{b6=WQh>k9H?ALC(iFwG}1QkYi}P^m?g0wr{tWtfRj*8Mn3c)uh&@g3cA+ znp~Nu%RB|sv}9=2q^eVmXk1PEqO#3Gpaxrh{8_VXAR8GdkAs9EX_SEbRXX<)U!`sD zom7t+ce@x{9Y!)<%hqncy}U{ThD#k9Bx_?cPqBvyi|N;kH3nn^dhO4e7@Kms$FqCs z8RZ`2F89f*3@jIZ944fB99IR2K_Ahq*7%aoGuT^Dzi!8|Qej1m^ zIiI-KLdDVu*k&V2d7=<)-_Hu~2rbQ^m|df=zX8MOv82{<=55O(@-IK1qEvSEL~k5N zv_t$mfRI!$1+BlP030Cz0N&pL#MRcp($UPs#L1EAug70eq&8Dug>W7}w0#Iw-HkW8 z<{k-uL^jHLG4n z(&dWC<3raQ{MPXZ7bP1Esf;zxxrcoL&oSGzYJ#TB>{cEw@Gy2pyMn^#i0*zmEtZWp zUc{(w`hZ-_sCR68Vmga&BRD&g_+3hU^;T7*c6DRbk=*1jReE5Iso}IyJezknXmq@M z3>9rh=$)+G*mn6CuVc2HLd@v%mZ8mC-v^QGrJIw$80vl+#8EWEZcu$+pCid0qHmK4 z^seIsnB40hdQc`0N3I(c>O`;O&%Hj&<(YPYn$59*D1zN(AFn*~)}3uhBn&vk5skCvgK643Tau0-K#ExYprZp?^2B51;cdtlyxYB+cmFzXc5DKs&D3kc9YLyulJsUQQq6* z^DSgn%;P}!Vyp7&U0qwqKRK%@M#X)5{-^9?|3*~vRkIICSh)m#za z=v~}r#mi{6#oQdx()C)P;v()qPM*U4-KpxdQ)Ep9#&D+Cjos_Z%71c!6Pw;zJ%*-W=TQuc2*vpMK0u$* zqd7fVv*R|D@YBcYP4>BxXQYEB{;mx?pwvji-m6mJ4nxDuA8)a6p<2pgwd(4)aRA{P@&i2G-mF_VyhTt*QE_XOD8!u zZE}+H_Q7JthtXAT+wWWs$MyrE>$m834b~T&ey0V>Y2dy$>m0Hj2Nz&4mX;nrvfEhF z!CH$lPym2$pavg;@&=K zYcHsu^E^xK>ASH_i~&>O=nvi?m#Gkz(FH6^qAYmX_`x=s?7Xe`GnYFlJ1SO|v=LRb znOa8wl11p^ATPSE+F$aqT=KDUuJuQ=?)@Wqgh%6|qgbC*LT8NT3z9 z3ueO1*%b}yb_MllL6jfDJRq6ALow0^YqeKGK}i>L^# z;$#eqg|ruSGOJG<{l3OumwQhfm9#RW3}~bo<3#mN9nk!u7BM*^Nr36jpzX5R$o( z-Q(wl(kSqR6>^VwZk8d4#IFhry-U9UWsr9rgt8^k^QkN{=#vSaRW8$K^3!K|qcXf@ zg8LJp=p_Lz^H1ynt+b9<{FPtTO#A|WM0bh&JpMj2i~WIpxS4X4)alM6@uzG%R-WAlI)GmLaHlN{aLjaSCb{9 ze4_&^Iu0XtrOsh%*p4A0ct~CCX31 zPV*bA(#C`Y>47L^L8o0I@>-Xa#dB8q%5hxIKrzvthZ~i6b4g0CQCMj#a!LqtokB9q zjk6VW^QHEj4`%twhW?o0pFg+PYE{sa0@=}0;7`atlAEOsh!*hxe}7lyBcWV2B3aO* z$scYvvsmPw=xl3(kn@G3L(vWd2O>id&e??y?skRhZNisorKO;fKU54d|K7H%7T`b3 zbIRVdpRPWxd0&7Ci5<72D-F8gO#ks}L-Gw!qR^A+)#9booaj8o{RTS5n>r(nd@CYaxdO!`MP_g1# zp~@zltuR+vK46s}SM;RsypUt+MdG4_8BjPLxkJ2W4d=MSa!BIu5`jD$7f&_Tqcp+R z4{7e5&!Tj3Y9dNFT{F) zJ_RvxHg>T%c4u+=eXriAbMo34R_B5Xj_pU0D7fBt;zItZSI+zP@Fnn_z6H5Z>U(!~ zD5jNJGTHb}^pG*J8nTfl0G$(rRz}YOYj{g{?#)x&%af&aQc*JlZ;&zt|hVC{WH=&{es}(V%He^nfO%mf#+-;qCT(8xE z_csQLtmLbK8^qh5%nQUPopz6AHwMZsPuX2p?IEbyNfVY%4K+dxz4C}l%8|I2hr)$} z246r-6pO;9-u_tkka8B4o1Iq=_i36Zw-s5u&nHlUl|sBsKLb>vC}S-apz;Z73st*s zfhwcCuyjty>}Cy>Cqy+xI3l5y41=`0bW&djNeA89r4lG*w6)2i>O>-vxsO0#bvllu zB;;DQ%PvOlK}B*J{2Njl{JK!et=q{sdTZ~{niD<%dnw)iD^xisbb=bcZzf(gaZoBG z?z7Y<-O!pt-X6H#hb7vJH7oX$pYB3GVbD&CNCd6ROU9%bAL~#Ogqwnw>->-DRyRUNA;SP=OEUD;j2%?&ACi!RI1a%A7A%I;d?%@@HMig zYnZiZ6S%Q>PS5PS0I?Cc2vv0~hDhw6h=fbk%zCN zYD@=^12bPLT|~z{8`sOQo-SIpP;oBUbi2Es<-JEPq&_Y=CN;{61Qre%yU~WhkMok? z&)(VT+Pl1lm*KRR+(nHaNEY->RDp~TW2}ng3k_eC*7Ppw-At(sY7f!l2el4Soytrj?<3K!b!l z?;hPoq!bIJr1)tvE#_gz+1FK&J9oe_ut-ldv@%MAok=7SqH5Ixm|B-TIME7!ms;?u z3~;Mj1?~|-hsYU5m!EORV%LcczM)UWuHOO+5$9qD_Cug2x@uj~%*33jUai#}do)qe zB=$qAGZpJx;=q2ns4_9U{f_Q3FJVDZ$nRm4*+H+v6s!_p;9E)>JAugQl5+Ek+Fsb* z@5d|k$ zvl~CMZE9^NbddqM)SVHqC6TUTT=o>Jb_}-}dJT%X3udYk4&x`>Cic@Az(9&70cX_G zeE%l!etRNopQZ6^3`Sb~>z=3+nQsr&5@r*z5CNMdLu9GV$Nb(@xbR0`oTts*m{WJ3nhF#po&Z+?xM|SM~LY@W-sQNaR8)uyC-o5T<3I4 z7+6}b;;{-;?{cj|5nQ?s%>JehmJHV}SHxOodQ8TTl%{5SbU4tR3u4UcKuySrrevN8 z_6ne?E6geHSEwA(nkO%5!U>8L6#(JPw!U(L;y$^Iqy`Jpvt5ZOb!Pg$ulHEc2-eGb z&gr*n*t4~U!_8m+2zyi!xE5#8YW{rvaZ+0oaTQwZbW|nx~We61HW{`lg5_YmdPX7|{KEL*j7_al8&KI8@M-&K)cpPe&fSQem_Pyp>)K#jna# zxT;oRjYA=Zb)Fiw_$*Cm$EIE0JjzEhGej3WA4IR(%OBiH+$;%7>Se2vZquNa)1@Cu zEIGdXbt?9(>8>3T!#;n%T)Odzr#HMfr>V+lgV5%|yXIFyGp+q}hj_tuqxHIdb)s@2 z*KHjAgcEmu`hw9GwKjr!*B;2@+7ZF<0~Cq>Y=I%@D{1G)TUWTC%V^<(!FhLJ7POzd zhk92${xNZA;rMtdfiI@AD};~w z2>6jflDuSbN%^^O-OT%Ofox}o&qinCj@NF`&nv$0X<6{`^e9*%mf!8fX7Rzj&uka! zp)7t*tg7Koj~e<@X>c3nxH$6(BWZEdwkC;7zjh3&xTxCYao5JS4xF3&j?|{^4Shv? ze4FCFb*M1h`^M7T7@k#D_HAkex=6^<7!oB9x&E-BDoa`~2bfZKrh+$&Wg#9FH}TTPHqP z|5NDhl{U|gi`zXRk;1_|g$K>&Lg$OEupm*Jt7`PX0UFYaID&dY8FVyJQscMw4b(`u z{)#;Cyg%~V`F99^HbBjqS)My)HKG#l|J0zCSd$#tQy($y-9-w!3m9n`9a}DVsk*&g zi8U`kl>6E+vT+tIPfQe_SKQ;hFq&AX;8>qfk0ml>geQdW#ahT%2z%tg)cFq zKVfg76~(8dHBh=2ktv!Dcb$OrcQoXon;O2iP(IyTQ0~>GZzQwHyQTGi;wXR24# z|76$qlC{O#*chJa<6K^?B?P4>ZkJJnU_XGEGY=DvqHIrwJt~x`e zycHzJy8cG#Jy9=HK>~%*<6qS|(pX0K75$f-Snk^Qj(bY%v*FfaQJ=>Q-$i!{ z)!9PpSin)a?l4UTiJN5l6ao{tcn;r7()`*d5}metYU9gZt6D_6(}igNw!uJ{@7d^g z^vl9`D+*85UGqvbDypRXTAg>u-9A%AUv@u)#9bw-d}Y2$@NX>vUFIia(6=>{VT;4b?65p7>k8tw zygQ9T8l?5Z-j7Y@WpsHQaV9OZqV(ruA;;O*k;^O6*jx!QJNjSXeAx(>gbKl|mA3rL z0wcp3x3&G3%ZVZ^fxGrh=7O-Pc`*cP+seB?j`2T!-k4W?J;JwqohaJhR`&a_!O+&$ z^0%Euf#u}8nbCtQFzx`9QC~3BrctmJ5g>AlJZVzd^X_TB(=a&ALRA*`V4QTcBD!|S zW5J7vl>T&Qdw$FkST(DbuGNDNu(MR*tXO$l$Y6ngL&TrS+GmlL(UEnSW5tYiSI zk~xck6>N`%0)h&_v@nq=rMEPH`S@c9Fm8 zdF|Pe+)2Rjl{S_bbM7ZhrEsoDnDziBPE|*ehhs5{O)WX&ZtA26rPTx{jsbkN{>AcA z%U-cH*5*Oxct+*NZ9f^%~f*WH@=Il?I%Ll-Xf21WHxfn@(#3$?|qIn z=D3!djZah_r`*C~6krP(eHnUEr)_2uckk3|PV}j>|E*~ydJc>=uY!+yT>yf=7vOJY z``^p(m*Vxt$sqYu4LlIb z#?e^xW)c-0zX_Y%_gDVG+RO(?WFQdpcjg}9s=X3Ff8z}$pV(@FzRBF;zs6>q^fNU# z<;}B5jq^(4ZRO7JdWU|;SzTKp4!D3e!8TR}A#b%0dKd`9rWxKMT`*oEiFjMD+e{pez4pBX(853;wc{5O1gL9pI2))y>0CmuObFD^ADY_D)=Ke=)E?bcbFEI-*r>sw0Ij2!NE^o@|qJvKw<&@@2;U&1^Vmf z_bTQ8zlZ2g>OXsU{-y;0{2(g-H}!uydj2H;v(x1t^1;{P|4ZM?p8$WJbp8jR&L0u} zFM$7g?)fLmpF4wpP)PCrE6QK{gg;UKOgH|6;s*gF`U~Zs6r{nmC_?GN1;{U^q{E73YQT~IoO%4G37ZX*Kfq89j0ss(S O|3R-_i=FcK+y4MPt}`Y8 literal 0 HcmV?d00001 diff --git a/resources/master_data/scripts/build_단위표기_801_5.py b/resources/master_data/scripts/build_단위표기_801_5.py new file mode 100644 index 000000000..cd2f58d86 --- /dev/null +++ b/resources/master_data/scripts/build_단위표기_801_5.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +"""M01 로직 단위 글을 건설품셈 원문 꼴로 (PLAN 801-5 · 요구 663-2) — 글만 바꿈 · 셈 지문 그대로. + +돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/build_단위표기_801_5.py [--check | --list 파일.xlsx] +- 대상 = M01 로직 줄의 입력 · 중간 변수 `단위` · `결과단위` — 규칙 · 근거 절 = `master_unit_text.RULES`. +- 계수 · 소요량 표 값칸 · 호표 줄 단위 · 사용자가 이미 원문 꼴로 쓴 칸은 안 건드림(이미 같으면 그대로 — 두 번 돌려도 같음). +- 쓰기 전 지문 대조 = 줄마다 엔진 셈 · 끝수 지문이 전후 같을 때만 씀(다르면 멈춤). +- `--check` = 쓰지 않고 쓸 것이 남았는지만(남았으면 종료 코드 1) · `--list` = 검토 목록 xlsx 만 씀(마스터 안 씀). +""" + +from __future__ import annotations + +import copy +import hashlib +import json +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE.parents[2])) +import master_auto as ma # noqa: E402 +import master_cut as mcut # noqa: E402 +import master_formula as mf # noqa: E402 +import master_unit_text as ut # noqa: E402 + +from M01_MasterData import M01_MasterData_Store as st # noqa: E402 + + +def plan(files: dict[str, dict]) -> tuple[list[dict], list[dict], dict[str, dict]]: + """(저장 묶음, 목록 줄들, 새 files) — 읽기 전용.""" + batch, rows, new_files = [], [], {} + for name, data in files.items(): + if data.get("그룹") != "로직": + new_files[name] = data + continue + changes, new_data = [], copy.deepcopy(data) + for row in new_data.get("줄", []): + hit = False + for kind in ("입력", "중간"): + for spec in row.get(kind) or []: + now = ut.canon(spec.get("단위")) + if now != (spec.get("단위") or ""): + rows.append(_item(row, kind, spec["이름"], spec["단위"], now)) + spec["단위"], hit = now, True + if row.get("결과단위"): + now = ut.canon(row["결과단위"]) + if now != row["결과단위"]: + rows.append(_item(row, "결과단위", "", row["결과단위"], now)) + row["결과단위"], hit = now, True + if hit: + changes.append({"op": "edit", "key": row["키"], "row": row}) + new_files[name] = new_data + if changes: + batch.append({"file": name, "version": st.read(name)[1], "changes": changes}) + return batch, rows, new_files + + +def _item(row: dict, kind: str, var: str, before: str, after: str) -> dict: + return { + "로직": row["키"], + "이름": row.get("이름", ""), + "칸": kind, + "변수": var, + "지금": before, + "원문 꼴": after, + "규칙": ut.rule_of(before), + } + + +def fingerprint(whole: mf.Master, row: dict) -> str: + """줄 하나의 엔진 셈 · 끝수 지문 — 멈춤도 지문.""" + try: + key = str(row["키"]) + raw = mf.run( + whole, + key, + { + **ma.values(whole, row), + **{k: v for k, v in ma.defaults(whole, row).items() if v is not None}, + }, + ) + text = json.dumps(raw, default=str, sort_keys=True, ensure_ascii=False) + text += json.dumps( + mcut.engine(whole, key, raw), default=str, sort_keys=True, ensure_ascii=False + ) + except Exception as e: # noqa: BLE001 + text = f"ERR {type(e).__name__} {e}" + return hashlib.sha256(text.encode()).hexdigest()[:16] + + +def differing(old: dict[str, dict], new: dict[str, dict]) -> list[str]: + """지문이 바뀐 로직 키들 — 고친 줄만 대조.""" + before, after = mf.Master(old), mf.Master(new) + out = [] + for name, data in new.items(): + if data.get("그룹") != "로직": + continue + was = {r["키"]: r for r in old[name]["줄"]} + for row in data["줄"]: + if row != was[row["키"]] and fingerprint(before, was[row["키"]]) != fingerprint( + after, row + ): + out.append(row["키"]) + return out + + +def write_list(rows: list[dict], path: Path) -> None: + import openpyxl + + book = openpyxl.Workbook() + sheet = book.active + sheet.title = "바뀜" + cols = ["로직", "이름", "칸", "변수", "지금", "원문 꼴", "규칙"] + sheet.append(cols) + for r in rows: + sheet.append([r[c] for c in cols]) + rule = book.create_sheet("대응표") + rule.append(["규칙", "바꿀 글", "근거 절(건설공사 표준품셈 본문 아래)", "바뀐 칸 수"]) + for name, _, to, _, files in ut.RULES: + rule.append([name, to, " · ".join(files), sum(name in r["규칙"] for r in rows)]) + path.parent.mkdir(parents=True, exist_ok=True) + book.save(path) + + +def main() -> None: + files, _ = st.loaded() + batch, rows, new_files = plan(files) + print(f"쓸 것 — 로직 {len({r['로직'] for r in rows})} · 칸 {len(rows)}") + if "--list" in sys.argv: + write_list(rows, Path(sys.argv[sys.argv.index("--list") + 1])) + return + if "--check" in sys.argv: + raise SystemExit(1 if batch else 0) + if batch: + bad = differing(files, new_files) + if bad: + raise SystemExit(f"셈 지문이 달라지는 로직 {len(bad)} — 멈춤: {bad[:10]}") + print(st.save(batch)) + + +if __name__ == "__main__": + main() diff --git a/resources/master_data/scripts/master_unit_text.py b/resources/master_data/scripts/master_unit_text.py new file mode 100644 index 000000000..f2bea8989 --- /dev/null +++ b/resources/master_data/scripts/master_unit_text.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +"""M01 로직 단위 글 → 건설품셈 원문 꼴(PLAN 801-5 · 요구 663-2) — 글만 바꿈 · 셈 · 환산 안 바꿈. + +`canon(단위 글)` = 아래 `RULES` 를 차례로 적용(이미 원문 꼴이면 그대로 · 두 번 돌려도 같음). +`RULES` 한 줄 = (이름, 지금 글 정규식, 바꿀 글, 원문 글, 근거 절들) — 근거 절 = `EVIDENCE_ROOT` 아래 파일 · +그 파일 안에 원문 글이 실제로 있어야 함(시험 `test_801_5_units`). 원문이 두 꼴을 다 쓰는 단위(㎞ · km · 분 · min)는 안 건드림. +단위 칸 중 원문 표를 그대로 옮긴 계수 · 소요량 표 값칸 · 호표 줄 단위(요소 단위)는 대상 아님. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +EVIDENCE_ROOT = Path("resources/knowledge/original/원가계산/건설공사_표준품셈/본문") +_A = r"(? str: + """원문 꼴 단위 글 — 규칙에 안 걸리면 그대로.""" + text = str(unit or "") + for _, pat, to, _, _ in _COMPILED: + text = pat.sub(to, text) + return text + + +def rule_of(before: str) -> str: + """바뀐 단위가 걸린 규칙 이름들(검토 목록 · 대응표용).""" + out, text = [], str(before or "") + for name, pat, to, _, _ in _COMPILED: + if pat.search(text): + out.append(name) + text = pat.sub(to, text) + return " · ".join(out) diff --git a/resources/master_data/로직_건설품셈_01장_적용기준.json b/resources/master_data/로직_건설품셈_01장_적용기준.json index db6024264..e2ecee7d4 100644 --- a/resources/master_data/로직_건설품셈_01장_적용기준.json +++ b/resources/master_data/로직_건설품셈_01장_적용기준.json @@ -217,7 +217,7 @@ { "이름": "인원", "식": "무게 / 운반량", "단위": "인" }, { "이름": "환산계수", "식": "만약(경사각도 > 0, 찾기(CC000347, 각도=경사각도).환산계수, 1)" }, { "이름": "환산거리", "식": "환산계수 * 운반거리", "단위": "km" }, - { "이름": "왕복평균속도", "식": "찾기(CC000345, 도로상태=도로상태).왕복평균속도", "단위": "km/hr" }, + { "이름": "왕복평균속도", "식": "찾기(CC000345, 도로상태=도로상태).왕복평균속도", "단위": "㎞/hr" }, { "이름": "준비작업시간", "식": "찾기(CC000346).준비작업시간", "단위": "분" }, { "이름": "작업시간", "식": "만약(실작업시간 > 0, 실작업시간, 찾기(CC000342).실작업시간)", "단위": "분" } ], @@ -280,7 +280,7 @@ "구분": "건설품셈 공통", "상세구분": "01장 적용기준", "이름": "건설폐기물 발생량 — 건설 공통 1-5-4", - "결과단위": "TON", + "결과단위": "ton", "출처": "건설품셈 공통 1-5-4 2", "개정": [2011, 2014, 2017, 2020], "소유": "공용", @@ -293,7 +293,7 @@ { "이름": "면적", "설명": "단위면적 산출 면적", "단위": "㎡", "출처": "건설품셈 공통 1-5-4 2" }, { "이름": "증가율", "설명": "폐콘크리트 수량 증가(%) — 주③ 파일 15 · 20% 이내 · 주④ 세륜 개소당 3% 이내 · 0 = 없음", "단위": "%", "출처": "건설품셈 공통 1-5-4 2 주③ ④" } ], - "중간": [{ "이름": "원단위", "식": "찾기(CC000519, 공사=공사, 구조=구조, 폐기물=폐기물).발생량", "단위": "TON/㎡" }, { "이름": "증가", "식": "만약(폐기물 == '폐콘크리트류', 증가율 / 100, 0)" }], + "중간": [{ "이름": "원단위", "식": "찾기(CC000519, 공사=공사, 구조=구조, 폐기물=폐기물).발생량", "단위": "ton/㎡" }, { "이름": "증가", "식": "만약(폐기물 == '폐콘크리트류', 증가율 / 100, 0)" }], "결과": { "식": "면적 * 원단위 * (1 + 증가)", "출처": "건설품셈 공통 1-5-4 2" }, "끝수": null, "변수표": { diff --git a/resources/master_data/로직_건설품셈_01장_철골공사.json b/resources/master_data/로직_건설품셈_01장_철골공사.json index 7a61f589c..b14759e61 100644 --- a/resources/master_data/로직_건설품셈_01장_철골공사.json +++ b/resources/master_data/로직_건설품셈_01장_철골공사.json @@ -12,13 +12,13 @@ "구분": "건설품셈 건축", "상세구분": "01장 철골공사", "이름": "철골 공장가공조립(기본철골공수)", - "결과단위": "원/t", + "결과단위": "원/ton", "출처": "건설품셈 건축 1-1-1", "개정": [2008, 2013], "소유": "공용", "비고": "공수에 비계 · 보조공 포함(주①) · 제경비에 산재보험료 · 간접노무비 · 일반관리비 · 이윤 없음(주③) · 용접은 1-1-3 · 부자재는 1-1-2(현장 세우기 제외) · 서비스볼트 규격은 설계에 따름 · 해석: 작업난이도(1-1-2)를 공수에 곱함", "입력": [ - { "이름": "강재총사용량", "단위": "t", "출처": "건설품셈 건축 1-1-1" }, + { "이름": "강재총사용량", "단위": "ton", "출처": "건설품셈 건축 1-1-1" }, { "이름": "부재", "고르기": ["전용접부재", "H형강부재"], "출처": "건설품셈 건축 1-1-1 비고", "비고": "전용접부재(Built up) · H형강부재(Rolled shape)" }, { "이름": "작업난이도", "단위": "배", "범위": [0.8, 1.2], "출처": "건설품셈 건축 1-1-2", "비고": "구조공별 — 가공부재 종류 적은 구조 0.8∼0.95 · 표준라멘 1.0 · 종류 많은 구조 1.05∼1.2" }, { "이름": "공수직종", "고르기": ["1011", "1012"], "출처": "건설품셈 건축 1-1-1", "비고": "원문 「인·일」에 직종 없음 — 설계자가 고름(1011 철골공 · 1012 용접공) · 공장제작 노임을 쓰면 관리자가 바꿈" } @@ -81,13 +81,13 @@ "구분": "건설품셈 건축", "상세구분": "01장 철골공사", "이름": "철골 공장용접(기본용접공수)", - "결과단위": "원/t", + "결과단위": "원/ton", "출처": "건설품셈 건축 1-1-3", "소유": "공용", "비고": "해석: 기본용접공수 = 용접공 · 1-1-4 보정계수(강재총사용량)를 공수에 곱함 · 용접재료는 환산용접길이(m/t) × m당 양 · 제경비에 산재보험료 · 간접노무비 · 일반관리비 · 이윤 없음(주③) · 특수 구조물은 기계설비부문 플랜트용접 참조(주⑤)", "입력": [ - { "이름": "환산용접길이", "단위": "m/t", "출처": "건설품셈 건축 1-1-3 주④", "비고": "용접길이 × 환산계수(필릿 · V·K·X 판두께별 계수표) — 필릿 각장 6㎜ 환산" }, - { "이름": "강재총사용량", "단위": "t", "출처": "건설품셈 건축 1-1-4" }, + { "이름": "환산용접길이", "단위": "m/ton", "출처": "건설품셈 건축 1-1-3 주④", "비고": "용접길이 × 환산계수(필릿 · V·K·X 판두께별 계수표) — 필릿 각장 6㎜ 환산" }, + { "이름": "강재총사용량", "단위": "ton", "출처": "건설품셈 건축 1-1-4" }, { "이름": "부재", "고르기": ["전용접부재", "H형강부재"], "출처": "건설품셈 건축 1-1-1 비고", "비고": "전용접부재(Built up) · H형강부재(Rolled shape)" }, { "이름": "용접방식", "고르기": ["수용접", "반자동용접", "자동용접"], "출처": "건설품셈 건축 1-1-4", "비고": "반자동용접이 표준" } ], @@ -348,7 +348,7 @@ "개정": [2008, 2018, 2023], "소유": "공용", "비고": "본조임 · 조임검사 포함 · 철골설계수량 300ton 이상은 보정계수 a를 곱함(주④)", - "입력": [{ "이름": "볼트본수본t", "단위": "본/t", "출처": "건설품셈 건축 1-2-4" }, { "이름": "강재총사용량", "단위": "t", "출처": "건설품셈 건축 1-2-4 주④" }], + "입력": [{ "이름": "볼트본수본t", "단위": "본/ton", "출처": "건설품셈 건축 1-2-4" }, { "이름": "강재총사용량", "단위": "ton", "출처": "건설품셈 건축 1-2-4 주④" }], "중간": [ { "이름": "보정a", "식": "만약(강재총사용량 < 300, 1, 찾기(CC000165, '강재총사용량(t)'=강재총사용량, '1t당 볼트본수(본)'=볼트본수본t).'보정계수 a')", "출처": "건설품셈 건축 1-2-4 주④" }, { "이름": "철골공", "식": "찾기(QC001195, '1t당 볼트본수(본/t)'=볼트본수본t).철골공 * 보정a" }, diff --git a/resources/master_data/로직_건설품셈_02장_가설공사.json b/resources/master_data/로직_건설품셈_02장_가설공사.json index e1da75c16..96567f50f 100644 --- a/resources/master_data/로직_건설품셈_02장_가설공사.json +++ b/resources/master_data/로직_건설품셈_02장_가설공사.json @@ -1740,7 +1740,7 @@ "개정": [2018], "소유": "공용", "비고": "재료량 할증 포함 · 그물망 손율 100% · 보강재 손율 원문 없음 — 재료량 그대로", - "입력": [{ "이름": "결속선량", "단위": "kg/10㎡", "범위": [0.3, 0.4], "출처": "건설품셈 공통 2-8-7 재료량" }], + "입력": [{ "이름": "결속선량", "단위": "㎏/10㎡", "범위": [0.3, 0.4], "출처": "건설품셈 공통 2-8-7 재료량" }], "중간": [{ "이름": "비계공", "식": "찾기(QC000084, 구분='비계공').수량 / 10" }, { "이름": "보통인부", "식": "찾기(QC000084, 구분='보통인부').수량 / 10" }], "호표": [ { "종류": "인력", "요소": "LB000006", "이름": "비계공", "단위": "인", "수량": "비계공", "비목": "노무비" }, diff --git a/resources/master_data/로직_건설품셈_05장_기초공사.json b/resources/master_data/로직_건설품셈_05장_기초공사.json index 04b775b60..1f4eecfae 100644 --- a/resources/master_data/로직_건설품셈_05장_기초공사.json +++ b/resources/master_data/로직_건설품셈_05장_기초공사.json @@ -1609,9 +1609,9 @@ "이름": "t1", "식": "만약(타설깊이 <= 25, 찾기(QC000348, L='25이하').t_1, 만약(타설깊이 <= 30, 찾기(QC000348, L='30이하').t_1, 만약(타설깊이 <= 35, 찾기(QC000348, L='35이하').t_1, 만약(타설깊이 <= 40, 찾기(QC000348, L='40이하').t_1, 만약(타설깊이 <= 45, 찾기(QC000348, L='45이하').t_1, 만약(타설깊이 <= 50, 찾기(QC000348, L='50이하').t_1, 만약(타설깊이 <= 55, 찾기(QC000348, L='55이하').t_1, 0)))))))", "출처": "건설품셈 공통 5-2-3 준비 및 이동시간", - "단위": "초" + "단위": "sec" }, - { "이름": "cm", "식": "t1 + 타설깊이 / 찾기(QC000349, N치=N치).V_1 + 타설깊이 / 찾기(QC000349, N치=N치).V_2", "출처": "건설품셈 공통 5-2-3 cm", "단위": "초" }, + { "이름": "cm", "식": "t1 + 타설깊이 / 찾기(QC000349, N치=N치).V_1 + 타설깊이 / 찾기(QC000349, N치=N치).V_2", "출처": "건설품셈 공통 5-2-3 cm", "단위": "sec" }, { "이름": "Q", "식": "3600 * 타설깊이 * 찾기(QC000347, 구분=현장).효율 / cm", "출처": "건설품셈 공통 5-2-3 Q", "단위": "m/hr" }, { "이름": "T", "식": "1 / Q" }, { "이름": "특별인부", "식": "찾기(QC000344, 구분='특별인부').수량 * T / 8" }, diff --git a/resources/master_data/로직_건설품셈_07장_항만공사.json b/resources/master_data/로직_건설품셈_07장_항만공사.json index dd00df9cf..a9dc00bcd 100644 --- a/resources/master_data/로직_건설품셈_07장_항만공사.json +++ b/resources/master_data/로직_건설품셈_07장_항만공사.json @@ -168,7 +168,7 @@ "구분": "건설품셈 토목", "상세구분": "07장 항만공사", "이름": "수중고르기 잠수부 1조 시간당 고르기 면적", - "결과단위": "㎡/시간", + "결과단위": "㎡/hr", "출처": "건설품셈 토목 7-2-5", "개정": [2021], "소유": "공용", @@ -323,7 +323,7 @@ "입력": [ { "이름": "관경", - "단위": "mm", + "단위": "㎜", "고르기": ["250이하", "300", "350", "400", "510", "560", "610", "630", "660", "685", "710", "760", "840", "860"], "출처": "건설품셈 토목 7-4-1" }, @@ -376,7 +376,7 @@ "소유": "공용", "비고": "크레인 규격 띄우개 관경 430∼1,400 10톤 · 1,500 이상 15톤(주③ 장비규격 표) · 같은 톤 크롤러·타이어 크레인 가능(주④) · 철거는 본 품 30%까지 감(주①) — 감률 설계자 입력 · 소운반 포함 · 체결부 절단 별도", "입력": [ - { "이름": "관경", "단위": "mm", "고르기": [430, 500, 600, 700, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600], "출처": "건설품셈 토목 7-4-2" }, + { "이름": "관경", "단위": "㎜", "고르기": [430, 500, 600, 700, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600], "출처": "건설품셈 토목 7-4-2" }, { "이름": "작업", "고르기": ["설치", "철거"], "출처": "건설품셈 토목 7-4-2 주①" }, { "이름": "철거감률", "단위": "%", "범위": [0, 30], "출처": "건설품셈 토목 7-4-2 주①" }, { @@ -419,7 +419,7 @@ "소유": "공용", "비고": "크레인 규격 관경 200∼710 10톤 · 760 이상 15톤(주④ 장비규격 표) · 해상관 1set = 배송관 1본+고무슬리브 1본+띄우개 1본 · 침설관 1set = 배송관 2본+고무슬리브 1본 · 배송관 예인·침설 제외(주①)", "입력": [ - { "이름": "배송관관경", "단위": "mm", "고르기": [200, 250, 300, 350, 400, 510, 560, 610, 630, 660, 685, 710, 760, 840, 860], "출처": "건설품셈 토목 7-4-3" }, + { "이름": "배송관관경", "단위": "㎜", "고르기": [200, 250, 300, 350, 400, 510, 560, 610, 630, 660, 685, 710, 760, 840, 860], "출처": "건설품셈 토목 7-4-3" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], diff --git a/resources/master_data/로직_건설품셈_08장_건설기계.json b/resources/master_data/로직_건설품셈_08장_건설기계.json index bef20560b..4c05877a6 100644 --- a/resources/master_data/로직_건설품셈_08장_건설기계.json +++ b/resources/master_data/로직_건설품셈_08장_건설기계.json @@ -1308,13 +1308,13 @@ { "이름": "협소현장", "설명": "주택가 상하수도관로부설 등 협소현장(주⑦)", "고르기": ["해당 없음", "상하수도관로부설 등 협소현장"], "출처": "건설품셈 공통 8-2-3 2 주⑦" }, { "이름": "f", "설명": "체적환산계수 — 토질 · 상태 변화율(건설 공통 1-2-3)", "출처": "건설품셈 공통 8-1-4" }, { "이름": "E입력", "설명": "작업효율을 직접 넣을 때 — 0 이면 원문 표 값", "범위": [0, 1], "출처": "건설품셈 공통 8-2-3" }, - { "이름": "싸이클입력", "설명": "1회 싸이클시간을 직접 넣을 때 — 0 이면 표", "단위": "초", "범위": [0, 30], "출처": "건설품셈 공통 8-2-3 3" } + { "이름": "싸이클입력", "설명": "1회 싸이클시간을 직접 넣을 때 — 0 이면 표", "단위": "sec", "범위": [0, 30], "출처": "건설품셈 공통 8-2-3 3" } ], "중간": [ { "이름": "K", "식": "찾기(CC000042, 현장조건=버킷조건).K" }, { "이름": "보정", "식": "만약(E보정 == '해당 없음', 0, 찾기(CC000046, 항목=E보정).보정값)" }, { "이름": "E", "식": "만약(E입력 > 0, E입력, 만약(협소현장 == '해당 없음', 찾기(CC000043, 현장조건=현장조건, 등급=등급, 토질명=토질명).E, 찾기(CC000044, 등급=등급, 토질명=토질명).E) - 보정)" }, - { "이름": "cm", "식": "만약(싸이클입력 > 0, 싸이클입력, 찾기(CC000045, 규격=규격구분, 각도=선회각도).시간)", "단위": "초" } + { "이름": "cm", "식": "만약(싸이클입력 > 0, 싸이클입력, 찾기(CC000045, 규격=규격구분, 각도=선회각도).시간)", "단위": "sec" } ], "결과": { "식": "3600 * 버킷용량 * K * f * E / cm", "출처": "건설품셈 공통 8-2-3" }, "끝수": { "대상": "결과", "자리": 3, "방법": "반올림", "출처": "건설품셈 공통 8-1-4 주①" }, @@ -1385,7 +1385,7 @@ { "이름": "협소현장", "설명": "주택가 상하수도관로부설 등 협소현장(주⑦)", "고르기": ["해당 없음", "상하수도관로부설 등 협소현장"], "출처": "건설품셈 공통 8-2-3 2 주⑦" }, { "이름": "f", "설명": "체적환산계수 — 토질 · 상태 변화율(건설 공통 1-2-3)", "출처": "건설품셈 공통 8-1-4" }, { "이름": "E입력", "설명": "작업효율을 직접 넣을 때 — 0 이면 원문 표 값", "범위": [0, 1], "출처": "건설품셈 공통 8-2-3" }, - { "이름": "싸이클입력", "설명": "1회 싸이클시간을 직접 넣을 때 — 0 이면 표", "단위": "초", "범위": [0, 30], "출처": "건설품셈 공통 8-2-3 3" }, + { "이름": "싸이클입력", "설명": "1회 싸이클시간을 직접 넣을 때 — 0 이면 표", "단위": "sec", "범위": [0, 30], "출처": "건설품셈 공통 8-2-3 3" }, { "이름": "기계", "설명": "굴착기(무한궤도) 규격", @@ -1528,12 +1528,12 @@ { "이름": "E입력", "설명": "작업효율을 직접 넣을 때 — 0 이면 원문 표 값", "범위": [0, 1], "출처": "건설품셈 공통 8-2-5" } ], "중간": [ - { "이름": "t1", "식": "찾기(CC000047, 기종=기종, 작업방법=작업방법, 현장조건=적재조건).t1", "단위": "초" }, + { "이름": "t1", "식": "찾기(CC000047, 기종=기종, 작업방법=작업방법, 현장조건=적재조건).t1", "단위": "sec" }, { "이름": "K", "식": "만약(K표 == '산적상태 적재', 찾기(CC000048, 현장조건=K조건).계수, 찾기(CC000049, 현장조건=K조건).계수)" }, - { "이름": "m", "식": "찾기(CC000051, 기종=기종).m", "단위": "초/m" }, + { "이름": "m", "식": "찾기(CC000051, 기종=기종).m", "단위": "sec/m" }, { "이름": "l", "식": "만약(주행거리 > 0, 주행거리, 찾기(CC000052, 항목='편도주행거리(표준)').값)", "단위": "m" }, - { "이름": "t2", "식": "찾기(CC000052, 항목='기본시간(t2, 기어변화 등)').값", "단위": "초" }, - { "이름": "cm", "식": "m * l + t1 + t2", "단위": "초" }, + { "이름": "t2", "식": "찾기(CC000052, 항목='기본시간(t2, 기어변화 등)').값", "단위": "sec" }, + { "이름": "cm", "식": "m * l + t1 + t2", "단위": "sec" }, { "이름": "보정", "식": "만약(E보정 == '해당 없음', 0, 찾기(CC000053, 항목=E보정).보정값)" }, { "이름": "E", "식": "만약(E입력 > 0, E입력, 찾기(CC000050, 현장조건=현장조건, 등급=등급, 토질명=토질명).E - 보정)" } ], @@ -1907,7 +1907,7 @@ }, "입력": [ { "이름": "적재용량", "설명": "덤프트럭의 적재용량(T)", "단위": "ton", "출처": "건설품셈 공통 8-2-8" }, - { "이름": "단위중량", "설명": "자연상태 토석의 단위중량(γt · 습윤밀도)", "단위": "t/㎥", "출처": "건설품셈 공통 8-2-8" }, + { "이름": "단위중량", "설명": "자연상태 토석의 단위중량(γt · 습윤밀도)", "단위": "ton/㎥", "출처": "건설품셈 공통 8-2-8" }, { "이름": "체적변화율", "설명": "체적환산계수에서의 체적변화율(L = 흐트러진/자연)", "출처": "건설품셈 공통 8-2-8" }, { "이름": "f", "설명": "체적환산계수", "출처": "건설품셈 공통 8-2-8" }, { "이름": "운반거리", "설명": "운반거리(편도)", "단위": "㎞", "출처": "건설품셈 공통 8-2-8 2" }, @@ -1943,7 +1943,7 @@ { "이름": "덮개", "설명": "적재함 덮개 설치 · 해체(t5)", "고르기": ["해당 없음", "인력에 의한 경우", "자동덮개시설의 경우"], "출처": "건설품셈 공통 8-2-8 6" }, { "이름": "세륜", "설명": "세륜기 통과(t6)", "고르기": ["안 함", "함"], "출처": "건설품셈 공통 8-2-8 7" }, { "이름": "적재방법", "설명": "적재시간(t1) 산출 갈래", "고르기": ["적재기계", "인력", "직접 입력"], "출처": "건설품셈 공통 8-2-8 1 · 8 · 9" }, - { "이름": "적재싸이클", "설명": "적재기계의 1회 싸이클시간(cms)", "단위": "초", "출처": "건설품셈 공통 8-2-8 8" }, + { "이름": "적재싸이클", "설명": "적재기계의 1회 싸이클시간(cms)", "단위": "sec", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "적재버킷", "설명": "적재기계의 버킷용량(q)", "단위": "㎥", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "버킷계수", "설명": "적재기계의 버킷계수(k)", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "적재효율", "설명": "적재기계의 작업효율(Es)", "출처": "건설품셈 공통 8-2-8 8" }, @@ -2032,7 +2032,7 @@ }, "입력": [ { "이름": "적재용량", "설명": "덤프트럭의 적재용량(T)", "단위": "ton", "출처": "건설품셈 공통 8-2-8" }, - { "이름": "단위중량", "설명": "자연상태 토석의 단위중량(γt · 습윤밀도)", "단위": "t/㎥", "출처": "건설품셈 공통 8-2-8" }, + { "이름": "단위중량", "설명": "자연상태 토석의 단위중량(γt · 습윤밀도)", "단위": "ton/㎥", "출처": "건설품셈 공통 8-2-8" }, { "이름": "체적변화율", "설명": "체적환산계수에서의 체적변화율(L = 흐트러진/자연)", "출처": "건설품셈 공통 8-2-8" }, { "이름": "f", "설명": "체적환산계수", "출처": "건설품셈 공통 8-2-8" }, { "이름": "운반거리", "설명": "운반거리(편도)", "단위": "㎞", "출처": "건설품셈 공통 8-2-8 2" }, @@ -2068,7 +2068,7 @@ { "이름": "덮개", "설명": "적재함 덮개 설치 · 해체(t5)", "고르기": ["해당 없음", "인력에 의한 경우", "자동덮개시설의 경우"], "출처": "건설품셈 공통 8-2-8 6" }, { "이름": "세륜", "설명": "세륜기 통과(t6)", "고르기": ["안 함", "함"], "출처": "건설품셈 공통 8-2-8 7" }, { "이름": "적재방법", "설명": "적재시간(t1) 산출 갈래", "고르기": ["적재기계", "인력", "직접 입력"], "출처": "건설품셈 공통 8-2-8 1 · 8 · 9" }, - { "이름": "적재싸이클", "설명": "적재기계의 1회 싸이클시간(cms)", "단위": "초", "출처": "건설품셈 공통 8-2-8 8" }, + { "이름": "적재싸이클", "설명": "적재기계의 1회 싸이클시간(cms)", "단위": "sec", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "적재버킷", "설명": "적재기계의 버킷용량(q)", "단위": "㎥", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "버킷계수", "설명": "적재기계의 버킷계수(k)", "출처": "건설품셈 공통 8-2-8 8" }, { "이름": "적재효율", "설명": "적재기계의 작업효율(Es)", "출처": "건설품셈 공통 8-2-8 8" }, @@ -3431,7 +3431,7 @@ "중간": [ { "이름": "f", "식": "만약(f입력 > 0, f입력, 찾기(CC000474, 분류=f분류, 상태=f상태, N값=fN값).f)" }, { "이름": "k", "식": "만약(k입력 > 0, k입력, 찾기(CC000475, 분류=k분류, 상태=k상태, N값=kN값, 버킷용량=버킷용량).k)" }, - { "이름": "cm", "식": "찾기(CC000476, 버킷용량=버킷용량).싸이클시간 + (평균수심 - 찾기(CC000481, 항목='기준 수심').값) * 찾기(CC000481, 항목='수심 1m 증감당 싸이클시간 증감').값", "단위": "초" }, + { "이름": "cm", "식": "찾기(CC000476, 버킷용량=버킷용량).싸이클시간 + (평균수심 - 찾기(CC000481, 항목='기준 수심').값) * 찾기(CC000481, 항목='수심 1m 증감당 싸이클시간 증감').값", "단위": "sec" }, { "이름": "E", "식": "찾기(CC000477, 구분=E1).E * 찾기(CC000479, 구분=E2).E" } ], "결과": { "식": "3600 * 버킷용량 * k * f * E / cm", "출처": "건설품셈 공통 8-2-30" }, diff --git a/resources/master_data/로직_건설품셈_08장_지반조사.json b/resources/master_data/로직_건설품셈_08장_지반조사.json index b8e14f4ab..4a09b29f5 100644 --- a/resources/master_data/로직_건설품셈_08장_지반조사.json +++ b/resources/master_data/로직_건설품셈_08장_지반조사.json @@ -744,7 +744,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -805,7 +805,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -866,7 +866,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -927,7 +927,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-1" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -989,7 +989,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-2" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350, 400, 450, 500], "출처": "건설품셈 토목 8-4-2" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -1051,7 +1051,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350], "출처": "건설품셈 토목 8-4-2" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350], "출처": "건설품셈 토목 8-4-2" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -1122,7 +1122,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300, 350], "출처": "건설품셈 토목 8-4-2" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300, 350], "출처": "건설품셈 토목 8-4-2" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -1193,7 +1193,7 @@ "소유": "공용", "비고": "해머식 착정공법 · 고성능 착정기(엔진 335.70㎾) · 굴착심도 200m 이하(8-4-2 주①) · 해석: 윙비트·에어해머·버튼비트는 원문 한 칸(병합) — 모든 규격에 같은 값 · 착정기 조종원 직종 설계자 고름(원문 운전사 구분에 없음) · 공시에 없는 재료 = 재료_자체", "입력": [ - { "이름": "규격", "단위": "mm", "고르기": [100, 150, 200, 250, 300], "출처": "건설품셈 토목 8-4-2" }, + { "이름": "규격", "단위": "㎜", "고르기": [100, 150, 200, 250, 300], "출처": "건설품셈 토목 8-4-2" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], diff --git a/resources/master_data/로직_건설품셈_10장_창호_및_유리공사.json b/resources/master_data/로직_건설품셈_10장_창호_및_유리공사.json index bec39851c..deee13ab2 100644 --- a/resources/master_data/로직_건설품셈_10장_창호_및_유리공사.json +++ b/resources/master_data/로직_건설품셈_10장_창호_및_유리공사.json @@ -181,7 +181,7 @@ "구분": "건설품셈 건축", "상세구분": "10장 창호 및 유리공사", "이름": "커튼월 프레임 설치(스틱월)", - "결과단위": "원/kg", + "결과단위": "원/㎏", "출처": "건설품셈 건축 10-1-8", "개정": [2014, 2020, 2026], "소유": "공용", diff --git a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json index 0957158b1..5c0bda9fd 100644 --- a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json +++ b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json @@ -770,7 +770,7 @@ { "이름": "용접공품", "단위": "인", "출처": "건설품셈 기계설비 13-2-2", "비고": "원문 범위 — 예 두께 3㎜ 0.0055∼0.0037" }, { "이름": "특별인부품", "단위": "인", "출처": "건설품셈 기계설비 13-2-2", "비고": "원문 범위 — 예 두께 3㎜ 0.0027∼0.0019" }, { "이름": "산소량", "단위": "ℓ", "출처": "건설품셈 기계설비 13-2-2 주④", "비고": "원문 범위 — 예 두께 3㎜ 16.5∼25.1" }, - { "이름": "LPG량", "단위": "kg", "출처": "건설품셈 기계설비 13-2-2 주④", "비고": "원문 범위 — 예 두께 3㎜ 0.016∼0.025" } + { "이름": "LPG량", "단위": "㎏", "출처": "건설품셈 기계설비 13-2-2 주④", "비고": "원문 범위 — 예 두께 3㎜ 0.016∼0.025" } ], "중간": [], "호표": [ diff --git a/resources/master_data/로직_산림품셈_03장_작업장_관리.json b/resources/master_data/로직_산림품셈_03장_작업장_관리.json index 6ac21427c..7669f53be 100644 --- a/resources/master_data/로직_산림품셈_03장_작업장_관리.json +++ b/resources/master_data/로직_산림품셈_03장_작업장_관리.json @@ -81,7 +81,7 @@ "구분": "산림품셈", "상세구분": "03장 작업장 관리", "이름": "임산물 운반로 신설 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 3-4-2 1", "개정": [2026], "소유": "공용", diff --git a/resources/master_data/로직_산림품셈_05장_식재_및_녹화.json b/resources/master_data/로직_산림품셈_05장_식재_및_녹화.json index 74600f8fe..e4f409b5b 100644 --- a/resources/master_data/로직_산림품셈_05장_식재_및_녹화.json +++ b/resources/master_data/로직_산림품셈_05장_식재_및_녹화.json @@ -1078,7 +1078,7 @@ "시험입력": { "토성": "양토", "막갈이깊이": 15 }, "입력": [ { "이름": "토성", "설명": "흙의 성질", "고르기": ["사토", "양토", "식토"], "출처": "산림품셈 5-26-1" }, - { "이름": "막갈이깊이", "설명": "가는 깊이(표의 값 중)", "단위": "cm", "고르기": [9, 12, 15, 18, 21], "출처": "산림품셈 5-26-1" } + { "이름": "막갈이깊이", "설명": "가는 깊이(표의 값 중)", "단위": "㎝", "고르기": [9, 12, 15, 18, 21], "출처": "산림품셈 5-26-1" } ], "중간": [], "호표": [{ "종류": "인력", "요소": "LB000002", "이름": "보통인부", "단위": "인", "수량": "찾기(QF000139, 토성=토성, '막갈이깊이(cm)'=막갈이깊이).수량", "비목": "노무비" }], @@ -1100,7 +1100,7 @@ "시험입력": { "토성": "양토", "막갈이깊이": 15 }, "입력": [ { "이름": "토성", "설명": "흙의 성질", "고르기": ["사토", "양토", "식토"], "출처": "산림품셈 5-26-2" }, - { "이름": "막갈이깊이", "설명": "가는 깊이(표의 값 중)", "단위": "cm", "고르기": [9, 12, 15, 18, 21], "출처": "산림품셈 5-26-2" } + { "이름": "막갈이깊이", "설명": "가는 깊이(표의 값 중)", "단위": "㎝", "고르기": [9, 12, 15, 18, 21], "출처": "산림품셈 5-26-2" } ], "중간": [], "호표": [{ "종류": "인력", "요소": "LB000002", "이름": "보통인부", "단위": "인", "수량": "찾기(QF000140, 토성=토성, '막갈이깊이(cm)'=막갈이깊이).수량", "비목": "노무비" }], @@ -1197,7 +1197,7 @@ "구분": "산림품셈", "상세구분": "05장 식재 및 녹화", "이름": "복사이식 굴착기 시간당 작업량(Q1)", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 5-29 주④", "개정": [2026], "소유": "공용", diff --git a/resources/master_data/로직_산림품셈_06장_생육보조.json b/resources/master_data/로직_산림품셈_06장_생육보조.json index 4d5f5b655..96aee9ff8 100644 --- a/resources/master_data/로직_산림품셈_06장_생육보조.json +++ b/resources/master_data/로직_산림품셈_06장_생육보조.json @@ -10,7 +10,7 @@ "구분": "산림품셈", "상세구분": "06장 생육보조", "이름": "비료주기", - "결과단위": "원/100kg", + "결과단위": "원/100㎏", "출처": "산림품셈 6-1", "개정": [2026], "소유": "공용", diff --git a/resources/master_data/로직_산림품셈_09장_토공.json b/resources/master_data/로직_산림품셈_09장_토공.json index 6cca8eb43..b56362e4b 100644 --- a/resources/master_data/로직_산림품셈_09장_토공.json +++ b/resources/master_data/로직_산림품셈_09장_토공.json @@ -44,7 +44,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "토사깍기(기계 · 굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-3-2", "개정": [2026], "소유": "공용", @@ -55,7 +55,7 @@ { "이름": "K", "식": "찾기(QF000249, 구분='K').적용" }, { "이름": "f", "식": "찾기(CF000122, 구분='f').적용" }, { "이름": "E", "식": "E입력" }, - { "이름": "Cm", "식": "찾기(QF000249, 구분='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000249, 구분='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -156,7 +156,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "암절취 집토(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-4-2", "개정": [2026], "소유": "공용", @@ -167,7 +167,7 @@ { "이름": "K", "식": "찾기(QF000251, 구분='K').적용" }, { "이름": "f", "식": "찾기(QF000251, 구분='f').적용원문" }, { "이름": "E", "식": "찾기(QF000251, 구분='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000251, 구분='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000251, 구분='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -297,7 +297,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "발파암 집토(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-5-3", "개정": [2026], "소유": "공용", @@ -308,7 +308,7 @@ { "이름": "K", "식": "찾기(QF000254, 구분='K').적용" }, { "이름": "f", "식": "찾기(QF000254, 구분='f').적용원문" }, { "이름": "E", "식": "찾기(QF000254, 구분='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000254, 구분='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000254, 구분='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -386,7 +386,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "콘크리트 깨기 집토(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-7-1 주⑥", "개정": [2026], "소유": "공용", @@ -396,7 +396,7 @@ { "이름": "K", "식": "찾기(QF000257, 구분='K').적용" }, { "이름": "f", "식": "찾기(QF000257, 구분='f').적용" }, { "이름": "E", "식": "찾기(QF000257, 구분='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000257, 구분='㎝(초)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000257, 구분='㎝(초)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -823,7 +823,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "측구터파기 토사 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-12-1", "개정": [2026], "소유": "공용", @@ -833,7 +833,7 @@ { "이름": "K", "식": "찾기(QF000267, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='k').적용" }, { "이름": "f", "식": "찾기(QF000267, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='f').적용" }, { "이름": "E", "식": "찾기(QF000267, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000267, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000267, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -2026,7 +2026,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "되메우기 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-1", "개정": [2026], "소유": "공용", @@ -2036,7 +2036,7 @@ { "이름": "K", "식": "찾기(QF000288, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='k').적용" }, { "이름": "f", "식": "찾기(QF000288, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='f').적용" }, { "이름": "E", "식": "찾기(QF000288, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000288, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000288, 이름='유압식백호우(무한궤도,0.7㎥)', 항목='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -2078,7 +2078,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "다짐(플레이트콤펙터) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-2", "개정": [2026], "소유": "공용", @@ -2119,7 +2119,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "표토제거 답(畓)구간(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎡/시간", + "결과단위": "㎡/hr", "출처": "산림품셈 9-15-1", "개정": [2026], "소유": "공용", @@ -2163,7 +2163,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "표토제거 답(畓)외구간(불도저 19ton) 시간당 작업량", - "결과단위": "㎡/시간", + "결과단위": "㎡/hr", "출처": "산림품셈 9-15-2", "개정": [2026], "소유": "공용", @@ -2222,7 +2222,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "노체포설 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-16-1 주②", "개정": [2026], "소유": "공용", @@ -2232,7 +2232,7 @@ { "이름": "K", "식": "찾기(CF000097, 구분='K').적용" }, { "이름": "f", "식": "찾기(CF000097, 구분='f').적용" }, { "이름": "E", "식": "찾기(CF000097, 구분='E').적용" }, - { "이름": "Cm", "식": "찾기(CF000097, 구분='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(CF000097, 구분='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q0 * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -2268,13 +2268,13 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "노체다짐 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-16-2 주②", "개정": [2026], "소유": "공용", "입력": [], "중간": [ - { "이름": "V", "식": "찾기(CF000099, 구분='V(다짐속도,km/hr)').적용", "단위": "km/hr" }, + { "이름": "V", "식": "찾기(CF000099, 구분='V(다짐속도,km/hr)').적용", "단위": "㎞/hr" }, { "이름": "W", "식": "찾기(CF000099, 구분='W(롤러 유효폭,m)').적용", "단위": "m" }, { "이름": "E", "식": "찾기(CF000099, 구분='E(작업효율)').적용" }, { "이름": "D", "식": "찾기(CF000099, 구분='D(펴는 흙의 두께,m)').적용", "단위": "m" }, @@ -2339,7 +2339,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "비다짐(정지 · 불도저 19ton) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-17-2", "개정": [2026], "소유": "공용", @@ -2393,7 +2393,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "층따기(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-18", "개정": [2026], "소유": "공용", @@ -2404,7 +2404,7 @@ { "이름": "K", "식": "찾기(QF000293, 구분='K').적용" }, { "이름": "f", "식": "찾기(QF000293, 구분='f').적용원문" }, { "이름": "E", "식": "찾기(QF000293, 구분='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000293, 구분='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000293, 구분='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", @@ -2639,7 +2639,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "섞기(굴착기 0.7㎥) 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-22", "개정": [2026], "소유": "공용", diff --git a/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json b/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json index 3eb744162..f56db170c 100644 --- a/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json +++ b/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json @@ -10,7 +10,7 @@ "구분": "산림품셈", "상세구분": "10장 자재․장비 운반", "이름": "골재운반 덤프트럭 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 10-3-1 주", "개정": [2026], "소유": "공용", @@ -671,7 +671,7 @@ "구분": "산림품셈", "상세구분": "10장 자재․장비 운반", "이름": "불도저 운반 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 10-11 주②", "개정": [2026], "소유": "공용", @@ -778,19 +778,19 @@ { "이름": "로더cm", "식": "찾기(CC000051, 기종='타이어식').m * 찾기(CC000052, 항목='편도주행거리(표준)').값 + 찾기(CC000047, 기종='타이어식', 작업방법='산적상태에서 담을 때', 현장조건='용이한 경우').t1 + 찾기(CC000052, 항목='기본시간(t2, 기어변화 등)').값", - "단위": "초", + "단위": "sec", "출처": "건설품셈 공통 8-2-5 1" }, { "이름": "Cm", "식": "만약(로더 == 1, 로더cm, 만약(적재기계 == '0201-0070', 찾기(QF000336, 구분='㎝(초)').값, 찾기(CC000045, 규격=규격구분, 각도=180).시간))", - "단위": "초", + "단위": "sec", "출처": "산림품셈 10-12-1 주⑤ · 건설품셈 공통 8-2-3 3" }, { "이름": "Cms", "식": "만약(로더 == 1, 로더cm, 만약(적재기계 == '0201-0070', 20, 찾기(CC000045, 규격=규격구분, 각도=135).시간))", - "단위": "초", + "단위": "sec", "출처": "산림품셈 10-12-1 2. 운반 · 건설품셈 공통 8-2-3 3" }, { "이름": "Es", "식": "만약(로더 == 1, E0, 0.85)", "출처": "산림품셈 10-12-1 2. 운반" }, diff --git a/resources/master_data/로직_산림품셈_11장_가설.json b/resources/master_data/로직_산림품셈_11장_가설.json index 765f1bfa6..cfd04b648 100644 --- a/resources/master_data/로직_산림품셈_11장_가설.json +++ b/resources/master_data/로직_산림품셈_11장_가설.json @@ -91,7 +91,7 @@ "구분": "산림품셈", "상세구분": "11장 가설", "이름": "쇄석·혼합석 부설 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 11-4 주①", "개정": [2026], "소유": "공용", @@ -102,7 +102,7 @@ { "이름": "k", "식": "찾기(QF000349, 구분='k(버킷계수)').적용" }, { "이름": "f", "식": "찾기(QF000349, 구분='f(체적환산계수)').적용" }, { "이름": "E", "식": "찾기(QF000349, 구분='E(작업효율)').적용" }, - { "이름": "Cm", "식": "찾기(QF000349, 구분='㎝(sec, 1회 사이클 시간)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000349, 구분='㎝(sec, 1회 사이클 시간)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * k * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", diff --git a/resources/master_data/로직_산림품셈_13장_구조물.json b/resources/master_data/로직_산림품셈_13장_구조물.json index f4d3dd08a..42ed818c5 100644 --- a/resources/master_data/로직_산림품셈_13장_구조물.json +++ b/resources/master_data/로직_산림품셈_13장_구조물.json @@ -176,7 +176,7 @@ "소유": "공용", "비고": "원문 플레이트콤팩트 1.54ton — 기계 요소는 1.5ton 하나(1730-0015)", "입력": [ - { "이름": "규격", "단위": "mm" }, + { "이름": "규격", "단위": "㎜" }, { "이름": "지역", "고르기": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], @@ -1321,7 +1321,7 @@ "구분": "산림품셈", "상세구분": "13장 구조물", "이름": "토양개량제 부설 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 13-13-2", "개정": [2026], "소유": "공용", @@ -1332,7 +1332,7 @@ { "이름": "K", "식": "찾기(QF000465, 이름='굴착기(0.2㎥)', 항목='k').적용" }, { "이름": "f", "식": "찾기(QF000465, 이름='굴착기(0.2㎥)', 항목='f').적용" }, { "이름": "E", "식": "찾기(QF000465, 이름='굴착기(0.2㎥)', 항목='E').적용" }, - { "이름": "Cm", "식": "찾기(QF000465, 이름='굴착기(0.2㎥)', 항목='㎝(sec)').적용", "단위": "초" } + { "이름": "Cm", "식": "찾기(QF000465, 이름='굴착기(0.2㎥)', 항목='㎝(sec)').적용", "단위": "sec" } ], "결과": { "식": "반올림(3600 * q * K * f * E / Cm, 3)", "출처": "건설품셈 공통 8-1-4 주①" }, "끝수": "소수 3자리 사사오입(건설품셈 공통 8-1-4 주①)", diff --git a/resources/master_data/로직_자체.json b/resources/master_data/로직_자체.json index cebcff437..4c95ce3ac 100644 --- a/resources/master_data/로직_자체.json +++ b/resources/master_data/로직_자체.json @@ -37,7 +37,7 @@ { "이름": "상차", "설명": "채집한 돌을 덤프에 싣는지", "고르기": ["포함", "안 함"] }, { "이름": "덤프", "설명": "소운반 덤프트럭", "고르기": ["0602-0025", "0602-0045", "0602-0060", "0602-0080"], "출처": "건설품셈 공통 8-2-8" }, { "이름": "운반거리", "설명": "소운반 편도 거리 — 0 이면 소운반 없음", "단위": "㎞", "출처": "건설품셈 공통 8-2-8" }, - { "이름": "쌓기무게", "설명": "쌓기 1㎡ 돌 무게", "단위": "t/㎡" }, + { "이름": "쌓기무게", "설명": "쌓기 1㎡ 돌 무게", "단위": "ton/㎡" }, { "이름": "적하조건", "설명": "덤프 적하 작업조건", "고르기": ["불량", "보통", "양호"], "출처": "건설품셈 공통 8-2-8 4" }, { "이름": "지역", @@ -51,7 +51,7 @@ { "이름": "규격구분", "식": "만약(굴착기 == '0201-0020' 또는 굴착기 == '0201-0040', '0.12∼0.4', 만약(굴착기 == '0201-0100', '1.0∼1.2', '0.6∼0.8'))" }, { "이름": "K", "식": "찾기(CC000042, 현장조건='버킷에 넣기 어렵고 불규칙한 공극이 생기는 것으로서 발파 또는 리퍼작업 등에 의하여 얻어진 암과 파쇄암, 호박돌, 역 등인 경우').K" }, { "이름": "E", "식": "찾기(CC000043, 현장조건='흐트러진 상태', 등급=등급, 토질명='파쇄암').E" }, - { "이름": "cm", "식": "찾기(CC000045, 규격=규격구분, 각도=선회각도).시간", "단위": "초" }, + { "이름": "cm", "식": "찾기(CC000045, 규격=규격구분, 각도=선회각도).시간", "단위": "sec" }, { "이름": "Q", "식": "로직(GC001147, 버킷용량=q, 규격구분=규격구분, 선회각도=선회각도, 버킷조건='버킷에 넣기 어렵고 불규칙한 공극이 생기는 것으로서 발파 또는 리퍼작업 등에 의하여 얻어진 암과 파쇄암, 호박돌, 역 등인 경우', 현장조건='흐트러진 상태', 등급=등급, 토질명='파쇄암', E보정='해당 없음', 협소현장='해당 없음', f=1, E입력=0, 싸이클입력=0)", diff --git a/resources/master_template/labor/item1.json b/resources/master_template/labor/item1.json index 668bfd289..9f69c693a 100644 --- a/resources/master_template/labor/item1.json +++ b/resources/master_template/labor/item1.json @@ -352,7 +352,7 @@ } }, "사본": { - "판": "5bcfbe99102ef55d", + "판": "7a470ae53b9da0de", "시점": "2026-11-07T15:23:23", "로직": [ { @@ -2211,7 +2211,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "되메우기 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-1", "개정": [ 2026 @@ -2702,7 +2702,7 @@ "입력": [ { "이름": "규격", - "단위": "mm" + "단위": "㎜" }, { "이름": "지역", diff --git a/resources/master_template/labor/item1_2.json b/resources/master_template/labor/item1_2.json index 5ebb2840a..ec3a1f504 100644 --- a/resources/master_template/labor/item1_2.json +++ b/resources/master_template/labor/item1_2.json @@ -940,7 +940,7 @@ } }, "사본": { - "판": "5bcfbe99102ef55d", + "판": "7a470ae53b9da0de", "시점": "2026-11-07T15:23:23", "로직": [ { @@ -2799,7 +2799,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "되메우기 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-1", "개정": [ 2026 @@ -3290,7 +3290,7 @@ "입력": [ { "이름": "규격", - "단위": "mm" + "단위": "㎜" }, { "이름": "지역", diff --git a/resources/master_template/labor/item3.json b/resources/master_template/labor/item3.json index 85613451a..bfa937dcd 100644 --- a/resources/master_template/labor/item3.json +++ b/resources/master_template/labor/item3.json @@ -247,7 +247,7 @@ } }, "사본": { - "판": "4066584ac7f0910f", + "판": "90e75b6182c18dcb", "시점": "2026-11-07T15:23:23", "로직": [ { @@ -858,7 +858,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "노체포설 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-16-1 주②", "개정": [ 2026 diff --git a/resources/master_template/labor/item3_2.json b/resources/master_template/labor/item3_2.json index facc3ce30..a9e72fd03 100644 --- a/resources/master_template/labor/item3_2.json +++ b/resources/master_template/labor/item3_2.json @@ -508,7 +508,7 @@ } }, "사본": { - "판": "bf62fc43e50cd058", + "판": "69839ebda29c8cc5", "시점": "2026-11-07T15:23:24", "로직": [ { @@ -1622,7 +1622,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "되메우기 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-1", "개정": [ 2026 @@ -1955,7 +1955,7 @@ "입력": [ { "이름": "규격", - "단위": "mm" + "단위": "㎜" }, { "이름": "지역", diff --git a/resources/master_template/labor/item3_3.json b/resources/master_template/labor/item3_3.json index 7579c3959..3927a310e 100644 --- a/resources/master_template/labor/item3_3.json +++ b/resources/master_template/labor/item3_3.json @@ -1060,7 +1060,7 @@ } }, "사본": { - "판": "5bcfbe99102ef55d", + "판": "7a470ae53b9da0de", "시점": "2026-11-07T15:23:24", "로직": [ { @@ -2919,7 +2919,7 @@ "구분": "산림품셈", "상세구분": "09장 토공", "이름": "되메우기 굴착기 시간당 작업량", - "결과단위": "㎥/시간", + "결과단위": "㎥/hr", "출처": "산림품셈 9-14-1", "개정": [ 2026 @@ -3410,7 +3410,7 @@ "입력": [ { "이름": "규격", - "단위": "mm" + "단위": "㎜" }, { "이름": "지역", diff --git a/resources/tester/test_796_3_var_names.py b/resources/tester/test_796_3_var_names.py index 6fcf46898..d78a10233 100644 --- a/resources/tester/test_796_3_var_names.py +++ b/resources/tester/test_796_3_var_names.py @@ -163,7 +163,7 @@ def test_표본_기호_단위_원문_같음(): "작업효율", "체적환산계수", ) - assert (t["cm"]["이름"], t["cm"]["단위"]) == ("1회 싸이클시간", "초") + assert (t["cm"]["이름"], t["cm"]["단위"]) == ("1회 싸이클시간", "sec") # 801-5 원문 표 머리 「싸이클시간(Sec)」 꼴 carry = (BOOK / "제01장_적용기준/1-02_설계_및_수량.md").read_text(encoding="utf-8") for line in ( diff --git a/resources/tester/test_801_5_units.py b/resources/tester/test_801_5_units.py new file mode 100644 index 000000000..50cc98587 --- /dev/null +++ b/resources/tester/test_801_5_units.py @@ -0,0 +1,138 @@ +"""801-5 M01 로직 단위 글 = 건설품셈 원문 꼴(요구 663-2) 지킴 — 글만 바꿈 · 금액 그대로. + +① 대응표 근거 — 규칙마다 근거 절 파일이 있고 그 안에 원문 글이 실제로 있음(+ 지금 글은 같은 꼴로 안 쓰임) +② 섞임 0 — M01 로직 입력 · 중간 · 결과단위가 원문 꼴로 다 바뀌어 있음(규칙이 더 바꿀 것 없음 · 두 번 돌려도 같음) +③ 금액 같음 — 변수 단위 글을 지워도 엔진 셈 · 끝수 지문이 같음(단위 글은 셈에 안 듦) +④ M02 사본 도구 — 같은 규칙으로 사본 줄 단위만 바뀌고 `판` 다시 셈 +""" + +from __future__ import annotations + +import copy +import importlib +import re + +import pytest + +from M01_MasterData import M01_MasterData_Store as store + +ut = importlib.import_module("master_unit_text") +mf = importlib.import_module("master_formula") +build = importlib.import_module("build_단위표기_801_5") + + +def _rows(): + for data in store.loaded()[0].values(): + if data.get("그룹") == "로직": + yield from data["줄"] + + +def test_대응표_근거_절에_원문_글이_있음(): + assert len(ut.RULES) >= 7 + for name, _, to, token, files in ut.RULES: + assert files and to == token, name + for rel in files: + path = ut.EVIDENCE_ROOT / rel + assert path.is_file(), (name, rel) + assert token in path.read_text(encoding="utf-8"), (name, rel) + + +def test_규칙_예(): + got = { + u: ut.canon(u) + for u in ("㎥/시간", "㎡/시간", "㎢/시간", "원/시간", "초", "초/m", "mm", "TON/㎡") + } + assert got == { + "㎥/시간": "㎥/hr", + "㎡/시간": "㎡/hr", + "㎢/시간": "㎢/시간", # 원문 9-5-4 가 쓰는 꼴 — 안 건드림 + "원/시간": "원/시간", + "초": "sec", + "초/m": "sec/m", + "mm": "㎜", + "TON/㎡": "ton/㎡", + } + for u in got.values(): + assert ut.canon(u) == u # 두 번 돌려도 같음 + + +def test_섞임_0_원문_꼴로_다_바뀜(): + left = [] + for row in _rows(): + units = [row.get("결과단위")] + [ + s.get("단위") for k in ("입력", "중간") for s in row.get(k) or [] + ] + left += [(row["키"], u) for u in units if u and ut.canon(u) != u] + assert not left, left[:5] + + +def test_굴착기_Cm_sec_Q_hr(): + row = store.loaded()[1].logic("GF000088") # 토사깍기(굴착기 0.7㎥) 시간당 작업량 + assert {s["단위"] for s in row["중간"] if s["이름"] == "Cm"} == {"sec"} + assert row["결과단위"] == "㎥/hr" + + +_OLD = ( # 원문 꼴 → 옛 글(되돌려 본 꼴) — 두 꼴로 셈이 같음을 대조 + (re.compile(r"(? str: + for pat, old in _OLD: + text = pat.sub(old, text) + return text + + +def test_금액_같음_옛_글로_되돌려도_셈_지문_같음(): + files = store.loaded()[0] + old = copy.deepcopy(files) + for data in old.values(): + if data.get("그룹") == "로직": + for row in data["줄"]: + for k in ("입력", "중간"): + for s in row.get(k) or []: + s["단위"] = _legacy(s.get("단위") or "") + row["결과단위"] = _legacy(row.get("결과단위") or "") + now, was = mf.Master(files), mf.Master(old) + keys = [r["키"] for r in _rows()] + hit = { + r["키"] + for r in _rows() + if any( + _legacy(s.get("단위") or "") != (s.get("단위") or "") + for k in ("입력", "중간") + for s in r.get(k) or [] + ) + } + assert len(hit) >= 40 + for key in sorted(hit) + keys[::25]: + assert build.fingerprint(now, now.logic(key)) == build.fingerprint(was, was.logic(key)), key + + +def test_M02_사본_도구(): + cu = importlib.import_module("M02_MasterTemplete.M02_Copy_Units") + old = { + "로직": [ + { + "키": "GC000001", + "결과단위": "㎥/시간", + "입력": [{"이름": "a", "단위": "mm"}], + "중간": [{"이름": "Cm", "단위": "초"}], + } + ], + "판": "x", + } + new, done, _ = cu.fill(old, None) + row = new["로직"][0] + assert done == 3 and row["결과단위"] == "㎥/hr" and row["입력"][0]["단위"] == "㎜" + assert row["중간"][0]["단위"] == "sec" and new["판"] != "x" + assert cu.fill(new, None)[1] == 0 # 두 번째 = 바꿀 것 없음 + + +if __name__ == "__main__": + pytest.main([__file__, "-q"])