From 017c5120b9f2761bffd6d214c9877e53874ee27d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 13 Sep 2026 12:29:19 +0900 Subject: [PATCH] =?UTF-8?q?knowledge(=EA=B2=BD=EC=9F=81=EC=82=AC):=20STmat?= =?UTF-8?q?e=20=EC=9E=90=EC=9E=AC=20=EC=B1=84=ED=83=9D=20=EA=B7=9C?= =?UTF-8?q?=EC=B9=99=20=ED=99=95=EC=A0=95=20=E2=80=94=200=20=EC=95=84?= =?UTF-8?q?=EB=8B=8C=20=ED=9B=84=EB=B3=B4=20=EC=A4=91=20=EC=B5=9C=EC=A0=80?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 07번이 C등급으로 두고 「최저가·최신가·특정 슬롯 우선 중 어느 하나라고 단정할 근거가 없다」 한 항목을 실증으로 확정. 출력 XLSX 의 자재단가대비표에 후보 다섯 칸(조달가격·물가자료·물가정보· 거래가격·견적단가)과 적용 단가·출처가 나란히 있어 직접 검정함. - 실무 6건 전수 186건 중 186건이 후보 최저가와 일치 - 후보 값이 2종 이상인 76건 전부 최저가 - 후보 값이 3종 이상인 36건 전부 최저가 - 최저가가 아닌 후보를 택한 건 0, 후보 밖 값을 쓴 건 0 곁들여 - COSTN 의 여섯 칸 중 여섯 번째가 적용 단가 칸으로 보임 - 후보 없이 적용만 있는 25건은 유가 자료, 적용 0 인 49건은 별도계상 - 남은 것은 사용자 수동 고정 시의 우선순위 (T10) 09번이 꼽은 여섯 호환성 차단점 중 자재 자동채택이 풀림. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Prk9BCHG1EMAywk9k8wegA --- .../20_분석/00_분석_요약과_읽는법.md | 1 + .../07_규칙2_3_4_5_직접비_단가_중기_자재.md | 8 ++- .../STmate 분석/20_분석/17_단가조립_검증.md | 35 +++++++++- .../40_스크립트/material_adoption_test.py | 64 +++++++++++++++++++ 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 resources/knowledge/original/경쟁사 프로그램/STmate 분석/40_스크립트/material_adoption_test.py diff --git a/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/00_분석_요약과_읽는법.md b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/00_분석_요약과_읽는법.md index c16be6e5..7e6b7296 100644 --- a/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/00_분석_요약과_읽는법.md +++ b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/00_분석_요약과_읽는법.md @@ -58,6 +58,7 @@ | 차수 인계 | 「가정하지 말 것」 경고만 | **A 실증** — `BOQ12`가 현재안, 다음 파일 차수1로 인계 | 16번 §4.5·§4.6, 08번 | | 중기 시간당 사용료 | 손료 계수 없음 | **A** — 손료율·취득가·연료·잡품률 25개 장비 실측 | 17번 §3·§4 | | 호표 성분 소계 반올림 | 미확정 | **A** — 절사 (중기 398건 100%) | 17번 §2 | +| 자재 6후보 자동채택 | C | **A 실증** — 0 아닌 후보 중 **최저가**. 복수후보 76/76 | 17번 §5.5, 07번 §5 | 분석일: 2026-09-13 대상: `C:\경영정보(주)` diff --git a/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/07_규칙2_3_4_5_직접비_단가_중기_자재.md b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/07_규칙2_3_4_5_직접비_단가_중기_자재.md index e21330c2..c8ad48e1 100644 --- a/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/07_규칙2_3_4_5_직접비_단가_중기_자재.md +++ b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/07_규칙2_3_4_5_직접비_단가_중기_자재.md @@ -89,7 +89,13 @@ price_candidate(material_id, revision_id, slot_no, source_name, page, observed_p price_adoption(material_id, revision_id, chosen_slot_no?, adopted_price, method, user_override) ``` -자동선정은 현재 C등급이다. 최저가·최신가·특정 슬롯 우선 중 어느 하나라고 단정할 근거가 없다. 동일 구현 전에는 후보 조합을 통제한 STmate 시험이 필수다: 6개 슬롯에 서로 다른 값, 동률, 0, 공란을 배치하고 채택값과 `PG_1` 변화를 기록한다. +**2026-09-13 — 자동선정은 「0이 아닌 후보 중 최저가」로 확정됐다. A등급 실증.** + +출력 XLSX의 `자재단가대비표`에 후보 다섯 칸과 적용 단가·출처가 나란히 있어 직접 검정했다. 실무 6건 전수에서 **후보 값이 2종 이상인 76건 전부, 3종 이상인 36건 전부 최저가**였고, 최저가가 아닌 후보를 택한 건은 0이다. 상세는 17번 §5.5. + +`COSTN`의 여섯 칸 중 **여섯 번째가 적용 단가 칸**으로 보이며, 이는 「노무 L은 후보6과 채택가가 거의 일치」라는 위 관찰과 맞는다. + +남은 것은 **사용자 수동 고정이 걸렸을 때의 우선순위**다. 표본에는 수동 고정 흔적이 없었다. 통제시험(13번 T10)에서 6개 슬롯에 서로 다른 값·동률·0·공란을 배치하고 수동 고정을 섞어 채택값과 `PG_1` 변화를 기록한다. ## 6. 반올림 경계 diff --git a/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/17_단가조립_검증.md b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/17_단가조립_검증.md index ed75c1d2..82fd72db 100644 --- a/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/17_단가조립_검증.md +++ b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/20_분석/17_단가조립_검증.md @@ -106,6 +106,38 @@ `깬잡석찰쌓기 58,889`가 제 1 호표의 합계와 같다. **호표 사이에 참조가 걸리므로 위상순서로 계산해야 한다** — 09번 §3이 말한 의존 그래프가 실제로 필요하다. +## 5.5 자재 채택 규칙 — **최저가**, A등급 실증 + +출력 XLSX의 `자재단가대비표` 시트에 **후보 다섯 칸과 적용 단가·출처가 나란히** 있다. + +```text +호표 | 명칭 | 규격 | 단위 | 조달가격 | 물가자료 | 물가정보 | 거래가격 | 견적단가 | 적용단가 | 출처 + 1 | 철못 | N 50 | KG | 0 | 1,665 | 1,935 | 1,680 | 0 | 1,665 | 물자 + 6 | PVC파이프 | VG1 | M | 0 | 4,327 | 4,702 | 3,475 | 0 | 3,475 | 거래 + 19 | 와이어메쉬 | #6 | M2 | 3,546 | 4,970 | 5,510 | 0 | 4,545 | 3,546 | 조달 +``` + +실무 프로젝트 6건 전수로 검정했다. + +| 구분 | 건수 | 적용단가 = 후보 최저 | +|---|---:|---:| +| 전체 (후보 1개 이상) | 186 | **186 (100%)** | +| **후보 값이 2종 이상** | 76 | **76 (100%)** | +| **후보 값이 3종 이상** | 36 | **36 (100%)** | +| 최저가가 아닌 후보를 택한 건 | — | **0** | +| 어느 후보와도 다른 건 | — | **0** | + +⇒ **자동채택은 「0이 아닌 후보 중 최저가」다.** 07번 §5가 「최저가·최신가·특정 슬롯 우선 중 어느 하나라고 단정할 근거가 없다」며 C등급으로 둔 항목이 **실증으로 확정**됐다. + +곁들여 확인된 것. + +- 후보 슬롯은 화면상 **다섯**(조달가격·물가자료·물가정보·거래가격·견적단가)이고, `COSTN`의 `AMT_11..AMT_61` 여섯 칸 중 **여섯 번째가 적용 단가 칸**으로 보인다. 07번이 「노무 L은 후보6과 채택가가 거의 일치」라 관찰한 것과 맞는다. +- 후보가 하나도 없는데 적용 단가만 있는 행이 25건 있다(경유·휘발유 등 **유가 자료**, 별도 배포 단가). +- 적용 단가가 0인 행 49건은 `별도계상`(이형철근·레미콘)·`조달수수료(재료비×0.54%)`처럼 **다른 자리에서 계산되는** 것이다. +- **출처 표시는 슬롯 이름의 축약**이다(물자=물가자료, 물정=물가정보, 거래, 조달, 견적). 슬롯 번호에 기관명을 코드로 박으면 안 된다는 07번 경고는 그대로 유효하다 — `Start8x.Ini`가 표시명을 바꾼다. + +⚠ **사용자 수동 고정이 있으면 이 규칙을 벗어난다.** 표본 6건에는 수동 고정 흔적이 없었을 뿐이며, 수동 우선순위는 여전히 통제시험(T10) 대상이다. + ## 6. 이로써 바뀌는 판정 | 항목 | 15번의 판정 | 지금 | @@ -113,12 +145,13 @@ | 중기 시간당 단가 재현 | ❌ 손료 계수가 어디에도 없음 | ✅ **호표에 손료율·취득가·연료·잡품률이 전부 있음** | | 단가 조립 규칙 | ❌ `BDQTY` 암호화로 불가 | ✅ **출력 시트로 조립 규칙·반올림 확인됨** | | 성분 소계 반올림 | 미확정 | ✅ **절사** (중기 398건 100%) | +| 자재 6후보 채택 | ❌ C등급, 규칙 없음 | ✅ **최저가** — 복수후보 76/76 | 여전히 막힌 것은 **입력 쪽**이다. - `BDQTY`의 수량 **할증** 필드(`X_QTY`·`X_HAL`) 실값 — 출력에는 할증 후 수량만 남는다 - 품셈에서 구성 수량이 **어떻게 뽑혔는가** -- 자재 6후보 중 **채택 규칙** +- 사용자가 **수동 고정**했을 때의 우선순위 (T10) ## 7. 다음 diff --git a/resources/knowledge/original/경쟁사 프로그램/STmate 분석/40_스크립트/material_adoption_test.py b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/40_스크립트/material_adoption_test.py new file mode 100644 index 00000000..51fbc708 --- /dev/null +++ b/resources/knowledge/original/경쟁사 프로그램/STmate 분석/40_스크립트/material_adoption_test.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +"""자재 채택 규칙 검정 — 적용단가가 후보 중 무엇인가.""" +import sys, io, pathlib, warnings, collections +from decimal import Decimal as D +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") +warnings.filterwarnings("ignore"); import openpyxl +def dec(v): + if v in (None,""): return None + try: return D(str(v)) + except Exception: return None +ROOT = pathlib.Path(r"D:\aislo-wt\sub\resources\knowledge\original\실무문서") +SLOTS = [("조달가격",5),("물가자료",7),("물가정보",9),("거래가격",11),("견적단가",13)] +APPLY = 15 +stat = collections.Counter(); examples=[] +files=0 +for p in sorted(ROOT.rglob("*.xlsx")): + if p.name.startswith("~$"): continue + try: wb = openpyxl.load_workbook(p, data_only=True, read_only=True) + except Exception: continue + if "자재단가대비표" not in wb.sheetnames: continue + files += 1 + ws = wb["자재단가대비표"] + for row in ws.iter_rows(min_row=5, max_row=ws.max_row, max_col=17, values_only=True): + name = row[1] + if not name or not str(name).strip(): continue + cands = {nm: dec(row[i-1]) for nm,i in SLOTS} + nz = {k:v for k,v in cands.items() if v not in (None, D(0))} + app = dec(row[APPLY-1]); src = (str(row[APPLY]) if len(row)>APPLY and row[APPLY] else "").strip() + if app in (None, D(0)): + stat["적용단가 없음(별도계상 등)"] += 1; continue + if not nz: + stat["후보 없이 적용단가만(유가·별도자료)"] += 1; continue + mn = min(nz.values()); mx = max(nz.values()) + if len(set(nz.values())) >= 2: + stat["복수후보 총"] += 1 + if app == mn: stat["복수후보 최저"] += 1 + elif app in nz.values(): stat["복수후보 최저아닌 후보"] += 1 + else: stat["복수후보 후보밖"] += 1 + if len(set(nz.values())) >= 3: + stat["3개이상 총"] += 1 + if app == mn: stat["3개이상 최저"] += 1 + if app == mn: stat["최저가와 일치"] += 1 + elif app == mx: stat["최고가와 일치"] += 1 + elif app in nz.values(): stat["후보 중 하나지만 최저 아님"] += 1 + else: + stat["어느 후보와도 다름"] += 1 + if len(examples)<6: examples.append((p.stem[:26], str(name)[:18], nz, app, src)) + stat["총"] += 1 + +print("### 자재 채택 규칙 검정 — 파일 %d개" % files) +tot = stat["총"] +for k in ("최저가와 일치","후보 중 하나지만 최저 아님","최고가와 일치","어느 후보와도 다름"): + print(" %-24s %4d건 %5.1f%%" % (k, stat[k], 100*stat[k]/tot if tot else 0)) +print(" %-24s %4d건" % ("(후보 없이 적용만)", stat["후보 없이 적용단가만(유가·별도자료)"])) +print(" %-24s %4d건" % ("(적용단가 0/별도계상)", stat["적용단가 없음(별도계상 등)"])) +print(" ── 검정 대상 합계 %d건" % tot) +print() +for k in ("복수후보 총","복수후보 최저","복수후보 최저아닌 후보","복수후보 후보밖","3개이상 총","3개이상 최저"): + print(" %-22s %4d건" % (k, stat[k])) +if examples: + print("\n### 어느 후보와도 다른 예") + for f,n,nz,app,src in examples: + print(" %-26s %-18s 적용 %-10s 출처 %-6s 후보 %s" % (f,n,app,src, + ", ".join("%s=%s"%(k,v) for k,v in nz.items())))