-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
182 lines (155 loc) · 7.92 KB
/
app.py
File metadata and controls
182 lines (155 loc) · 7.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import streamlit as st
import folium
from streamlit_folium import st_folium
from folium.plugins import HeatMap
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
import time
import json
import os
# 1. Page Configuration
st.set_page_config(page_title="PineGuard Strategic Analysis", layout="wide")
# Industrial-grade CSS
st.markdown("""
<style>
.stApp { background-color: #0E1117; }
.signature { position: absolute; top: 10px; right: 10px; color: #8B949E; font-family: 'Courier New', monospace; font-size: 0.9rem; z-index: 999; }
.metric-container { display: flex; justify-content: space-between; margin-bottom: 25px; gap: 15px; }
.metric-card { background-color: #161B22; border: 1px solid #30363D; padding: 20px; border-radius: 6px; flex: 1; }
.status-light { height: 10px; width: 10px; border-radius: 50%; display: inline-block; margin-right: 8px; }
.metric-label { color: #8B949E; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 1px; }
.metric-value { color: #C9D1D9; font-size: 1.8rem; font-weight: 700; font-family: 'Courier New', monospace; }
</style>
<div class="signature">Author: Tanghao Chen (Dios)</div>
""", unsafe_allow_html=True)
st.title("PineGuard: Strategic Spatio-Temporal Analysis")
st.markdown("---")
# --- Sidebar ---
st.sidebar.header("Operational Module")
module = st.sidebar.radio("Select System Mode", ["Historical Observation", "Strategic Projection"])
if 'obs_year' not in st.session_state: st.session_state.obs_year = 1984
if 'proj_year' not in st.session_state: st.session_state.proj_year = 2026
if 'playing' not in st.session_state: st.session_state.playing = False
# Color configurations
if module == "Historical Observation":
st.sidebar.subheader("Observation Control")
year = st.sidebar.slider("Historical Year", 1984, 2025, value=st.session_state.obs_year)
st.session_state.obs_year = year
point_color = "#E63946"
heatmap_gradient = {0.2: '#0000FF', 0.4: '#00FFFF', 0.6: '#FFFF00', 0.9: '#FF0000'}
mode_tag = "OBSERVATION"
else:
st.sidebar.subheader("Projection Settings")
year = st.sidebar.slider("Projection Year", 2026, 2045, value=st.session_state.proj_year)
st.session_state.proj_year = year
point_color = "#00D4FF"
heatmap_gradient = {0.1: '#01012b', 0.3: '#0000FF', 0.6: '#00D4FF', 1.0: '#FFFFFF'}
mode_tag = "STRATEGIC PROJECTION"
# Play Logic
btn_col1, btn_col2 = st.sidebar.columns(2)
if btn_col1.button("Play"):
if module == "Historical Observation": st.session_state.obs_year = 1984
else: st.session_state.proj_year = 2026
st.session_state.playing = True
st.rerun()
if btn_col2.button("Pause"):
st.session_state.playing = False
show_heatmap = st.sidebar.checkbox("Enable Heatmap", value=True)
map_style = st.sidebar.selectbox("Base Layer", ["Satellite (Google)", "Terrain (OSM)"])
# --- NEW: Direct File Loading Functions (No API needed) ---
def load_local_data(target_year):
"""Reads processed JSON files directly from the data/processed folder"""
file_path = os.path.join("data", "processed", f"stress_{target_year}.json")
if os.path.exists(file_path):
with open(file_path, "r") as f:
return json.load(f)
return None
def get_historical_stats():
"""Generates statistics from local files for model training"""
stats = []
for y in range(1984, 2026):
data = load_local_data(y)
if data:
stats.append({"year": y, "outbreak_count": data["outbreak_count"]})
return pd.DataFrame(stats)
try:
# 1. Train Model using local data
df_stats = get_historical_stats()
if df_stats.empty:
st.error("Data repository not found. Ensure 'data/processed/' contains the JSON files.")
st.stop()
model = LinearRegression().fit(df_stats[['year']], df_stats['outbreak_count'])
center = [37.1174, -119.6043]
display_year = st.session_state.obs_year if module == "Historical Observation" else st.session_state.proj_year
# 2. Logic for loading or predicting data
if module == "Historical Observation":
data = load_local_data(display_year)
if data:
locations = data["locations"]
count = data["outbreak_count"]
else:
st.warning(f"No local data found for year {display_year}")
locations, count = [], 0
else:
# Prediction Logic
count = int(model.predict([[display_year]])[0])
np.random.seed(display_year)
spread = 0.08 + (display_year - 2025) * 0.005
lats = np.random.uniform(center[0]-spread, center[0]+spread, count)
lons = np.random.uniform(center[1]-spread, center[1]+spread, count)
scores = np.random.uniform(0.4, 0.9, count)
locations = [{"latitude": lat, "longitude": lon, "stress_score": score} for lat, lon, score in zip(lats, lons, scores)]
# --- Analytics & Dashboard ---
hotspot = max(locations, key=lambda x: x['stress_score']) if locations else None
velocity = 1.2 + (display_year - 1984) * 0.05 if display_year > 1984 else 0.0
if count < 15: risk_lvl, risk_col = "STABLE", "#28A745"
elif count < 80: risk_lvl, risk_col = "WATCH", "#FFC107"
else: risk_lvl, risk_col = "ALERT", "#DC3545"
st.markdown(f"""
<div class="metric-container">
<div class="metric-card"><div class="metric-label">Observation Year</div><div class="metric-value">{display_year}</div></div>
<div class="metric-card"><div class="metric-label">Anomalies Detected</div><div class="metric-value">{count}</div></div>
<div class="metric-card"><div class="metric-label">Spread Velocity</div><div class="metric-value">{velocity:.1f} km/yr</div></div>
<div class="metric-card">
<div class="metric-label">Security Status</div>
<div class="metric-value"><span class="status-light" style="background-color:{risk_col};"></span>{risk_lvl}</div>
</div>
</div>
""", unsafe_allow_html=True)
col_map, col_data = st.columns([3, 1.2])
with col_map:
tiles = 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}' if map_style == "Satellite (Google)" else 'OpenStreetMap'
m = folium.Map(location=center, zoom_start=10, tiles=tiles, attr='PineGuard GIS')
heat_data = [[l["latitude"], l["longitude"], l["stress_score"]] for l in locations]
if show_heatmap and heat_data:
HeatMap(heat_data, radius=18, blur=15, gradient=heatmap_gradient, min_opacity=0.3).add_to(m)
for l in locations:
folium.CircleMarker([l["latitude"], l["longitude"]], radius=3, color=point_color, fill=True, weight=1).add_to(m)
if hotspot:
folium.Marker(
[hotspot["latitude"], hotspot["longitude"]],
icon=folium.Icon(color="white", icon_color=risk_col, icon="warning-sign"),
tooltip="Primary Critical Hotspot"
).add_to(m)
st_folium(m, width=900, height=550, key=f"map_{module}_{display_year}")
with col_data:
st.subheader("Inventory Registry")
df_display = pd.DataFrame(locations).rename(columns={'latitude':'LAT','longitude':'LON','stress_score':'SCORE'})
st.dataframe(df_display.style.format("{:.4f}"), height=400)
csv = df_display.to_csv(index=False).encode('utf-8')
st.download_button("Export Dataset (CSV)", csv, f"pineguard_{mode_tag}_{display_year}.csv", "text/csv", use_container_width=True)
# Play Engine
if st.session_state.playing:
time.sleep(0.4)
if module == "Historical Observation" and st.session_state.obs_year < 2025:
st.session_state.obs_year += 1
st.rerun()
elif module == "Strategic Projection" and st.session_state.proj_year < 2045:
st.session_state.proj_year += 1
st.rerun()
else:
st.session_state.playing = False
st.rerun()
except Exception as e:
st.error(f"Operational Error: {e}")