feat(810-2): 원파일 구조 줄 → 수식 문서 변환 · 원파일 우선 올림 · 전수 대조

- 새 M02_Doc_NativeEst(EST 산근 블록 → 가져온 로직 · 단가 \x03 · 변수 \x11 · 줄참조 \x0c 표지로 정함 · 변수 이름 그대로 · 분수 그림 공식 = 위아래 줄 칸 · 기계 · 산근 · 견적 세 줄 = 로직 값묶음 한 줄 · 소계 61 = 합 밖 · 율 58 · 62 = 덧줄 · 못 읽은 줄 = 보인 값 얼림 + 참고 표지 · 품셈 조건 글 = 추정 표지)
- 새 M02_Doc_FromNative(원파일 읽기 · 둘 꼴 · 블록 번 = 엑셀 길과 같음 · STmate = 원문 그대로 M02_San_Emit.stmate · 같은 공사 판정 · EST 일위대가 → 실무자료 호표 꼴)
- M02_Sangeun_FromHopyo — 산근 자리 칸 원파일 · 원파일 우선 emit · 안쪽 산근 원파일 코드로 · upload_native(같은 공사 엑셀 HP 에 붙음 · 없으면 새 HP)
- M02_Hopyo_Router 올림 — .estx · .stc 이면 upload_native(한 덩이)
- M02_Doc_Import — 원파일 문서 소계 덧줄 비목 칸 비움 · 수 하나 변수 출처 원파일 · 머리 원파일
- 수식 문서 셈 짝(master_doc · master_doc_calc ↔ common_util_formula_doc · _calc) — 합밖 + 비목 비움 = 금액만(소계 보임) · 그 밖 그대로
- M02_San_Estx 구조 줄 색인(Index) 칸 · 어디서 왔나 카드 「EST · 원파일」
- 전수 대조 — EST 산근 1,849 · STC 371 블록 수식 문서 셈 = 원파일 저장값(STC 계약단가 82 = 낙찰률 버림 뒤) · 표지 없는 틀린 줄 0 · 목록 docs/raw/요구_1104/_목록_810-2_원파일변환.xlsx
- 시험 test_810_2_native_doc(전수 · 꼴 · 문서 셈 거울 · 올림 분기)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-08 20:11:03 +09:00
co-authored by Claude Opus 5.5
parent 7a7f545cc4
commit 53210f9a7a
15 changed files with 1168 additions and 13 deletions
+3 -1
View File
@@ -478,7 +478,9 @@ def check(doc: dict) -> list[dict]:
uses[head] = uses.get(head, []) + need(line.get("식"), head, False, head)
for at, line in enumerate(doc.get("금액") or []):
where = f"금액 {line.get('이름') or at + 1}"
if line.get("비목") not in (*COSTS, "묶음"):
if line.get("비목") not in (*COSTS, "묶음") and not (
line.get("합밖") and not line.get("비목")
):
out.append(_mark("금액 줄", f"{where} 비목 「{line.get('비목')}」"))
for k in ("수량", "단가"):
if k == "단가" or line.get(k) not in (None, ""):
@@ -404,13 +404,15 @@ def _money(run: _Run, doc: dict, rules: dict):
)
price = run.expr(line.get("단가"), where, scope)
kind = line.get("비목")
parts, why = {}, None
parts, why, bare = {}, None, None
if isinstance(qty, Stop) or isinstance(price, Stop):
stopped, why = True, (qty if isinstance(qty, Stop) else price).why
elif isinstance(qty, Blank):
why = qty.why
elif not isinstance(qty, Decimal):
stopped, why = True, run.mark(Stop(f"{where} 수량이 수 아님", "셈 틀림", where)).why
elif qty == 0 and not kind and line.get("합밖"):
bare = Decimal(0)
elif qty == 0:
parts = {k: Decimal(0) for k in (COSTS if kind == "묶음" else (kind,))}
elif isinstance(price, Blank):
@@ -427,6 +429,8 @@ def _money(run: _Run, doc: dict, rules: dict):
)
elif kind in COSTS and isinstance(price, Decimal):
parts = {kind: qty * price}
elif not kind and line.get("합밖") and isinstance(price, Decimal):
bare = qty * price # 비목 없는 합 밖 줄(소계 보임 · 810-2) — 금액만 · 비목 칸 비움
else:
what = (
"묶음 값 — 칸(.계 · .노무비 …) 필요"
@@ -437,6 +441,8 @@ def _money(run: _Run, doc: dict, rules: dict):
rule = line.get("자리") or rules["줄"]
parts = {k: cut(v, rule) for k, v in parts.items()}
amount = sum(parts.values(), Decimal(0)) if parts else None
if bare is not None:
amount = cut(bare, rule)
if i not in skip:
_add(running, parts)
if line.get("기호"):