|
1 | | -import streamlit as st |
2 | | -import pandas as pd |
3 | | -import sqlite3 |
4 | | -from datetime import date |
5 | | -import os |
6 | | - |
7 | | -# Set page config |
8 | | -st.set_page_config(page_title="The Vault - Point One Percent Dashboard", layout="wide") |
9 | | - |
10 | | -st.title("The Vault - AgentPay Dashboard") |
11 | | - |
12 | | -# Database path - located in project root |
13 | | -DB_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "pop_state.db")) |
14 | | - |
15 | | - |
16 | | -def _ensure_settings_table(conn: sqlite3.Connection) -> None: |
17 | | - """Create the dashboard_settings table if it does not exist.""" |
18 | | - conn.execute( |
19 | | - "CREATE TABLE IF NOT EXISTS dashboard_settings " |
20 | | - "(key TEXT PRIMARY KEY, value TEXT)" |
21 | | - ) |
22 | | - conn.commit() |
23 | | - |
24 | | - |
25 | | -def _read_setting(key: str, default: str = "") -> str: |
26 | | - """Read a setting from the dashboard_settings table.""" |
27 | | - if not os.path.exists(DB_PATH): |
28 | | - return default |
29 | | - try: |
30 | | - with sqlite3.connect(DB_PATH) as conn: |
31 | | - _ensure_settings_table(conn) |
32 | | - row = conn.execute( |
33 | | - "SELECT value FROM dashboard_settings WHERE key = ?", (key,) |
34 | | - ).fetchone() |
35 | | - return row[0] if row else default |
36 | | - except Exception: |
37 | | - return default |
38 | | - |
39 | | - |
40 | | -def _write_setting(key: str, value: str) -> None: |
41 | | - """Write a setting to the dashboard_settings table (upsert).""" |
42 | | - with sqlite3.connect(DB_PATH) as conn: |
43 | | - _ensure_settings_table(conn) |
44 | | - conn.execute( |
45 | | - "INSERT INTO dashboard_settings (key, value) VALUES (?, ?) " |
46 | | - "ON CONFLICT(key) DO UPDATE SET value = excluded.value", |
47 | | - (key, value), |
48 | | - ) |
49 | | - conn.commit() |
50 | | - |
51 | | - |
52 | | -# Read persisted slider value (default 500) |
53 | | -_saved_budget = int(_read_setting("max_daily_budget", "500")) |
54 | | - |
55 | | -# Sidebar |
56 | | -st.sidebar.header("Vault Settings") |
57 | | -max_daily_budget = st.sidebar.slider("Max Daily Budget ($)", 10, 2000, _saved_budget) |
58 | | - |
59 | | -# Write back to DB whenever the slider value changes |
60 | | -if max_daily_budget != _saved_budget: |
61 | | - _write_setting("max_daily_budget", str(max_daily_budget)) |
62 | | - |
63 | | -if st.sidebar.button("Refresh Data"): |
64 | | - st.rerun() |
65 | | - |
66 | | -# Helper function to get data |
67 | | -def load_data(): |
68 | | - if not os.path.exists(DB_PATH): |
69 | | - # Return empty structures if DB doesn't exist |
70 | | - return pd.DataFrame(columns=["seal_id", "amount", "vendor", "status", "timestamp"]), 0.0 |
71 | | - |
72 | | - with sqlite3.connect(DB_PATH) as conn: |
73 | | - try: |
74 | | - # Main Screen: Load all issued seals |
75 | | - issued_df = pd.read_sql_query("SELECT * FROM issued_seals ORDER BY timestamp DESC", conn) |
76 | | - except (pd.errors.DatabaseError, sqlite3.OperationalError): |
77 | | - # Table doesn't exist yet |
78 | | - issued_df = pd.DataFrame(columns=["seal_id", "amount", "vendor", "status", "timestamp"]) |
79 | | - |
80 | | - try: |
81 | | - # Budget Tracking: Query daily_budget for today's spent_amount |
82 | | - today = date.today().isoformat() |
83 | | - budget_query = "SELECT spent_amount FROM daily_budget WHERE date = ?" |
84 | | - budget_df = pd.read_sql_query(budget_query, conn, params=(today,)) |
85 | | - spent_today = budget_df['spent_amount'].iloc[0] if not budget_df.empty else 0.0 |
86 | | - except (pd.errors.DatabaseError, sqlite3.OperationalError): |
87 | | - spent_today = 0.0 |
88 | | - |
89 | | - return issued_df, spent_today |
90 | | - |
91 | | -# Load data |
92 | | -issued_df, spent_today = load_data() |
93 | | - |
94 | | -# Budget Tracking Section |
95 | | -remaining_budget = max(0.0, max_daily_budget - spent_today) |
96 | | - |
97 | | -col1, col2, col3 = st.columns(3) |
98 | | -col1.metric("Today's Spending", f"${spent_today:,.2f}") |
99 | | -col2.metric("Remaining Budget", f"${remaining_budget:,.2f}") |
100 | | -col3.metric("Max Daily Budget", f"${max_daily_budget:,.2f}") |
101 | | - |
102 | | -# Progress bar: spending relative to the slider's max budget |
103 | | -progress_val = min(1.0, spent_today / max_daily_budget) if max_daily_budget > 0 else 0 |
104 | | -st.write(f"**Budget Utilization ({progress_val*100:.1f}%)**") |
105 | | -st.progress(progress_val) |
106 | | - |
107 | | -st.write("---") |
108 | | - |
109 | | -# Main Screen: Issued Seals |
110 | | -st.subheader("Issued Seals & Activity") |
111 | | -if not issued_df.empty: |
112 | | - st.dataframe(issued_df, use_container_width=True) |
113 | | -else: |
114 | | - st.info("No records found in 'issued_seals' table.") |
115 | | - |
116 | | -# Rejected Summary (Optional) |
117 | | -st.write("---") |
118 | | -st.subheader("Rejected Summary") |
119 | | -if not issued_df.empty and 'status' in issued_df.columns: |
120 | | - rejected_df = issued_df[issued_df['status'].str.lower() == 'rejected'] |
121 | | - if not rejected_df.empty: |
122 | | - st.dataframe(rejected_df, use_container_width=True) |
123 | | - else: |
124 | | - st.success("No rejected attempts found.") |
125 | | -else: |
126 | | - st.info("No data available to show rejected attempts.") |
127 | | - |
128 | | -st.write("---") |
129 | | -st.markdown("*Point One Percent MVP Dashboard - Live Database Stream*") |
0 commit comments