"""Tests for hameter.cost.""" from hameter.config import RateComponent from hameter.cost import calculate_incremental_cost class TestCalculateIncrementalCost: """Tests for the cost calculation engine.""" def test_single_per_unit_rate(self): factors = [RateComponent(name="Supply", rate=0.14742, type="per_unit")] result = calculate_incremental_cost(100.0, factors) assert result.delta == 100.0 assert abs(result.total_incremental_cost - 14.742) < 0.001 assert len(result.component_costs) == 1 assert result.component_costs[0]["name"] == "Supply" assert abs(result.component_costs[0]["cost"] - 14.742) < 0.001 def test_multiple_per_unit_rates(self): factors = [ RateComponent(name="Supply", rate=0.14742, type="per_unit"), RateComponent(name="Distribution", rate=0.09443, type="per_unit"), RateComponent(name="Transmission", rate=0.04673, type="per_unit"), ] result = calculate_incremental_cost(961.0, factors) expected = 961.0 * (0.14742 + 0.09443 + 0.04673) assert abs(result.total_incremental_cost - expected) < 0.01 def test_negative_rate_credit(self): factors = [ RateComponent(name="Supply", rate=0.10, type="per_unit"), RateComponent(name="Credit", rate=-0.02, type="per_unit"), ] result = calculate_incremental_cost(100.0, factors) # 100 * 0.10 + 100 * (-0.02) = 10.0 - 2.0 = 8.0 assert abs(result.total_incremental_cost - 8.0) < 0.001 def test_fixed_charges_excluded(self): factors = [ RateComponent(name="Supply", rate=0.10, type="per_unit"), RateComponent(name="Customer Charge", rate=9.65, type="fixed"), ] result = calculate_incremental_cost(100.0, factors) # Only per_unit: 100 * 0.10 = 10.0 assert abs(result.total_incremental_cost - 10.0) < 0.001 # Fixed component should have cost=0 fixed = [c for c in result.component_costs if c["type"] == "fixed"] assert len(fixed) == 1 assert fixed[0]["cost"] == 0.0 def test_zero_delta(self): factors = [RateComponent(name="Supply", rate=0.10, type="per_unit")] result = calculate_incremental_cost(0.0, factors) assert result.total_incremental_cost == 0.0 assert result.delta == 0.0 def test_empty_cost_factors(self): result = calculate_incremental_cost(100.0, []) assert result.total_incremental_cost == 0.0 assert result.component_costs == [] def test_full_bill_example(self): """Match the user's electric bill: 961 kWh, $313.10 total.""" factors = [ RateComponent(name="Generation", rate=0.14742, type="per_unit"), RateComponent(name="Dist 1", rate=0.09443, type="per_unit"), RateComponent(name="Dist 2", rate=0.01037, type="per_unit"), RateComponent(name="Transition", rate=-0.00077, type="per_unit"), RateComponent(name="Transmission", rate=0.04673, type="per_unit"), RateComponent(name="Net Meter Recovery", rate=0.00625, type="per_unit"), RateComponent(name="Revenue Decoupling", rate=-0.00044, type="per_unit"), RateComponent(name="Distributed Solar", rate=0.00583, type="per_unit"), RateComponent(name="Renewable Energy", rate=0.00050, type="per_unit"), RateComponent(name="Energy Efficiency", rate=0.02506, type="per_unit"), RateComponent(name="EV Program", rate=0.00238, type="per_unit"), RateComponent(name="Customer Charge", rate=9.65, type="fixed"), ] result = calculate_incremental_cost(961.0, factors) # Per-unit total (excluding fixed and the tiered distribution nuance) # Sum of per-unit rates: 0.32776 per_unit_sum = sum(cf.rate for cf in factors if cf.type == "per_unit") expected = 961.0 * per_unit_sum assert abs(result.total_incremental_cost - expected) < 0.01 def test_component_costs_preserve_all_fields(self): factors = [ RateComponent(name="Supply", rate=0.10, type="per_unit"), RateComponent(name="Fixed", rate=5.0, type="fixed"), ] result = calculate_incremental_cost(50.0, factors) assert len(result.component_costs) == 2 for c in result.component_costs: assert "name" in c assert "rate" in c assert "type" in c assert "cost" in c