RoyAalekh's picture
refactored project structure. renamed scheduler dir to src
6a28f91
"""Integration tests for simulation engine.
Tests multi-day simulation, case progression, ripeness tracking, and outcome validation.
"""
from datetime import date
import pytest
from src.data.case_generator import CaseGenerator
from src.simulation.engine import CourtSim, CourtSimConfig
@pytest.mark.integration
@pytest.mark.simulation
class TestSimulationBasics:
"""Test basic simulation execution."""
def test_single_day_simulation(self, small_case_set, temp_output_dir):
"""Test running a 1-day simulation."""
config = CourtSimConfig(
start=date(2024, 1, 15), # Monday
days=1,
seed=42,
courtrooms=2,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, small_case_set)
result = sim.run()
assert result is not None
assert result.hearings_total >= 0
assert result.end_date == config.start
def test_week_simulation(self, sample_cases, temp_output_dir):
"""Test running a 1-week (5 working days) simulation."""
config = CourtSimConfig(
start=date(2024, 1, 15), # Monday
days=7,
seed=42,
courtrooms=3,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, sample_cases)
result = sim.run()
assert result.hearings_total > 0
# Should have had some disposals
assert result.disposals >= 0
@pytest.mark.slow
def test_month_simulation(self, sample_cases, temp_output_dir):
"""Test running a 30-day simulation."""
config = CourtSimConfig(
start=date(2024, 1, 1),
days=30,
seed=42,
courtrooms=5,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, sample_cases)
result = sim.run()
assert result.hearings_total > 0
assert (
result.hearings_heard + result.hearings_adjourned == result.hearings_total
)
# Check disposal rate is reasonable
if result.hearings_total > 0:
disposal_rate = result.disposals / len(sample_cases)
assert 0.0 <= disposal_rate <= 1.0
@pytest.mark.integration
@pytest.mark.simulation
class TestOutcomeTracking:
"""Test tracking of simulation outcomes."""
def test_disposal_counting(self, small_case_set, temp_output_dir):
"""Test that disposals are counted correctly."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=30,
seed=42,
courtrooms=2,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, small_case_set)
result = sim.run()
# Count disposed cases
disposed_count = sum(1 for case in small_case_set if case.is_disposed())
# Should match result
assert result.disposals == disposed_count
def test_adjournment_rate(self, sample_cases, temp_output_dir):
"""Test that adjournment rate is realistic."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=30,
seed=42,
courtrooms=5,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, sample_cases)
result = sim.run()
if result.hearings_total > 0:
adj_rate = result.hearings_adjourned / result.hearings_total
# Realistic adjournment rate: 20-60%
assert 0.0 <= adj_rate <= 1.0
def test_utilization_calculation(self, sample_cases, temp_output_dir):
"""Test courtroom utilization calculation."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=20,
seed=42,
courtrooms=3,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, sample_cases)
result = sim.run()
# Utilization should be 0-100%
assert 0.0 <= result.utilization <= 100.0
@pytest.mark.integration
@pytest.mark.simulation
class TestStageProgression:
"""Test case stage progression during simulation."""
def test_cases_progress_stages(self, sample_cases, temp_output_dir):
"""Test that cases progress through stages."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=90,
seed=42,
courtrooms=5,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
# Record initial stages
initial_stages = {case.case_id: case.current_stage for case in sample_cases}
sim = CourtSim(config, sample_cases)
sim.run()
# Check if any cases progressed
progressed = sum(
1
for case in sample_cases
if case.current_stage != initial_stages.get(case.case_id)
)
# At least some cases should progress
assert progressed >= 0
def test_terminal_stage_handling(self, sample_cases, temp_output_dir):
"""Test that cases in terminal stages are handled correctly."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=60,
seed=42,
courtrooms=5,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, sample_cases)
sim.run()
# Check disposed cases are in terminal stages
from src.data.config import TERMINAL_STAGES
for case in sample_cases:
if case.is_disposed():
assert case.current_stage in TERMINAL_STAGES
@pytest.mark.integration
@pytest.mark.simulation
class TestRipenessIntegration:
"""Test ripeness classification integration."""
def test_ripeness_reevaluation(self, sample_cases, temp_output_dir):
"""Test that ripeness is re-evaluated during simulation."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=30,
seed=42,
courtrooms=5,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, sample_cases)
result = sim.run()
# Check ripeness transitions tracked
assert result.ripeness_transitions >= 0
def test_unripe_filtering(self, temp_output_dir):
"""Test that unripe cases are filtered from scheduling."""
# Create mix of ripe and unripe cases
generator = CaseGenerator(
start=date(2024, 1, 1), end=date(2024, 1, 10), seed=42
)
cases = generator.generate(50)
# Mark some as unripe
for i, case in enumerate(cases):
if i % 3 == 0:
case.service_status = "PENDING"
case.purpose_of_hearing = "FOR SUMMONS"
config = CourtSimConfig(
start=date(2024, 2, 1),
days=10,
seed=42,
courtrooms=3,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, cases)
result = sim.run()
# Should have filtered some unripe cases
assert result.unripe_filtered >= 0
@pytest.mark.integration
@pytest.mark.edge_case
class TestSimulationEdgeCases:
"""Test simulation edge cases."""
def test_zero_initial_cases(self, temp_output_dir):
"""Test simulation with no initial cases."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=10,
seed=42,
courtrooms=2,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, [])
result = sim.run()
# Should complete without errors
assert result.hearings_total == 0
assert result.disposals == 0
def test_all_cases_disposed_early(self, temp_output_dir):
"""Test when all cases dispose before simulation end."""
# Create very simple cases that dispose quickly
generator = CaseGenerator(start=date(2024, 1, 1), end=date(2024, 1, 5), seed=42)
cases = generator.generate(5)
# Set all to near-disposal stage
for case in cases:
case.current_stage = "ORDERS"
case.service_status = "SERVED"
config = CourtSimConfig(
start=date(2024, 2, 1),
days=90,
seed=42,
courtrooms=2,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, cases)
result = sim.run()
# Should handle gracefully
assert result.disposals <= len(cases)
@pytest.mark.failure
def test_invalid_start_date(self, small_case_set, temp_output_dir):
"""Test simulation with invalid start date."""
with pytest.raises(ValueError):
CourtSimConfig(
start="invalid-date", # Should be date object
days=10,
seed=42,
courtrooms=2,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
@pytest.mark.failure
def test_negative_days(self, small_case_set, temp_output_dir):
"""Test simulation with negative days."""
with pytest.raises(ValueError):
CourtSimConfig(
start=date(2024, 1, 15),
days=-10,
seed=42,
courtrooms=2,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
@pytest.mark.integration
@pytest.mark.simulation
class TestEventLogging:
"""Test event logging functionality."""
def test_events_written(self, small_case_set, temp_output_dir):
"""Test that events are written to CSV."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=5,
seed=42,
courtrooms=2,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, small_case_set)
sim.run()
# Check if events file exists
events_file = temp_output_dir / "events.csv"
if events_file.exists():
# Verify it's readable
import pandas as pd
df = pd.read_csv(events_file)
assert len(df) >= 0
def test_event_count_matches_hearings(self, small_case_set, temp_output_dir):
"""Test that event count matches total hearings."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=10,
seed=42,
courtrooms=2,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir,
)
sim = CourtSim(config, small_case_set)
sim.run()
# Events should correspond to hearings
events_file = temp_output_dir / "events.csv"
if events_file.exists():
import pandas as pd
pd.read_csv(events_file)
# Event count should match or be close to hearings_total
# (may have additional events for filings, etc.)
@pytest.mark.integration
@pytest.mark.simulation
class TestPolicyComparison:
"""Test different scheduling policies."""
def test_fifo_policy(self, sample_cases, temp_output_dir):
"""Test simulation with FIFO policy."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=20,
seed=42,
courtrooms=3,
daily_capacity=50,
policy="fifo",
log_dir=temp_output_dir / "fifo",
)
sim = CourtSim(config, sample_cases.copy())
result = sim.run()
assert result.hearings_total > 0
def test_age_policy(self, sample_cases, temp_output_dir):
"""Test simulation with age-based policy."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=20,
seed=42,
courtrooms=3,
daily_capacity=50,
policy="age",
log_dir=temp_output_dir / "age",
)
sim = CourtSim(config, sample_cases.copy())
result = sim.run()
assert result.hearings_total > 0
def test_readiness_policy(self, sample_cases, temp_output_dir):
"""Test simulation with readiness policy."""
config = CourtSimConfig(
start=date(2024, 1, 15),
days=20,
seed=42,
courtrooms=3,
daily_capacity=50,
policy="readiness",
log_dir=temp_output_dir / "readiness",
)
sim = CourtSim(config, sample_cases.copy())
result = sim.run()
assert result.hearings_total > 0