Spaces:
Paused
Paused
Update early_warning_module.py
Browse files- early_warning_module.py +45 -72
early_warning_module.py
CHANGED
|
@@ -2,8 +2,8 @@ from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
|
|
| 2 |
from collections import Counter
|
| 3 |
from datetime import datetime, timedelta
|
| 4 |
import random
|
|
|
|
| 5 |
|
| 6 |
-
# ββ Trigger keywords theo tα»«ng loαΊ‘i vαΊ₯n Δα» βββββββββββββββββββββββββββ
|
| 7 |
WARNING_RULES = {
|
| 8 |
"waste_overflow": {
|
| 9 |
"keywords": ["overflow", "overflowing", "full bin", "no bin", "trash everywhere", "garbage everywhere"],
|
|
@@ -37,95 +37,71 @@ WARNING_RULES = {
|
|
| 37 |
},
|
| 38 |
}
|
| 39 |
|
| 40 |
-
SEVERITY_EMOJI = {
|
| 41 |
-
"CRITICAL": "π¨",
|
| 42 |
-
"HIGH": "π΄",
|
| 43 |
-
"MEDIUM": "π‘",
|
| 44 |
-
"LOW": "π’"
|
| 45 |
-
}
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
base = risk_score
|
| 51 |
-
history = []
|
| 52 |
for i in range(6, 0, -1):
|
| 53 |
-
date =
|
| 54 |
-
# GiαΊ£ lαΊp fluctuation quanh base
|
| 55 |
noise = random.uniform(-0.15, 0.15)
|
| 56 |
-
score = round(max(0.0, min(1.0,
|
| 57 |
-
|
| 58 |
-
#
|
| 59 |
-
|
| 60 |
-
return
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
if not reviews_text.strip():
|
| 65 |
-
return "
|
| 66 |
|
| 67 |
reviews = [r.strip().lower() for r in reviews_text.strip().split("\n") if r.strip()]
|
| 68 |
-
analyzer = SentimentIntensityAnalyzer()
|
| 69 |
|
| 70 |
-
# ββ
|
| 71 |
triggered = []
|
| 72 |
for rule_name, rule in WARNING_RULES.items():
|
| 73 |
hits = []
|
| 74 |
for review in reviews:
|
| 75 |
found = [kw for kw in rule["keywords"] if kw in review]
|
| 76 |
-
|
| 77 |
-
hits.extend(found)
|
| 78 |
if hits:
|
| 79 |
-
freq = Counter(hits)
|
| 80 |
triggered.append({
|
| 81 |
"rule": rule_name,
|
| 82 |
"message": rule["message"],
|
| 83 |
"severity": rule["severity"],
|
| 84 |
-
"hits": dict(
|
| 85 |
"count": len(hits)
|
| 86 |
})
|
| 87 |
|
| 88 |
-
# Sort by severity
|
| 89 |
severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
|
| 90 |
triggered.sort(key=lambda x: severity_order.get(x["severity"], 99))
|
| 91 |
|
| 92 |
-
# ββ
|
| 93 |
-
|
| 94 |
-
trend_values = [
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
if len(trend_values) >= 3:
|
| 99 |
-
recent_change = trend_values[-1] - trend_values[-3]
|
| 100 |
-
spike = recent_change > 0.15
|
| 101 |
-
|
| 102 |
-
# Draw trend chart (text-based)
|
| 103 |
-
trend_rows = ""
|
| 104 |
-
for h in history:
|
| 105 |
-
bar_len = int(h["score"] * 20)
|
| 106 |
-
bar = "β" * bar_len
|
| 107 |
-
flag = " β οΈ" if h["score"] > 0.6 else ""
|
| 108 |
-
trend_rows += f"| {h['date']} | {bar:<20} | {h['score']:.2f}{flag} |\n"
|
| 109 |
-
|
| 110 |
-
# ββ Step 3: Format output βββββββββββββββββββββββββββββββββββββββββ
|
| 111 |
if not triggered:
|
| 112 |
warning_section = "β
**No warning triggers detected.** Reviews appear safe.\n"
|
| 113 |
else:
|
| 114 |
warning_section = ""
|
| 115 |
for t in triggered:
|
| 116 |
-
emoji
|
| 117 |
-
kw_str = ", ".join([f"'{k}'
|
| 118 |
warning_section += f"""
|
| 119 |
#### {emoji} {t["severity"]} β {t["message"]}
|
| 120 |
- **Keywords found:** {kw_str}
|
| 121 |
-
- **Frequency:** {t["count"]} mention(s)
|
| 122 |
"""
|
| 123 |
|
| 124 |
-
spike_alert = ""
|
| 125 |
-
if spike:
|
| 126 |
-
spike_alert = "\n> π¨ **SPIKE DETECTED:** Risk score increased significantly in the last 48 hours β possible emerging issue!\n"
|
| 127 |
-
|
| 128 |
-
# Overall alert level
|
| 129 |
if any(t["severity"] == "CRITICAL" for t in triggered):
|
| 130 |
overall = "π¨ CRITICAL β Immediate intervention required"
|
| 131 |
elif any(t["severity"] == "HIGH" for t in triggered):
|
|
@@ -135,31 +111,28 @@ def check_early_warning(reviews_text: str, risk_score: float) -> str:
|
|
| 135 |
else:
|
| 136 |
overall = "π’ ALL CLEAR β No immediate warnings"
|
| 137 |
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
### Overall Status: {overall}
|
| 141 |
{spike_alert}
|
| 142 |
|
| 143 |
---
|
| 144 |
|
| 145 |
-
### π Warning Triggers
|
| 146 |
{warning_section}
|
| 147 |
|
| 148 |
---
|
| 149 |
|
| 150 |
-
### π Risk Score Trend (Last 7 Days)
|
| 151 |
-
|
| 152 |
-
| Date | Risk Level | Score |
|
| 153 |
-
|------|-----------|-------|
|
| 154 |
-
{trend_rows}
|
| 155 |
-
|
| 156 |
-
{'β οΈ **Trend is RISING** β investigate before peak season.' if spike else 'π Trend is stable or improving.'}
|
| 157 |
-
|
| 158 |
-
---
|
| 159 |
-
|
| 160 |
### π¬ Auto-Alert Summary
|
| 161 |
-
|
| 162 |
|
| 163 |
-
|
|
|
|
| 164 |
"""
|
| 165 |
-
return
|
|
|
|
| 2 |
from collections import Counter
|
| 3 |
from datetime import datetime, timedelta
|
| 4 |
import random
|
| 5 |
+
import pandas as pd
|
| 6 |
|
|
|
|
| 7 |
WARNING_RULES = {
|
| 8 |
"waste_overflow": {
|
| 9 |
"keywords": ["overflow", "overflowing", "full bin", "no bin", "trash everywhere", "garbage everywhere"],
|
|
|
|
| 37 |
},
|
| 38 |
}
|
| 39 |
|
| 40 |
+
SEVERITY_EMOJI = {"CRITICAL": "π¨", "HIGH": "π΄", "MEDIUM": "π‘", "LOW": "π’"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
+
def build_trend_df(risk_score: float) -> pd.DataFrame:
|
| 43 |
+
"""TαΊ‘o DataFrame 7 ngΓ y Δα» vαΊ½ line chart thαΊt bαΊ±ng gr.LinePlot."""
|
| 44 |
+
rows = []
|
|
|
|
|
|
|
| 45 |
for i in range(6, 0, -1):
|
| 46 |
+
date = datetime.now() - timedelta(days=i)
|
|
|
|
| 47 |
noise = random.uniform(-0.15, 0.15)
|
| 48 |
+
score = round(max(0.0, min(1.0, risk_score + noise - 0.04 * i)), 3)
|
| 49 |
+
rows.append({"date": date, "risk_score": score, "period": "Historical"})
|
| 50 |
+
# HΓ΄m nay = Δiα»m thαΊt
|
| 51 |
+
rows.append({"date": datetime.now(), "risk_score": round(risk_score, 3), "period": "Today"})
|
| 52 |
+
return pd.DataFrame(rows)
|
| 53 |
+
|
| 54 |
+
def check_early_warning(reviews_text: str, risk_score: float):
|
| 55 |
+
"""
|
| 56 |
+
Returns:
|
| 57 |
+
warning_text (str): markdown output
|
| 58 |
+
trend_df (pd.DataFrame): dΓΉng cho gr.LinePlot
|
| 59 |
+
"""
|
| 60 |
+
empty_df = pd.DataFrame({"date": [], "risk_score": []})
|
| 61 |
+
|
| 62 |
if not reviews_text.strip():
|
| 63 |
+
return "β οΈ Run Text Analysis first (Tab 1).", empty_df
|
| 64 |
|
| 65 |
reviews = [r.strip().lower() for r in reviews_text.strip().split("\n") if r.strip()]
|
|
|
|
| 66 |
|
| 67 |
+
# ββ Trigger detection βββββββββββββββββββββββββββββββββββββββββββββ
|
| 68 |
triggered = []
|
| 69 |
for rule_name, rule in WARNING_RULES.items():
|
| 70 |
hits = []
|
| 71 |
for review in reviews:
|
| 72 |
found = [kw for kw in rule["keywords"] if kw in review]
|
| 73 |
+
hits.extend(found)
|
|
|
|
| 74 |
if hits:
|
|
|
|
| 75 |
triggered.append({
|
| 76 |
"rule": rule_name,
|
| 77 |
"message": rule["message"],
|
| 78 |
"severity": rule["severity"],
|
| 79 |
+
"hits": dict(Counter(hits)),
|
| 80 |
"count": len(hits)
|
| 81 |
})
|
| 82 |
|
|
|
|
| 83 |
severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
|
| 84 |
triggered.sort(key=lambda x: severity_order.get(x["severity"], 99))
|
| 85 |
|
| 86 |
+
# ββ Trend βββββββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½οΏ½βββββββββββ
|
| 87 |
+
trend_df = build_trend_df(risk_score)
|
| 88 |
+
trend_values = trend_df["risk_score"].tolist()
|
| 89 |
+
spike = len(trend_values) >= 3 and (trend_values[-1] - trend_values[-3]) > 0.15
|
| 90 |
+
|
| 91 |
+
# ββ Format warnings βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
if not triggered:
|
| 93 |
warning_section = "β
**No warning triggers detected.** Reviews appear safe.\n"
|
| 94 |
else:
|
| 95 |
warning_section = ""
|
| 96 |
for t in triggered:
|
| 97 |
+
emoji = SEVERITY_EMOJI.get(t["severity"], "β οΈ")
|
| 98 |
+
kw_str = ", ".join([f"'{k}' Γ{v}" for k, v in t["hits"].items()])
|
| 99 |
warning_section += f"""
|
| 100 |
#### {emoji} {t["severity"]} β {t["message"]}
|
| 101 |
- **Keywords found:** {kw_str}
|
| 102 |
+
- **Frequency:** {t["count"]} mention(s)
|
| 103 |
"""
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
if any(t["severity"] == "CRITICAL" for t in triggered):
|
| 106 |
overall = "π¨ CRITICAL β Immediate intervention required"
|
| 107 |
elif any(t["severity"] == "HIGH" for t in triggered):
|
|
|
|
| 111 |
else:
|
| 112 |
overall = "π’ ALL CLEAR β No immediate warnings"
|
| 113 |
|
| 114 |
+
spike_alert = "\n> π¨ **SPIKE DETECTED:** Risk increased significantly in last 48h!\n" if spike else ""
|
| 115 |
+
|
| 116 |
+
alert_list = "\n".join([
|
| 117 |
+
f"- **{t['severity']}** β {t['message']}" for t in triggered
|
| 118 |
+
]) if triggered else "- No alerts triggered."
|
| 119 |
+
|
| 120 |
+
warning_text = f"""## β‘ Early Warning System
|
| 121 |
|
| 122 |
### Overall Status: {overall}
|
| 123 |
{spike_alert}
|
| 124 |
|
| 125 |
---
|
| 126 |
|
| 127 |
+
### π Warning Triggers
|
| 128 |
{warning_section}
|
| 129 |
|
| 130 |
---
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
### π¬ Auto-Alert Summary
|
| 133 |
+
{alert_list}
|
| 134 |
|
| 135 |
+
---
|
| 136 |
+
*(Risk trend chart shown below)*
|
| 137 |
"""
|
| 138 |
+
return warning_text, trend_df
|