Spaces:
Sleeping
Sleeping
Aryan Jain commited on
Commit Β·
02f9977
1
Parent(s): d854fcb
consider incident type as string and apply colors
Browse files- 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 |
-
"
|
| 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 |
-
|
| 98 |
-
|
| 99 |
-
df[
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 444 |
-
if
|
| 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 |
-
|
| 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 |
-
|
| 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
|
| 538 |
-
bar_kwargs["x"] = "_incident_label" if
|
| 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
|
| 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
|
| 573 |
-
line_kwargs["x"] = "_incident_label" if
|
| 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
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|