feat(master_data): 산림품셈 13장 로직 36줄 · 기계 경비 로직 둘(일반 · 굴착기+부착용 집게)

- 로직_산림품셈_13장: 13-2 ~ 13-16 돈 로직 35 · 작업량 로직 1 (13-4-1 그대로)
- 로직_건설품셈_공통8장: 8 기계 시간당 경비 · 8 굴착기+부착용 집게 시간당 경비(잡재료 16% · 집게 손료)
- 계수: 8-4-8 주⑤ 조합 잡재료율 · 13-13-2 비율
- 로직_산림품셈_9장: 인력 10% · 장비 90% 줄 비고 「해석: 전량 인력 품 × 비율」
- master_formula: 범위규칙 「이하 · 칸마다 위 끝만」 · 「마지막 칸 이하 포함」
- 시험 셋 더함(14 통과)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
This commit is contained in:
2026-09-19 16:32:17 +09:00
co-authored by Claude Opus 5
parent 2fe69782b6
commit 83b0083f4c
7 changed files with 1901 additions and 12 deletions
@@ -251,11 +251,18 @@ def _match(table: dict, row: dict, cond: str, want) -> bool:
have = row[cond]
if table.get("조건", {}).get(cond) == "범위":
low, high = have
rule = table.get("범위규칙", "초과·이하").split(" · ")[0]
rule, *more = table.get("범위규칙", "초과·이하").split(" · ")
if rule == "초과·이하":
return (low is None or want > low) and (high is None or want <= high)
if rule == "이상·미만":
return (low is None or want >= low) and (high is None or want < high)
# 「마지막 칸(80~100)은 이하 포함」 — 가장 윗줄만 위 끝 포함
top = max(r[cond][1] for r in table[""] if r.get(cond) and r[cond][1] is not None)
closed = any("이하 포함" in x for x in more) and high == top
return (low is None or want >= low) and (
high is None or want < high or (closed and want == high)
)
if rule == "이하": # 칸마다 위 끝만 — 맞는 줄 가운데 위 끝이 가장 작은 줄(find)
return (low is None or want > low) and (high is None or want <= high)
raise FormulaError(f"모르는 범위규칙 「{table.get('범위규칙')}")
return have == want
@@ -265,6 +272,11 @@ def find(table: dict, conds: dict) -> dict:
if cond not in table.get("조건", {}):
raise FormulaError(f"{table['열쇠']}」 에 없는 조건 「{cond}")
hits = [row for row in table[""] if all(_match(table, row, c, v) for c, v in conds.items())]
if len(hits) > 1 and table.get("범위규칙", "").split(" · ")[0] == "이하":
for c in conds:
ups = [r[c][1] for r in hits if table["조건"].get(c) == "범위" and r.get(c)]
if ups and None not in ups:
hits = [r for r in hits if not r.get(c) or r[c][1] == min(ups)]
if len(hits) != 1:
raise FormulaError(f"{table['열쇠']}{conds} 맞는 줄 {len(hits)}")
return hits[0]