RoyAalekh commited on
Commit
7a39bba
·
1 Parent(s): b5f21c3

Fixed app.py and data insights page

Browse files
scheduler/dashboard/app.py CHANGED
@@ -1,13 +1,11 @@
1
  """Main dashboard application for Court Scheduling System.
2
 
3
  This is the entry point for the Streamlit multi-page dashboard.
4
- Launch with: uv run court-scheduler dashboard
5
  """
6
 
7
  from __future__ import annotations
8
 
9
- import subprocess
10
- from pathlib import Path
11
 
12
  import streamlit as st
13
 
@@ -21,29 +19,17 @@ st.set_page_config(
21
  initial_sidebar_state="expanded",
22
  )
23
 
24
- # Enforce `uv` availability for all dashboard-triggered commands
25
- try:
26
- uv_check = subprocess.run(["uv", "--version"], capture_output=True, text=True)
27
- if uv_check.returncode != 0:
28
- raise RuntimeError(uv_check.stderr or "uv not available")
29
- except Exception:
30
- import streamlit as st
31
-
32
- st.error(
33
- "'uv' is required to run this dashboard's commands. Please install uv and rerun.\n\n"
34
- "Install on macOS/Linux: `curl -LsSf https://astral.sh/uv/install.sh | sh`\n"
35
- "Install on Windows (PowerShell): `irm https://astral.sh/uv/install.ps1 | iex`"
36
- )
37
- st.stop()
38
-
39
  # Main page content
40
  st.title("Court Scheduling System Dashboard")
41
- st.markdown("**Karnataka High Court - Algorithmic Decision Support for Fair Scheduling**")
 
 
42
 
43
  st.markdown("---")
44
 
45
  # Introduction
46
- st.markdown("""
 
47
  ### Overview
48
 
49
  This system provides data-driven scheduling recommendations while maintaining judicial control and autonomy.
@@ -57,7 +43,8 @@ This system provides data-driven scheduling recommendations while maintaining ju
57
  - Reinforcement learning optimization
58
 
59
  Use the sidebar to navigate between sections.
60
- """)
 
61
 
62
  # System status
63
  status_header_col1, status_header_col2 = st.columns([3, 1])
@@ -93,47 +80,57 @@ with col3:
93
  st.caption("Run EDA pipeline to generate visualizations")
94
 
95
  # Setup Controls
96
- eda_ready = data_status["cleaned_data"] and data_status["parameters"] and data_status["eda_figures"]
 
 
 
 
97
 
98
  if not eda_ready:
99
  st.markdown("---")
100
  st.markdown("### Initial Setup")
101
- st.warning("Run the EDA pipeline to process historical data and extract parameters.")
 
 
102
 
103
  col1, col2 = st.columns([2, 1])
104
 
105
  with col1:
106
- st.markdown("""
 
107
  The EDA pipeline:
108
  - Loads and cleans historical court case data
109
  - Extracts statistical parameters (distributions, transition probabilities)
110
  - Generates analysis visualizations
111
 
112
  This is required before using other dashboard features.
113
- """)
 
114
 
115
  with col2:
116
  if st.button("Run EDA Pipeline", type="primary", use_container_width=True):
117
- import subprocess
 
 
118
 
119
  with st.spinner("Running EDA pipeline... This may take a few minutes."):
120
  try:
121
- result = subprocess.run(
122
- ["uv", "run", "court-scheduler", "eda"],
123
- capture_output=True,
124
- text=True,
125
- cwd=str(Path.cwd()),
126
- )
127
-
128
- if result.returncode == 0:
129
- st.success("EDA pipeline completed")
130
- st.rerun()
131
- else:
132
- st.error(f"Pipeline failed with error code {result.returncode}")
133
- with st.expander("Show error details"):
134
- st.code(result.stderr, language="text")
135
  except Exception as e:
136
- st.error(f"Error running pipeline: {e}")
 
 
137
 
138
  with st.expander("Run manually via CLI"):
139
  st.code("uv run court-scheduler eda", language="bash")
@@ -148,7 +145,8 @@ st.markdown("### Dashboard Sections")
148
  col1, col2 = st.columns(2)
149
 
150
  with col1:
151
- st.markdown("""
 
152
  #### 1. Data & Insights
153
  Explore historical case data, view analysis visualizations, and review extracted parameters.
154
 
@@ -157,10 +155,12 @@ with col1:
157
 
158
  #### 3. Simulation Workflow
159
  Generate cases, configure simulation parameters, run scheduling simulations, and view results.
160
- """)
 
161
 
162
  with col2:
163
- st.markdown("""
 
164
  #### 4. Cause Lists & Overrides
165
  View generated cause lists, make judge overrides, and track modification history.
166
 
@@ -169,13 +169,15 @@ with col2:
169
 
170
  #### 6. Analytics & Reports
171
  Compare simulation runs, analyze performance metrics, and export comprehensive reports.
172
- """)
 
173
 
174
  st.markdown("---")
175
 
176
  # Typical Workflow
177
  with st.expander("Typical Usage Workflow"):
178
- st.markdown("""
 
179
  **Step 1: Initial Setup**
180
  - Run EDA pipeline to process historical data (one-time setup)
181
 
@@ -202,7 +204,8 @@ with st.expander("Typical Usage Workflow"):
202
  - Use Analytics & Reports to evaluate fairness and efficiency
203
  - Compare different scheduling policies
204
  - Identify bottlenecks and improvement opportunities
205
- """)
 
206
 
207
  # Footer
208
  st.markdown("---")
 
1
  """Main dashboard application for Court Scheduling System.
2
 
3
  This is the entry point for the Streamlit multi-page dashboard.
4
+ Launch with: uv run court-scheduler dashboard (or `streamlit run` directly)
5
  """
6
 
7
  from __future__ import annotations
8
 
 
 
9
 
10
  import streamlit as st
11
 
 
19
  initial_sidebar_state="expanded",
20
  )
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  # Main page content
23
  st.title("Court Scheduling System Dashboard")
24
+ st.markdown(
25
+ "**Karnataka High Court - Algorithmic Decision Support for Fair Scheduling**"
26
+ )
27
 
28
  st.markdown("---")
29
 
30
  # Introduction
31
+ st.markdown(
32
+ """
33
  ### Overview
34
 
35
  This system provides data-driven scheduling recommendations while maintaining judicial control and autonomy.
 
43
  - Reinforcement learning optimization
44
 
45
  Use the sidebar to navigate between sections.
46
+ """
47
+ )
48
 
49
  # System status
50
  status_header_col1, status_header_col2 = st.columns([3, 1])
 
80
  st.caption("Run EDA pipeline to generate visualizations")
81
 
82
  # Setup Controls
83
+ eda_ready = (
84
+ data_status["cleaned_data"]
85
+ and data_status["parameters"]
86
+ and data_status["eda_figures"]
87
+ )
88
 
89
  if not eda_ready:
90
  st.markdown("---")
91
  st.markdown("### Initial Setup")
92
+ st.warning(
93
+ "Run the EDA pipeline to process historical data and extract parameters."
94
+ )
95
 
96
  col1, col2 = st.columns([2, 1])
97
 
98
  with col1:
99
+ st.markdown(
100
+ """
101
  The EDA pipeline:
102
  - Loads and cleans historical court case data
103
  - Extracts statistical parameters (distributions, transition probabilities)
104
  - Generates analysis visualizations
105
 
106
  This is required before using other dashboard features.
107
+ """
108
+ )
109
 
110
  with col2:
111
  if st.button("Run EDA Pipeline", type="primary", use_container_width=True):
112
+ from eda.load_clean import run_load_and_clean
113
+ from eda.exploration import run_exploration
114
+ from eda.parameters import run_parameter_export
115
 
116
  with st.spinner("Running EDA pipeline... This may take a few minutes."):
117
  try:
118
+ # Step 1: Load & clean data
119
+ run_load_and_clean()
120
+
121
+ # Step 2: Generate visualizations
122
+ run_exploration()
123
+
124
+ # Step 3: Extract parameters
125
+ run_parameter_export()
126
+
127
+ st.success("EDA pipeline completed")
128
+ st.rerun()
129
+
 
 
130
  except Exception as e:
131
+ st.error("Pipeline failed while running inside the dashboard.")
132
+ with st.expander("Show error details"):
133
+ st.exception(e)
134
 
135
  with st.expander("Run manually via CLI"):
136
  st.code("uv run court-scheduler eda", language="bash")
 
145
  col1, col2 = st.columns(2)
146
 
147
  with col1:
148
+ st.markdown(
149
+ """
150
  #### 1. Data & Insights
151
  Explore historical case data, view analysis visualizations, and review extracted parameters.
152
 
 
155
 
156
  #### 3. Simulation Workflow
157
  Generate cases, configure simulation parameters, run scheduling simulations, and view results.
158
+ """
159
+ )
160
 
161
  with col2:
162
+ st.markdown(
163
+ """
164
  #### 4. Cause Lists & Overrides
165
  View generated cause lists, make judge overrides, and track modification history.
166
 
 
169
 
170
  #### 6. Analytics & Reports
171
  Compare simulation runs, analyze performance metrics, and export comprehensive reports.
172
+ """
173
+ )
174
 
175
  st.markdown("---")
176
 
177
  # Typical Workflow
178
  with st.expander("Typical Usage Workflow"):
179
+ st.markdown(
180
+ """
181
  **Step 1: Initial Setup**
182
  - Run EDA pipeline to process historical data (one-time setup)
183
 
 
204
  - Use Analytics & Reports to evaluate fairness and efficiency
205
  - Compare different scheduling policies
206
  - Identify bottlenecks and improvement opportunities
207
+ """
208
+ )
209
 
210
  # Footer
211
  st.markdown("---")
scheduler/dashboard/pages/1_Data_And_Insights.py CHANGED
@@ -70,7 +70,9 @@ def load_dashboard_data():
70
 
71
  with st.spinner("Loading data..."):
72
  try:
73
- cases_df, hearings_df, params, stats, total_cases, total_hearings = load_dashboard_data()
 
 
74
  except Exception as e:
75
  st.error(f"Error loading data: {e}")
76
  st.info("Please run the EDA pipeline first: `uv run court-scheduler eda`")
@@ -96,28 +98,25 @@ if cases_df.empty and hearings_df.empty:
96
 
97
  with col1:
98
  if st.button("Run EDA Pipeline Now", type="primary", use_container_width=True):
99
- import subprocess
 
 
100
 
101
  with st.spinner("Running EDA pipeline... This will take a few minutes."):
102
  try:
103
- result = subprocess.run(
104
- ["uv", "run", "court-scheduler", "eda"],
105
- capture_output=True,
106
- text=True,
107
- cwd=str(Path.cwd()),
108
- )
109
-
110
- if result.returncode == 0:
111
- st.success("EDA pipeline completed successfully!")
112
- st.info("Reload this page to see the data.")
113
- if st.button("Reload Page"):
114
- st.rerun()
115
- else:
116
- st.error(f"Pipeline failed with error code {result.returncode}")
117
- with st.expander("Error details"):
118
- st.code(result.stderr, language="text")
119
  except Exception as e:
120
- st.error(f"Error: {e}")
 
121
 
122
  with col2:
123
  with st.expander("Alternative: Run via CLI"):
@@ -133,7 +132,9 @@ col1, col2, col3, col4, col5 = st.columns(5)
133
  with col1:
134
  st.metric("Total Cases", f"{total_cases:,}")
135
  if "YEAR_FILED" in cases_df.columns:
136
- year_range = f"{cases_df['YEAR_FILED'].min():.0f}-{cases_df['YEAR_FILED'].max():.0f}"
 
 
137
  st.caption(f"Years: {year_range}")
138
 
139
  with col2:
@@ -176,7 +177,9 @@ with col5:
176
  st.markdown("---")
177
 
178
  # Main tabs
179
- tab1, tab2, tab3 = st.tabs(["Historical Analysis", "Interactive Exploration", "Parameters"])
 
 
180
 
181
  # TAB 1: Historical Analysis - Pre-generated figures
182
  with tab1:
@@ -188,11 +191,15 @@ with tab1:
188
  figures_dir = Path("reports/figures")
189
 
190
  if not figures_dir.exists():
191
- st.warning("EDA figures not found. Run the EDA pipeline to generate visualizations.")
 
 
192
  st.code("uv run court-scheduler eda")
193
  else:
194
  # Find latest versioned directory
195
- version_dirs = [d for d in figures_dir.iterdir() if d.is_dir() and d.name.startswith("v")]
 
 
196
 
197
  if not version_dirs:
198
  st.warning(
@@ -207,7 +214,9 @@ with tab1:
207
  # List available figures from the versioned directory
208
  # Exclude deprecated/removed visuals like the monthly waterfall
209
  figure_files = [
210
- f for f in sorted(latest_dir.glob("*.html")) if "waterfall" not in f.name.lower()
 
 
211
  ]
212
 
213
  if not figure_files:
@@ -227,10 +236,14 @@ with tab1:
227
  if any(x in f.name for x in ["stage", "sankey", "transition"])
228
  ]
229
  time_figs = [
230
- f for f in figure_files if any(x in f.name for x in ["monthly", "load", "gap"])
 
 
231
  ]
232
  other_figs = [
233
- f for f in figure_files if f not in distribution_figs + stage_figs + time_figs
 
 
234
  ]
235
 
236
  # Category 1: Case Distributions
@@ -325,7 +338,9 @@ with tab2:
325
  selected_stages = st.sidebar.multiselect(
326
  "Stages",
327
  options=available_stages,
328
- default=available_stages[:10] if len(available_stages) > 10 else available_stages,
 
 
329
  key="stage_filter",
330
  )
331
  else:
@@ -334,12 +349,16 @@ with tab2:
334
 
335
  # Apply filters with copy to ensure clean dataframes
336
  if selected_case_types and case_type_col:
337
- filtered_cases = cases_df[cases_df[case_type_col].isin(selected_case_types)].copy()
 
 
338
  else:
339
  filtered_cases = cases_df.copy()
340
 
341
  if selected_stages and stage_col:
342
- filtered_hearings = hearings_df[hearings_df[stage_col].isin(selected_stages)].copy()
 
 
343
  else:
344
  filtered_hearings = hearings_df.copy()
345
 
@@ -370,9 +389,9 @@ with tab2:
370
 
371
  with col4:
372
  if "Outcome" in filtered_hearings.columns and len(filtered_hearings) > 0:
373
- adj_rate_filtered = (filtered_hearings["Outcome"] == "ADJOURNED").sum() / len(
374
- filtered_hearings
375
- )
376
  st.metric("Adjournment Rate", f"{adj_rate_filtered:.1%}")
377
  else:
378
  st.metric("Adjournment Rate", "N/A")
@@ -387,9 +406,15 @@ with tab2:
387
  with sub_tab1:
388
  st.markdown("#### Case Distribution by Type")
389
 
390
- if case_type_col and case_type_col in filtered_cases.columns and len(filtered_cases) > 0:
 
 
 
 
391
  # Compute value counts and ensure proper structure
392
- case_type_counts = filtered_cases[case_type_col].value_counts().reset_index()
 
 
393
  # Rename columns for clarity (works across pandas versions)
394
  case_type_counts.columns = ["CaseType", "Count"]
395
 
@@ -428,7 +453,11 @@ with tab2:
428
  with sub_tab2:
429
  st.markdown("#### Stage Analysis")
430
 
431
- if stage_col and stage_col in filtered_hearings.columns and len(filtered_hearings) > 0:
 
 
 
 
432
  stage_counts = filtered_hearings[stage_col].value_counts().reset_index()
433
  stage_counts.columns = ["Stage", "Count"]
434
 
@@ -465,7 +494,10 @@ with tab2:
465
  not_adjourned = total_hearings - adjourned
466
 
467
  outcome_df = pd.DataFrame(
468
- {"Outcome": ["ADJOURNED", "NOT ADJOURNED"], "Count": [adjourned, not_adjourned]}
 
 
 
469
  )
470
 
471
  fig_pie = px.pie(
@@ -474,7 +506,10 @@ with tab2:
474
  names="Outcome",
475
  title=f"Outcome Distribution (Total: {total_hearings:,})",
476
  color="Outcome",
477
- color_discrete_map={"ADJOURNED": "#ef4444", "NOT ADJOURNED": "#22c55e"},
 
 
 
478
  )
479
  fig_pie.update_layout(height=400)
480
  st.plotly_chart(fig_pie, use_container_width=True)
@@ -483,7 +518,9 @@ with tab2:
483
  st.markdown("**By Stage**")
484
  adj_by_stage = (
485
  filtered_hearings.groupby(stage_col)["Outcome"]
486
- .apply(lambda x: (x == "ADJOURNED").sum() / len(x) if len(x) > 0 else 0)
 
 
487
  .reset_index()
488
  )
489
  adj_by_stage.columns = ["Stage", "Rate"]
@@ -507,7 +544,9 @@ with tab2:
507
  with sub_tab4:
508
  st.markdown("#### Raw Data")
509
 
510
- data_view = st.radio("Select data to view:", ["Cases", "Hearings"], horizontal=True)
 
 
511
 
512
  if data_view == "Cases":
513
  st.dataframe(
@@ -516,7 +555,9 @@ with tab2:
516
  height=600,
517
  )
518
 
519
- st.markdown(f"**Showing first 500 of {len(filtered_cases):,} filtered cases**")
 
 
520
 
521
  # Download button
522
  csv = filtered_cases.to_csv(index=False).encode("utf-8")
@@ -533,7 +574,9 @@ with tab2:
533
  height=600,
534
  )
535
 
536
- st.markdown(f"**Showing first 500 of {len(filtered_hearings):,} filtered hearings**")
 
 
537
 
538
  # Download button
539
  csv = filtered_hearings.to_csv(index=False).encode("utf-8")
@@ -559,7 +602,10 @@ with tab3:
559
  st.markdown("#### Case Types")
560
  if "case_types" in params and params["case_types"]:
561
  case_types_df = pd.DataFrame(
562
- {"Case Type": params["case_types"], "Index": range(len(params["case_types"]))}
 
 
 
563
  )
564
  st.dataframe(case_types_df, use_container_width=True, hide_index=True)
565
  st.caption(f"Total: {len(params['case_types'])} case types")
@@ -594,9 +640,13 @@ with tab3:
594
  with st.expander(f"From: {stage}"):
595
  trans_df = pd.DataFrame(transitions)
596
  if not trans_df.empty:
597
- st.dataframe(trans_df, use_container_width=True, hide_index=True)
 
 
598
 
599
- st.caption(f"Total: {len(params['stage_graph'])} stages with transition data")
 
 
600
  else:
601
  st.info("No stage transition data found")
602
 
@@ -609,8 +659,12 @@ with tab3:
609
 
610
  # Create heatmap
611
  adj_stats = params["adjournment_stats"]
612
- stages_list = list(adj_stats.keys())[:20] # Limit to 20 stages for readability
613
- case_types_list = params.get("case_types", [])[:15] # Limit to 15 case types
 
 
 
 
614
 
615
  if stages_list and case_types_list:
616
  heatmap_data = []
@@ -656,7 +710,12 @@ with tab3:
656
  """)
657
 
658
  config_tab1, config_tab2, config_tab3, config_tab4 = st.tabs(
659
- ["EDA Parameters", "Ripeness Classifier", "Case Generator", "Simulation Defaults"]
 
 
 
 
 
660
  )
661
 
662
  with config_tab1:
@@ -857,7 +916,10 @@ UNRIPE cases: 0.7x priority
857
  from scheduler.data.config import MONTHLY_SEASONALITY
858
 
859
  season_df = pd.DataFrame(
860
- [{"Month": i, "Factor": MONTHLY_SEASONALITY.get(i, 1.0)} for i in range(1, 13)]
 
 
 
861
  )
862
  st.dataframe(season_df, use_container_width=True, hide_index=True)
863
  st.caption("1.0 = average, >1.0 = more cases, <1.0 = fewer cases")
@@ -900,7 +962,9 @@ Ripe purposes (80% probability):
900
  """,
901
  language="text",
902
  )
903
- st.caption("Early ADMISSION: 40% bottleneck, Advanced stages: mostly ripe")
 
 
904
 
905
  with config_tab4:
906
  st.markdown("#### Simulation Defaults")
@@ -930,8 +994,12 @@ Formula:
930
  st.markdown("**Courtroom Capacity**")
931
  if params and "court_capacity_global" in params:
932
  cap = params["court_capacity_global"]
933
- st.metric("Median slots/day", f"{cap.get('slots_median_global', 151):.0f}")
934
- st.metric("P90 slots/day", f"{cap.get('slots_p90_global', 200):.0f}")
 
 
 
 
935
  else:
936
  st.info("Run EDA to load capacity statistics")
937
 
 
70
 
71
  with st.spinner("Loading data..."):
72
  try:
73
+ cases_df, hearings_df, params, stats, total_cases, total_hearings = (
74
+ load_dashboard_data()
75
+ )
76
  except Exception as e:
77
  st.error(f"Error loading data: {e}")
78
  st.info("Please run the EDA pipeline first: `uv run court-scheduler eda`")
 
98
 
99
  with col1:
100
  if st.button("Run EDA Pipeline Now", type="primary", use_container_width=True):
101
+ from eda.load_clean import run_load_and_clean
102
+ from eda.exploration import run_exploration
103
+ from eda.parameters import run_parameter_export
104
 
105
  with st.spinner("Running EDA pipeline... This will take a few minutes."):
106
  try:
107
+ # Step 1: Load & clean data
108
+ run_load_and_clean()
109
+ # Step 2: Generate visualizations
110
+ run_exploration()
111
+ # Step 3: Extract parameters
112
+ run_parameter_export()
113
+ st.success("EDA pipeline completed successfully!")
114
+ st.info("Reload this page to see the updated data.")
115
+ if st.button("Reload Page"):
116
+ st.rerun()
 
 
 
 
 
 
117
  except Exception as e:
118
+ with st.expander("Error details"):
119
+ st.exception(e)
120
 
121
  with col2:
122
  with st.expander("Alternative: Run via CLI"):
 
132
  with col1:
133
  st.metric("Total Cases", f"{total_cases:,}")
134
  if "YEAR_FILED" in cases_df.columns:
135
+ year_range = (
136
+ f"{cases_df['YEAR_FILED'].min():.0f}-{cases_df['YEAR_FILED'].max():.0f}"
137
+ )
138
  st.caption(f"Years: {year_range}")
139
 
140
  with col2:
 
177
  st.markdown("---")
178
 
179
  # Main tabs
180
+ tab1, tab2, tab3 = st.tabs(
181
+ ["Historical Analysis", "Interactive Exploration", "Parameters"]
182
+ )
183
 
184
  # TAB 1: Historical Analysis - Pre-generated figures
185
  with tab1:
 
191
  figures_dir = Path("reports/figures")
192
 
193
  if not figures_dir.exists():
194
+ st.warning(
195
+ "EDA figures not found. Run the EDA pipeline to generate visualizations."
196
+ )
197
  st.code("uv run court-scheduler eda")
198
  else:
199
  # Find latest versioned directory
200
+ version_dirs = [
201
+ d for d in figures_dir.iterdir() if d.is_dir() and d.name.startswith("v")
202
+ ]
203
 
204
  if not version_dirs:
205
  st.warning(
 
214
  # List available figures from the versioned directory
215
  # Exclude deprecated/removed visuals like the monthly waterfall
216
  figure_files = [
217
+ f
218
+ for f in sorted(latest_dir.glob("*.html"))
219
+ if "waterfall" not in f.name.lower()
220
  ]
221
 
222
  if not figure_files:
 
236
  if any(x in f.name for x in ["stage", "sankey", "transition"])
237
  ]
238
  time_figs = [
239
+ f
240
+ for f in figure_files
241
+ if any(x in f.name for x in ["monthly", "load", "gap"])
242
  ]
243
  other_figs = [
244
+ f
245
+ for f in figure_files
246
+ if f not in distribution_figs + stage_figs + time_figs
247
  ]
248
 
249
  # Category 1: Case Distributions
 
338
  selected_stages = st.sidebar.multiselect(
339
  "Stages",
340
  options=available_stages,
341
+ default=available_stages[:10]
342
+ if len(available_stages) > 10
343
+ else available_stages,
344
  key="stage_filter",
345
  )
346
  else:
 
349
 
350
  # Apply filters with copy to ensure clean dataframes
351
  if selected_case_types and case_type_col:
352
+ filtered_cases = cases_df[
353
+ cases_df[case_type_col].isin(selected_case_types)
354
+ ].copy()
355
  else:
356
  filtered_cases = cases_df.copy()
357
 
358
  if selected_stages and stage_col:
359
+ filtered_hearings = hearings_df[
360
+ hearings_df[stage_col].isin(selected_stages)
361
+ ].copy()
362
  else:
363
  filtered_hearings = hearings_df.copy()
364
 
 
389
 
390
  with col4:
391
  if "Outcome" in filtered_hearings.columns and len(filtered_hearings) > 0:
392
+ adj_rate_filtered = (
393
+ filtered_hearings["Outcome"] == "ADJOURNED"
394
+ ).sum() / len(filtered_hearings)
395
  st.metric("Adjournment Rate", f"{adj_rate_filtered:.1%}")
396
  else:
397
  st.metric("Adjournment Rate", "N/A")
 
406
  with sub_tab1:
407
  st.markdown("#### Case Distribution by Type")
408
 
409
+ if (
410
+ case_type_col
411
+ and case_type_col in filtered_cases.columns
412
+ and len(filtered_cases) > 0
413
+ ):
414
  # Compute value counts and ensure proper structure
415
+ case_type_counts = (
416
+ filtered_cases[case_type_col].value_counts().reset_index()
417
+ )
418
  # Rename columns for clarity (works across pandas versions)
419
  case_type_counts.columns = ["CaseType", "Count"]
420
 
 
453
  with sub_tab2:
454
  st.markdown("#### Stage Analysis")
455
 
456
+ if (
457
+ stage_col
458
+ and stage_col in filtered_hearings.columns
459
+ and len(filtered_hearings) > 0
460
+ ):
461
  stage_counts = filtered_hearings[stage_col].value_counts().reset_index()
462
  stage_counts.columns = ["Stage", "Count"]
463
 
 
494
  not_adjourned = total_hearings - adjourned
495
 
496
  outcome_df = pd.DataFrame(
497
+ {
498
+ "Outcome": ["ADJOURNED", "NOT ADJOURNED"],
499
+ "Count": [adjourned, not_adjourned],
500
+ }
501
  )
502
 
503
  fig_pie = px.pie(
 
506
  names="Outcome",
507
  title=f"Outcome Distribution (Total: {total_hearings:,})",
508
  color="Outcome",
509
+ color_discrete_map={
510
+ "ADJOURNED": "#ef4444",
511
+ "NOT ADJOURNED": "#22c55e",
512
+ },
513
  )
514
  fig_pie.update_layout(height=400)
515
  st.plotly_chart(fig_pie, use_container_width=True)
 
518
  st.markdown("**By Stage**")
519
  adj_by_stage = (
520
  filtered_hearings.groupby(stage_col)["Outcome"]
521
+ .apply(
522
+ lambda x: (x == "ADJOURNED").sum() / len(x) if len(x) > 0 else 0
523
+ )
524
  .reset_index()
525
  )
526
  adj_by_stage.columns = ["Stage", "Rate"]
 
544
  with sub_tab4:
545
  st.markdown("#### Raw Data")
546
 
547
+ data_view = st.radio(
548
+ "Select data to view:", ["Cases", "Hearings"], horizontal=True
549
+ )
550
 
551
  if data_view == "Cases":
552
  st.dataframe(
 
555
  height=600,
556
  )
557
 
558
+ st.markdown(
559
+ f"**Showing first 500 of {len(filtered_cases):,} filtered cases**"
560
+ )
561
 
562
  # Download button
563
  csv = filtered_cases.to_csv(index=False).encode("utf-8")
 
574
  height=600,
575
  )
576
 
577
+ st.markdown(
578
+ f"**Showing first 500 of {len(filtered_hearings):,} filtered hearings**"
579
+ )
580
 
581
  # Download button
582
  csv = filtered_hearings.to_csv(index=False).encode("utf-8")
 
602
  st.markdown("#### Case Types")
603
  if "case_types" in params and params["case_types"]:
604
  case_types_df = pd.DataFrame(
605
+ {
606
+ "Case Type": params["case_types"],
607
+ "Index": range(len(params["case_types"])),
608
+ }
609
  )
610
  st.dataframe(case_types_df, use_container_width=True, hide_index=True)
611
  st.caption(f"Total: {len(params['case_types'])} case types")
 
640
  with st.expander(f"From: {stage}"):
641
  trans_df = pd.DataFrame(transitions)
642
  if not trans_df.empty:
643
+ st.dataframe(
644
+ trans_df, use_container_width=True, hide_index=True
645
+ )
646
 
647
+ st.caption(
648
+ f"Total: {len(params['stage_graph'])} stages with transition data"
649
+ )
650
  else:
651
  st.info("No stage transition data found")
652
 
 
659
 
660
  # Create heatmap
661
  adj_stats = params["adjournment_stats"]
662
+ stages_list = list(adj_stats.keys())[
663
+ :20
664
+ ] # Limit to 20 stages for readability
665
+ case_types_list = params.get("case_types", [])[
666
+ :15
667
+ ] # Limit to 15 case types
668
 
669
  if stages_list and case_types_list:
670
  heatmap_data = []
 
710
  """)
711
 
712
  config_tab1, config_tab2, config_tab3, config_tab4 = st.tabs(
713
+ [
714
+ "EDA Parameters",
715
+ "Ripeness Classifier",
716
+ "Case Generator",
717
+ "Simulation Defaults",
718
+ ]
719
  )
720
 
721
  with config_tab1:
 
916
  from scheduler.data.config import MONTHLY_SEASONALITY
917
 
918
  season_df = pd.DataFrame(
919
+ [
920
+ {"Month": i, "Factor": MONTHLY_SEASONALITY.get(i, 1.0)}
921
+ for i in range(1, 13)
922
+ ]
923
  )
924
  st.dataframe(season_df, use_container_width=True, hide_index=True)
925
  st.caption("1.0 = average, >1.0 = more cases, <1.0 = fewer cases")
 
962
  """,
963
  language="text",
964
  )
965
+ st.caption(
966
+ "Early ADMISSION: 40% bottleneck, Advanced stages: mostly ripe"
967
+ )
968
 
969
  with config_tab4:
970
  st.markdown("#### Simulation Defaults")
 
994
  st.markdown("**Courtroom Capacity**")
995
  if params and "court_capacity_global" in params:
996
  cap = params["court_capacity_global"]
997
+ st.metric(
998
+ "Median slots/day", f"{cap.get('slots_median_global', 151):.0f}"
999
+ )
1000
+ st.metric(
1001
+ "P90 slots/day", f"{cap.get('slots_p90_global', 200):.0f}"
1002
+ )
1003
  else:
1004
  st.info("Run EDA to load capacity statistics")
1005
 
scheduler/dashboard/pages/2_Ripeness_Classifier.py CHANGED
@@ -99,7 +99,9 @@ RipenessClassifier.set_thresholds(
99
  )
100
 
101
  # Main content
102
- tab1, tab2, tab3 = st.tabs(["Current Configuration", "Interactive Testing", "Batch Classification"])
 
 
103
 
104
  with tab1:
105
  st.markdown("### Current Classifier Configuration")
@@ -153,7 +155,10 @@ with tab1:
153
  stage_rules = {
154
  "PRE-TRIAL": {"min_days": 60, "keywords": ["affidavit filed", "reply filed"]},
155
  "TRIAL": {"min_days": 45, "keywords": ["evidence complete", "cross complete"]},
156
- "POST-TRIAL": {"min_days": 30, "keywords": ["arguments complete", "written note"]},
 
 
 
157
  "FINAL DISPOSAL": {"min_days": 15, "keywords": ["disposed", "judgment"]},
158
  }
159
 
@@ -190,8 +195,12 @@ with tab2:
190
  service_hearings_count = st.number_input(
191
  "Service Hearings", min_value=0, max_value=20, value=3
192
  )
193
- days_in_stage = st.number_input("Days in Stage", min_value=0, max_value=365, value=45)
194
- case_age = st.number_input("Case Age (days)", min_value=0, max_value=3650, value=120)
 
 
 
 
195
 
196
  # Keywords
197
  has_keywords = st.multiselect(
@@ -213,7 +222,7 @@ with tab2:
213
 
214
  test_case = Case(
215
  case_id=case_id,
216
- case_type=case_type, # Use string directly instead of CaseType enum
217
  filed_date=filed_date,
218
  current_stage=case_stage,
219
  status=CaseStatus.PENDING,
@@ -286,15 +295,25 @@ with tab3:
286
 
287
  with col1:
288
  pct = classifications["RIPE"] / len(cases) * 100
289
- st.metric("RIPE Cases", f"{classifications['RIPE']:,}", f"{pct:.1f}%")
 
 
290
 
291
  with col2:
292
  pct = classifications["UNKNOWN"] / len(cases) * 100
293
- st.metric("UNKNOWN Cases", f"{classifications['UNKNOWN']:,}", f"{pct:.1f}%")
 
 
 
 
294
 
295
  with col3:
296
  pct = classifications["UNRIPE"] / len(cases) * 100
297
- st.metric("UNRIPE Cases", f"{classifications['UNRIPE']:,}", f"{pct:.1f}%")
 
 
 
 
298
 
299
  # Pie chart
300
  fig = px.pie(
@@ -302,7 +321,11 @@ with tab3:
302
  names=list(classifications.keys()),
303
  title="Classification Distribution",
304
  color=list(classifications.keys()),
305
- color_discrete_map={"RIPE": "green", "UNKNOWN": "orange", "UNRIPE": "red"},
 
 
 
 
306
  )
307
  st.plotly_chart(fig, use_container_width=True)
308
 
@@ -311,4 +334,6 @@ with tab3:
311
 
312
  # Footer
313
  st.markdown("---")
314
- st.markdown("*Adjust thresholds in the sidebar to see real-time impact on classification*")
 
 
 
99
  )
100
 
101
  # Main content
102
+ tab1, tab2, tab3 = st.tabs(
103
+ ["Current Configuration", "Interactive Testing", "Batch Classification"]
104
+ )
105
 
106
  with tab1:
107
  st.markdown("### Current Classifier Configuration")
 
155
  stage_rules = {
156
  "PRE-TRIAL": {"min_days": 60, "keywords": ["affidavit filed", "reply filed"]},
157
  "TRIAL": {"min_days": 45, "keywords": ["evidence complete", "cross complete"]},
158
+ "POST-TRIAL": {
159
+ "min_days": 30,
160
+ "keywords": ["arguments complete", "written note"],
161
+ },
162
  "FINAL DISPOSAL": {"min_days": 15, "keywords": ["disposed", "judgment"]},
163
  }
164
 
 
195
  service_hearings_count = st.number_input(
196
  "Service Hearings", min_value=0, max_value=20, value=3
197
  )
198
+ days_in_stage = st.number_input(
199
+ "Days in Stage", min_value=0, max_value=365, value=45
200
+ )
201
+ case_age = st.number_input(
202
+ "Case Age (days)", min_value=0, max_value=3650, value=120
203
+ )
204
 
205
  # Keywords
206
  has_keywords = st.multiselect(
 
222
 
223
  test_case = Case(
224
  case_id=case_id,
225
+ case_type=case_type,
226
  filed_date=filed_date,
227
  current_stage=case_stage,
228
  status=CaseStatus.PENDING,
 
295
 
296
  with col1:
297
  pct = classifications["RIPE"] / len(cases) * 100
298
+ st.metric(
299
+ "RIPE Cases", f"{classifications['RIPE']:,}", f"{pct:.1f}%"
300
+ )
301
 
302
  with col2:
303
  pct = classifications["UNKNOWN"] / len(cases) * 100
304
+ st.metric(
305
+ "UNKNOWN Cases",
306
+ f"{classifications['UNKNOWN']:,}",
307
+ f"{pct:.1f}%",
308
+ )
309
 
310
  with col3:
311
  pct = classifications["UNRIPE"] / len(cases) * 100
312
+ st.metric(
313
+ "UNRIPE Cases",
314
+ f"{classifications['UNRIPE']:,}",
315
+ f"{pct:.1f}%",
316
+ )
317
 
318
  # Pie chart
319
  fig = px.pie(
 
321
  names=list(classifications.keys()),
322
  title="Classification Distribution",
323
  color=list(classifications.keys()),
324
+ color_discrete_map={
325
+ "RIPE": "green",
326
+ "UNKNOWN": "orange",
327
+ "UNRIPE": "red",
328
+ },
329
  )
330
  st.plotly_chart(fig, use_container_width=True)
331
 
 
334
 
335
  # Footer
336
  st.markdown("---")
337
+ st.markdown(
338
+ "*Adjust thresholds in the sidebar to see real-time impact on classification*"
339
+ )