feat(793-3a): 근거 제안 엔진 — 번호 ∩ 값 ∩ 제목 ∩ 기계 상수 → M01 기초 + 옵션 · 품 검산 · 세 단
- M01 값 역색인 · 숫자 정규화 한 벌(N1~N10 · 단위 기준 · % ↔ 소수 · t ↔ kg · 인/양 몫) · M01 지문마다 한 번 · 서버 시작 때 뒤에서 지음
- 값 길(상수 지문 → 로직 · 우연 보정 유의 4) · 합친 길(분석 648-D2 차례 · 후보 2~6 엔진 품 대조) · 검산 · 옵션(B1~B6 → 값 길 V → 고르기 맞댐 → 입력 필요)
- 세 단 확정 · 근접(판 차이 · 현장 값 · 감률 · 할증 배수 · 단위 기준 차이) · 사람(+ 값 약한 1 등) · 없음(M01 보강 목록)
- 길 GET hopyo-basis/{키} · hopyo-basis/{키}/{번} · 파생 저장본 · 올림 때 HP 숨은식(STmate G 식) · 끝내기 때 지움 · 판정 길 그대로
- 보정 자료 = 여덟 파일 상수 1,094 · 시험 test_793_3a 11(459 블록 기초 260 · 옵션 전부 52 · 품 같음 141 · 없음 85)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
"""793-3a 지킴 — 근거 제안 엔진(`M02_San_Basis*`) · 실무 여덟 파일 산근 459 블록.
|
||||
|
||||
판정 수 ≥ 분석 648-D2 합친 길(기초 짚음 260 · 옵션 전부 52 · 품 검산 맞음 141 · 없음 94) ·
|
||||
손 확인 견본(합판거푸집 넷 · 철근 Type-Ⅱ 가공 + 조립 · 레미콘 소량감률 50 · 터파기 #51 ·
|
||||
야면석소운반 GC001140 석재류 · 불량 · 큰돌쌓기 ÷10 근접) · 지어 넣은 값 0 · 보정 자료 = 여덟 파일 ·
|
||||
저장본 길(임시 폴더). 엑셀 = `helper_m02_hopyo_golden.FILES`(읽기만 · 없으면 skip) ·
|
||||
STmate 숨은 G 식 = 올림 길과 같은 `M02_San_Basis_Store.hidden`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from collections import Counter
|
||||
from functools import lru_cache
|
||||
|
||||
import helper_m02_hopyo_golden as golden
|
||||
import pytest
|
||||
|
||||
from M02_MasterTemplete import M02_Hopyo_Judge as judge
|
||||
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
|
||||
from M02_MasterTemplete import M02_San_Basis as sb
|
||||
from M02_MasterTemplete import M02_San_Basis_Const as bc
|
||||
from M02_MasterTemplete import M02_San_Basis_Index as bi
|
||||
from M02_MasterTemplete import M02_San_Basis_Store as store
|
||||
from M02_MasterTemplete import M02_San_Book as san_book
|
||||
|
||||
WAYS = {
|
||||
"B1",
|
||||
"B2",
|
||||
"B3",
|
||||
"B4",
|
||||
"B5",
|
||||
"B6",
|
||||
"V",
|
||||
"검산",
|
||||
"검산(같은 품 여럿)",
|
||||
"근접",
|
||||
"근접(같은 품 여럿)",
|
||||
}
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def books() -> list[tuple]:
|
||||
"""[(파일, HP 꼴 책 + 숨은식)] — 없는 파일이면 skip."""
|
||||
out = []
|
||||
for tag, path in golden.FILES.items():
|
||||
book = golden.read(tag) if path.exists() else None
|
||||
if book is None:
|
||||
pytest.skip(f"실무 엑셀 없음 {tag}")
|
||||
sheets = san_book.sheets(path.name, path.read_bytes()) if book["꼴"] == "STmate" else {}
|
||||
out.append((tag, {**book, "숨은식": store.hidden(book, sheets)}))
|
||||
return out
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def results() -> tuple[dict, float]:
|
||||
sb.ctx()
|
||||
t0 = time.time()
|
||||
got = {}
|
||||
for tag, book in books():
|
||||
for blk in book["단산"]:
|
||||
got[f"{tag}#{blk['번']}"] = store.one(book, int(blk["번"]))
|
||||
return got, time.time() - t0
|
||||
|
||||
|
||||
def test_counts_at_least_d2():
|
||||
got, sec = results()
|
||||
kind = Counter(r["꼴"] for r in got.values())
|
||||
tier = Counter(r["단"] for r in got.values())
|
||||
same = sum(
|
||||
1
|
||||
for r in got.values()
|
||||
if r["꼴"].startswith("확정")
|
||||
and "다름" not in (st := [sb.checked(s) for s in sb.chosen_units(r)])
|
||||
and "같음" in st
|
||||
)
|
||||
print(f"\n블록 {len(got)} · 꼴 {dict(kind)} · 단 {dict(tier)} · 품 같음 {same} · {sec:.1f} 초")
|
||||
assert len(got) == 459
|
||||
assert sum(v for k, v in kind.items() if k.startswith("확정")) >= 260
|
||||
assert kind["확정·전부"] >= 52
|
||||
assert same >= 141
|
||||
assert tier["없음"] <= 94 and kind["없음"] <= 94
|
||||
|
||||
|
||||
def _first(bid: str) -> dict:
|
||||
return results()[0][bid]["근거후보"]
|
||||
|
||||
|
||||
def test_plywood_four_same():
|
||||
for bid in ("북부청임도#24", "북부청사방#66", "거창#41", "동부청영월#1841"):
|
||||
(c,) = _first(bid)
|
||||
assert (c["기초"], c["단"], c["품검산"]) == ("GC000211", "확정", "같음"), bid
|
||||
assert {c["옵션"][k] for k in ("사용횟수", "유형")} == {"4회", "보통"}, bid
|
||||
assert {"높이감률", "구멍땜"} <= set(c["입력필요"]), bid # 글에 없는 감률 = 입력 필요
|
||||
|
||||
|
||||
def test_rebar_type2_cut_and_join():
|
||||
got = {c["기초"]: c for c in _first("북부청임도#72")}
|
||||
assert set(got) == {"GC000206", "GC000207"}
|
||||
assert got["GC000206"]["옵션"]["Type"] == got["GC000207"]["옵션"]["Type"] == "Type-Ⅱ"
|
||||
assert all(c["단"] == "확정" for c in got.values())
|
||||
|
||||
|
||||
def test_remicon_small_50():
|
||||
(c,) = _first("북부청임도#21")
|
||||
assert c["기초"] == "GC000198" and c["단"] == "확정"
|
||||
assert c["옵션"]["소량감률"] == 50 and c["옵션길"]["소량감률"] == "B5"
|
||||
|
||||
|
||||
def test_excavation_51():
|
||||
(c,) = _first("북부청임도#51")
|
||||
assert (c["기초"], c["단"], c["옵션"]["Type"], c["옵션"]["깊이"]) == (
|
||||
"GC000089",
|
||||
"확정",
|
||||
"TypeⅢ",
|
||||
"5m 이하",
|
||||
)
|
||||
|
||||
|
||||
def test_rubble_carry_gc001140():
|
||||
r = results()[0]["동부청영월#1466"]
|
||||
c = r["근거후보"][0]
|
||||
assert r["단"] == "사람" and c["기초"] == "GC001140"
|
||||
assert c["옵션"] == {"재료갈래": "석재류", "운반로": "불량"}
|
||||
assert set(c["옵션길"].values()) == {"V"}
|
||||
|
||||
|
||||
def test_big_stone_near_unit_basis():
|
||||
r = results()[0]["북부청임도#32"]
|
||||
(c,) = r["근거후보"]
|
||||
assert (r["단"], c["기초"], c["단"], c["품검산"]) == ("근접", "GF000226", "근접", "다름")
|
||||
assert {t for t, _ in c["까닭"]} == {"단위 기준 차이"} and "÷10" in c["까닭"][0][1]
|
||||
|
||||
|
||||
def test_no_made_up_values():
|
||||
"""옵션마다 출처 꼬리표 · 입력 필요 · 0 간주 칸은 옵션에 없음 ·
|
||||
없음 단은 근거후보 없이 보강 까닭만."""
|
||||
for bid, r in results()[0].items():
|
||||
for c in r["근거후보"]:
|
||||
assert set(c["옵션"]) <= set(c.get("옵션길") or {}), bid
|
||||
assert set((c.get("옵션길") or {}).values()) <= WAYS, bid
|
||||
assert not set(c["옵션"]) & set(c["입력필요"]), bid
|
||||
assert all(v is not None for v in c["옵션"].values()), bid
|
||||
assert c["단"] in ("확정", "근접", "사람")
|
||||
if r["단"] == "없음":
|
||||
assert r["근거후보"] == [] and r["보강"]["까닭"], bid
|
||||
|
||||
|
||||
def test_corpus_is_eight_files():
|
||||
"""보정 자료 = 여덟 파일 상수(일반 뺌) — 뽑기 규칙을 바꾸면 다시 지어 넣음."""
|
||||
preps = [
|
||||
bc.prep(blk, book["꼴"], (book["숨은식"] or {}).get(str(blk["번"])))
|
||||
for _, book in books()
|
||||
for blk in book["단산"]
|
||||
]
|
||||
shipped = json.loads(bi.CORPUS.read_text(encoding="utf-8"))
|
||||
assert sorted(map(json.dumps, bc.corpus(preps))) == sorted(map(json.dumps, shipped))
|
||||
|
||||
|
||||
def test_store_round_trip(tmp_path):
|
||||
"""임시 폴더 HP → 뒤 셈 저장본 · 목록 · 블록 하나 · 끝내기."""
|
||||
_, book = books()[0]
|
||||
small = {**book, "단산": [b for b in book["단산"] if b["번"] in (21, 51)]}
|
||||
row = hopyo_store.save(tmp_path, "시험.xls", small, {"id": 0, "이름": "시험"})
|
||||
key = row["키"]
|
||||
assert store.solved(tmp_path, key) is None
|
||||
got = store.solve_all(tmp_path, key)
|
||||
assert set(got) == {"21", "51"} and store.solved(tmp_path, key) == json.loads(json.dumps(got))
|
||||
assert store.listing(tmp_path, key)["셈중"] is False
|
||||
assert store.block(tmp_path, key, 51)["근거후보"][0]["기초"] == "GC000089"
|
||||
with pytest.raises(Exception):
|
||||
store.block(tmp_path, key, 999)
|
||||
store.forget(tmp_path, key)
|
||||
assert store.solved(tmp_path, key) is None
|
||||
judge.forget(tmp_path, key)
|
||||
|
||||
|
||||
def test_hidden_g_lines():
|
||||
book = {"꼴": "STmate", "단산": [{"번": "3", "행": [2, 4]}]}
|
||||
rows = [[None] * 7, [None] * 6 + ["_x0007_L=20"], [None] * 6 + ["_x0007_"], [None] * 6 + ["x"]]
|
||||
assert store.hidden(book, {store.SHEET: rows}) == {"3": ["L=20"]}
|
||||
assert store.hidden({"꼴": "엑셀꼴", "단산": []}, {}) == {}
|
||||
Reference in New Issue
Block a user