feat(B09): 불도저 단(gear)별 속도표·보정식 읽어 비다짐(정지) 세움
- 8-2-1 속도표를 단(1·2·3단)까지 전부 담음. 무한궤도 13톤·타이어의 없는 후진 3단은 담지 않음 — 없는 속도로 싸이클이 서면 값이 조용히 틀림. - 「(0.55-0.1)」 같은 보정식을 읽음 (8-2-1 [주]④ 정지작업 −0.1). 범위 표기와 갈라 다룸 — 범위는 여전히 확정값 아님. - 갈래 없는 표(「f | 1/1.3」)도 읽음. - 결과: FP-09-17-02 비다짐(정지) 1,410.1 원/㎥ 섬 (3단 75/98, E=0.45, 19톤). - 시험 4건 추가, 228건 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,14 +36,47 @@ from B09_Estimation.B09_Estimation_MachineProductivity import (
|
||||
_ZERO = Decimal(0)
|
||||
|
||||
|
||||
_DOZER_SPEEDS_2ND_GEAR = {
|
||||
Decimal("4"): (Decimal(57), Decimal(85)),
|
||||
Decimal("7"): (Decimal(67), Decimal(78)),
|
||||
Decimal("10"): (Decimal(64), Decimal(75)),
|
||||
Decimal("12"): (Decimal(55), Decimal(70)),
|
||||
Decimal("13"): (Decimal(55), Decimal(70)),
|
||||
Decimal("19"): (Decimal(55), Decimal(70)),
|
||||
Decimal("32"): (Decimal(52), Decimal(58)),
|
||||
#: 전진·후진 속도 (m/분) — 8-2-1 2.가·나. **단(gear)마다 다르다.**
|
||||
#: {규격(ton): {단: (전진, 후진)}}
|
||||
#: ⚠ 무한궤도 4·13 톤과 타이어 전 규격은 **후진 3단이 표에 없다**(「-」) — 그 단은
|
||||
#: 아예 안 담는다. 담으면 없는 속도로 싸이클이 서서 값이 조용히 틀린다.
|
||||
#: ⚠ **단은 작업이 정한다** (8-2-1 2.가 [주]) — 굴착·굴착운반 1단, 흐트러진 토사운반
|
||||
#: 2단, 평탄 정지·전압 3단. 그래서 임도 표가 「55m/분(2단)」처럼 단을 적어 온다.
|
||||
_DOZER_SPEEDS = {
|
||||
"무한궤도": {
|
||||
Decimal("4"): {1: (Decimal(40), Decimal(63)), 2: (Decimal(57), Decimal(85))},
|
||||
Decimal("7"): {
|
||||
1: (Decimal(43), Decimal(53)),
|
||||
2: (Decimal(67), Decimal(78)),
|
||||
3: (Decimal(92), Decimal(107)),
|
||||
},
|
||||
Decimal("10"): {
|
||||
1: (Decimal(42), Decimal(50)),
|
||||
2: (Decimal(64), Decimal(75)),
|
||||
3: (Decimal(88), Decimal(105)),
|
||||
},
|
||||
Decimal("12"): {
|
||||
1: (Decimal(40), Decimal(48)),
|
||||
2: (Decimal(55), Decimal(70)),
|
||||
3: (Decimal(75), Decimal(100)),
|
||||
},
|
||||
Decimal("13"): {1: (Decimal(40), Decimal(48)), 2: (Decimal(55), Decimal(70))},
|
||||
Decimal("19"): {
|
||||
1: (Decimal(40), Decimal(46)),
|
||||
2: (Decimal(55), Decimal(70)),
|
||||
3: (Decimal(75), Decimal(98)),
|
||||
},
|
||||
Decimal("32"): {
|
||||
1: (Decimal(40), Decimal(43)),
|
||||
2: (Decimal(52), Decimal(58)),
|
||||
3: (Decimal(70), Decimal(78)),
|
||||
},
|
||||
},
|
||||
"타이어": {
|
||||
Decimal("15"): {1: (Decimal(83), Decimal(92)), 2: (Decimal(200), Decimal(125))},
|
||||
Decimal("28"): {1: (Decimal(92), Decimal(92)), 2: (Decimal(200), Decimal(200))},
|
||||
Decimal("33"): {1: (Decimal(92), Decimal(110)), 2: (Decimal(210), Decimal(250))},
|
||||
},
|
||||
}
|
||||
#: 기어 변속시간 (분) — 8-2-1 「t: 기어 변속시간(0.25분)」
|
||||
_DOZER_GEAR_SHIFT_MIN = Decimal("0.25")
|
||||
@@ -100,9 +133,11 @@ def dozer_hourly_output(factors: DozerFactors) -> Decimal:
|
||||
return output
|
||||
|
||||
|
||||
def dozer_speeds(tonnage: Decimal) -> tuple[Decimal, Decimal] | None:
|
||||
"""그 규격의 전진·후진 속도(2단). 표에 없는 규격이면 `None` — 지어내지 않는다."""
|
||||
return _DOZER_SPEEDS_2ND_GEAR.get(tonnage)
|
||||
def dozer_speeds(
|
||||
tonnage: Decimal, gear: int = 2, track: str = "무한궤도"
|
||||
) -> tuple[Decimal, Decimal] | None:
|
||||
"""그 규격·그 단의 전진·후진 속도. 표에 없으면 `None` — 지어내지 않는다."""
|
||||
return _DOZER_SPEEDS.get(track, {}).get(tonnage, {}).get(gear)
|
||||
|
||||
|
||||
#: 삽날 용량 q゚(㎥) — 8-2-1 1.가. **규격을 되짚는 열쇠**로도 쓴다.
|
||||
@@ -120,13 +155,6 @@ _DOZER_BLADE_M3 = {
|
||||
("타이어", Decimal("33")): Decimal("5.7"),
|
||||
}
|
||||
|
||||
#: 타이어형 2단 속도 — 8-2-1 2.나.
|
||||
_DOZER_TIRE_SPEEDS_2ND_GEAR = {
|
||||
Decimal("15"): (Decimal(200), Decimal(125)),
|
||||
Decimal("28"): (Decimal(200), Decimal(200)),
|
||||
Decimal("33"): (Decimal(210), Decimal(250)),
|
||||
}
|
||||
|
||||
#: 습지·초습지 갈래는 카탈로그 이름이 따로다 — 「습지 불도저」.
|
||||
_DOZER_WET_TONS = (Decimal("4"), Decimal("13"))
|
||||
|
||||
@@ -149,11 +177,6 @@ _DOZER_GROUP_KEYS = ("E", "f")
|
||||
_RE_GEAR = re.compile(r"(\d+)\s*단")
|
||||
|
||||
|
||||
def dozer_tire_speeds(tonnage: Decimal) -> tuple[Decimal, Decimal] | None:
|
||||
"""타이어형 전진·후진 속도(2단). 표에 없으면 `None`."""
|
||||
return _DOZER_TIRE_SPEEDS_2ND_GEAR.get(tonnage)
|
||||
|
||||
|
||||
def resolve_dozer(
|
||||
blade_m3: Decimal,
|
||||
forward: Decimal,
|
||||
@@ -167,16 +190,13 @@ def resolve_dozer(
|
||||
|
||||
q゚ 3.2㎥ + 55/70 m/분(2단) → 불도저(무한궤도) 19 톤
|
||||
|
||||
⚠ **2단만 되짚는다.** 1·3·4단 속도표는 여기 안 들고 있어, 다른 단이면 `None` 이다.
|
||||
⚠ **단(gear)까지 맞아야 한다** — 같은 삽날이라도 단이 다르면 다른 규격을 가리킨다.
|
||||
"""
|
||||
if gear != 2:
|
||||
return None
|
||||
candidates = []
|
||||
for (track, tonnage), blade in _DOZER_BLADE_M3.items():
|
||||
if blade != blade_m3:
|
||||
continue
|
||||
speeds = dozer_speeds(tonnage) if track == "무한궤도" else dozer_tire_speeds(tonnage)
|
||||
if speeds == (forward, reverse):
|
||||
if dozer_speeds(tonnage, gear, track) == (forward, reverse):
|
||||
candidates.append((track, tonnage))
|
||||
if len(candidates) != 1:
|
||||
return None
|
||||
@@ -235,7 +255,11 @@ def extract_dozer_factors(
|
||||
current = head
|
||||
label = cells[1] if len(cells) > 1 else ""
|
||||
value = _first_measure(cells[2:])
|
||||
if label and value is not None:
|
||||
if value is None:
|
||||
# 갈래가 없는 표 — 「f | 1/1.3」처럼 값이 바로 붙는다. 갈래 이름은 빈 문자열.
|
||||
value = _measure_with_adjustment(label)
|
||||
label = ""
|
||||
if value is not None:
|
||||
groups[head][_normalize_label(label)] = value
|
||||
elif current is not None:
|
||||
# 딸린 줄 — 「파쇄암 | 1/1.35」. 값이 없으면 갈래 줄이 아니다.
|
||||
@@ -295,6 +319,23 @@ def extract_dozer_factors(
|
||||
)
|
||||
|
||||
|
||||
_RE_ADJUSTED = re.compile(r"^\(?\s*(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*\)?$")
|
||||
|
||||
|
||||
def _measure_with_adjustment(cell: str) -> Decimal | None:
|
||||
"""계수 한 칸. **「(0.55-0.1)」 같은 보정식도 읽는다.**
|
||||
|
||||
8-2-1 [주]④⑤ 가 「정지작업을 겸하면 0.1 을, 터파기는 0.05 를 뺀 값」이라 적어,
|
||||
임도 표가 뺄셈을 그대로 적어 온다(비다듬기·정지 9-17-2). ⚠ **범위(「0.55∼0.45」)와
|
||||
헷갈리면 안 된다** — 범위는 확정값이 아니라 `parse_measure` 가 `None` 을 낸다.
|
||||
여기서 읽는 것은 **하이픈 뺄셈 한 가지**뿐이다.
|
||||
"""
|
||||
found = _RE_ADJUSTED.match(" ".join(str(cell).split()))
|
||||
if found:
|
||||
return Decimal(found.group(1)) - Decimal(found.group(2))
|
||||
return parse_measure(cell)
|
||||
|
||||
|
||||
def _normalize_label(text: str) -> str:
|
||||
return "".join(str(text).split())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user