refactor(812-1): 옛 엑셀 엔진 걷기 2 · 실무 11 금액 100 % · NativeEst 자리 dict 묶음 고침 · 옛 시험 정리

- M02_Doc_NativeEst.triple: 세 줄 수량 식이 같은지 = repr 로 맞댐(자리 규칙 dict 가 set 에 안 들어가 TypeError — 엑셀 줄은 보인 값이 반올림 자리를 타 원파일에 없던 꼴 · 812-3 끝나 브레인이 맡김)
- 엑셀 읽기기: 절 소계 덧셈이 뺀 오타 줄 = 비목 없음(블록 금액도 그래야 맞을 때만) · 세 줄 계 금액 칸 비목 하나 = 옮김 · 소계 61 = 금액 칸 비목 하나 · 「.1」 수 · 총괄표 당초 칸 표지 · STmate 엑셀 줄 금액 · 계 꼬리(검산 줄)
- 화면: 가져온 식 규격 칸 꼬리표 = 원파일 · 엑셀 갈림(line_stack_sourced)
- 시험: 793_1 지움(옛 EST · STmate 엑셀 글 추정 엔진 · 812_1 골든이 대신) · 793_2_san_emit EST 시험 지움(STmate 남김) · 797_1a 옛 표지 ① ② · 줄 단위 합격 · 표본 밖 수 · 버림1 감시 지움(옛 엔진) · 810_1 (c) 엑셀 엔진 대조 지움 · 793_2 실무 블록 · 793_3b 끝전 = 새 길 · 812_1 금액 엇갈림 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-09 08:38:12 +09:00
co-authored by Claude Opus 5.5
parent 5779943540
commit 61bb15fbde
16 changed files with 112 additions and 634 deletions
+10 -5
View File
@@ -52,7 +52,10 @@ def pack(got: dict, name: str) -> dict[str, Any]:
out: dict[str, Any] = {"꼴": got["꼴"], "파일": name, "표지": got.get("표지") or []}
out |= {"엑셀": True} if got.get("엑셀") else {} # 812-1 엑셀 읽기기(`M02_San_Xlsx`) — 같은 꼴
head = ("코드", "번", "이름", "규격", "단위", "저장")
if got.get("엑셀"): # 엑셀 읽기기 — 줄 행 = 시트 행(원문 카드 · 옛 엑셀 길과 같은 자리)
if got.get(
"엑셀"
): # 엑셀 읽기기 — 줄 행 = 시트 행(원문 카드 · 옛 엑셀 길과 같은 자리) · 블록 표지
head += ("표지", "몸", "꼬리")
got = {**got, "산근": [_rows(b) for b in got["산근"]]}
if got["꼴"] == "EST":
out["산근"] = [
@@ -106,27 +109,29 @@ def emit(saved: dict, no: int, keys: bool = True) -> dict[str, Any]:
names = {b["코드"]: n for n, b in numbers(saved).items()}
out = ne.block(blk, int(no), keys, saved["파일"], names)
out["원본"] |= {"원코드": blk["코드"]} | ({"규격": blk["규격"]} if blk.get("규격") else {})
return _excel(saved, out)
return _excel(saved, out, blk)
from M02_MasterTemplete import M02_San_Stmate as stm
nodes = [{"행": ln["행"], "글": ln["글"], **stm.parse_line(ln["글"])} for ln in blk["줄"]]
saved_amt = blk.get("저장") or {}
head = {"C": saved_amt.get("T"), "D": saved_amt.get("L"), "E": saved_amt.get("M")}
head["F"] = saved_amt.get("S")
block = {"코드": blk["코드"], "이름": blk.get("이름"), "몸": [], "꼬리": [], "머리": head}
block = {"코드": blk["코드"], "이름": blk.get("이름"), "머리": head}
block |= {"몸": blk.get("몸") or [], "꼬리": blk.get("꼬리") or []} # 엑셀 = 줄 금액 · 계 꼬리
out = em.stmate(block, nodes, saved["단가"], saved["끝전"], keys, saved["파일"])
marks = sum(1 for one in out["원본"]["글줄"] if one.get("표지"))
sure = len(out["로직"]["호표"]) + len(out["로직"].get("덧줄") or []) + len(out["로직"]["중간"])
out["원본"] |= {"원파일": True, "갈래": "STmate", "확정": {"확정": sure, "표지": marks}}
return _excel(saved, out)
return _excel(saved, out, blk)
def _excel(saved: dict, out: dict) -> dict[str, Any]:
def _excel(saved: dict, out: dict, blk: dict) -> dict[str, Any]:
"""엑셀 읽기기 꼴(812-1) — 원본 `원파일` 거짓 · `엑셀` 참(화면 꼬리표 「엑셀」 · 원코드 = 단산 번)."""
if saved.get("엑셀"):
out["원본"] |= {"원파일": False, "엑셀": True}
out["원본"] |= {"시트": "단가산출"} if saved["꼴"] == "EST" else {}
out["원본"].pop("원코드", None)
out["원본"]["표지"] += [m for m in blk.get("표지") or [] if m.startswith("참고 · ")]
return out
+1 -1
View File
@@ -302,7 +302,7 @@ class Block:
"""기계 · 산근 · 견적 세 줄 → 로직 값묶음 한 줄(수량 식 · 자리 같을 때) · 아니면 줄마다."""
got = [self.money(ln) for ln in rows]
parts = [ln["비목"] for ln in rows]
same = all(got) and len({g for g in got}) == 1 and sorted(parts) == ["L", "M", "S"]
same = all(got) and len({repr(g) for g in got}) == 1 and sorted(parts) == ["L", "M", "S"]
if not same:
for ln, g in zip(rows, got):
self.machine_line(ln, g)
+3 -2
View File
@@ -141,8 +141,9 @@ def resources(sheets: dict[str, list]) -> dict[str, list[dict[str, Any]]]:
got = []
for one in _table(sheets.get(sheet) or [], ()):
r = list(one["줄"]) + [None] * 30
vals = {k: float(v or 0) for k, v in book.applied(r).items()}
got.append({**_drop(one), "표": sheet, "값": vals})
vals, note = book.applied_how(r)
vals = {k: float(v or 0) for k, v in vals.items()}
got.append({**_drop(one), "표": sheet, "값": vals} | ({"칸": note} if note else {}))
out[kind] = got
return out
+42
View File
@@ -300,6 +300,45 @@ class Lines:
self.plain(ln)
return self.done(ln)
def sections(self, saved: dict) -> None:
"""절 소계(가 · 나 … 머리 아래 합계 줄 「노무비 : a + b = X」)가 뺀 줄 하나 = 합에 안 든 줄(비목 없음) —
절(앞 숫자 머리부터) 안 그 비목 줄 결과 합 − X 가 꼭 한 줄 값이고 나머지 줄 값은 모두 덧셈 글에 있을
때만 · 블록 금액도 그 줄을 빼야 맞을 때만(엑셀 글 덧셈 · 원파일 SCheck 0)."""
lines, start = self.lines, 0
for i, ln in enumerate(lines):
text = ln["보임"]
if ln["단"] == 1 and re.match(r"^\s*\d", text):
start = i
if not (ln["합계"] and ln["비목"] in ("L", "M", "S") and ln["결과"] and "+" in text):
continue
head = next((k for k in range(i - 1, start - 1, -1) if lines[k]["단"] == 1), None)
if head is None or not re.match(r"^\s*[가-하]", lines[head]["보임"]):
continue
part, want = ln["비목"], ln["결과"][-1]
kids = [
x for x in lines[start:head] if x["비목"] == part and x["결과"] and not x["합계"]
]
kids += [
x for x in lines[start:head] if x["비목"] == part and x["줄참조"] and not x["결과"]
]
value = {id(x): (x["결과"] or x["줄참조"])[-1] for x in kids}
extra = sum(value.values()) - want
odd = [x for x in kids if abs(value[id(x)] - extra) < 0.5]
colon = _COLON.search(text)
adds = [v for _, _, v in _nums(text, colon.end() if colon else 0, text.rfind("="))]
rest = [value[id(x)] for x in kids if odd and x is not odd[0]]
listed = all(any(_close(v, a) for a in adds) for v in rest)
if len(odd) != 1 or abs(extra) < 0.5 or not listed:
continue
every = sum(
(x["결과"] or x["줄참조"])[-1]
for x in lines
if x["비목"] == part and not x["합계"] and (x["결과"] or x["줄참조"])
)
block = float(saved.get(part) or 0) # 블록 금액이 그 줄을 빼야 맞을 때만
if abs(every - block) >= 1 and abs(every - extra - block) < 1:
odd[0]["비목"] = None
def unused(self) -> None:
"""머리 아닌 줄의 기계 이름 — 아래 세 줄이 안 쓰면 글 줄(설명 속 장비 이름)."""
for h in self.heads:
@@ -597,10 +636,13 @@ def read(sheets: dict[str, list]) -> dict[str, Any]:
}
if not one:
tags.append(f"{sx.NOTE}총괄표에 단산{b['번']} 없음")
elif one.get("칸"): # 총괄표 당초 / 적용 칸(797-1a)
tags.append(f"참고 · {one['칸']}")
work = Lines(look, tags)
for src in b["줄"]:
work.run(src)
work.unused()
work.sections(head["저장"] or {})
lines = []
for ln in work.lines:
marks = ln.pop("_marks")
+8 -1
View File
@@ -59,7 +59,14 @@ def read(sheets: dict[str, list]) -> dict[str, Any]:
head = {"코드": blk["코드"], "이름": blk.get("이름") or me.get("이름")}
head |= {"규격": me.get("규격"), "단위": me.get("단위"), "저장": _amt(blk["머리"])}
_, tot = ev.run_stmate([ln["풀이"] for ln in lines], prices)
out["산근"].append({**head, "줄": lines, "줄합": tot, "표지": tags, "엑셀행": blk["행"]})
body = [{k: ln.get(k) for k in ("행", "B", "C", "D", "E", "F")} for ln in blk["몸"]]
out["산근"].append(
{**head, "줄": lines, "줄합": tot, "표지": tags, "엑셀행": blk["행"]}
| {
"몸": body,
"꼬리": blk["꼬리"],
} # 엑셀 줄 금액 · 계 꼬리(검산 · `M02_San_Emit.stmate`)
)
rule, got = vote(out["산근"], prices)
out["옵션"] = {"SAN_TYP": RULE[rule]}
out["내역"] = None