RoyAalekh commited on
Commit
21b5794
·
1 Parent(s): 8d2e8fa

Refine override validation and cleanup

Browse files
Files changed (1) hide show
  1. scheduler/core/algorithm.py +39 -13
scheduler/core/algorithm.py CHANGED
@@ -37,6 +37,7 @@ class SchedulingResult:
37
  scheduled_cases: Mapping of courtroom_id to list of scheduled cases
38
  explanations: Decision explanations for each case (scheduled + sample unscheduled)
39
  applied_overrides: List of overrides that were successfully applied
 
40
  unscheduled_cases: Cases not scheduled with reasons (e.g., unripe, capacity full)
41
  ripeness_filtered: Count of cases filtered due to unripe status
42
  capacity_limited: Count of cases that didn't fit due to courtroom capacity
@@ -47,11 +48,12 @@ class SchedulingResult:
47
 
48
  # Core output
49
  scheduled_cases: Dict[int, List[Case]]
50
-
51
  # Transparency
52
  explanations: Dict[str, SchedulingExplanation]
53
  applied_overrides: List[Override]
54
-
 
55
  # Diagnostics
56
  unscheduled_cases: List[Tuple[Case, str]]
57
  ripeness_filtered: int
@@ -132,18 +134,31 @@ class SchedulingAlgorithm:
132
  unscheduled: List[Tuple[Case, str]] = []
133
  applied_overrides: List[Override] = []
134
  explanations: Dict[str, SchedulingExplanation] = {}
135
-
 
 
136
  # Validate overrides if provided
137
  if overrides:
138
  validator = OverrideValidator()
139
  for override in overrides:
140
- if not validator.validate(override):
141
- # Skip invalid overrides but log them
 
 
 
 
 
 
 
 
142
  unscheduled.append(
143
- (None, f"Invalid override rejected: {override.override_type.value} - {validator.get_errors()}")
 
 
 
 
144
  )
145
- overrides = [o for o in overrides if o != override]
146
-
147
  # Filter disposed cases
148
  active_cases = [c for c in cases if c.status != CaseStatus.DISPOSED]
149
 
@@ -154,7 +169,7 @@ class SchedulingAlgorithm:
154
 
155
  # CHECKPOINT 1: Ripeness filtering with override support
156
  ripe_cases, ripeness_filtered = self._filter_by_ripeness(
157
- active_cases, current_date, overrides, applied_overrides
158
  )
159
 
160
  # CHECKPOINT 2: Eligibility check (min gap requirement)
@@ -168,9 +183,9 @@ class SchedulingAlgorithm:
168
  prioritized = self.policy.prioritize(eligible_cases, current_date)
169
 
170
  # CHECKPOINT 5: Apply manual overrides (add/remove/reorder/priority)
171
- if overrides:
172
  prioritized = self._apply_manual_overrides(
173
- prioritized, overrides, applied_overrides, unscheduled, active_cases
174
  )
175
 
176
  # CHECKPOINT 6: Allocate to courtrooms
@@ -208,11 +223,14 @@ class SchedulingAlgorithm:
208
  below_threshold=False
209
  )
210
  explanations[case.case_id] = explanation
211
-
 
 
212
  return SchedulingResult(
213
  scheduled_cases=scheduled_allocation,
214
  explanations=explanations,
215
  applied_overrides=applied_overrides,
 
216
  unscheduled_cases=unscheduled,
217
  ripeness_filtered=ripeness_filtered,
218
  capacity_limited=capacity_limited,
@@ -400,5 +418,13 @@ class SchedulingAlgorithm:
400
  if case.case_id in case_to_courtroom:
401
  courtroom_id = case_to_courtroom[case.case_id]
402
  allocation[courtroom_id].append(case)
403
-
404
  return allocation, capacity_limited
 
 
 
 
 
 
 
 
 
37
  scheduled_cases: Mapping of courtroom_id to list of scheduled cases
38
  explanations: Decision explanations for each case (scheduled + sample unscheduled)
39
  applied_overrides: List of overrides that were successfully applied
40
+ override_rejections: Structured records for rejected overrides
41
  unscheduled_cases: Cases not scheduled with reasons (e.g., unripe, capacity full)
42
  ripeness_filtered: Count of cases filtered due to unripe status
43
  capacity_limited: Count of cases that didn't fit due to courtroom capacity
 
48
 
49
  # Core output
50
  scheduled_cases: Dict[int, List[Case]]
51
+
52
  # Transparency
53
  explanations: Dict[str, SchedulingExplanation]
54
  applied_overrides: List[Override]
55
+ override_rejections: List[Dict[str, str]]
56
+
57
  # Diagnostics
58
  unscheduled_cases: List[Tuple[Case, str]]
59
  ripeness_filtered: int
 
134
  unscheduled: List[Tuple[Case, str]] = []
135
  applied_overrides: List[Override] = []
136
  explanations: Dict[str, SchedulingExplanation] = {}
137
+ override_rejections: List[Dict[str, str]] = []
138
+ validated_overrides: List[Override] = []
139
+
140
  # Validate overrides if provided
141
  if overrides:
142
  validator = OverrideValidator()
143
  for override in overrides:
144
+ if validator.validate(override):
145
+ validated_overrides.append(override)
146
+ else:
147
+ errors = validator.get_errors()
148
+ rejection_reason = "; ".join(errors) if errors else "Validation failed"
149
+ override_rejections.append({
150
+ "judge": override.judge_id,
151
+ "context": override.override_type.value,
152
+ "reason": rejection_reason
153
+ })
154
  unscheduled.append(
155
+ (
156
+ None,
157
+ f"Invalid override rejected (judge {override.judge_id}): "
158
+ f"{override.override_type.value} - {rejection_reason}"
159
+ )
160
  )
161
+
 
162
  # Filter disposed cases
163
  active_cases = [c for c in cases if c.status != CaseStatus.DISPOSED]
164
 
 
169
 
170
  # CHECKPOINT 1: Ripeness filtering with override support
171
  ripe_cases, ripeness_filtered = self._filter_by_ripeness(
172
+ active_cases, current_date, validated_overrides, applied_overrides
173
  )
174
 
175
  # CHECKPOINT 2: Eligibility check (min gap requirement)
 
183
  prioritized = self.policy.prioritize(eligible_cases, current_date)
184
 
185
  # CHECKPOINT 5: Apply manual overrides (add/remove/reorder/priority)
186
+ if validated_overrides:
187
  prioritized = self._apply_manual_overrides(
188
+ prioritized, validated_overrides, applied_overrides, unscheduled, active_cases
189
  )
190
 
191
  # CHECKPOINT 6: Allocate to courtrooms
 
223
  below_threshold=False
224
  )
225
  explanations[case.case_id] = explanation
226
+
227
+ self._clear_temporary_case_flags(active_cases)
228
+
229
  return SchedulingResult(
230
  scheduled_cases=scheduled_allocation,
231
  explanations=explanations,
232
  applied_overrides=applied_overrides,
233
+ override_rejections=override_rejections,
234
  unscheduled_cases=unscheduled,
235
  ripeness_filtered=ripeness_filtered,
236
  capacity_limited=capacity_limited,
 
418
  if case.case_id in case_to_courtroom:
419
  courtroom_id = case_to_courtroom[case.case_id]
420
  allocation[courtroom_id].append(case)
421
+
422
  return allocation, capacity_limited
423
+
424
+ @staticmethod
425
+ def _clear_temporary_case_flags(cases: List[Case]) -> None:
426
+ """Remove temporary scheduling flags to keep case objects clean between runs."""
427
+
428
+ for case in cases:
429
+ if hasattr(case, "_priority_override"):
430
+ delattr(case, "_priority_override")