Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1265,8 +1265,13 @@ except Exception as e:
|
|
| 1265 |
# ✅ Panel 3: ONLY Coverage = 100% AND Slope > 0 → Avg/Month
|
| 1266 |
# ✅ Estetik: Sortable, Hover, Zebra, PLN Blue, No Emoticons
|
| 1267 |
|
| 1268 |
-
|
|
|
|
|
|
|
|
|
|
| 1269 |
|
|
|
|
|
|
|
| 1270 |
|
| 1271 |
# ✅ Enhanced CSS + Minimal Sortable JS
|
| 1272 |
st.markdown("""
|
|
@@ -1404,7 +1409,6 @@ function makeSortable(tableId) {
|
|
| 1404 |
}
|
| 1405 |
setTimeout(() => {
|
| 1406 |
makeSortable('tbl-locations');
|
| 1407 |
-
makeSortable('tbl-divisions');
|
| 1408 |
makeSortable('tbl-issues');
|
| 1409 |
}, 800);
|
| 1410 |
</script>
|
|
@@ -1473,58 +1477,14 @@ def predict_locations(df):
|
|
| 1473 |
df_res = pd.DataFrame(results)
|
| 1474 |
return df_res.sort_values('Trend Slope', ascending=True) if not df_res.empty else df_res # most negative first
|
| 1475 |
|
| 1476 |
-
# # ——————— 2. Divisions ———————
|
| 1477 |
-
# def predict_divisions(df):
|
| 1478 |
-
# if 'nama' not in df.columns:
|
| 1479 |
-
# return pd.DataFrame()
|
| 1480 |
-
|
| 1481 |
-
# start_month = df['created_at'].min().to_period('M')
|
| 1482 |
-
# end_month = df['created_at'].max().to_period('M')
|
| 1483 |
-
# all_months = pd.period_range(start=start_month, end=end_month, freq='M')
|
| 1484 |
-
|
| 1485 |
-
# df_monthly = (
|
| 1486 |
-
# df.groupby(['nama', df['created_at'].dt.to_period('M')])
|
| 1487 |
-
# .size()
|
| 1488 |
-
# .unstack(fill_value=0)
|
| 1489 |
-
# .reindex(columns=all_months, fill_value=0)
|
| 1490 |
-
# .stack()
|
| 1491 |
-
# .reset_index(name='count')
|
| 1492 |
-
# )
|
| 1493 |
-
# df_monthly.columns = ['Division', 'Month', 'Count']
|
| 1494 |
-
|
| 1495 |
-
# results = []
|
| 1496 |
-
# for div, group in df_monthly.groupby('Division'):
|
| 1497 |
-
# ts = group.set_index('Month')['Count']
|
| 1498 |
-
# total = len(all_months)
|
| 1499 |
-
# active = (ts > 0).sum()
|
| 1500 |
-
# gaps = total - active
|
| 1501 |
-
# coverage = active / total if total > 0 else 0
|
| 1502 |
-
|
| 1503 |
-
# if gaps > 2:
|
| 1504 |
-
# status = "<span class='status-inactive'>Inactive</span>"
|
| 1505 |
-
# elif gaps == 0:
|
| 1506 |
-
# status = "<span class='status-active'>Active</span>"
|
| 1507 |
-
# else:
|
| 1508 |
-
# status = "<span class='status-neutral'>Neutral</span>"
|
| 1509 |
-
|
| 1510 |
-
# bar = ''.join(['●' if c > 0 else '○' for c in ts.values])
|
| 1511 |
-
# trend_line = f"<span class='spark' style='color:#003DA5;'>{bar}</span>"
|
| 1512 |
-
# results.append({
|
| 1513 |
-
# 'Division': div,
|
| 1514 |
-
# 'Active Months': int(active),
|
| 1515 |
-
# 'Total Months': int(total),
|
| 1516 |
-
# 'Coverage (%)': round(coverage * 100, 1),
|
| 1517 |
-
# 'Status': status,
|
| 1518 |
-
# 'Trend': trend_line
|
| 1519 |
-
# })
|
| 1520 |
-
# df_res = pd.DataFrame(results)
|
| 1521 |
-
# return df_res.sort_values('Coverage (%)', ascending=True) if not df_res.empty else df_res
|
| 1522 |
-
|
| 1523 |
# ——————— 3. Issues: ONLY Coverage=100% & Trend Slope > 0 → Avg/Month ———————
|
| 1524 |
def predict_issues(df):
|
| 1525 |
if 'kategori' not in df.columns or df.empty:
|
| 1526 |
return pd.DataFrame()
|
| 1527 |
|
|
|
|
|
|
|
|
|
|
| 1528 |
start_month = df['created_at'].min().to_period('M')
|
| 1529 |
end_month = df['created_at'].max().to_period('M')
|
| 1530 |
all_months = pd.period_range(start=start_month, end=end_month, freq='M')
|
|
@@ -1576,7 +1536,6 @@ def predict_issues(df):
|
|
| 1576 |
|
| 1577 |
# ——————— RUN ———————
|
| 1578 |
df_loc = predict_locations(df_filtered)
|
| 1579 |
-
df_div = predict_divisions(df_filtered)
|
| 1580 |
df_issue = predict_issues(df_filtered)
|
| 1581 |
|
| 1582 |
# 🎯 PANEL 1: Locations (FILTERED: Coverage < 90% & Slope < 0)
|
|
@@ -1615,46 +1574,11 @@ if not df_loc.empty:
|
|
| 1615 |
# )
|
| 1616 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 1617 |
|
| 1618 |
-
#
|
| 1619 |
-
# st.markdown("<div class='predictive-panel'>", unsafe_allow_html=True)
|
| 1620 |
-
# st.markdown("<div class='predictive-header'>2. Which Areas Are Likely to Be Frequently or Rarely Inspected?</div>", unsafe_allow_html=True)
|
| 1621 |
-
# if not df_div.empty:
|
| 1622 |
-
# cols = ['Division', 'Active Months', 'Total Months', 'Coverage (%)', 'Status', 'Trend']
|
| 1623 |
-
|
| 1624 |
-
# # 🔵 Rename ONLY for display (Status → Forecast Inspection)
|
| 1625 |
-
# df_display = df_div[cols].rename(columns={
|
| 1626 |
-
# "Status": "Forecast Inspection"
|
| 1627 |
-
# })
|
| 1628 |
-
|
| 1629 |
-
# html = df_display.to_html(escape=False, index=False, table_id="tbl-divisions")
|
| 1630 |
-
# st.markdown(f"<div class='predictive-table-wrapper'>{html}</div>", unsafe_allow_html=True)
|
| 1631 |
-
|
| 1632 |
-
# # st.markdown(
|
| 1633 |
-
# # "<div class='predictive-note'>"
|
| 1634 |
-
# # "<strong>Forecast Inspection:</strong> "
|
| 1635 |
-
# # "<span class='status-active'>Active</span> (0 gaps), "
|
| 1636 |
-
# # "<span class='status-neutral'>Neutral</span> (1–2 gaps), "
|
| 1637 |
-
# # "<span class='status-inactive'>Inactive</span> (>2 gaps)."
|
| 1638 |
-
# # "</div>",
|
| 1639 |
-
# # unsafe_allow_html=True
|
| 1640 |
-
# # )
|
| 1641 |
-
# # else:
|
| 1642 |
-
# # st.markdown(
|
| 1643 |
-
# # "<div class='predictive-table-wrapper'>"
|
| 1644 |
-
# # "<p style='text-align:center; color:#666; padding:24px; font-style:italic;'>"
|
| 1645 |
-
# # "Insufficient division data (≥2 months required)."
|
| 1646 |
-
# # "</p></div>",
|
| 1647 |
-
# # unsafe_allow_html=True
|
| 1648 |
-
# # )
|
| 1649 |
-
|
| 1650 |
-
# st.markdown("</div>", unsafe_allow_html=True)
|
| 1651 |
-
|
| 1652 |
-
|
| 1653 |
-
# 🎯 PANEL 3: Issues (FILTERED: Coverage=100% & Rising)
|
| 1654 |
st.markdown("<div class='predictive-panel'>", unsafe_allow_html=True)
|
| 1655 |
st.markdown(
|
| 1656 |
"<div class='predictive-header'>"
|
| 1657 |
-
"3. Which Issue Categories Are Likely to Appear in the Next 3 Months"
|
| 1658 |
"<span style='font-size:0.75em; font-weight:400; color:#003DA5;'>"
|
| 1659 |
" (* Categorization uses NLP — Natural Language Processing from random text)"
|
| 1660 |
"</span>"
|
|
@@ -1696,8 +1620,6 @@ if not df_issue.empty:
|
|
| 1696 |
|
| 1697 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 1698 |
|
| 1699 |
-
|
| 1700 |
-
|
| 1701 |
# =================== 6. ✅ AI INSIGHT ENGINE (BARU - BERDASARKAN DATA & RATIO) ===================
|
| 1702 |
|
| 1703 |
st.markdown("<h3 class='section-title'>OBJECTIVE 7 - Insight and Recommendation</h3>", unsafe_allow_html=True)
|
|
|
|
| 1265 |
# ✅ Panel 3: ONLY Coverage = 100% AND Slope > 0 → Avg/Month
|
| 1266 |
# ✅ Estetik: Sortable, Hover, Zebra, PLN Blue, No Emoticons
|
| 1267 |
|
| 1268 |
+
import streamlit as st
|
| 1269 |
+
import plotly.graph_objects as go
|
| 1270 |
+
import numpy as np
|
| 1271 |
+
import pandas as pd
|
| 1272 |
|
| 1273 |
+
# =================== OBJECTIVE 6 - Predictive Dashboard & Early Warning Signals ===================
|
| 1274 |
+
st.markdown("<h3 class='section-title'>OBJECTIVE 6 — Predictive Dashboard & Early Warning Signals</h3>", unsafe_allow_html=True)
|
| 1275 |
|
| 1276 |
# ✅ Enhanced CSS + Minimal Sortable JS
|
| 1277 |
st.markdown("""
|
|
|
|
| 1409 |
}
|
| 1410 |
setTimeout(() => {
|
| 1411 |
makeSortable('tbl-locations');
|
|
|
|
| 1412 |
makeSortable('tbl-issues');
|
| 1413 |
}, 800);
|
| 1414 |
</script>
|
|
|
|
| 1477 |
df_res = pd.DataFrame(results)
|
| 1478 |
return df_res.sort_values('Trend Slope', ascending=True) if not df_res.empty else df_res # most negative first
|
| 1479 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1480 |
# ——————— 3. Issues: ONLY Coverage=100% & Trend Slope > 0 → Avg/Month ———————
|
| 1481 |
def predict_issues(df):
|
| 1482 |
if 'kategori' not in df.columns or df.empty:
|
| 1483 |
return pd.DataFrame()
|
| 1484 |
|
| 1485 |
+
# 🔥 Filter: Hanya yang bukan 'Positive'
|
| 1486 |
+
df = df[df['kategori'] != 'Positive'].copy() # ✅ Filter non-Positive
|
| 1487 |
+
|
| 1488 |
start_month = df['created_at'].min().to_period('M')
|
| 1489 |
end_month = df['created_at'].max().to_period('M')
|
| 1490 |
all_months = pd.period_range(start=start_month, end=end_month, freq='M')
|
|
|
|
| 1536 |
|
| 1537 |
# ——————— RUN ———————
|
| 1538 |
df_loc = predict_locations(df_filtered)
|
|
|
|
| 1539 |
df_issue = predict_issues(df_filtered)
|
| 1540 |
|
| 1541 |
# 🎯 PANEL 1: Locations (FILTERED: Coverage < 90% & Slope < 0)
|
|
|
|
| 1574 |
# )
|
| 1575 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 1576 |
|
| 1577 |
+
# 🎯 PANEL 3: Issues (FILTERED: Coverage=100% & Rising) — Hanya Non-Positive
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1578 |
st.markdown("<div class='predictive-panel'>", unsafe_allow_html=True)
|
| 1579 |
st.markdown(
|
| 1580 |
"<div class='predictive-header'>"
|
| 1581 |
+
"3. Which Issue Categories Are Likely to Appear in the Next 3 Months (Non-Positive Only)"
|
| 1582 |
"<span style='font-size:0.75em; font-weight:400; color:#003DA5;'>"
|
| 1583 |
" (* Categorization uses NLP — Natural Language Processing from random text)"
|
| 1584 |
"</span>"
|
|
|
|
| 1620 |
|
| 1621 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 1622 |
|
|
|
|
|
|
|
| 1623 |
# =================== 6. ✅ AI INSIGHT ENGINE (BARU - BERDASARKAN DATA & RATIO) ===================
|
| 1624 |
|
| 1625 |
st.markdown("<h3 class='section-title'>OBJECTIVE 7 - Insight and Recommendation</h3>", unsafe_allow_html=True)
|