feat(812-1): 엑셀 읽기기 새로 — xlsx · xls → 810-1 구조 줄 꼴 → 원파일 길 한 벌 · 골든 시험 · 올림 길 교체

- M02_San_Xlsx(공통 · 글자 칸 배치 · 금액 칸 · 자원 표) · M02_San_Xlsx_Est(EST 글 → 표지: 변수 = 가장 늦은 대입 값 하나 · 칸 나눔 꼴은 닫는 표지 2 칸 · 합계 줄 · 세 줄 머리 · 노임 · 자재 단가 짝 · 소계 61 · 율 58 · 62 · 구간 글자) · M02_San_Xlsx_Stmate(숨은 G 열 원문 = stc_line · 끝전 파일 투표)
- M02_Doc_FromNative: 엑셀 둘 꼴(번 · 시트 행 · 원본 엑셀 참 · 원파일 거짓) · M02_Doc_Import: 엑셀 길도 소계 비목 칸 비움 · 추정 칸(출처 = 엑셀 꼬리표)
- M02_Sangeun_FromHopyo: 산근 블록 = 새 읽기기(옛 San_Est · Emit.est 부름 걷음 · 옛 저장은 한 번 지어 둠)
- test_812_1: 실제 짝(거창 · STC 다섯) 같음 · 합성 배치 = 거창 실제 · 합성 EST 1,849 · STC 371 · 금액 실무 11(엇갈림 고정 목록)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-09 07:34:28 +09:00
co-authored by Claude Opus 5.5
parent 5531e5f39f
commit 8cd7baea5e
7 changed files with 1369 additions and 85 deletions
+30 -9
View File
@@ -50,19 +50,27 @@ def read(name: str, data: bytes) -> dict[str, Any]:
def pack(got: dict, name: str) -> dict[str, Any]:
"""파서 답 → 둘 꼴(셈 · 변환에 쓰는 칸만 · JSON)."""
out: dict[str, Any] = {"꼴": got["꼴"], "파일": name, "표지": got.get("표지") or []}
head = ("코드", "이름", "규격", "단위", "저장")
out |= {"엑셀": True} if got.get("엑셀") else {} # 812-1 엑셀 읽기기(`M02_San_Xlsx`) — 같은 꼴
head = ("코드", "번", "이름", "규격", "단위", "저장")
if got.get("엑셀"): # 엑셀 읽기기 — 줄 행 = 시트 행(원문 카드 · 옛 엑셀 길과 같은 자리)
got = {**got, "산근": [_rows(b) for b in got["산근"]]}
if got["꼴"] == "EST":
out["산근"] = [
{**{k: b.get(k) for k in head}, "줄": [_est_line(ln) for ln in b["줄"]]}
{**{k: b.get(k) for k in head if k in b}, "줄": [_est_line(ln) for ln in b["줄"]]}
for b in got["산근"]
]
out["일위"] = [{**{k: b.get(k) for k in head}, "줄": b["줄"]} for b in got["일위"]]
out["일위"] = [
{**{k: b.get(k) for k in head if k in b}, "줄": b["줄"]} for b in got["일위"]
]
return out
out["산근"] = [
{**{k: b.get(k) for k in head}, "줄": [{"행": ln["행"], "글": ln["글"]} for ln in b["줄"]]}
{
**{k: b.get(k) for k in head if k in b},
"줄": [{"행": ln["행"], "글": ln["글"]} for ln in b["줄"]],
}
for b in got["산근"]
]
out["일위"] = [{k: b.get(k) for k in head} for b in got["일위"]]
out["일위"] = [{k: b.get(k) for k in head if k in b} for b in got["일위"]]
res = got.get("자원") or {}
out["단가"] = {
code: {**p, **{k: (res.get(code) or {}).get(k) for k in ("이름", "규격", "단위")}}
@@ -72,6 +80,10 @@ def pack(got: dict, name: str) -> dict[str, Any]:
return out
def _rows(b: dict) -> dict:
return {**b, "줄": [{**ln, "행": ln.get("엑셀행") or ln["행"]} for ln in b["줄"]]}
def _est_line(ln: dict) -> dict:
one = {k: ln.get(k) for k in _EST_KEYS if ln.get(k) not in (None, [], False, "")}
if one.get("자원"):
@@ -81,9 +93,9 @@ def _est_line(ln: dict) -> dict:
# ── 블록 ──────────────────────────────────────────────────────────────
def numbers(saved: dict) -> dict[int, dict]:
"""블록 번 → 산근 블록(엑셀 길 번과 같음)."""
"""블록 번 → 산근 블록(엑셀 길 번과 같음 · 엑셀 읽기기 EST = 시트 「N.」 번)."""
if saved["꼴"] == "EST":
return {i: b for i, b in enumerate(saved["산근"], 1)}
return {int(b.get("번") or i): b for i, b in enumerate(saved["산근"], 1)}
return {int(str(b["코드"])[1:]): b for b in saved["산근"] if str(b["코드"])[1:].isdigit()}
@@ -91,10 +103,10 @@ def emit(saved: dict, no: int, keys: bool = True) -> dict[str, Any]:
"""원파일 산근 블록 하나 → `{로직, 원본}`(원본 `원파일` True · 코드 · 확정 수) · 없는 번 = KeyError."""
blk = numbers(saved)[int(no)]
if saved["꼴"] == "EST":
names = {b["코드"]: i for i, b in enumerate(saved["산근"], 1)}
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 out
return _excel(saved, out)
from M02_MasterTemplete import M02_San_Stmate as stm
nodes = [{"행": ln["행"], "글": ln["글"], **stm.parse_line(ln["글"])} for ln in blk["줄"]]
@@ -106,6 +118,15 @@ def emit(saved: dict, no: int, keys: bool = True) -> dict[str, Any]:
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)
def _excel(saved: dict, out: dict) -> dict[str, Any]:
"""엑셀 읽기기 꼴(812-1) — 원본 `원파일` 거짓 · `엑셀` 참(화면 꼬리표 「엑셀」 · 원코드 = 단산 번)."""
if saved.get("엑셀"):
out["원본"] |= {"원파일": False, "엑셀": True}
out["원본"] |= {"시트": "단가산출"} if saved["꼴"] == "EST" else {}
out["원본"].pop("원코드", None)
return out