waste-prediction-api / tests /test_logistics_engine.py
ALAMDIENG's picture
feat(logistics): audit and rebuild operational logistics engine, fleet throughput, and dynamic efficiency
62e0a0e
Raw
History Blame Contribute Delete
5.72 kB
"""
Unit Tests for AETERNA AI Operational Logistics Engine
Validates transparent fleet sizing, crew allocation, throughput-based collection duration,
operational efficiency, and zero/large volume edge cases.
"""
import pytest
import math
from services.logistics_engine import (
LOGISTICS_CONFIG,
calculate_fleet_requirements,
calculate_manpower_requirements,
calculate_collection_time,
calculate_operational_efficiency_score,
calculate_forecast_reliability_score,
calculate_full_logistics_plan
)
def test_1_fleet_calculation():
"""
Test 1: Fleet Calculation for forecast = 1135.63 ton.
Verifies dimensional correctness and transparent formula:
effective_capacity = 15.0 * 0.95 = 14.25 ton
base_trucks = ceil(1135.63 / 14.25) = 80
recommended_trucks = ceil(80 * 1.05) = 84
required_truck_loads = 1135.63 / 15.0 = 75.71
"""
forecast = 1135.63
fleet = calculate_fleet_requirements(forecast)
assert fleet["truck_capacity_ton"] == 15.0
assert fleet["load_factor"] == 0.95
assert fleet["effective_capacity_ton"] == 14.25
assert fleet["base_trucks"] == 80
assert fleet["operational_buffer_percent"] == 5.0
assert fleet["recommended_trucks"] == 84
assert fleet["required_truck_loads"] == 75.71
def test_2_manpower_calculation():
"""
Test 2: Manpower staffing derived directly from recommended fleet.
Standard crew: 1 driver + 2 collectors = 3 personnel per truck.
"""
# Test with 80 base trucks
manpower_80 = calculate_manpower_requirements(80)
assert manpower_80["drivers"] == 80
assert manpower_80["collectors"] == 160
assert manpower_80["total_personnel"] == 240
assert manpower_80["crew_per_truck"] == 3
# Test with 84 recommended trucks
manpower_84 = calculate_manpower_requirements(84)
assert manpower_84["drivers"] == 84
assert manpower_84["collectors"] == 168
assert manpower_84["total_personnel"] == 252
assert manpower_84["total_personnel"] == 84 * 3
def test_3_collection_time_uses_throughput_not_volume_div_capacity():
"""
Test 3: Collection time MUST use fleet throughput, NOT volume / truck_capacity.
Volume / 15 gives 75.7 (which is truck loads, NOT hours).
Throughput = recommended_trucks * collection_rate_ton_per_hour (2.0 T/h).
"""
forecast = 1135.63
truck_capacity = 15.0
faulty_time = forecast / truck_capacity # 75.70866...
rec_trucks = 84
collection = calculate_collection_time(
forecast_volume_ton=forecast,
recommended_trucks=rec_trucks,
collection_rate_per_truck=2.0,
rainfall_mm=0.0,
has_event=False,
operational_efficiency=0.98
)
# Collection time must NOT equal faulty volume / capacity
assert collection["adjusted_hours"] != round(faulty_time, 1)
assert collection["raw_hours"] != round(faulty_time, 1)
# Dimensional verification:
# fleet_throughput = 84 * 2.0 = 168.0 ton/hour
# raw_hours = 1135.63 / 168.0 = 6.7597... ~ 6.8 hours
assert collection["raw_hours"] == 6.8
# adjusted_hours = (6.7597 * 1.1) / 0.98 = 7.587... ~ 7.6 hours
assert collection["adjusted_hours"] == 7.6
def test_4_zero_forecast_handling():
"""
Test 4: Zero forecast volume edge case.
If forecast_volume = 0, trucks = 0, crew = 0, collection time = 0.
"""
plan = calculate_full_logistics_plan(0.0)
assert plan["forecast_volume_ton"] == 0.0
assert plan["trucks_needed"] == 0
assert plan["manpower"] == 0
assert plan["estimated_duration_hours"] == 0.0
assert plan["required_truck_loads"] == 0.0
fleet = plan["recommended_fleet"]
assert fleet["base_trucks"] == 0
assert fleet["recommended_trucks"] == 0
assert fleet["required_truck_loads"] == 0.0
crew = plan["manpower_breakdown"]
assert crew["drivers"] == 0
assert crew["collectors"] == 0
assert crew["total_personnel"] == 0
def test_5_large_volume_scaling():
"""
Test 5: Large volume handling without hardcoded limits.
For citywide 10,000 tons surge, all formulas scale consistently.
"""
large_volume = 10000.0
plan = calculate_full_logistics_plan(large_volume)
# Effective capacity = 14.25
expected_base_trucks = math.ceil(10000.0 / 14.25) # 702
expected_rec_trucks = math.ceil(expected_base_trucks * 1.05) # 738
expected_personnel = expected_rec_trucks * 3 # 2214
assert plan["recommended_fleet"]["base_trucks"] == expected_base_trucks
assert plan["recommended_fleet"]["recommended_trucks"] == expected_rec_trucks
assert plan["manpower"] == expected_personnel
assert plan["required_truck_loads"] == round(10000.0 / 15.0, 2)
assert plan["estimated_duration_hours"] > 0.0
assert plan["operational_efficiency"]["score_percent"] > 0.0
def test_6_operational_factors_adjustment():
"""
Test 6: Real-world adjustments (rain, traffic, events) affect collection duration.
Heavy rain (>25mm) and event crowd increase estimated duration.
"""
base_plan = calculate_collection_time(
forecast_volume_ton=1000.0,
recommended_trucks=50,
rainfall_mm=0.0,
has_event=False,
operational_efficiency=0.85
)
rain_event_plan = calculate_collection_time(
forecast_volume_ton=1000.0,
recommended_trucks=50,
rainfall_mm=30.0, # Heavy rain (factor 1.20)
has_event=True, # Event crowd (factor 1.10)
operational_efficiency=0.85
)
assert rain_event_plan["adjusted_hours"] > base_plan["adjusted_hours"]
assert rain_event_plan["factors"]["weather_factor"] == 1.20
assert rain_event_plan["factors"]["event_factor"] == 1.10