RoyAalekh commited on
Commit
efb0735
·
1 Parent(s): 6a714c3

fix: Add UTF-8 encoding to CSV writers for Windows compatibility

Browse files

- Fixed Windows 'charmap' codec error with Unicode characters
- Added encoding='utf-8' to all CSV file operations
- Updated EventWriter in scheduler/simulation/events.py
- Updated metrics CSV writer in scheduler/simulation/engine.py
- Fixed interactive mode to work with OutputManager

Changes:
- scheduler/simulation/events.py: UTF-8 encoding for events.csv
- scheduler/simulation/engine.py: UTF-8 encoding for metrics.csv
- court_scheduler_rl.py: Removed output_dir references in interactive mode

This fixes 'charmap' codec can't encode character '\\u2192' error
that occurred during full pipeline runs on Windows.

court_scheduler_rl.py CHANGED
@@ -531,7 +531,6 @@ def get_interactive_config() -> PipelineConfig:
531
 
532
  # Output
533
  console.print("\n[bold]Output Options[/bold]")
534
- output_dir = Prompt.ask("Output directory", default="data/hackathon_run")
535
  generate_cause_lists = Confirm.ask("Generate daily cause lists?", default=True)
536
  generate_visualizations = Confirm.ask("Generate performance visualizations?", default=True)
537
 
@@ -558,22 +557,18 @@ def interactive():
558
  console.print(f" RL Learning Rate: {config.rl_training.learning_rate}")
559
  console.print(f" Simulation: {config.sim_days} days")
560
  console.print(f" Policies: {', '.join(config.policies)}")
561
- console.print(f" Output: {config.output_dir}")
562
 
563
  if not Confirm.ask("\nProceed with this configuration?", default=True):
564
  console.print("Cancelled.")
565
  return
566
 
567
- # Save configuration
568
- config_file = Path(config.output_dir) / "pipeline_config.json"
569
- config_file.parent.mkdir(parents=True, exist_ok=True)
570
- with open(config_file, 'w') as f:
571
- json.dump(asdict(config), f, indent=2)
572
-
573
- # Execute pipeline
574
  pipeline = InteractivePipeline(config)
575
  start_time = time.time()
576
 
 
 
577
  pipeline.run()
578
 
579
  elapsed = time.time() - start_time
 
531
 
532
  # Output
533
  console.print("\n[bold]Output Options[/bold]")
 
534
  generate_cause_lists = Confirm.ask("Generate daily cause lists?", default=True)
535
  generate_visualizations = Confirm.ask("Generate performance visualizations?", default=True)
536
 
 
557
  console.print(f" RL Learning Rate: {config.rl_training.learning_rate}")
558
  console.print(f" Simulation: {config.sim_days} days")
559
  console.print(f" Policies: {', '.join(config.policies)}")
560
+ console.print(f" Output: outputs/runs/run_<timestamp>/")
561
 
562
  if not Confirm.ask("\nProceed with this configuration?", default=True):
563
  console.print("Cancelled.")
564
  return
565
 
566
+ # Execute pipeline (OutputManager handles output structure)
 
 
 
 
 
 
567
  pipeline = InteractivePipeline(config)
568
  start_time = time.time()
569
 
570
+ console.print(f"\n[dim]Run directory: {pipeline.output.run_dir}[/dim]\n")
571
+
572
  pipeline.run()
573
 
574
  elapsed = time.time() - start_time
scheduler/simulation/engine.py CHANGED
@@ -44,10 +44,32 @@ class CourtSimConfig:
44
  seed: int = 42
45
  courtrooms: int = COURTROOMS
46
  daily_capacity: int = DEFAULT_DAILY_CAPACITY
47
- policy: str = "readiness" # fifo|age|readiness
48
  duration_percentile: str = "median" # median|p90
49
  log_dir: Path | None = None # if set, write metrics and suggestions
50
  write_suggestions: bool = False # if True, write daily suggestion CSVs (slow)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
 
53
  @dataclass
@@ -68,7 +90,15 @@ class CourtSim:
68
  self.cases = cases
69
  self.calendar = CourtCalendar()
70
  self.params = load_parameters()
71
- self.policy = get_policy(self.cfg.policy)
 
 
 
 
 
 
 
 
72
  random.seed(self.cfg.seed)
73
  # month working-days cache
74
  self._month_working_cache: Dict[tuple, int] = {}
@@ -82,9 +112,9 @@ class CourtSim:
82
  self._log_dir = Path("data") / "sim_runs" / run_id
83
  self._log_dir.mkdir(parents=True, exist_ok=True)
84
  self._metrics_path = self._log_dir / "metrics.csv"
85
- with self._metrics_path.open("w", newline="") as f:
86
  w = csv.writer(f)
87
- w.writerow(["date", "total_cases", "scheduled", "heard", "adjourned", "disposals", "utilization"])
88
  # events
89
  self._events_path = self._log_dir / "events.csv"
90
  self._events = EventWriter(self._events_path)
@@ -407,7 +437,7 @@ class CourtSim:
407
  # write metrics row
408
  total_cases = sum(1 for c in self.cases if c.status != CaseStatus.DISPOSED)
409
  util = (day_total / capacity_today) if capacity_today else 0.0
410
- with self._metrics_path.open("a", newline="") as f:
411
  w = csv.writer(f)
412
  w.writerow([current.isoformat(), total_cases, day_total, day_heard, day_total - day_heard, self._disposals, f"{util:.4f}"])
413
  if sf:
 
44
  seed: int = 42
45
  courtrooms: int = COURTROOMS
46
  daily_capacity: int = DEFAULT_DAILY_CAPACITY
47
+ policy: str = "readiness" # fifo|age|readiness|rl
48
  duration_percentile: str = "median" # median|p90
49
  log_dir: Path | None = None # if set, write metrics and suggestions
50
  write_suggestions: bool = False # if True, write daily suggestion CSVs (slow)
51
+ rl_agent_path: Path | None = None # Required if policy="rl"
52
+
53
+ def __post_init__(self):
54
+ """Validate configuration parameters."""
55
+ # Validate RL policy requirements
56
+ if self.policy == "rl":
57
+ if self.rl_agent_path is None:
58
+ raise ValueError(
59
+ "RL policy requires 'rl_agent_path' parameter. "
60
+ "Train an agent first and pass the model file path."
61
+ )
62
+ if not isinstance(self.rl_agent_path, Path):
63
+ self.rl_agent_path = Path(self.rl_agent_path)
64
+ if not self.rl_agent_path.exists():
65
+ raise FileNotFoundError(
66
+ f"RL agent model not found at {self.rl_agent_path}. "
67
+ "Train the agent first or provide correct path."
68
+ )
69
+
70
+ # Ensure log_dir is Path if provided
71
+ if self.log_dir is not None and not isinstance(self.log_dir, Path):
72
+ self.log_dir = Path(self.log_dir)
73
 
74
 
75
  @dataclass
 
90
  self.cases = cases
91
  self.calendar = CourtCalendar()
92
  self.params = load_parameters()
93
+
94
+ # Initialize policy with RL agent path if needed
95
+ policy_kwargs = {}
96
+ if self.cfg.policy == "rl":
97
+ if not self.cfg.rl_agent_path:
98
+ raise ValueError("RL policy requires rl_agent_path in CourtSimConfig")
99
+ policy_kwargs["agent_path"] = self.cfg.rl_agent_path
100
+
101
+ self.policy = get_policy(self.cfg.policy, **policy_kwargs)
102
  random.seed(self.cfg.seed)
103
  # month working-days cache
104
  self._month_working_cache: Dict[tuple, int] = {}
 
112
  self._log_dir = Path("data") / "sim_runs" / run_id
113
  self._log_dir.mkdir(parents=True, exist_ok=True)
114
  self._metrics_path = self._log_dir / "metrics.csv"
115
+ with self._metrics_path.open("w", newline="", encoding="utf-8") as f:
116
  w = csv.writer(f)
117
+ w.writerow(["date", "total_cases", "scheduled", "heard", "adjourned", "disposals", "utilization"])
118
  # events
119
  self._events_path = self._log_dir / "events.csv"
120
  self._events = EventWriter(self._events_path)
 
437
  # write metrics row
438
  total_cases = sum(1 for c in self.cases if c.status != CaseStatus.DISPOSED)
439
  util = (day_total / capacity_today) if capacity_today else 0.0
440
+ with self._metrics_path.open("a", newline="", encoding="utf-8") as f:
441
  w = csv.writer(f)
442
  w.writerow([current.isoformat(), total_cases, day_total, day_heard, day_total - day_heard, self._disposals, f"{util:.4f}"])
443
  if sf:
scheduler/simulation/events.py CHANGED
@@ -25,7 +25,7 @@ class EventWriter:
25
  self.path.parent.mkdir(parents=True, exist_ok=True)
26
  self._buffer = [] # in-memory rows to append
27
  if not self.path.exists():
28
- with self.path.open("w", newline="") as f:
29
  w = csv.writer(f)
30
  w.writerow([
31
  "date", "type", "case_id", "case_type", "stage", "courtroom_id",
@@ -57,7 +57,7 @@ class EventWriter:
57
  def flush(self) -> None:
58
  if not self._buffer:
59
  return
60
- with self.path.open("a", newline="") as f:
61
  w = csv.writer(f)
62
  w.writerows(self._buffer)
63
  self._buffer.clear()
 
25
  self.path.parent.mkdir(parents=True, exist_ok=True)
26
  self._buffer = [] # in-memory rows to append
27
  if not self.path.exists():
28
+ with self.path.open("w", newline="", encoding="utf-8") as f:
29
  w = csv.writer(f)
30
  w.writerow([
31
  "date", "type", "case_id", "case_type", "stage", "courtroom_id",
 
57
  def flush(self) -> None:
58
  if not self._buffer:
59
  return
60
+ with self.path.open("a", newline="", encoding="utf-8") as f:
61
  w = csv.writer(f)
62
  w.writerows(self._buffer)
63
  self._buffer.clear()