feat(810-4): 연결 찾기 뜻 근거 · 옵션 값 꼴 · 전수 판정 목록

- 확정 = 줄 · 금액 같음 + 뜻 근거(품셈 절 번호 같음 또는 이름 낱말 겹침) — 값만 같은 남의 기초(구조물되메우기 ↔ 숲가꾸기 · 모르타르 ↔ 기초다짐 등)는 다른 항 「이름 다름」 = 근접
- 찾은 옵션 = 목록 값 꼴 그대로(수 = 수) — SK 입력값 · M01 다시 셈이 같은 값(간벌재직경 「22」 글이면 표 줄 못 찾던 것 고침)
- 전수 도우미 파일마다 열 갈래로 나눠 맡김(큰 파일 한 프로세스가 붙잡지 않게) · 시험 S8104_ROWS 줄 목록 저장
- 전수(M01 수식 문서 1,518 × 원파일 산근 2,220 블록) — 확정 98 · 근접 617 · 없음 1,505 · 확정 다시 셈 다름 0 · 요구 704 파형강관 Φ800 = GF000179 관경 800 근접(단위 환산 × 6 · 기계 규격 · 율 줄) · 목록 docs/raw/요구_1104/_목록_810-4_연결판정.xlsx

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-08 22:54:48 +09:00
co-authored by Claude Opus 5.5
parent 26bf63b726
commit 0b50fcab85
4 changed files with 55 additions and 10 deletions
+33 -4
View File
@@ -472,6 +472,7 @@ def _totals(got: dict) -> dict:
EXPLAINED = (
"이름 다름",
"단가 차이",
"단위 환산",
"기계 규격",
@@ -516,19 +517,21 @@ def _calc(doc: dict, links: Any, given: dict) -> dict | None:
def try_doc(
base: dict, links: Any, imported: dict, got_imp: dict, mine: list[dict], regions: set[str]
) -> dict:
"""M01 문서 하나 — 옵션 풀이 · 가장 나은 조합 판정."""
"""M01 문서 하나 — 옵션 풀이 · 가장 나은 조합 판정(이름 · 절 근거 없으면 「이름 다름」 = 확정 아님)."""
want = {
"수량": {x["수량"] for x in mine if x["수량"] is not None},
"단가": {x["단가"] for x in mine if x["단가"] is not None},
}
dom, ev = domains(base, imported, regions)
dom = narrow(base, links, dom, want, ev)
named = [] if same_name(imported, base) else [_unnamed(imported, base)]
best: dict | None = None
for pick in combos(base, dom) or [{}]:
got = _calc(base, links, _given(base, pick))
if got is None:
continue
off = [_off(base, k, pick[k], ev[k]) for k in ev if k in pick and pick[k] not in ev[k]]
off += named
res = verdict(mine, facts(base, got), got_imp, got, off)
rank = (res["판정"] == SURE, -len(off), res["같음"], res["짝"], -len(res["다른"]))
if best is None or rank > best["순위"]:
@@ -577,17 +580,43 @@ def match(imported: dict, links_imp: Any, index: Index, links: Any) -> dict[str,
}
def _shown(doc: dict, pick: dict) -> dict[str, str]:
"""고른 옵션(기본과 다른 것) — {열쇠: 값 글}."""
def _shown(doc: dict, pick: dict) -> dict[str, Any]:
"""고른 옵션(기본과 다른 것) — {열쇠: 값}(수 = 수 · 선택 목록의 수 값과 같은 꼴 · SK 입력값)."""
out = {}
for v in doc.get("변수") or []:
vid = md.var_id(v)
if vid in pick and mc.plain_text(mc.number(pick[vid])) != mc.plain_text(
mc.number((v.get("연결") or {}).get("값"))
):
out[str(v.get("열쇠") or vid)] = mc.plain_text(mc.number(pick[vid]))
out[str(v.get("열쇠") or vid)] = ml._norm(mc.number(pick[vid]))
return out
_STOP = {"설치", "해체", "인력", "기계", "작업", "운반", "타설", "제작"}
def _grams(texts: list[Any]) -> set[str]:
"""이름 낱말 — 한글 두 글자 묶음 · 영문 · 수 낱말(일반 낱말 뺌)."""
out: set[str] = set()
for t in texts:
t = re.sub(r"\s|및", "", str(t or ""))
for run in re.findall(r"[가-힣]+", t):
out |= {run[i : i + 2] for i in range(len(run) - 1)}
out |= {w.lower() for w in re.findall(r"[A-Za-z]{2,}", t)}
return out - _STOP
def same_name(imported: dict, base: dict) -> bool:
"""뜻 근거 — 품셈 절 번호가 같거나 이름 낱말이 겹침(값만 같은 남의 기초를 확정으로 안 묶음)."""
secs = sections([x.get("글") for x in (imported.get("가져온") or {}).get("추정") or []])
if secs & sections([base.get("출처")]):
return True
return bool(_grams([imported.get("이름")]) & _grams([base.get("이름")]))
def _unnamed(imported: dict, base: dict) -> dict:
return {"항": "이름 다름", "글": f"{imported.get('이름')} ↔ {base.get('이름')}"}
def _plain(d: dict) -> dict:
return {k: (None if v is None else float(v)) for k, v in d.items()}