- M02_Doc_Match_Note(새) — 율 = 율 줄 셈값 ÷ 대상 줄 금액 합(대상 비목) · 짝 줄 다른 항 대 참고 가르기 - M02_Doc_Match — 같은 줄 = 로직 같은 줄(코드 다름은 짝 대조로) · 연결후보 `참고` 칸 · 참고 적은 조합 · 후보 먼저 - 띠 글 「확정 — 로직 같음 · 참고: 단가 다름 n 줄 · 우리 단가로 셈 ○○ 원」 - test_810_4 기대 고침(단가 차이 · 끝수 = 참고로 옮김) · 전수 도우미 확정 다시 셈 = 우리 금액 · 확정 참고 셈 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
5.6 KiB
Python
141 lines
5.6 KiB
Python
"""810-4 전수 도우미 — 원파일 산근 블록 전부(EST 6 · STC 7) × M01 수식 문서 1,518 연결 찾기.
|
||
|
||
파일마다 프로세스 · 안쪽 산근(형제)은 같은 파일 블록을 우리 단가로 먼저 셈해 묶음 값으로 넣음(저장 층의
|
||
형제 SK 읽기와 같은 값) · 확정은 M01 문서를 찾은 옵션(입력값 꼴)으로 다시 셈해 금액 대조.
|
||
마지막 줄 = JSON {블록, 판정, 근접항, 확정참고, 확정참고블록, 오류, 확정다시셈다름} · `--rows <pkl>` = 줄 목록 저장(목록 xlsx 용).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import collections
|
||
import json
|
||
import os
|
||
import pickle
|
||
import sys
|
||
import time
|
||
from multiprocessing import Pool
|
||
from pathlib import Path
|
||
|
||
HERE = Path(__file__).resolve().parent
|
||
ROOT = HERE.parents[1]
|
||
for p in (ROOT, HERE):
|
||
if str(p) not in sys.path:
|
||
sys.path.insert(0, str(p))
|
||
|
||
|
||
def _ctx():
|
||
import test_810_2_native_doc as t
|
||
|
||
from M02_MasterTemplete import M02_Doc_Import as dim
|
||
from M02_MasterTemplete import M02_Doc_Match as dm
|
||
|
||
whole = t.whole()
|
||
docs = dm.ml.load_all()
|
||
return t, dim, dm, whole, docs, dm.Index(docs), dm.Memo(dm.ml.Links(whole, docs))
|
||
|
||
|
||
def file_docs(t, dim, dm, whole, kind: str, tag: str) -> dict[int, dict]:
|
||
"""파일 블록 → 가져온 식 문서(형제 = 우리 단가 묶음)."""
|
||
saved = t.native(kind, tag)
|
||
nums = t.fn.numbers(saved)
|
||
by_code = {str(b.get("코드")): n for n, b in nums.items()}
|
||
links = dim.Links(whole, "우리")
|
||
made: dict[int, dict] = {}
|
||
ours: dict[int, dict | None] = {}
|
||
|
||
def build(no: int, trail: tuple = ()) -> None:
|
||
if no in made:
|
||
return
|
||
out = t.fn.emit(saved, no)
|
||
doc = dim.derive(t.sk_of(out), None, whole)
|
||
for h in out["로직"]["호표"]:
|
||
res = h.get("자원") or {}
|
||
n = by_code.get(str(res.get("코드")))
|
||
if res.get("갈래") != "단가산출" or n is None or n in trail or n == no:
|
||
continue
|
||
build(n, (*trail, no))
|
||
got = ours.get(n)
|
||
line = next(
|
||
(x for x in doc["금액"] if x["이름"] == h["이름"] and x.get("비목") == "묶음"), None
|
||
) # noqa: E501
|
||
if not line or not got:
|
||
continue
|
||
for v in doc["변수"]:
|
||
if dm.md.var_id(v) == line["단가"] and (v.get("연결") or {}).get("갈래") == "숫자":
|
||
v["연결"] = {"갈래": "숫자", "값": {k: str(got[k]) for k in dm.md.COSTS}}
|
||
made[no] = doc
|
||
ours[no] = dm._calc(doc, links, {})
|
||
|
||
for no in nums:
|
||
build(no)
|
||
return made
|
||
|
||
|
||
PARTS = 10 # 파일마다 블록을 나눠 맡김(큰 파일 한 프로세스가 끝을 붙잡지 않게)
|
||
|
||
|
||
def sweep(arg: tuple[str, str, int]) -> list[dict]:
|
||
kind, tag, part = arg
|
||
t, dim, dm, whole, docs, index, links = _ctx()
|
||
rows = []
|
||
try:
|
||
made = file_docs(t, dim, dm, whole, kind, tag)
|
||
except Exception as e: # noqa: BLE001 — 원파일 없음(다른 PC) = 빈 줄
|
||
if part:
|
||
return []
|
||
return [{"파일": tag, "오류": repr(e)[:200], "판정": "오류", "없는파일": "없음" in repr(e)}]
|
||
for no, doc in made.items():
|
||
if no % PARTS != part:
|
||
continue
|
||
start = time.time()
|
||
try:
|
||
res = dm.match(doc, dim.Links(whole, "우리"), index, links)
|
||
except Exception as e: # noqa: BLE001
|
||
res = {"판정": "오류", "까닭": repr(e)[:200]}
|
||
row = {"파일": tag, "꼴": kind, "번": no, "블록": doc["이름"], "단위": doc["결과단위"]}
|
||
row |= {"규격": (doc.get("가져온") or {}).get("규격"), **res}
|
||
if res.get("판정") == dm.SURE:
|
||
again = dm._calc(docs[res["키"]], dm.ml.Links(whole, docs), dict(res["옵션"]))
|
||
money = res["금액"] # 다시 셈 = 우리 · 참고 없으면 가져온 = 우리(812-2)
|
||
row["다시셈같음"] = (
|
||
again is not None
|
||
and dm._plain(dm._totals(again)) == money["우리"]
|
||
and (bool(res.get("참고")) or money["우리"] == money["가져온"])
|
||
)
|
||
row["초"] = round(time.time() - start, 2)
|
||
rows.append(row)
|
||
return rows
|
||
|
||
|
||
def main() -> None:
|
||
import test_810_1_estx as e
|
||
import test_810_1_stc as c
|
||
|
||
tags = [("EST", k) for k in e.FILES] + [("STC", k) for k in c.FILES]
|
||
tags = [(kind, tag, part) for kind, tag in tags for part in range(PARTS)]
|
||
with Pool(min(len(tags), max(2, (os.cpu_count() or 4) - 2))) as pool:
|
||
rows = [r for part in pool.map(sweep, tags) for r in part]
|
||
if "--rows" in sys.argv:
|
||
pickle.dump(rows, open(sys.argv[sys.argv.index("--rows") + 1], "wb"))
|
||
real = [r for r in rows if not r.get("없는파일")]
|
||
out = {
|
||
"블록": sum(1 for r in real if r.get("판정") != "오류" or "번" in r),
|
||
"판정": dict(collections.Counter(r["판정"] for r in real)),
|
||
"근접항": dict(
|
||
collections.Counter(d["항"] for r in real if r["판정"] == "근접" for d in r["다른"])
|
||
), # noqa: E501
|
||
"확정참고": dict(
|
||
collections.Counter(
|
||
d["항"] for r in real if r["판정"] == "확정" for d in r.get("참고") or []
|
||
)
|
||
),
|
||
"확정참고블록": sum(1 for r in real if r["판정"] == "확정" and r.get("참고")),
|
||
"오류": sum(1 for r in real if r["판정"] == "오류"),
|
||
"확정다시셈다름": sum(1 for r in real if r.get("다시셈같음") is False),
|
||
}
|
||
print(json.dumps(out, ensure_ascii=False))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|