Aryan Jain commited on
Commit
02f9977
Β·
1 Parent(s): d854fcb

consider incident type as string and apply colors

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +88 -26
src/streamlit_app.py CHANGED
@@ -25,7 +25,7 @@ INCIDENT_COLOR_MAP = {
25
  "2": ("#eab308", "Rupt/Exp"),
26
  "3": ("#3b82f6", "EMS"),
27
  "4": ("#22c55e", "Hazardous"),
28
- "S": ("#a855f7", "Public Assist"),
29
  "6": ("#06b6d4", "Good Intent"),
30
  "7": ("#9ca3af", "False Alarms"),
31
  "8": ("#84cc16", "Weather"),
@@ -60,10 +60,17 @@ INCIDENT_NAME_COLOR_MAP = {
60
  "special type": ("#c0c0c0", "Special Type"),
61
  }
62
 
 
 
 
 
 
 
 
63
 
64
  def _incident_label_and_color(value) -> tuple[str, str]:
65
  """Return (display_label, hex_color) for a raw incidenttype value."""
66
- if value is None or (isinstance(value, float) and pd.isna(value)):
67
  return INCIDENT_NULL_COLOR[1], INCIDENT_NULL_COLOR[0]
68
  s = str(value).strip()
69
  if s == "":
@@ -90,13 +97,15 @@ def _incident_label_and_color(value) -> tuple[str, str]:
90
 
91
 
92
  def _add_incident_category(df: pd.DataFrame, col: str) -> pd.DataFrame:
93
- """Add _incident_label and _incident_color helper columns (non-destructive)."""
94
  df = df.copy()
95
  if col not in df.columns or df.empty:
96
  return df
97
- labels, colors = zip(*df[col].map(_incident_label_and_color))
98
- df["_incident_label"] = labels
99
- df["_incident_color"] = colors
 
 
 
100
  return df
101
 
102
 
@@ -423,25 +432,33 @@ def _guess_chart_type(df: pd.DataFrame) -> str:
423
 
424
  def render_chart(
425
  df: pd.DataFrame,
426
- incident_col: str | None = None,
427
  chart_type_raw: str | None = None,
428
  key_prefix: str = "chart",
429
  ):
430
  if df.empty:
431
  st.info("No data to chart.")
432
  return
 
 
 
 
 
 
433
 
434
  chart_type = _normalise_chart_type(chart_type_raw) or _guess_chart_type(df)
435
 
436
- df_plot = _add_incident_category(df, incident_col) if incident_col else df.copy()
 
 
437
 
438
  cols = list(df.columns)
439
  numeric = df.select_dtypes(include="number").columns.tolist()
440
  cat = df.select_dtypes(exclude="number").columns.tolist()
441
 
442
  default_x = (
443
- incident_col
444
- if incident_col and incident_col in cols
445
  else (cat[0] if cat else cols[0])
446
  )
447
  default_y = numeric[0] if numeric else (cols[1] if len(cols) > 1 else cols[0])
@@ -508,16 +525,11 @@ def render_chart(
508
 
509
  # ── Incident color mapping ─────────────────────────
510
  incident_color_map = None
511
- use_incident_x = False
512
- if (
513
- incident_col
514
- and "_incident_label" in df_plot.columns
515
- and "_incident_color" in df_plot.columns
516
- ):
517
  incident_color_map = dict(
518
  zip(df_plot["_incident_label"], df_plot["_incident_color"])
519
  )
520
- use_incident_x = x_col == incident_col
521
 
522
  # ── Build chart ────────────────────────────────────
523
  fig = None
@@ -526,6 +538,19 @@ def render_chart(
526
  # ───── BAR ─────
527
  if chart_type == "bar":
528
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
  if color_col:
530
  bar_kwargs = dict(
531
  x=x_col,
@@ -534,13 +559,13 @@ def render_chart(
534
  barmode="group",
535
  template=PLOTLY_TEMPLATE,
536
  )
537
- if incident_color_map and color_col == incident_col:
538
- bar_kwargs["x"] = "_incident_label" if use_incident_x else x_col
539
  bar_kwargs["color"] = "_incident_label"
540
  bar_kwargs["color_discrete_map"] = incident_color_map
541
  fig = px.bar(df_plot, **bar_kwargs)
542
 
543
- elif incident_color_map and use_incident_x:
544
  fig = px.bar(
545
  df_plot,
546
  x="_incident_label",
@@ -561,6 +586,19 @@ def render_chart(
561
  # ───── LINE ─────
562
  elif chart_type == "line":
563
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  if color_col:
565
  line_kwargs = dict(
566
  x=x_col,
@@ -569,13 +607,13 @@ def render_chart(
569
  markers=True,
570
  template=PLOTLY_TEMPLATE,
571
  )
572
- if incident_color_map and color_col == incident_col:
573
- line_kwargs["x"] = "_incident_label" if use_incident_x else x_col
574
  line_kwargs["color"] = "_incident_label"
575
  line_kwargs["color_discrete_map"] = incident_color_map
576
  fig = px.line(df_plot, **line_kwargs)
577
 
578
- elif incident_color_map and use_incident_x:
579
  fig = px.line(
580
  df_plot,
581
  x="_incident_label",
@@ -598,7 +636,21 @@ def render_chart(
598
  # ───── PIE ─────
599
  elif chart_type == "pie":
600
 
601
- if incident_color_map and use_incident_x:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  fig = px.pie(
603
  df_plot,
604
  names="_incident_label",
@@ -784,7 +836,7 @@ def render_message(msg):
784
  # ── Multi-view data panel ──────────────────────────────────────────────
785
  data = msg.get("data", [])
786
  chart_hint = msg.get("best_suitable_chart") # e.g. "bar", "line", "pie"
787
- incident_col = msg.get("incident_col")
788
 
789
  if data and len(data) > 0 and msg.get("status") == "success":
790
  df = pd.DataFrame(data)
@@ -823,7 +875,17 @@ def render_message(msg):
823
  # key_prefix is unique per message (uses timestamp) so each
824
  # chart's column selectors have independent Streamlit widget keys.
825
  chart_key = f"chart_{msg.get('ts', 'x').replace(':', '_')}"
826
- render_chart(df, incident_col, chart_hint, key_prefix=chart_key)
 
 
 
 
 
 
 
 
 
 
827
 
828
  elif msg.get("status") == "success" and not data:
829
  st.markdown(
 
25
  "2": ("#eab308", "Rupt/Exp"),
26
  "3": ("#3b82f6", "EMS"),
27
  "4": ("#22c55e", "Hazardous"),
28
+ "5": ("#a855f7", "Public Assist"),
29
  "6": ("#06b6d4", "Good Intent"),
30
  "7": ("#9ca3af", "False Alarms"),
31
  "8": ("#84cc16", "Weather"),
 
60
  "special type": ("#c0c0c0", "Special Type"),
61
  }
62
 
63
+ INCIDENT_COL_NAMES = {"incidenttype", "incident_type", "incidentclassification", "incident_classification", "incident_category", "incidentcategory"}
64
+
65
+ def _detect_incident_col(df: pd.DataFrame) -> str | None:
66
+ for col in df.columns:
67
+ if col.strip().lower() in INCIDENT_COL_NAMES:
68
+ return col
69
+ return None
70
 
71
  def _incident_label_and_color(value) -> tuple[str, str]:
72
  """Return (display_label, hex_color) for a raw incidenttype value."""
73
+ if value is None or value == "nan" or (isinstance(value, float) and pd.isna(value)):
74
  return INCIDENT_NULL_COLOR[1], INCIDENT_NULL_COLOR[0]
75
  s = str(value).strip()
76
  if s == "":
 
97
 
98
 
99
  def _add_incident_category(df: pd.DataFrame, col: str) -> pd.DataFrame:
 
100
  df = df.copy()
101
  if col not in df.columns or df.empty:
102
  return df
103
+ df = df.reset_index(drop=True)
104
+ # Force plain string series β€” eliminates Categorical/MultiIndex issues
105
+ plain = df[col].astype(str).where(df[col].notna(), other=None)
106
+ mapped = plain.map(_incident_label_and_color)
107
+ df["_incident_label"] = mapped.apply(lambda x: x[0])
108
+ df["_incident_color"] = mapped.apply(lambda x: x[1])
109
  return df
110
 
111
 
 
432
 
433
  def render_chart(
434
  df: pd.DataFrame,
435
+ incident_cols: list[str] | None = None,
436
  chart_type_raw: str | None = None,
437
  key_prefix: str = "chart",
438
  ):
439
  if df.empty:
440
  st.info("No data to chart.")
441
  return
442
+
443
+ df = df.copy()
444
+ for c in df.columns:
445
+ if hasattr(df[c], "cat"):
446
+ df[c] = df[c].astype(str).replace("nan", None)
447
+
448
 
449
  chart_type = _normalise_chart_type(chart_type_raw) or _guess_chart_type(df)
450
 
451
+ incident_cols = incident_cols or []
452
+ active_incident_col: str | None = None
453
+ df_plot = df.copy()
454
 
455
  cols = list(df.columns)
456
  numeric = df.select_dtypes(include="number").columns.tolist()
457
  cat = df.select_dtypes(exclude="number").columns.tolist()
458
 
459
  default_x = (
460
+ incident_cols[0]
461
+ if incident_cols and incident_cols[0] in cols
462
  else (cat[0] if cat else cols[0])
463
  )
464
  default_y = numeric[0] if numeric else (cols[1] if len(cols) > 1 else cols[0])
 
525
 
526
  # ── Incident color mapping ─────────────────────────
527
  incident_color_map = None
528
+ if active_incident_col and "_incident_label" in df_plot.columns:
 
 
 
 
 
529
  incident_color_map = dict(
530
  zip(df_plot["_incident_label"], df_plot["_incident_color"])
531
  )
532
+
533
 
534
  # ── Build chart ────────────────────────────────────
535
  fig = None
 
538
  # ───── BAR ─────
539
  if chart_type == "bar":
540
 
541
+ active_incident_col = x_col if x_col in incident_cols else None
542
+ df_plot = (
543
+ _add_incident_category(df, active_incident_col)
544
+ if active_incident_col
545
+ else df.copy()
546
+ )
547
+
548
+ incident_color_map = (
549
+ dict(zip(df_plot["_incident_label"].tolist(), df_plot["_incident_color"].tolist()))
550
+ if active_incident_col and "_incident_label" in df_plot.columns
551
+ else None
552
+ )
553
+
554
  if color_col:
555
  bar_kwargs = dict(
556
  x=x_col,
 
559
  barmode="group",
560
  template=PLOTLY_TEMPLATE,
561
  )
562
+ if incident_color_map and color_col in incident_cols:
563
+ bar_kwargs["x"] = "_incident_label" if active_incident_col is not None else x_col
564
  bar_kwargs["color"] = "_incident_label"
565
  bar_kwargs["color_discrete_map"] = incident_color_map
566
  fig = px.bar(df_plot, **bar_kwargs)
567
 
568
+ elif incident_color_map and active_incident_col is not None:
569
  fig = px.bar(
570
  df_plot,
571
  x="_incident_label",
 
586
  # ───── LINE ─────
587
  elif chart_type == "line":
588
 
589
+ active_incident_col = x_col if x_col in incident_cols else None
590
+ df_plot = (
591
+ _add_incident_category(df, active_incident_col)
592
+ if active_incident_col
593
+ else df.copy()
594
+ )
595
+
596
+ incident_color_map = (
597
+ dict(zip(df_plot["_incident_label"].tolist(), df_plot["_incident_color"].tolist()))
598
+ if active_incident_col and "_incident_label" in df_plot.columns
599
+ else None
600
+ )
601
+
602
  if color_col:
603
  line_kwargs = dict(
604
  x=x_col,
 
607
  markers=True,
608
  template=PLOTLY_TEMPLATE,
609
  )
610
+ if incident_color_map and color_col in incident_cols:
611
+ line_kwargs["x"] = "_incident_label" if active_incident_col is not None else x_col
612
  line_kwargs["color"] = "_incident_label"
613
  line_kwargs["color_discrete_map"] = incident_color_map
614
  fig = px.line(df_plot, **line_kwargs)
615
 
616
+ elif incident_color_map and active_incident_col is not None:
617
  fig = px.line(
618
  df_plot,
619
  x="_incident_label",
 
636
  # ───── PIE ─────
637
  elif chart_type == "pie":
638
 
639
+ active_incident_col = x_col if x_col in incident_cols else None
640
+ df_plot = (
641
+ _add_incident_category(df, active_incident_col)
642
+ if active_incident_col
643
+ else df.copy()
644
+ )
645
+
646
+ incident_color_map = (
647
+ dict(zip(df_plot["_incident_label"].tolist(), df_plot["_incident_color"].tolist()))
648
+ if active_incident_col and "_incident_label" in df_plot.columns
649
+ else None
650
+ )
651
+
652
+
653
+ if incident_color_map and active_incident_col is not None:
654
  fig = px.pie(
655
  df_plot,
656
  names="_incident_label",
 
836
  # ── Multi-view data panel ──────────────────────────────────────────────
837
  data = msg.get("data", [])
838
  chart_hint = msg.get("best_suitable_chart") # e.g. "bar", "line", "pie"
839
+
840
 
841
  if data and len(data) > 0 and msg.get("status") == "success":
842
  df = pd.DataFrame(data)
 
875
  # key_prefix is unique per message (uses timestamp) so each
876
  # chart's column selectors have independent Streamlit widget keys.
877
  chart_key = f"chart_{msg.get('ts', 'x').replace(':', '_')}"
878
+ incident_cols = []
879
+ if msg.get("incident_col"):
880
+ incident_cols.append(msg["incident_col"])
881
+ detected = _detect_incident_col(df)
882
+ if detected and detected not in incident_cols:
883
+ incident_cols.append(detected)
884
+
885
+ for col in incident_cols:
886
+ if col in df.columns:
887
+ df[col] = df[col].astype("category")
888
+ render_chart(df, incident_cols, chart_hint, key_prefix=chart_key)
889
 
890
  elif msg.get("status") == "success" and not data:
891
  st.markdown(