Files
Aislo/resources/tester/helper_810_4_sweep.py
T
eomsangdonandClaude Opus 5.5 1e4ce036c6 feat(812-2): 연결 찾기 확정 = 로직 — 단가 · 끝수 = 참고(확정을 안 막음) · 율 값 = 비율 · 노임 · 자재 종류 · 산출 단가 = 다른 항
- M02_Doc_Match_Note(새) — 율 = 율 줄 셈값 ÷ 대상 줄 금액 합(대상 비목) · 짝 줄 다른 항 대 참고 가르기
- M02_Doc_Match — 같은 줄 = 로직 같은 줄(코드 다름은 짝 대조로) · 연결후보 `참고` 칸 · 참고 적은 조합 · 후보 먼저
- 띠 글 「확정 — 로직 같음 · 참고: 단가 다름 n 줄 · 우리 단가로 셈 ○○ 원」
- test_810_4 기대 고침(단가 차이 · 끝수 = 참고로 옮김) · 전수 도우미 확정 다시 셈 = 우리 금액 · 확정 참고 셈

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

141 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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()