Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_2

This commit is contained in:
2026-09-19 16:21:08 +09:00
4 changed files with 1756 additions and 13 deletions
@@ -20,6 +20,7 @@ _NAME = re.compile(r"[\w가-힣]+")
_NUMBER = re.compile(r"\d+(?:\.\d+)?")
_HOLE = re.compile(r"\{([^{}]+)\}")
_OPS = ("<=", ">=", "==", "!=", "<", ">")
_ARITH = re.compile(r"[\d.\s+\-*/()]*\d[\d.\s+\-*/()]*") # 분수·식 값 칸(「1/1.30」)
class FormulaError(ValueError):
@@ -250,9 +251,12 @@ def _match(table: dict, row: dict, cond: str, want) -> bool:
have = row[cond]
if table.get("조건", {}).get(cond) == "범위":
low, high = have
if table.get("범위규칙", "초과·이하") != "초과·이하":
raise FormulaError(f"모르는 범위규칙 「{table.get('범위규칙')}")
return (low is None or want > low) and (high is None or want <= high)
rule = table.get("범위규칙", "초과·이하").split(" · ")[0]
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)
raise FormulaError(f"모르는 범위규칙 「{table.get('범위규칙')}")
return have == want
@@ -286,6 +290,8 @@ def evaluate(node, env: dict, master: Master, depth: int = 0):
value = base[node[2]]
if isinstance(value, list):
raise FormulaError(f"칸 「{node[2]}」 값이 범위 — 입력으로 받아야 함")
if isinstance(value, str) and _ARITH.fullmatch(value):
return evaluate(parse(value), env, master, depth)
return value
if kind == "neg":
return -evaluate(node[1], env, master, depth)
@@ -16,10 +16,7 @@
{ "구분": "E", "적용": 0.35 },
{ "구분": "㎝(sec)", "적용": 20, "적용원문": "20(135°)" }
],
"주": [
"① 장비는 무한궤도 굴착기(0.7㎥)를 적용한다.",
"② Q=3600×q0×K×f×E/㎝= ㎥/시간"
]
"주": ["① 장비는 무한궤도 굴착기(0.7㎥)를 적용한다.", "② Q=3600×q0×K×f×E/㎝= ㎥/시간"]
},
{
"열쇠": "9-16-1 주①",
@@ -102,6 +99,142 @@
{ "구분": "V", "적용": 15, "단위": "km/시간" }
],
"주": []
},
{
"열쇠": "9-3-2 주①",
"이름": "토사깍기 장비",
"기준": "",
"출처": "산림품셈 9-3-2 주①",
"조건": { "구분": "고르기" },
"값칸": { "적용": "㎥" },
"줄": [{ "구분": "무한궤도 굴착기", "적용": 0.7 }],
"주": []
},
{
"열쇠": "9-3-2 주④",
"이름": "토사깍기 체적환산계수",
"기준": "",
"출처": "산림품셈 9-3-2 주④",
"조건": { "구분": "고르기" },
"값칸": { "적용": "" },
"줄": [{ "구분": "f", "적용": 0.77 }],
"주": ["④ 체적환산계수는 f=1/L=1/1.30=0.77 적용한다."]
},
{
"열쇠": "9-4-2 주①",
"이름": "암절취 집토 장비",
"기준": "",
"출처": "산림품셈 9-4-2 주①",
"조건": { "구분": "고르기" },
"값칸": { "적용": "㎥" },
"줄": [{ "구분": "무한궤도 굴착기", "적용": 0.7 }],
"주": []
},
{
"열쇠": "9-5-3 주①",
"이름": "발파암 집토 장비",
"기준": "",
"출처": "산림품셈 9-5-3 주①",
"조건": { "구분": "고르기" },
"값칸": { "적용": "㎥" },
"줄": [{ "구분": "무한궤도 굴착기", "적용": 0.7 }],
"주": []
},
{
"열쇠": "9-7-1 주①",
"이름": "무근콘크리트 깨기 장비",
"기준": "",
"출처": "산림품셈 9-7-1 주①",
"조건": { "구분": "고르기" },
"값칸": { "적용": "㎥" },
"줄": [{ "구분": "무한궤도 굴착기", "적용": 0.7 }],
"주": []
},
{
"열쇠": "9-12-1 비율",
"이름": "측구터파기 토사 인력·장비 비율",
"기준": "",
"출처": "산림품셈 9-12-1",
"조건": { "구분": "고르기" },
"값칸": { "적용": "단위 칸 따름", "단위": "글" },
"줄": [
{ "구분": "인력", "적용": 10, "단위": "%" },
{ "구분": "장비", "적용": 90, "단위": "%" },
{ "구분": "굴착기 버킷", "적용": 0.7, "단위": "㎥" }
],
"주": ["표 머리 「인력(10%)」 · 「장비(90%)」 · 「유압식백호우(무한궤도,0.7㎥)」"]
},
{
"열쇠": "9-13-1 비율",
"이름": "구조물터파기 육상토사 인력·장비 비율",
"기준": "",
"출처": "산림품셈 9-13-1",
"조건": { "구분": "고르기" },
"값칸": { "적용": "단위 칸 따름", "단위": "글" },
"줄": [
{ "구분": "인력", "적용": 10, "단위": "%" },
{ "구분": "장비", "적용": 90, "단위": "%" },
{ "구분": "굴착기 버킷", "적용": 0.7, "단위": "㎥" }
],
"주": ["표 머리 「인력(10%)」 · 「장비(90%)」 · 「유압식백호우(무한궤도,0.7㎥)」"]
},
{
"열쇠": "9-13-4 비율",
"이름": "구조물터파기 용수토사 인력·장비 비율",
"기준": "",
"출처": "산림품셈 9-13-4",
"조건": { "구분": "고르기" },
"값칸": { "적용": "단위 칸 따름", "단위": "글" },
"줄": [
{ "구분": "인력", "적용": 10, "단위": "%" },
{ "구분": "장비", "적용": 90, "단위": "%" },
{ "구분": "굴착기 버킷", "적용": 0.7, "단위": "㎥" }
],
"주": ["표 머리 「인력(10%)」 · 「장비(90%)」 · 「유압식백호우(무한궤도,0.7㎥)」"]
},
{
"열쇠": "9-14-1 비율",
"이름": "되메우기 인력·장비 비율",
"기준": "",
"출처": "산림품셈 9-14-1",
"조건": { "구분": "고르기" },
"값칸": { "적용": "단위 칸 따름", "단위": "글" },
"줄": [
{ "구분": "인력", "적용": 10, "단위": "%" },
{ "구분": "장비", "적용": 90, "단위": "%" },
{ "구분": "굴착기 버킷", "적용": 0.7, "단위": "㎥" }
],
"주": ["표 머리 「인력(10%)」 · 「장비(90%)」 · 「유압식백호우(무한궤도,0.7㎥)」"]
},
{
"열쇠": "9-15-1 주①",
"이름": "표토제거 답구간 장비",
"기준": "",
"출처": "산림품셈 9-15-1 주①",
"조건": { "구분": "고르기" },
"값칸": { "적용": "㎥" },
"줄": [{ "구분": "무한궤도 굴착기", "적용": 0.7 }],
"주": []
},
{
"열쇠": "9-18 적용장비",
"이름": "층따기 장비",
"기준": "",
"출처": "산림품셈 9-18",
"조건": { "구분": "고르기" },
"값칸": { "적용": "㎥" },
"줄": [{ "구분": "무한궤도 굴착기", "적용": 0.7 }],
"주": []
},
{
"열쇠": "9-22 주①",
"이름": "섞기 장비",
"기준": "",
"출처": "산림품셈 9-22 주①",
"조건": { "구분": "고르기" },
"값칸": { "적용": "㎥" },
"줄": [{ "구분": "무한궤도 굴착기", "적용": 0.7 }],
"주": []
}
]
}
File diff suppressed because it is too large Load Diff
+17 -1
View File
@@ -83,6 +83,20 @@ def test_노체포설(whole):
]
def test_범위규칙_이상_미만(whole):
"""9-2 횡단경사 「60% 미만 · 60% 이상」 — 60 은 윗줄."""
qty = [
mf.run(whole, "산림품셈:9-2 노선 굴진 보조원", {"횡단경사": x})[""][0]["수량"]
for x in (59.9, 60)
]
assert qty == [5, 10]
def test_분수_글자_값(whole):
"""9-4-2 f 원문 「1/1.40」 — 3600×0.7×0.55×(1/1.40)×0.35/20."""
assert mf.run(whole, "산림품셈:9-4-2 암절취 집토 작업량", {})["결과"] == Decimal("17.325")
def test_범위_입력은_막음(whole):
with pytest.raises(mf.FormulaError):
mf.run(
@@ -96,7 +110,9 @@ def test_견본_파일_검사_통과(whole):
files = cm.load(SAMPLES)
assert len(files) == len(SAMPLES)
assert [x for name, data in files.items() for x in cm.check_form(name, data)] == []
assert cm.check_body(files) == []
# 9장 계수 표는 같은 절의 소요량 표와 함께 대조 — 소요량 파일 자체의 알림은 그 창 몫
with_09 = files | cm.load(["소요량_산림품셈_09장.json"])
assert [x for x in cm.check_body(with_09) if not x.startswith("소요량_산림품셈_09장")] == []
assert cm.check_logics(files, whole) == []