Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1
This commit is contained in:
@@ -38,6 +38,8 @@
|
||||
|
||||
## 관급 (자재품목 「관급」 열)
|
||||
|
||||
- 지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로).
|
||||
|
||||
| 항목 | 값 |
|
||||
| --- | --- |
|
||||
| 출처 | 조달청 종합쇼핑몰 OpenAPI(15129471) · 원본 `원가계산/자재단가/관급/<받은 날>/*.json.gz` · 설계 `ref/_설계_관급단가_갱신.md` |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
|
||||
`scripts/build_자재품목_분류.py` 가 만든 표 — 손으로 고치지 않음.
|
||||
|
||||
- 구분 24 · 상세구분 100 · 미분류 25 줄 (전체 30711 줄)
|
||||
- 구분 24 · 상세구분 91 · 미분류 25 줄 (전체 24092 줄)
|
||||
|
||||
## 규칙
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
## 구분별 상세구분 (줄 수)
|
||||
|
||||
### 관·이음관 (7152)
|
||||
### 관·이음관 (5399)
|
||||
|
||||
PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 461 · 파형강관 417 · 유리섬유·FRP관 398 · 흄관 222 · 동관·스텐관 133 · 단열이중관 77
|
||||
PE관 1154 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 461 · 파형강관 417 · 유리섬유·FRP관 398 · 흄관 222 · 동관·스텐관 133 · 단열이중관 77
|
||||
|
||||
### 식생·종자 (6128)
|
||||
### 식생·종자 (3456)
|
||||
|
||||
수목 5532 · 수목지주·보호 181 · 초본·야생화 138 · 종자 105 · 잔디·지피 97 · 농약·약제 42 · 비료·토양개량 26 · 비료 7
|
||||
수목 2933 · 초본·야생화 138 · 수목지주·보호 115 · 종자 105 · 잔디·지피 97 · 농약·약제 42 · 비료·토양개량 26
|
||||
|
||||
### 레미콘 (3203)
|
||||
|
||||
@@ -30,13 +30,9 @@ PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 46
|
||||
|
||||
형강 717 · 철근 497 · 강판 262 · 구조용강관 132 · 철선 93 · 철망·와이어메쉬 53
|
||||
|
||||
### 아스콘·포장 (1705)
|
||||
### 아스콘·포장 (1695)
|
||||
|
||||
아스콘 1479 · 포장블록 173 · 특수포장 30 · 아스팔트 13 · 투수포장재 10
|
||||
|
||||
### 안전시설 (1552)
|
||||
|
||||
표지판·지주 934 · 가드레일 458 · 낙석방지·휀스 74 · 낙석방지책 63 · 차선·방호 23
|
||||
아스콘 1479 · 포장블록 173 · 특수포장 30 · 아스팔트 13
|
||||
|
||||
### 볼트·앵커·철물 (1002)
|
||||
|
||||
@@ -50,10 +46,6 @@ PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 46
|
||||
|
||||
밸브 575 · 수도계량기·유량계 101 · 스프링클러·호스 51 · 펌프 34
|
||||
|
||||
### 매트·토목섬유 (678)
|
||||
|
||||
돌망태 372 · 부직포·토목섬유 113 · 식생매트 64 · 돌망태·사방 63 · 매트 54 · 부직포 8 · 잔디보호매트 4
|
||||
|
||||
### 운송·용역 (677)
|
||||
|
||||
화물운임 552 · 시험·검사 62 · 폐기물 61 · 상하차 2
|
||||
@@ -74,10 +66,6 @@ PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 46
|
||||
|
||||
도료 258 · 접착·혼화 174 · 방수·실링 140
|
||||
|
||||
### 석재 (535)
|
||||
|
||||
경계석 410 · 조경석·자연석 125
|
||||
|
||||
### 콘크리트제품 (494)
|
||||
|
||||
말뚝·파일 223 · 경계석·블록 172 · PC구조물 99
|
||||
@@ -86,9 +74,9 @@ PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 46
|
||||
|
||||
천장·바닥·마감재 250 · 문·창·유리 123 · 타일·기와 66 · 위생기구 15
|
||||
|
||||
### 목재 (431)
|
||||
### 안전시설 (329)
|
||||
|
||||
각재·판재 168 · 목재덱 161 · 합판 78 · 판재 24
|
||||
표지판·지주 122 · 가드레일 110 · 낙석방지·휀스 74 · 차선·방호 23
|
||||
|
||||
### 잡재료 (317)
|
||||
|
||||
@@ -98,6 +86,18 @@ PE관 2907 · 이음쇠·부속 1003 · 주철관 828 · 강관 706 · PVC관 46
|
||||
|
||||
단열재 284
|
||||
|
||||
### 목재 (246)
|
||||
|
||||
각재·판재 168 · 합판 78
|
||||
|
||||
### 매트·토목섬유 (230)
|
||||
|
||||
부직포·토목섬유 113 · 돌망태·사방 63 · 매트 54
|
||||
|
||||
### 석재 (207)
|
||||
|
||||
조경석·자연석 125 · 경계석 82
|
||||
|
||||
### 교량·구조부속 (133)
|
||||
|
||||
신축이음·받침 75 · 보강재·정착 58
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 설계 — 관급 단가 갱신 (조달청 → 자재품목 「관급」 열)
|
||||
|
||||
> 지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로).
|
||||
|
||||
## 결론
|
||||
|
||||
- **관급 단가는 조달청 종합쇼핑몰 API 3종에서 「분류명 부분 글자」로 받을 수 있음** — 레미콘·아스콘은 「조합공통품목」 줄이 지역별 관급 단가(다산소프트 관급 열과 같은 것). 인증키 = 기존 공공데이터포털 일반 인증키 하나(활용신청 끝남).
|
||||
|
||||
@@ -7,6 +7,9 @@ Cost2..5 = 물가지 넷 · Page2..5 = 면수. TC(채택) · Cost6(견적가)는
|
||||
그 밖의 줄에 남은 Cost1 은 출처가 조달인지 물가지인지 원문이 안 가름 — 값 열에 넣지 않고 비고 「가격정보 ○○」 로만.
|
||||
0 · 미저장 = 값 없음(null). 이름·규격은 원문 그대로(띄어쓰기·오자 포함). 코드 없는 빈 줄은 뺌.
|
||||
ESTX 에 대·중분류 칸이 없어 구분·상세구분은 build_자재품목_분류.py 가 품명 글자 규칙으로 채움(빌더 끝에서 부름).
|
||||
조달청 줄 · 조달청 관급 값은 넣지 않음(요구 822 — EST 그대로) · update_관급단가*.py 는 지금 정본에 적용 안 함.
|
||||
ESTX 밖 줄 = 품셈재료에서 옮긴 줄(출처 「품셈재료 …」 · PLAN 7-4b) — 지금 정본에서 그대로 이어 받음.
|
||||
대장 MT 키 = 이 표의 줄만 · 지운 줄의 키는 대장에서 뺌(「다음」 번호는 그대로 — 재사용 없음).
|
||||
|
||||
쓰기: ./venv/Scripts/python.exe resources/master_data/scripts/build_재료_자재품목.py
|
||||
쓴 뒤 원본과 대조.
|
||||
@@ -37,6 +40,10 @@ DROP = {
|
||||
"M0005250064": "전력",
|
||||
"M0005250065": "전력(주택용저압)",
|
||||
}
|
||||
CARRY = "품셈재료" # 출처 머리 — ESTX 밖 줄(7-4b)
|
||||
# 7-4b 덧말 — VR관 1본 길이(D300~D1200 · 이름 「VR관」 · 「VR관(관급)」)
|
||||
VR_NOTE = "1본 = 2.5m(표준 유효길이 D300~D1200 공통 — PLAN 7-4b 확인 · 출처: https://kr-con.co.kr/product/index.asp?pIdx=product3)"
|
||||
VR_SIZES = {f"D={d}mm" for d in (300, 400, 450, 500, 600, 700, 800, 900, 1000, 1100, 1200)}
|
||||
|
||||
|
||||
def num(v):
|
||||
@@ -63,6 +70,9 @@ def build(rows: list[dict]) -> list[dict]:
|
||||
base, page = num(r.get("Cost1")), r.get("Page1") or None
|
||||
public = "관급" in r.get("Name", "") and base is not None
|
||||
면수 = {slot: r.get(f"Page{i}") or None for i, slot in enumerate(MARKET, 2)}
|
||||
비고 = note(base, page, page if public else None)
|
||||
if r.get("Name") in ("VR관", "VR관(관급)") and r.get("Size") in VR_SIZES:
|
||||
비고 = " · ".join(x for x in (비고, VR_NOTE) if x)
|
||||
out.append(
|
||||
{
|
||||
"열쇠": code,
|
||||
@@ -74,19 +84,44 @@ def build(rows: list[dict]) -> list[dict]:
|
||||
**{slot: num(r.get(f"Cost{i}")) for i, slot in enumerate(MARKET, 2)},
|
||||
"관급": base if public else None,
|
||||
"출처": f"자재품목 {code}",
|
||||
"비고": note(base, page, page if public else None),
|
||||
"비고": 비고,
|
||||
"면수": 면수 if any(면수.values()) else None,
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def carried() -> list[dict]:
|
||||
"""지금 정본의 ESTX 밖 줄(출처 「품셈재료 …」) — 옛 모양(열쇠 = 대장 원문번호)으로 되돌려 adopt 가 같은 키로 받게."""
|
||||
if not OUT.is_file():
|
||||
return []
|
||||
book = mk.load_book()["키"]
|
||||
rows = json.loads(OUT.read_text(encoding="utf-8"))["줄"]
|
||||
return [
|
||||
{"열쇠": book[r["키"]]["원문번호"], **{k: v for k, v in r.items() if k != "키"}}
|
||||
for r in rows
|
||||
if str(r.get("출처") or "").startswith(CARRY)
|
||||
]
|
||||
|
||||
|
||||
def prune() -> int:
|
||||
"""대장의 이 파일 MT 키 가운데 표에 없는 키를 뺌 — 「다음」 · 「폐기」 는 그대로."""
|
||||
keep = {r["키"] for r in json.loads(OUT.read_text(encoding="utf-8"))["줄"]}
|
||||
book = mk.load_book()
|
||||
gone = [k for k, v in book["키"].items() if v["파일"] == OUT.name and k not in keep]
|
||||
for k in gone:
|
||||
del book["키"][k]
|
||||
mk.save_book(book)
|
||||
return len(gone)
|
||||
|
||||
|
||||
def check(rows: list[dict]) -> None:
|
||||
"""원본 대조 — 줄 수 · 값 열마다 합 · 관급·가격정보 가름 · 표본 쉰 줄."""
|
||||
data = json.loads(OUT.read_text(encoding="utf-8"))
|
||||
got = {r["열쇠"]: r for r in data["줄"]}
|
||||
raw = {r["Code"]: r for r in rows if r.get("Code") and r["Code"] not in DROP}
|
||||
assert len(got) == len(data["줄"]) == len(raw), (len(got), len(raw))
|
||||
got = {k: r for k, r in got.items() if not str(r["출처"]).startswith(CARRY)}
|
||||
assert len(got) == len(raw), (len(got), len(raw))
|
||||
print(
|
||||
f"줄 {len(got)} = 원본 코드 있는 줄 {len(raw)}"
|
||||
f" (코드 없는 빈 줄 {sum(1 for r in rows if not r.get('Code'))} · 유류 {len(DROP)} 뺌)"
|
||||
@@ -138,18 +173,25 @@ def main():
|
||||
"유류·전력은 뺌 — 유가·전기요금은 재료_유가전력(유류는 값이 비고 글뿐 · 중유는 받지 않음)",
|
||||
"원문번호(다산소프트 품번) 칸 없음 — 대조는 _키대장.json 에만",
|
||||
"관급 = 원문 「가격정보」 칸의 조달청 관급 단가(철근 · 레미콘 · 아스콘 · H형강 등) · 기간은 비고 「조달 26.7」",
|
||||
"조달청에만 있는 품목 380 줄은 관급 열만 채움 — 출처 「자재품목 조달청 …」 · 원문번호는 _키대장.json 에만",
|
||||
"조달청 줄 · 조달청 관급 값은 넣지 않음 — EST 그대로(요구 822)",
|
||||
"품셈재료에서 옮긴 줄(출처 「품셈재료 …」 · 값 없음)은 ESTX 밖 — 지금 정본에서 이어 받음",
|
||||
"원문 「가격정보」 칸에 시중 값이 남은 줄은 값 열에 넣지 않고 비고 「가격정보 ○○」 로만 둠 — 출처가 조달인지 물가지인지 원문이 안 가름",
|
||||
"구분 · 상세구분 = 품명 글자 규칙으로 채움(scripts/build_자재품목_분류.py · 묶음 목록 ref/_묶음_자재품목.md) · 조달청 줄은 조달청 품명을 따름 · 못 가린 줄은 「미분류」",
|
||||
"면수 = 물가지 값의 원문 면수 · 화면에는 접어 둠",
|
||||
],
|
||||
}
|
||||
dump_master(OUT, head, build(rows))
|
||||
extra = carried()
|
||||
dump_master(OUT, head, build(rows) + extra)
|
||||
check(rows)
|
||||
print("ESTX 밖 줄 이어 받음", len(extra))
|
||||
print("새 틀로 받음", mk.adopt()) # 열쇠 → 키 — 분류가 「키」 줄을 읽으므로 먼저
|
||||
# adopt 의 keep_text 가 구분 · 상세구분을 한 줄에 두 번 씀 — 읽어서(뒤 값) 다시 씀
|
||||
data = json.loads(OUT.read_text(encoding="utf-8"))
|
||||
dump_master(OUT, {k: v for k, v in data.items() if k != "줄"}, data["줄"])
|
||||
import build_자재품목_분류 as cls # noqa: E402
|
||||
|
||||
print("구분·상세구분 채움", len(cls.apply()), "줄")
|
||||
print("새 틀로 받음", mk.adopt()) # 열쇠 → 키
|
||||
print("대장에서 뺀 키", prune())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -20,7 +20,7 @@ import check_master as cm # noqa: E402
|
||||
import master_formula as mf # noqa: E402
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
ESTX = ROOT / "resources/knowledge/original/원가계산/다산소프트/2026-07-main.estx"
|
||||
ESTX = ROOT / "resources/knowledge/original/경쟁사 프로그램/EST Plus 2/다산소프트/2026-07-main.estx"
|
||||
|
||||
|
||||
# ── ESTX 읽기 ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""관급 단가 갱신 — 조달청 종합쇼핑몰 API 에서 받아 자재품목(재료_자재품목) 「관급」 열을 채움.
|
||||
|
||||
지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로).
|
||||
|
||||
다시 돌리는 법 (한 줄) — 받기 + 정본 관급 열 갱신 + 바뀐 줄 목록:
|
||||
./venv/Scripts/python.exe resources/master_data/scripts/update_관급단가.py update
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""관급 단가 — 「그 밖」 갈래 짝짓기(주철관 · 조경석 · 골재 · 시멘트 · 이음관 …) — `update_관급단가.py` 가 부름.
|
||||
|
||||
지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로).
|
||||
|
||||
짝 = 자재품목 규격의 숫자 조각(치수·종별)이 조달청 품명·규격에 모두 있고, 글자 조각(제품·산지)도 모두 있을 때.
|
||||
이름 쪽 글자는 점수만(같은 값이면 더 많이 맞는 줄 먼저). 지역이 이름에 있으면 그 지역 줄만. 여럿이면 가장 낮은 값(호출한 쪽 `pick`).
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""관급 단가 — 느슨한 두 번째 짝짓기(`update_관급단가.py` 가 엄격한 짝이 안 서면 부름).
|
||||
|
||||
지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로).
|
||||
|
||||
엄격한 짝짓기가 못 세운 줄만 다시 봄 — 같은 갈래 안에서 규격 글자를 느슨하게 맞춰 후보를 찾고,
|
||||
**값이 잡히면 다산소프트 옛 값과 ±5% 안일 때만** 채움(밖이면 안 채움 · 까닭에 조달청 값).
|
||||
· 후보가 같은 규격 여럿이면 최저가(제조사 비고) — 최저가가 ±5% 밖이고 후보가 등급·형이 달라
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""관급 보강 — 다산소프트에 없는 자재를 조달청 종합쇼핑몰에서 더 받아 후보 파일로 모음.
|
||||
|
||||
지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로).
|
||||
|
||||
./venv/Scripts/python.exe resources/master_data/scripts/update_관급단가_보강.py fetch 받기 → 지식DB 관급/<날짜>/보강/
|
||||
./venv/Scripts/python.exe resources/master_data/scripts/update_관급단가_보강.py build 후보 → ref/_관급_보강_후보.json
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""관급 보강 합치기 — `ref/_관급_보강_후보.json`(조달청 후보) → 자재품목 (`update_관급단가.py apply` 가 부름).
|
||||
|
||||
지금 정본에는 적용 안 함 — 요구 822(재료 = EST 그대로).
|
||||
|
||||
(1) 다산소프트에 같은 품명이 있는 것 — 그 줄의 치수와 같은 조달청 후보의 최저가를 관급 열에 채움(치수가 안 맞으면 비움)
|
||||
(2) 다산소프트에 없는 품명 가운데 NEW 목록의 갈래만 — 새 줄(관급만 · 물가지 네 열 null)
|
||||
나머지 후보(그레이팅 · 맨홀 · 수로 · 거푸집 · 가설재 · 철선류 …)는 더하지 않음.
|
||||
|
||||
@@ -2694,7 +2694,7 @@
|
||||
{ "종류": "인력", "요소": "LB000002", "이름": "보통인부", "단위": "인", "수량": "보통인부", "비목": "노무비" },
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": { "구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "토목용부직포" },
|
||||
"요소": { "구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "" },
|
||||
"이름": "부직포",
|
||||
"단위": "㎡",
|
||||
"수량": "만약(작업 == '부직포 깔기', 찾기(QC000144, 구분=작업, 자재='부직포').수량, 0)",
|
||||
|
||||
@@ -2763,7 +2763,7 @@
|
||||
{ "종류": "로직", "요소": "로직(GC000268, 기계=크레인, 지역=지역)", "이름": "크레인", "단위": "시간", "수량": "찾기(QC000397, 재료=재료, 구분='크레인').수량 * 1 * 8 / 시공량" },
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": { "구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "토목용부직포" },
|
||||
"요소": { "구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "" },
|
||||
"이름": "부직포",
|
||||
"단위": "㎡",
|
||||
"수량": "만약(재료 == '부직포', 찾기(QC000399, 구분='부직포').수량, 0)",
|
||||
|
||||
@@ -864,7 +864,7 @@
|
||||
"수량": "찾기(QF000369, 구분='유공관설치 - 유공관').적용",
|
||||
"비목": "재료비"
|
||||
},
|
||||
{ "종류": "재료", "요소": { "구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "토목용부직포" }, "이름": "부직포", "단위": "㎡", "수량": "부직포면적", "비목": "재료비", "비고": "원문 수량 빈칸 — 설계수량" },
|
||||
{ "종류": "재료", "요소": { "구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "" }, "이름": "부직포", "단위": "㎡", "수량": "부직포면적", "비목": "재료비", "비고": "원문 수량 빈칸 — 설계수량" },
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": { "구분": "시멘트·골재", "상세구분": "골재", "검색어": "잡석" },
|
||||
@@ -2018,7 +2018,7 @@
|
||||
"호표": [
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": {"구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "토목용부직포"},
|
||||
"요소": {"구분": "매트·토목섬유", "상세구분": "부직포", "검색어": ""},
|
||||
"이름": "부직포",
|
||||
"단위": "㎡",
|
||||
"수량": "찾기(QF000394, 구분='자재 - 부직포').적용",
|
||||
|
||||
@@ -2070,7 +2070,7 @@
|
||||
{"열쇠": "w_건축목공", "기호": "w", "아래": "건축목공", "위": "", "이름": "건축목공노임", "단위": "", "연결": {"갈래": "요소", "키": "LB000023"}},
|
||||
{"열쇠": "w_보통인부", "기호": "w", "아래": "보통인부", "위": "", "이름": "보통인부노임", "단위": "", "연결": {"갈래": "요소", "키": "LB000002"}},
|
||||
{"열쇠": "Q_수량3^표", "기호": "Q", "아래": "수량3", "위": "표", "이름": "재료량.수량", "단위": "줄 따름", "연결": {"갈래": "표", "표": "QC000144", "조건": {"구분": "S_작업", "자재": "'부직포'"}, "칸": "수량"}},
|
||||
{"열쇠": "P_부직포", "기호": "P", "아래": "부직포", "위": "", "이름": "부직포단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "토목용부직포"}, "단위": "㎡"}},
|
||||
{"열쇠": "P_부직포", "기호": "P", "아래": "부직포", "위": "", "이름": "부직포단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "매트·토목섬유", "상세구분": "부직포", "검색어": ""}, "단위": "㎡"}},
|
||||
{"열쇠": "Q_수량4^표", "기호": "Q", "아래": "수량4", "위": "표", "이름": "재료량.수량", "단위": "줄 따름", "연결": {"갈래": "표", "표": "QC000144", "조건": {"구분": "S_작업", "자재": "'하드롱지'"}, "칸": "수량"}},
|
||||
{"열쇠": "P_하드롱지", "기호": "P", "아래": "하드롱지", "위": "", "이름": "하드롱지단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "도배지(벽지) 하드롱지"}, "단위": "㎡"}},
|
||||
{"열쇠": "Q_수량5^표", "기호": "Q", "아래": "수량5", "위": "표", "이름": "재료량.수량", "단위": "줄 따름", "연결": {"갈래": "표", "표": "QC000144", "조건": {"구분": "S_작업", "자재": "'풀'"}, "칸": "수량"}},
|
||||
|
||||
@@ -1848,7 +1848,7 @@
|
||||
{"열쇠": "Q_수량3^표", "기호": "Q", "아래": "수량3", "위": "표", "이름": "차수재공.수량", "단위": "인·대", "연결": {"갈래": "표", "표": "QC000397", "조건": {"재료": "S_재료", "구분": "'크레인'"}, "칸": "수량"}},
|
||||
{"열쇠": "P_기계시간당", "기호": "P", "아래": "기계시간당", "위": "", "이름": "기계 시간당 기계경비(손료 보정 없는 기종)", "단위": "원/시간", "연결": {"갈래": "단가산출", "키": "GC000268", "입력": {"기계": "S_크레인", "지역": "S_지역"}}},
|
||||
{"열쇠": "Q_수량4^표", "기호": "Q", "아래": "수량4", "위": "표", "이름": "차수재공 재료량(참고자료).수량", "단위": "수", "연결": {"갈래": "표", "표": "QC000399", "조건": {"구분": "'부직포'"}, "칸": "수량"}},
|
||||
{"열쇠": "P_부직포", "기호": "P", "아래": "부직포", "위": "", "이름": "부직포단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "토목용부직포"}, "단위": "㎡"}},
|
||||
{"열쇠": "P_부직포", "기호": "P", "아래": "부직포", "위": "", "이름": "부직포단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "매트·토목섬유", "상세구분": "부직포", "검색어": ""}, "단위": "㎡"}},
|
||||
{"열쇠": "Q_수량5^표", "기호": "Q", "아래": "수량5", "위": "표", "이름": "차수재공 재료량(참고자료).수량", "단위": "수", "연결": {"갈래": "표", "표": "QC000399", "조건": {"구분": "'지오콤포지트'"}, "칸": "수량"}},
|
||||
{"열쇠": "P_지오콤포지트", "기호": "P", "아래": "지오콤포지트", "위": "", "이름": "지오콤포지트단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": ""}, "단위": "㎡"}},
|
||||
{"열쇠": "Q_수량6^표", "기호": "Q", "아래": "수량6", "위": "표", "이름": "차수재공 재료량(참고자료).수량", "단위": "수", "연결": {"갈래": "표", "표": "QC000399", "조건": {"구분": "'벤토나이트매트'"}, "칸": "수량"}},
|
||||
|
||||
@@ -615,7 +615,7 @@
|
||||
{"열쇠": "잡석량", "기호": "Q", "아래": "잡석", "위": "", "이름": "잡석량", "단위": "㎥/m", "연결": {"갈래": "입력", "값": 0}},
|
||||
{"열쇠": "Q_유공관설치^표", "기호": "Q", "아래": "유공관설치", "위": "표", "이름": "맹암거 유공관설치 - 유공관", "단위": "수", "연결": {"갈래": "표", "표": "QF000369", "조건": {"구분": "'유공관설치 - 유공관'"}, "칸": "적용"}},
|
||||
{"열쇠": "P_유공관", "기호": "P", "아래": "유공관", "위": "", "이름": "유공관(∅200㎜)단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "관·이음관", "상세구분": "PE관", "검색어": "PE 다중벽관 ∅200㎜"}, "단위": "m"}},
|
||||
{"열쇠": "P_부직포", "기호": "P", "아래": "부직포", "위": "", "이름": "부직포단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "토목용부직포"}, "단위": "㎡"}},
|
||||
{"열쇠": "P_부직포", "기호": "P", "아래": "부직포", "위": "", "이름": "부직포단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "매트·토목섬유", "상세구분": "부직포", "검색어": ""}, "단위": "㎡"}},
|
||||
{"열쇠": "P_잡석구입", "기호": "P", "아래": "잡석구입", "위": "", "이름": "잡석 구입단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "잡석"}, "단위": "㎥"}},
|
||||
{"열쇠": "Q_유공관설치2^표", "기호": "Q", "아래": "유공관설치2", "위": "표", "이름": "맹암거 유공관설치 - 배관공", "단위": "수", "연결": {"갈래": "표", "표": "QF000369", "조건": {"구분": "'유공관설치 - 배관공'"}, "칸": "적용"}},
|
||||
{"열쇠": "w_배관공", "기호": "w", "아래": "배관공", "위": "", "이름": "배관공노임", "단위": "", "연결": {"갈래": "요소", "키": "LB000039"}},
|
||||
@@ -1365,7 +1365,7 @@
|
||||
"출처": "산림품셈 12-23",
|
||||
"변수": [
|
||||
{"열쇠": "Q_자재부직포^표", "기호": "Q", "아래": "자재부직포", "위": "표", "이름": "부직포설치 자재 - 부직포", "단위": "수", "연결": {"갈래": "표", "표": "QF000394", "조건": {"구분": "'자재 - 부직포'"}, "칸": "적용"}},
|
||||
{"열쇠": "P_부직포", "기호": "P", "아래": "부직포", "위": "", "이름": "부직포단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "매트·토목섬유", "상세구분": "부직포", "검색어": "토목용부직포"}, "단위": "㎡"}},
|
||||
{"열쇠": "P_부직포", "기호": "P", "아래": "부직포", "위": "", "이름": "부직포단가", "단위": "", "연결": {"갈래": "요소", "재료": {"구분": "매트·토목섬유", "상세구분": "부직포", "검색어": ""}, "단위": "㎡"}},
|
||||
{"열쇠": "Q_인력보통인부^표", "기호": "Q", "아래": "인력보통인부", "위": "표", "이름": "부직포설치 인력 - 보통인부", "단위": "수", "연결": {"갈래": "표", "표": "QF000394", "조건": {"구분": "'인력 - 보통인부'"}, "칸": "적용"}},
|
||||
{"열쇠": "w_보통인부", "기호": "w", "아래": "보통인부", "위": "", "이름": "보통인부노임", "단위": "", "연결": {"갈래": "요소", "키": "LB000002"}},
|
||||
{"열쇠": "C_자재잡^표", "기호": "C", "아래": "자재잡", "위": "표", "이름": "부직포설치 자재 - 잡재료비(재료비의)", "단위": "수", "연결": {"갈래": "표", "표": "QF000394", "조건": {"구분": "'자재 - 잡재료비(재료비의)'"}, "칸": "적용"}}
|
||||
|
||||
+4774
-11392
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
"""830-1 재료 표 = EST 그대로(요구 822) — ESTX 원본을 빌더 build() 로 지은 기준 표와 정본 대조.
|
||||
|
||||
조달청 줄 · 조달청 관급 값 없음 · EST 줄 키 그대로 · 키대장 = 표 · 열 한 벌 그대로.
|
||||
기준과 달라도 되는 것(㉯) = 품셈재료에서 옮긴 줄(출처 「품셈재료 …」) · 분류가 단 비고 「이름 없음 …」.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT / "resources/master_data/scripts"))
|
||||
|
||||
import build_자재품목_분류 as cls # noqa: E402
|
||||
import build_재료_자재품목 as builder # noqa: E402
|
||||
import compare_estx as ce # noqa: E402
|
||||
import master_keys as mk # noqa: E402
|
||||
|
||||
MASTER = ROOT / "resources/master_data"
|
||||
COLS = (
|
||||
"키",
|
||||
"구분",
|
||||
"상세구분",
|
||||
"이름",
|
||||
"규격",
|
||||
"단위",
|
||||
"물가자료",
|
||||
"유통물가",
|
||||
"물가정보",
|
||||
"거래가격",
|
||||
"관급",
|
||||
"출처",
|
||||
"비고",
|
||||
"면수",
|
||||
)
|
||||
VALUES = ("물가자료", "유통물가", "물가정보", "거래가격", "관급")
|
||||
SAME = ("이름", "규격", "단위", *VALUES, "면수", "출처")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def t():
|
||||
if not ce.ESTX.is_file():
|
||||
pytest.skip("ESTX 원본 없음")
|
||||
book = mk.load_book()
|
||||
by = {v["원문번호"]: k for k, v in book["키"].items() if k.startswith("MT")}
|
||||
base = {by[r["열쇠"]]: r for r in builder.build(ce.Estx().table("StuffcDat"))}
|
||||
rows = json.loads((MASTER / "재료_자재품목.json").read_text(encoding="utf-8"))["줄"]
|
||||
return base, {r["키"]: r for r in rows}, rows, book
|
||||
|
||||
|
||||
def test_EST_줄_키_그대로_다섯_값_같음(t):
|
||||
base, cur, _, _ = t
|
||||
assert len(base) == 23973 and set(base) <= set(cur)
|
||||
assert [(k, c) for k in base for c in SAME if base[k][c] != cur[k][c]] == []
|
||||
note = [k for k in base if base[k]["비고"] != cur[k]["비고"]]
|
||||
assert all(cur[k]["비고"] == cls.NOTE_EMPTY and not cur[k]["이름"] for k in note)
|
||||
|
||||
|
||||
def test_조달청_줄_값_없음(t):
|
||||
_, cur, _, _ = t
|
||||
assert not [k for k, r in cur.items() if "조달청" in str(r["출처"])]
|
||||
assert not [k for k, r in cur.items() if str(r["비고"] or "").startswith("관급:")]
|
||||
assert all("관급" in r["이름"] for r in cur.values() if r["관급"] is not None)
|
||||
|
||||
|
||||
def test_EST_밖_줄은_품셈재료만(t):
|
||||
base, cur, _, _ = t
|
||||
extra = [r for k, r in cur.items() if k not in base]
|
||||
assert len(extra) == 119
|
||||
assert all(str(r["출처"]).startswith(builder.CARRY) for r in extra)
|
||||
assert all(r[c] is None for r in extra for c in VALUES)
|
||||
|
||||
|
||||
def test_키대장_표와_맞음_번호_재사용_없음(t):
|
||||
_, cur, _, book = t
|
||||
mt = {k for k, v in book["키"].items() if v["파일"] == "재료_자재품목.json"}
|
||||
assert mt == set(cur)
|
||||
assert book["다음"]["MT"] > max(int(k[2:]) for k in cur)
|
||||
assert not set(cur) & set(book.get("폐기", {}))
|
||||
|
||||
|
||||
def test_열_한_벌(t):
|
||||
_, _, rows, _ = t
|
||||
assert {tuple(r) for r in rows} == {COLS}
|
||||
@@ -724,19 +724,14 @@ def test_전력은_계약종별로_고름(whole):
|
||||
|
||||
|
||||
def test_자재품목_열_한_벌():
|
||||
"""모든 줄이 같은 열 한 벌(원문번호 없음) · 관급은 조달 값 · 기간은 비고 · 남은 가격정보는 비고에만."""
|
||||
"""모든 줄이 같은 열 한 벌(원문번호 없음) · 관급은 EST 원문 값(요구 822) · 기간은 비고 · 남은 가격정보는 비고에만."""
|
||||
data = cm.load(["재료_자재품목.json"])["재료_자재품목.json"]
|
||||
rows = {r["키"]: r for r in data["줄"]}
|
||||
assert {tuple(r) for r in rows.values()} == {cm.COLS["자재품목"]}
|
||||
assert "원문번호" not in cm.COLS["자재품목"] # 남의 품번 — 대조는 대장에만
|
||||
steel = rows["MT001658"]
|
||||
assert steel["관급"] == Decimal(1301590) and steel["비고"].startswith(
|
||||
"관급: 조달청 다수공급자계약"
|
||||
)
|
||||
assert steel["관급"] == Decimal(1228830) and steel["비고"].startswith("조달 26.")
|
||||
assert all(steel[s] is None for s in ("물가자료", "유통물가", "물가정보", "거래가격"))
|
||||
# 제조사별로 값이 갈린 줄 — 같은 규격 가운데 최저가와 그 제조사를 비고에
|
||||
low = rows["MT001637"]
|
||||
assert low["관급"] == Decimal(1228830) and "최저" in low["비고"]
|
||||
left = [r for r in rows.values() if str(r["비고"] or "").startswith("가격정보")]
|
||||
assert len(left) == 67 and all(r["관급"] is None for r in left)
|
||||
# 유류 다섯 줄(값이 비고 글뿐)은 뗌 — 대장에 폐기로 남음
|
||||
|
||||
Reference in New Issue
Block a user