feat(B09): 불도저 시공능력 공식 — 굴착기와 다른 식
운반 계열 착수. 품셈 원문 대조 결과 **셋이 서로 다른 식**임
```
굴착기 Q = 3600 ÷ Cm(초) × q · K · f · E (8-1-4, 이미 구현)
불도저 Q = 60 ÷ cm(분) × (q₀ × e) · f · E (8-2-1) ← 이번
cm = L/V1 + L/V2 + t, t = 기어 변속 0.25분
덤프 Q = 60 ÷ cm × q · f · E, q = T·L/γt (8-2-8) ← 다음
```
- ⚠ **밑수가 60(분)** 임 — 굴착기(3600초)와 섞으면 **60배 어긋남**. 주석에 못 박음
- ⚠ 속도가 「단」에 따라 다름(8-2-1 [주]) — 굴착·운반 1단 · **흐트러진 토사운반 2단** ·
평탄 정지 3단. **임도 운반은 [주]② 가 가장 가까워 2단 잠정 채택**(TODO 미결)
- 표에 없는 규격은 **속도를 지어내지 않음**(짝 시험)
검산: 19톤·L=20m·V1 55·V2 70 → cm 0.8994분 · **Q 86.709㎥/hr** · 1㎥당 0.01153hr
검증: pytest 218 통과(신규 3)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -299,3 +299,83 @@ _RE_RATIO = re.compile(r"[((]\s*(\d+(?:\.\d+)?)\s*%\s*[))]")
|
||||
def _ratio_of(cell: str) -> Decimal | None:
|
||||
found = _RE_RATIO.search(str(cell))
|
||||
return Decimal(found.group(1)) if found else None
|
||||
|
||||
|
||||
# ── 불도저 — 굴착기와 **공식이 다르다** (건설품셈 8-2-1) ──────────────────────
|
||||
#
|
||||
# Q = 60 · q · f · E ÷ cm q = q₀ × e
|
||||
# cm = L/V1 + L/V2 + t t = 기어 변속시간 0.25 분
|
||||
#
|
||||
# q₀ 거리를 고려하지 않은 삽날 용량(㎥) · e 운반거리계수 · L 운반거리(m)
|
||||
# V1 전진속도(m/분) · V2 후진속도(m/분)
|
||||
#
|
||||
# ⚠ **굴착기 식(3600 ÷ Cm 초)과 섞지 말 것** — 불도저 cm 은 **분**이고 왕복 주행으로
|
||||
# 만든다. 그래서 밑수가 60 이다. 섞으면 60배 어긋난다.
|
||||
#
|
||||
# ⚠ **속도는 「단」에 따라 다르다** (8-2-1 [주]) — 굴착·운반은 전진 1단·후진 1단,
|
||||
# 흐트러진 토사운반은 2단, 평탄 정지는 3단. **임도 운반은 [주]② 「흐트러진 상태의
|
||||
# 토사운반」이 가장 가까워 2단을 잠정 채택**한다. ⚠ TODO(미결) 사용자 확정 대기.
|
||||
_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)),
|
||||
}
|
||||
#: 기어 변속시간 (분) — 8-2-1 「t: 기어 변속시간(0.25분)」
|
||||
_DOZER_GEAR_SHIFT_MIN = Decimal("0.25")
|
||||
_MINUTES_PER_HOUR = Decimal(60)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DozerFactors:
|
||||
"""불도저 시공능력 계수 한 벌 (건설품셈 8-2-1)."""
|
||||
|
||||
work_item_code: str
|
||||
blade_capacity_m3: Decimal # q₀
|
||||
distance_factor: Decimal # e
|
||||
volume_factor: Decimal # f
|
||||
efficiency: Decimal # E
|
||||
haul_distance_m: Decimal # L
|
||||
forward_speed_m_min: Decimal # V1
|
||||
reverse_speed_m_min: Decimal # V2
|
||||
|
||||
@property
|
||||
def cycle_minutes(self) -> Decimal:
|
||||
"""cm = L/V1 + L/V2 + t — **분**이다."""
|
||||
return (
|
||||
self.haul_distance_m / self.forward_speed_m_min
|
||||
+ self.haul_distance_m / self.reverse_speed_m_min
|
||||
+ _DOZER_GEAR_SHIFT_MIN
|
||||
)
|
||||
|
||||
@property
|
||||
def formula_text(self) -> str:
|
||||
return (
|
||||
f"Q = 60 ÷ {self.cycle_minutes:.4f}분 × ({self.blade_capacity_m3} × "
|
||||
f"{self.distance_factor}) × {self.volume_factor} × {self.efficiency}"
|
||||
)
|
||||
|
||||
|
||||
def dozer_hourly_output(factors: DozerFactors) -> Decimal:
|
||||
"""불도저 시간당 작업량 `Q` (㎥/hr). **밑수는 60(분)** 이다."""
|
||||
if factors.cycle_minutes <= 0:
|
||||
raise ProductivityError(f"{factors.work_item_code}: 싸이클 시간이 0 이하입니다.")
|
||||
blade = factors.blade_capacity_m3 * factors.distance_factor
|
||||
output = (
|
||||
_MINUTES_PER_HOUR
|
||||
/ factors.cycle_minutes
|
||||
* blade
|
||||
* factors.volume_factor
|
||||
* factors.efficiency
|
||||
)
|
||||
if output <= 0:
|
||||
raise ProductivityError(f"{factors.work_item_code}: 시간당 작업량이 {output} 입니다.")
|
||||
return output
|
||||
|
||||
|
||||
def dozer_speeds(tonnage: Decimal) -> tuple[Decimal, Decimal] | None:
|
||||
"""그 규격의 전진·후진 속도(2단). 표에 없는 규격이면 `None` — 지어내지 않는다."""
|
||||
return _DOZER_SPEEDS_2ND_GEAR.get(tonnage)
|
||||
|
||||
Reference in New Issue
Block a user