Portal Designer — GeoSupply Streamlit Dashboard
Custom GeoSupply skill — Layer 0 admin portal + end-user intelligence portal
Portal Architecture
GeoSupply Portal (Streamlit)
├── Admin Portal (Layer 0)
│ ├── Agent Health Monitor
│ ├── Budget Dashboard
│ ├── Override Manager
│ ├── Audit Log Viewer
│ └── System Controls
└── Intelligence Portal (End Users)
├── Risk Score Dashboard
├── Supply Chain Map (India)
├── Supplier Risk Table
├── Weekly Report Viewer
└── Alert Feed
Auth: JWT tokens (portal:read for users, portal:admin for admin)
Framework: Streamlit >= 1.30.0
Location: src/geosupply/portal/
1. App Structure
python
1# src/geosupply/portal/app.py — main entry point
2
3import streamlit as st
4from geosupply.portal.pages import (
5 admin_health, admin_budget, admin_overrides,
6 intel_risk_map, intel_suppliers, intel_alerts
7)
8from geosupply.portal.auth import verify_jwt_token
9
10st.set_page_config(
11 page_title="GeoSupply AI",
12 page_icon="🌏",
13 layout="wide",
14 initial_sidebar_state="expanded",
15)
16
17# Auth gate
18token = st.session_state.get("jwt_token")
19if not token or not verify_jwt_token(token):
20 _show_login_page()
21 st.stop()
22
23# Role-based routing
24scopes = st.session_state.get("jwt_scopes", [])
25if "portal:admin" in scopes:
26 _render_admin_sidebar()
27else:
28 _render_user_sidebar()
2. Admin Portal Pages
Agent Health Monitor (admin_health.py)
python
1def render_health_page(health_agent):
2 st.title("🤖 Agent Health Monitor")
3
4 col1, col2, col3, col4 = st.columns(4)
5 with col1:
6 st.metric("Active Agents", "8/8", delta="0")
7 with col2:
8 st.metric("Pipeline SLA", "8.3 min", delta="-1.2 min", delta_color="normal")
9 with col3:
10 st.metric("Tasks/Hour", "142", delta="+12")
11 with col4:
12 st.metric("Error Rate", "0.7%", delta="-0.2%", delta_color="inverse")
13
14 # Agent state table
15 health_data = asyncio.run(health_agent.execute("check_all", {}))
16 df = pd.DataFrame([
17 {
18 "Agent": name,
19 "State": info["state"],
20 "Last Seen": info["last_seen"],
21 "Tasks Done": info["tasks_done"],
22 "Cost (₹)": f"₹{info['cost_inr']:.2f}",
23 }
24 for name, info in health_data["result"]["agents"].items()
25 ])
26
27 def color_state(val):
28 colors = {"IDLE": "green", "BUSY": "blue", "ERROR": "red", "RECOVERY": "orange"}
29 return f"color: {colors.get(val, 'black')}"
30
31 st.dataframe(df.style.applymap(color_state, subset=["State"]), use_container_width=True)
Budget Dashboard (admin_budget.py)
python
1def render_budget_page(budget_agent):
2 st.title("💰 Budget Dashboard")
3
4 status = asyncio.run(budget_agent.execute("get_status", {}))["result"]
5 projection = asyncio.run(budget_agent.execute("get_projection", {}))["result"]
6
7 # Gauge: monthly burn
8 monthly_pct = (status["monthly_spend_inr"] / 500) * 100
9 col1, col2 = st.columns(2)
10 with col1:
11 st.metric(
12 "Monthly Spend",
13 f"₹{status['monthly_spend_inr']:.2f}",
14 delta=f"₹{500 - status['monthly_spend_inr']:.2f} remaining",
15 )
16 st.progress(monthly_pct / 100, text=f"{monthly_pct:.1f}% of ₹500 cap")
17
18 with col2:
19 if projection["days_until_exhaustion"]:
20 st.metric("Days Until Cap", f"{projection['days_until_exhaustion']:.0f} days")
21 st.metric("Burn Rate", f"₹{projection['burn_rate_inr_per_day']:.2f}/day")
22
23 # Tier breakdown bar chart
24 tier_data = status["spend_by_tier"]
25 if tier_data:
26 st.bar_chart(tier_data)
27
28 # Alert banner
29 if status["tier3_blocked"]:
30 st.error("🚨 Tier-3 calls BLOCKED — daily critical threshold exceeded")
31 elif status["daily_spend_inr"] >= 250:
32 st.warning("⚠️ Daily spend warning threshold reached")
3. Intelligence Portal Pages
Risk Score Dashboard (intel_risk_map.py)
python
1def render_risk_map():
2 st.title("🌏 India Supply Chain Risk Map")
3
4 # Region filter
5 selected_regions = st.multiselect(
6 "Filter Regions",
7 options=list(INDIA_RISK_REGIONS.keys()),
8 default=list(INDIA_RISK_REGIONS.keys()),
9 )
10
11 # Risk score cards
12 cols = st.columns(3)
13 for i, region in enumerate(selected_regions[:6]):
14 with cols[i % 3]:
15 score = _get_region_risk_score(region)
16 color = "🟢" if score < 0.3 else "🟡" if score < 0.55 else "🔴"
17 st.metric(
18 f"{color} {region}",
19 f"{score:.0%} risk",
20 help=f"Composite risk score for {region}",
21 )
22
23 # India map (plotly choropleth)
24 st.plotly_chart(_build_india_choropleth(selected_regions), use_container_width=True)
Supplier Risk Table (intel_suppliers.py)
python
1def render_supplier_table():
2 st.title("🏭 Supplier Risk Assessment")
3
4 # Search + filter
5 search = st.text_input("Search supplier name")
6 action_filter = st.selectbox("Filter by recommendation", ["ALL", "MONITOR", "DUAL_SOURCE", "REPLACE", "BLOCK"])
7
8 suppliers = _get_supplier_scores(search=search, action=action_filter)
9
10 # Color-coded table
11 df = pd.DataFrame(suppliers)
12 action_colors = {
13 "MONITOR": "background-color: #d4edda",
14 "DUAL_SOURCE":"background-color: #fff3cd",
15 "REPLACE": "background-color: #f8d7da",
16 "BLOCK": "background-color: #842029; color: white",
17 }
18
19 def highlight_action(row):
20 return [action_colors.get(row["recommended_action"], "")] * len(row)
21
22 st.dataframe(
23 df.style.apply(highlight_action, axis=1),
24 use_container_width=True,
25 height=400,
26 )
27
28 # Export
29 if st.button("Export to CSV"):
30 st.download_button("Download CSV", df.to_csv(index=False), "suppliers.csv")
4. Override Manager (admin_overrides.py)
python
1def render_overrides_page(override_agent):
2 st.title("⚙️ Manual Overrides")
3 st.warning("All overrides are logged, HMAC-signed, and reviewed by LoopholeHunter.")
4
5 # Recent override history
6 st.subheader("Recent Overrides")
7 overrides = asyncio.run(override_agent.execute("list_recent", {"limit": 20}))["result"]
8 st.dataframe(pd.DataFrame(overrides), use_container_width=True)
9
10 # New override (admin:portal scope required)
11 st.subheader("Submit Override")
12 with st.form("override_form"):
13 action = st.selectbox("Override Action", [
14 "force_approve_spend",
15 "retrigger_worker",
16 "clear_circuit_breaker",
17 "escalate_task_priority",
18 ])
19 reason = st.text_area("Reason (required)", max_chars=500)
20 submitted = st.form_submit_button("Submit Override")
21 if submitted:
22 if not reason.strip():
23 st.error("Reason is required for all overrides")
24 else:
25 result = asyncio.run(override_agent.execute("submit", {
26 "action": action, "reason": reason,
27 "admin_jwt": st.session_state["jwt_token"],
28 }))
29 st.success(f"Override submitted: {result['override_id']}")
5. Streamlit Worker (StreamlitWorker)
python
1class StreamlitWorker(BaseWorker):
2 """
3 Prepares aggregated dashboard data for Streamlit portal.
4 Tier: CPU_ONLY — just data aggregation, no LLM.
5 """
6 name = "streamlit"
7 tier = LLMTier.CPU_ONLY
8
9 async def process(self, input_data: dict) -> dict:
10 view = input_data.get("view", "risk_overview")
11 data_funcs = {
12 "risk_overview": self._aggregate_risk_data,
13 "supplier_table": self._aggregate_supplier_data,
14 "budget_status": self._aggregate_budget_data,
15 "agent_health": self._aggregate_health_data,
16 "alert_feed": self._aggregate_alert_data,
17 }
18 if view not in data_funcs:
19 return self._error("VALIDATION", f"Unknown view: {view}", input_data)
20
21 result = await data_funcs[view](input_data)
22 return {"result": result, "meta": {"cost_inr": 0.0}}
6. Portal File Structure
src/geosupply/portal/
├── __init__.py
├── app.py # Main Streamlit app
├── auth.py # JWT verification, session management
├── pages/
│ ├── admin_health.py
│ ├── admin_budget.py
│ ├── admin_overrides.py
│ ├── admin_audit_log.py
│ ├── intel_risk_map.py
│ ├── intel_suppliers.py
│ ├── intel_alerts.py
│ └── intel_reports.py
├── components/
│ ├── risk_gauge.py # Reusable risk score gauge
│ ├── india_map.py # Plotly India choropleth
│ └── cost_chart.py # Budget visualization components
└── data/
└── india_geojson.json # India states GeoJSON for maps
7. Portal Launch
bash
1# Run portal (development)
2PYTHONPATH=src streamlit run src/geosupply/portal/app.py \
3 --server.port 8501 \
4 --server.address 0.0.0.0
budget-controller — Budget data displayed in admin portal
product-manager — User stories driving portal features
india-intel — India risk map data source
supply-chain-analyst — Supplier table data source