Files
Aislo/M02_MasterTemplete/M02_Hopyo_Clause.py
T
eomsangdonandClaude Opus 5.5 c5067aa2b6 feat(809-1a): 일위대가 불러오기 = 경쟁사 로직 인정 · 요소만 연결 · [수정] 모달 = 일위대가 컨테이너 부품 · 기초 판정 걷음
- [수정] 모달 = Labor_Table(읽기 · 덧칸 표지 · 엑셀 금액) · 줄 = 로직(단가산출 · 일위대가) / 요소(연결됨 · 후보 · 못 이음) · 단가산출 줄 = 가져온 식 SK 수식 탭 모달(Labor_SkEdit) · 서버 hopyo-view(M02_Hopyo_View)
- 엑셀 보기 목록 · 칠 = 줄 표지(View.listing) · 단가산출 탭 = 블록 목록 + [가져온 식으로 열기] · [내 단가산출로]
- 걷음(요구 703): 판정 체계 785(Judge · Solve · Bind · San_Basis 다섯 · Make.Move · Row 1차 판정 · 화면 Check · CheckRows · Rows · Block · JudgeApi · Picks · Judge.css) · [우리 기초로 바꾸기](to-base) · 근거 기초 제안 · Clause 기초 판정
- 시험 지움: 642_2(Bind · Solve) · 642_3(Row 1차 판정) · 642_5(옮기기 Move) · 642_6(판정 화면) · 786_2(옛 [수정] 모달) · 793_3a(근거 → 기초 제안) — 모두 걷은 기초 판정 길 몫
- 시험 고침: 642_1 · 794_2a2 · 794_2b(근거 제안 부분만 뺌) · 793_2 · 793_3b(기초 바꾸기 기대 → 없음) · 627 · 779_2 · 785_4 · 807_1 · m02_hopyo_excel · grid · stmate(judge 길 → view 길)
- 새 시험 test_809_1_import_labor(기초 길 0 · 표지 1,350 줄 · 가져온 식 SK · 모달 = Labor_Table · 금액 = 엑셀)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 19:17:58 +09:00

122 lines
5.4 KiB
Python

"""M02 일위대가 실무자료 단가산출 출처 풀이(PLAN 785-1 · 809-1 · 요구 642 · 702) — 엑셀 「단가산출」 블록 품셈 번호 풀이.
출처 풀이(`settle` · 781-1) = 건설품셈(부문 모름) 번호 → 뜻 낱말로 부문 · 임도품셈(옛) → 대응표 현행 산림 번호.
풀이 글 = 원문 패널 「엑셀 글의 품셈 정보」 카드(807-1) 몫 · 조항 → 우리 기초 판정(색인 · 블록 근거 · 기초 없음 표지)은
809-1 에서 지움(경쟁사 로직 인정 · 요구 703).
"""
from __future__ import annotations
import csv
import re
from functools import lru_cache
from typing import Any
from M01_MasterData import M01_MasterData_Store as m01
from M01_MasterData import M01_MasterData_Store_Make as make
def _flat(value: Any) -> str:
return re.sub(r"\s+", "", str(value or ""))
# ── 우리 쪽 조항 · 직종 ────────────────────────────────────────────────
# ── 출처 풀이(781-1 · 781-2) — 건설 부문 · 임도 옛 번호 대응 · 기초 없음 ───────────
OLD = "(옛)"
_ROAD = "임도품셈" + OLD
_ROAD_MAP = make.BODY.parent / "경쟁사 프로그램/STmate/old/30_원자료/임도품셈_판_대응표.csv"
_OLD_NO = re.compile(r"^(\d+(?:-\d+)+)(\s+[가-하])?$")
_STOP_GRAMS = frozenset("설치 해체 작업 인력 기계 장비 보완 신설 공사".split())
@lru_cache(maxsize=1)
def road_map() -> dict[str, str]:
"""임도품셈(옛) 번호 → 현행 산림 번호 — 지식DB 대응표 「확정」 한 대응만(일대다 · 미확인 뺌) ·
옛 번호가 가 · 나 항이면 현행 윗마디(9-13-7 → 9-13 · 그 항만 대응하므로)."""
with open(_ROAD_MAP, encoding="utf-8-sig", newline="") as f:
rows = list(csv.DictReader(f))
out = {}
for row in rows:
old, new = _OLD_NO.match(row["과거_절"].strip()), row["현행_절"].strip()
if row["판정"] != "확정" or not old or not _OLD_NO.fullmatch(new) or " " in new:
continue
out[old.group(1)] = new.rsplit("-", 1)[0] if old.group(2) and new.count("-") > 1 else new
return out
def _grams(text: Any) -> set[str]:
"""뜻 낱말 — 낱말 안 한글 두 글자 묶음(괄호 글 포함 · 굴삭 = 굴착)."""
words = re.findall(r"[가-힣]+", str(text or "").replace("굴삭", "굴착"))
return {w[i : i + 2] for w in words for i in range(len(w) - 1)} - _STOP_GRAMS
@lru_cache(maxsize=2)
def _sections(book: str) -> dict[tuple[str, str], str]:
"""{(부문, 절 번호): 제목(괄호 글 · 「:」 뒤 뺌)} — 원문 본문."""
return {
(s["division"], s["section"]): re.sub(r"\(.*?\)", "", s["title"]).split(":")[0].strip()
for s in make._all_sections(book)
}
@lru_cache(maxsize=2)
def _named(whole_id: int) -> dict[tuple[str, str], list[str]]:
"""{(구분, 원문번호): [로직 이름]} — M01 로직."""
out: dict[tuple[str, str], list[str]] = {}
for data in m01.loaded()[0].values():
if data.get("그룹") == "로직":
for row in data.get("줄") or []:
key = (str(row.get("구분") or ""), str(row.get("원문번호") or "").split(" ")[0])
out.setdefault(key, []).append(str(row.get("이름") or ""))
return out
def _part(number: str, name: Any, text: Any = "") -> str:
"""건설품셈(부문 모름) 번호 → 원문에 그 번호가 있는 부문 중 블록 이름(안 맞으면 블록 글 ·
두 묶음)이 원문 제목 · 같은 번호 로직 이름과 뜻 낱말을 나누는 부문이 하나 = 그 부문 ·
둘 이상 = 모름 · 없음 = 「(옛)」(번호 뜻 안 맞음)."""
if not _grams(name):
return "건설품셈"
named = _named(id(m01.loaded()[1]))
texts = {
part: [title, *named.get((f"건설품셈 {part}", no), [])]
for (part, no), title in _sections("건설품셈").items()
if no == number
}
hits = []
for mine, least in ((_grams(name), 1), (_grams(text), 2)): # 글은 두 묶음 이상
hits = [p for p, ts in texts.items() if any(len(mine & _grams(t)) >= least for t in ts)]
if hits:
break
if len(hits) == 1:
return f"건설품셈 {hits[0]}"
return "건설품셈" if hits else "건설품셈" + OLD
def resolve(theirs: list[list[str]], name: Any, text: Any = "") -> list[list[str]]:
"""블록 출처 → 부문 풀이 · 임도 옛 번호 대응(`road_map`) 뒤 출처."""
out: list[list[str]] = []
for book, number in theirs:
one = [book, number]
if book == _ROAD and number in road_map():
one = ["산림품셈", road_map()[number]]
elif book == "건설품셈":
one = [_part(number, name, text), number]
if one not in out:
out.append(one)
return out
def settle(book: dict[str, Any]) -> dict[str, Any]:
"""Book 단산 블록 출처를 제자리에서 풀이(줄 `근거.출처` 가 같은 리스트를 가리킴)."""
for block in book.get("단산") or []:
if block.get("출처"):
text = " ".join(str(t) for t in block.get("글") or [])
block["출처"][:] = resolve(block["출처"], block.get("이름"), text)
return book
# ── 조항 색인 · 블록 근거(785-1) ──────────────────────────────────────────