Denisijcu commited on
Commit
c738fc9
·
verified ·
1 Parent(s): b4ba022

Update app/dashboard.py

Browse files
Files changed (1) hide show
  1. app/dashboard.py +142 -189
app/dashboard.py CHANGED
@@ -1,189 +1,142 @@
1
- import streamlit as st
2
- import requests
3
- import plotly.graph_objects as go
4
- import plotly.express as px
5
- import pandas as pd
6
- from fpdf import FPDF
7
- import json
8
- import os
9
-
10
- # --- CONFIGURACIÓN DE RUTAS Y RED ---
11
- BACKEND_URL = os.getenv("BACKEND_URL", "http://vertex-backend:8010")
12
- WATCHLIST_FILE = "app/watchlist.json"
13
-
14
- def load_watchlist():
15
- try:
16
- if os.path.exists(WATCHLIST_FILE):
17
- with open(WATCHLIST_FILE, "r") as f:
18
- return json.load(f)
19
- except Exception: pass
20
- return ["INTC", "TSLA", "AAPL", "SAVE"]
21
-
22
- def save_watchlist(watchlist):
23
- os.makedirs(os.path.dirname(WATCHLIST_FILE), exist_ok=True)
24
- with open(WATCHLIST_FILE, "w") as f:
25
- json.dump(watchlist, f)
26
-
27
- # --- MOTOR DE REPORTES PDF ---
28
- class VertexReport(FPDF):
29
- def header(self):
30
- self.set_font('Arial', 'B', 15)
31
- self.cell(0, 10, 'VERTEX CODERS LLC - AUDIT REPORT', 0, 1, 'C')
32
- self.ln(10)
33
-
34
- def generate_pdf(data):
35
- pdf = VertexReport()
36
- pdf.set_auto_page_break(auto=True, margin=15)
37
- pdf.add_page()
38
- pdf.set_font("Arial", 'B', 14)
39
- pdf.set_fill_color(151, 231, 225)
40
- pdf.cell(0, 12, f"AUDIT REPORT: {data.get('ticker', 'N/A')}", 1, 1, 'C', fill=True)
41
- pdf.ln(5)
42
- pdf.set_font("Arial", 'B', 12)
43
- pdf.cell(0, 10, f"FINAL STATUS: {data.get('status', 'UNKNOWN')}", 0, 1)
44
- semantic = data.get("semantic_analysis", {})
45
- msg = str(semantic.get('summary', data.get('msg', 'No data'))).encode('latin-1', 'replace').decode('latin-1')
46
- pdf.ln(5)
47
- pdf.set_font("Arial", 'B', 11)
48
- pdf.cell(0, 10, "1. EXECUTIVE VERDICT", 0, 1)
49
- pdf.set_font("Arial", size=10)
50
- pdf.multi_cell(0, 8, msg)
51
- pdf.ln(5)
52
- pdf.set_font("Arial", 'B', 11)
53
- pdf.cell(0, 10, "2. FINANCIAL METRICS", 0, 1)
54
- z_score = data.get("numeric_analysis", {}).get("altman_z") or data.get("z_score", 0)
55
- pdf.cell(0, 8, f"- Altman Z-Score: {float(z_score):.2f}", 0, 1)
56
- return pdf.output(dest='S')
57
-
58
- # --- CONFIGURACIÓN DE LA UI ---
59
- st.set_page_config(page_title="Vertex Risk Terminal | Némesis", page_icon="🛡️", layout="wide")
60
-
61
- if "watchlist" not in st.session_state:
62
- st.session_state.watchlist = load_watchlist()
63
-
64
- # --- SIDEBAR ---
65
- st.sidebar.title("🏢 Stock Watchlist")
66
- new_ticker = st.sidebar.text_input("Add Ticker").upper()
67
-
68
- if st.sidebar.button("➕ Add"):
69
- if new_ticker and new_ticker not in st.session_state.watchlist:
70
- st.session_state.watchlist.append(new_ticker)
71
- save_watchlist(st.session_state.watchlist)
72
- st.sidebar.success(f" {new_ticker} saved!")
73
- st.rerun()
74
-
75
- selected_ticker = st.sidebar.selectbox("Analyze Company", st.session_state.watchlist)
76
-
77
- st.sidebar.divider()
78
- st.sidebar.subheader("📡 Bunker Status")
79
- def check_health(url):
80
- try: return "🟢 ONLINE" if requests.get(url, timeout=2).status_code == 200 else "🔴 OFFLINE"
81
- except: return "🔴 OFFLINE"
82
-
83
- st.sidebar.write(f"Backend Engine: {check_health(BACKEND_URL + '/docs')}")
84
-
85
- # --- CUERPO PRINCIPAL ---
86
- st.title("🛡️ Vertex Risk Terminal")
87
- st.caption("Quantum Risk Analysis Platform | Enterprise Edition")
88
-
89
- # REPARACIÓN DE TABS: Declaración única de las 4 pestañas
90
- tab1, tab2, tab3, tab4, tab5 = st.tabs(["📈 Stock Audit", "🔗 Web3 Audit", "🔍 Auditoría Individual", "📊 Comparativa Vertex", "⚙️ Settings"])
91
-
92
- with tab1:
93
- if st.button("🚀 RUN FULL STOCK AUDIT", type="primary", use_container_width=True):
94
- with st.spinner(f"Auditing {selected_ticker} through Némesis Engine..."):
95
- try:
96
- r = requests.get(f"{BACKEND_URL}/audit/{selected_ticker}", timeout=25)
97
- r.raise_for_status()
98
- st.session_state.last_audit = r.json()
99
- st.rerun()
100
- except Exception as e:
101
- st.error(f"🔌 Connection Failure: {e}")
102
-
103
- if "last_audit" in st.session_state:
104
- res = st.session_state.last_audit
105
- st.divider()
106
- col_l, col_r = st.columns(2)
107
- with col_l:
108
- st.metric("FINAL STATUS", res.get("status", "UNKNOWN"))
109
- z_val = res.get("numeric_analysis", {}).get("altman_z") or res.get("z_score", 0)
110
- fig = go.Figure(go.Indicator(
111
- mode="gauge+number",
112
- value=float(z_val),
113
- gauge={'axis': {'range': [0, 5]},
114
- 'steps': [{'range': [0, 1.1], 'color': "lightcoral"},
115
- {'range': [1.1, 2.9], 'color': "lightyellow"},
116
- {'range': [2.9, 5], 'color': "lightgreen"}]}))
117
- fig.update_layout(height=300)
118
- st.plotly_chart(fig, use_container_width=True)
119
- with col_r:
120
- st.subheader("🧠 Semantic Analysis")
121
- sem = res.get("semantic_analysis", {})
122
- st.info(sem.get("summary", res.get("msg", "No additional data.")))
123
- st.download_button("📥 DOWNLOAD PDF REPORT", generate_pdf(res), f"Vertex_{selected_ticker}.pdf", "application/pdf", use_container_width=True)
124
-
125
- with tab2:
126
- st.subheader("🔗 Web3 Smart Contract Scanner")
127
- contract = st.text_input("Dirección del Token (0x...)")
128
- if st.button("🔍 SCAN WEB3 ASSET", use_container_width=True):
129
- if contract:
130
- with st.spinner("Escaneando seguridad..."):
131
- try:
132
- r = requests.get(f"{BACKEND_URL}/audit_contract/{contract}", timeout=120)
133
- res_w3 = r.json()
134
- st.divider()
135
- status_w3 = res_w3.get("status", "UNKNOWN")
136
- if status_w3 == "SAFE": st.success(f"✅ STATUS: {status_w3}")
137
- elif status_w3 == "DANGER": st.error(f"🚨 STATUS: {status_w3}")
138
-
139
- vulns = res_w3.get("vulnerabilities", [])
140
- if vulns:
141
- for v in vulns: st.error(f"**{v['description']}**")
142
- with st.expander("Ver Código Fuente"):
143
- st.code(res_w3.get("source_preview", ""), language='solidity')
144
- except Exception as e: st.error(f"Error: {e}")
145
-
146
- with tab3:
147
- st.subheader("🔍 Auditoría Individual")
148
- st.write(f"Vigilancia activa sobre: **{selected_ticker}**")
149
- st.info("Este módulo utiliza análisis heurístico para reportes rápidos.")
150
-
151
- with tab4:
152
- st.subheader("📊 Comparativa de Salud Financiera")
153
- comparison_list = st.multiselect("Compañías:", options=st.session_state.watchlist, default=st.session_state.watchlist[:3])
154
- if st.button("📊 GENERAR COMPARATIVA", use_container_width=True):
155
- comp_data = []
156
- with st.spinner("Calculando ranking..."):
157
- for t in comparison_list:
158
- try:
159
- r = requests.get(f"{BACKEND_URL}/audit/{t}", timeout=10)
160
- if r.status_code == 200:
161
- res = r.json()
162
- z = res.get("numeric_analysis", {}).get("altman_z") or res.get("z_score", 0)
163
- comp_data.append({"Ticker": t, "Z-Score": float(z)})
164
- except: continue
165
- if comp_data:
166
- df = pd.DataFrame(comp_data)
167
- fig_bar = px.bar(df, x='Ticker', y='Z-Score', color='Z-Score', color_continuous_scale=['red', 'yellow', 'green'], range_y=[0, 5])
168
- fig_bar.add_hline(y=1.1, line_dash="dash", line_color="red")
169
- fig_bar.add_hline(y=2.9, line_dash="dash", line_color="green")
170
- st.plotly_chart(fig_bar, use_container_width=True)
171
-
172
- with tab5:
173
- st.header("⚙️ Configuración del Sistema")
174
- st.info("Configura las credenciales de Telegram para que Némesis te envíe alertas automáticas.")
175
-
176
- # Cargar configuraciones actuales si existen
177
- if "settings" not in st.session_state:
178
- st.session_state.settings = {"bot_token": "", "chat_id": ""}
179
-
180
- with st.form("settings_form"):
181
- bot_token = st.text_input("Telegram Bot Token", value=st.session_state.settings["bot_token"], type="password", help="El token que te dio BotFather")
182
- chat_id = st.text_input("Telegram Chat ID", value=st.session_state.settings["chat_id"], help="Tu ID de usuario o el del grupo")
183
-
184
- if st.form_submit_button("💾 Guardar Configuración"):
185
- st.session_state.settings = {"bot_token": bot_token, "chat_id": chat_id}
186
- # Aquí guardaríamos en un archivo que n8n vigile
187
- with open("app/settings.json", "w") as f:
188
- json.dump(st.session_state.settings, f)
189
- st.success("✅ Configuración guardada. n8n ahora usará estas credenciales.")
 
1
+ import streamlit as st
2
+ import requests
3
+ import plotly.graph_objects as go
4
+ from fpdf import FPDF
5
+ import json
6
+ import os
7
+
8
+ # --- CONFIGURACIÓN DE URL (EL PUENTE HACIA TU BACKEND) ---
9
+ # Cambiamos el localhost por tu URL real de Hugging Face
10
+ BACKEND_URL = "https://denisijcu-vertex-risk-engine.hf.space"
11
+
12
+ # --- PERSISTENCE CONFIG ---
13
+ WATCHLIST_FILE = "watchlist.json"
14
+
15
+ def load_watchlist():
16
+ if os.path.exists(WATCHLIST_FILE):
17
+ with open(WATCHLIST_FILE, "r") as f:
18
+ return json.load(f)
19
+ return ["INTC", "TSLA", "AAPL"]
20
+
21
+ def save_watchlist(watchlist):
22
+ with open(WATCHLIST_FILE, "w") as f:
23
+ json.dump(watchlist, f)
24
+
25
+ # --- CONFIG ---
26
+ st.set_page_config(
27
+ page_title="Vertex Risk Terminal | Némesis Engine",
28
+ page_icon="🛡️",
29
+ layout="wide"
30
+ )
31
+
32
+ if "watchlist" not in st.session_state:
33
+ st.session_state.watchlist = load_watchlist()
34
+
35
+ # --- SIDEBAR: WATCHLIST & ADD ---
36
+ st.sidebar.title("🏢 Stock Watchlist")
37
+ new_ticker = st.sidebar.text_input("Add Company Ticker").upper()
38
+ if st.sidebar.button(" Add"):
39
+ if new_ticker and new_ticker not in st.session_state.watchlist:
40
+ st.session_state.watchlist.append(new_ticker)
41
+ save_watchlist(st.session_state.watchlist)
42
+ st.sidebar.success(f" {new_ticker} saved!")
43
+
44
+ selected_ticker = st.sidebar.selectbox("Analyze Company", st.session_state.watchlist)
45
+
46
+ # --- PDF GENERATOR ---
47
+ class VertexReport(FPDF):
48
+ def header(self):
49
+ self.set_font('Arial', 'B', 15)
50
+ self.cell(0, 10, 'VERTEX CODERS LLC - AUDIT REPORT', 0, 1, 'C')
51
+ self.ln(10)
52
+
53
+ def generate_pdf(data):
54
+ pdf = VertexReport()
55
+ pdf.set_auto_page_break(auto=True, margin=15)
56
+ pdf.add_page()
57
+ pdf.set_text_color(0, 0, 0)
58
+ pdf.set_font("Arial", 'B', 14)
59
+ pdf.set_fill_color(151, 231, 225)
60
+ pdf.cell(0, 12, f"AUDIT REPORT: {data.get('ticker', 'N/A')}", 1, 1, 'C', fill=True)
61
+ pdf.ln(5)
62
+ pdf.set_font("Arial", 'B', 12)
63
+ pdf.cell(0, 10, f"FINAL STATUS: {data.get('status', 'UNKNOWN')}", 0, 1)
64
+ pdf.ln(5)
65
+ pdf.set_font("Arial", 'B', 11)
66
+ pdf.cell(0, 10, "1. EXECUTIVE VERDICT", 0, 1)
67
+ pdf.set_font("Arial", size=10)
68
+ msg = str(data.get('msg', 'No data')).encode('latin-1', 'replace').decode('latin-1')
69
+ pdf.multi_cell(0, 8, msg)
70
+ pdf.ln(5)
71
+ pdf.set_font("Arial", 'B', 11)
72
+ pdf.cell(0, 10, "2. FINANCIAL METRICS", 0, 1)
73
+ pdf.set_font("Arial", size=10)
74
+ z_score = data.get("numeric_analysis", {}).get("altman_z", 0)
75
+ pdf.cell(0, 8, f"- Altman Z-Score: {float(z_score):.2f}", 0, 1)
76
+ return pdf.output(dest='S')
77
+
78
+ # --- MAIN UI ---
79
+ st.title("🛡️ Vertex Risk Terminal")
80
+ st.caption("Enterprise-Level Analysis Platform | Némesis Engine v1.0")
81
+
82
+ tab1, tab2 = st.tabs(["📈 Stock Audit", "🔗 Web3 Audit (Rug Pull Detect)"])
83
+
84
+ with tab1:
85
+ run_audit = st.button("🚀 RUN FULL STOCK AUDIT", type="primary")
86
+
87
+ if run_audit:
88
+ with st.spinner(f"🔍 Analyzing {selected_ticker}..."):
89
+ try:
90
+ # Usamos la constante BACKEND_URL definida arriba
91
+ r = requests.get(f"{BACKEND_URL}/audit/{selected_ticker}", timeout=30)
92
+ r.raise_for_status()
93
+ data = r.json()
94
+ if data.get("status") == "ERROR":
95
+ st.error(f"❌ {data.get('msg', 'Unknown error')}")
96
+ else:
97
+ st.session_state.last_audit = data
98
+ st.rerun()
99
+ except Exception as e:
100
+ st.error(f"🔌 Error conectando al búnker: {e}")
101
+
102
+ if "last_audit" in st.session_state:
103
+ data = st.session_state.last_audit
104
+ st.success("✅ Audit completed!")
105
+ col1, col2 = st.columns(2)
106
+ with col1:
107
+ status = data.get("status", "UNKNOWN")
108
+ st.metric("FINAL STATUS", f"🛡️ {status}")
109
+ st.info(data.get("msg", "No message"))
110
+
111
+ z = data.get("numeric_analysis", {}).get("altman_z", 0)
112
+ fig = go.Figure(go.Indicator(
113
+ mode="gauge+number",
114
+ value=z,
115
+ gauge={'axis': {'range': [0, 5]}, 'steps': [
116
+ {'range': [0, 1.8], 'color': "red"},
117
+ {'range': [1.8, 3], 'color': "yellow"},
118
+ {'range': [3, 5], 'color': "green"}]}
119
+ ))
120
+ st.plotly_chart(fig, use_container_width=True)
121
+
122
+ with col2:
123
+ st.subheader("🧠 Semantic Analysis")
124
+ st.write(data.get("semantic_analysis", {}).get("summary", "No summary available."))
125
+
126
+ with tab2:
127
+ st.subheader("Smart Contract Security Scanner")
128
+ contract_address = st.text_input("Enter Ethereum Contract Address (0x...)")
129
+
130
+ if st.button("🔍 SCAN SMART CONTRACT"):
131
+ if contract_address:
132
+ with st.spinner("Scanning..."):
133
+ try:
134
+ # Corregido el error de concatenación que tenías
135
+ r = requests.get(f"{BACKEND_URL}/audit_contract/{contract_address}", timeout=60)
136
+ contract_data = r.json()
137
+ st.warning(f"Audit Status: {contract_data['status']}")
138
+ if contract_data.get("vulnerabilities"):
139
+ for v in contract_data["vulnerabilities"]:
140
+ st.write(f"- 🚩 {v['pattern']}: {v['risk']}")
141
+ except Exception as e:
142
+ st.error(f"Connection Error: {e}")