Files
Pier_v1/Code/교각모델링앱/engine/recommend.py
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환
- Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용
- pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관)
- worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md)
- D:\Rhino-MCP 경로 참조를 새 위치로 치환

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:20:18 +09:00

97 lines
4.2 KiB
Python

"""Template recommendation from a drawing set (S02, 2026-10-15): estimate the fit questions from the drawing texts and
rank the protected templates by the number of matching answers.
Only questions the texts can answer are estimated; the rest stay None ("판단 불가") and are answered by the user.
Estimates are proposals with their evidence, never confirmations. Rules were checked on 9 drawing sets only
(Ungok P1-P4, Ulsan P1-P2, Ramp-A P1, Okseong P1, Saejae-1 P1); weak ones are marked.
"""
import re
Q_PILE, Q_SOFFIT, Q_BEARING = "foundation", "soffit", "bearings"
QUESTIONS = [
(Q_PILE, "기초 형식(말뚝 유무)"),
(Q_SOFFIT, "코핑 하면 형상"),
(Q_BEARING, "받침 개수"),
]
R_RE = re.compile(r"(?:^|[\s(])R\s*=?\s*(\d[\d,]*(?:\.\d+)?)\s*$")
def profile(params):
"""Answers a template's params.json stands for."""
piles = "piles" in params or "pile" in params
cp = params.get("coping", {})
if "soffit_radius" in cp:
soffit = "곡선"
elif cp.get("form") == "y_shaft":
soffit = "나팔형"
else:
soffit = "직선 경사"
found = "말뚝만" if ("footing" not in params and piles) else ("기초판+말뚝" if piles else "기초판만")
return {Q_PILE: found, Q_SOFFIT: soffit, Q_BEARING: len(params.get("bearing_seats", {}).get("bearings", []))}
def _r_mm(text):
m = R_RE.search(text.strip())
if not m:
return None
s = m.group(1)
return round(float(s) * 1000) if re.fullmatch(r"\d+\.\d{3}", s) else round(float(s.replace(",", "")))
def _where(t):
return f"{t.get('dwg', '?')} {t.get('layer', '')} ({t['x']:.0f}, {t['y']:.0f}) '{t['text'][:40]}'"
def count_bearings(rows):
"""Largest run 1..n of integer first cells under the ELEVATION TABLE title."""
n = 0
for r in rows:
c = r["cells"][0]["text"].strip() if r.get("cells") else ""
if c.isdigit() and int(c) == n + 1:
n += 1
return n
def estimate(texts, table_rows=None):
"""{question: (answer or None, evidence str, confidence)}; table_rows = rows of the ELEVATION TABLE (reader.table_candidates)."""
out = {}
piles = [t for t in texts if "말뚝" in t["text"]]
cast = [t for t in piles if "현장타설말뚝" in t["text"]]
if cast:
out[Q_PILE] = ("말뚝만", "현장타설말뚝 문자: " + _where(cast[0]), "약함")
elif piles:
out[Q_PILE] = ("기초판+말뚝", f"'말뚝' 문자 {len(piles)}개: " + _where(piles[0]), "보통")
else:
out[Q_PILE] = ("기초판만", "'말뚝' 문자 없음", "보통")
rs = [(v, t) for t in texts if len(t["text"]) < 30 and (v := _r_mm(t["text"])) is not None]
big = [(v, t) for v, t in rs if v >= 10000]
mid = [(v, t) for v, t in rs if 1000 < v < 10000]
if big:
out[Q_SOFFIT] = ("곡선", f"R {big[0][0]} mm: " + _where(big[0][1]), "보통")
elif mid:
out[Q_SOFFIT] = ("나팔형", f"R {mid[0][0]} mm: " + _where(mid[0][1]), "약함")
else:
small = f"(R {rs[0][0]} mm는 평면 모서리 등으로 보고 제외)" if rs else ""
out[Q_SOFFIT] = ("직선 경사", "정면도 R 문자 없음 " + small, "약함")
n = count_bearings(table_rows) if table_rows else 0
out[Q_BEARING] = (n, f"ELEVATION TABLE 행 {n}개", "보통") if n else (None, "ELEVATION TABLE을 찾지 못함", "")
return out
def rank(est, profiles):
"""Templates ordered by the number of estimated answers they match: [{name, matched, asked, hits{q: bool}}]."""
asked = [q for q, _ in QUESTIONS if est.get(q, (None,))[0] is not None]
rows = []
for name, prof in profiles.items():
hits = {q: prof[q] == est[q][0] for q in asked}
rows.append({"name": name, "matched": sum(hits.values()), "asked": len(asked), "hits": hits, "profile": prof})
rows.sort(key=lambda r: -r["matched"])
return rows
def mismatches(answers, prof):
"""Questions whose answer differs from the template (strings compared; 'rebar_*' = '아니오' counts); empty = same form."""
out = [label for q, label in QUESTIONS if str(prof[q]) != str(answers.get(q, "")).strip()]
out += [label for key, label in (("rebar_shape", "철근 형상 종류"), ("rebar_method", "철근 생성 방식")) if answers.get(key) == "아니오"]
return out