AshenH commited on
Commit
6524787
·
verified ·
1 Parent(s): ff521d6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -216
app.py CHANGED
@@ -1,217 +1,67 @@
1
- import gradio as gr
2
- import pandas as pd
3
- import numpy as np
4
  import os
5
- import statsmodels.api as sm
6
- from io import StringIO
7
-
8
- # --- LangChain Imports ---
9
- from langchain_groq import ChatGroq
10
- from langchain.agents import AgentExecutor, create_tool_calling_agent
11
- from langchain_core.prompts import ChatPromptTemplate
12
- from langchain_core.tools import tool
13
- from langchain_core.messages import SystemMessage
14
-
15
- # --- ASSUMPTION ---
16
- # Assuming you have a file named 'sql_tools.py' in the same directory
17
- # with your pre-built and decorated @tool functions.
18
- try:
19
- from sql_tools import run_duckdb_query, get_table_schema
20
- except ImportError:
21
- print("WARNING: Could not import from 'sql_tools.py'.")
22
- print("Using placeholder functions. Please create 'sql_tools.py'.")
23
-
24
- # Create placeholder tools if the file is missing, so the app can start
25
- @tool
26
- def run_duckdb_query(query: str) -> str:
27
- """
28
- [PLACEHOLDER] Runs a read-only SQL query.
29
- Please create sql_tools.py to implement this.
30
- """
31
- if "schema" in query.lower() or "describe" in query.lower():
32
- return "report_date DATE, portfolio_id VARCHAR, sector VARCHAR, market_value_usd DOUBLE"
33
- return "Error: 'sql_tools.py' not found. This is a placeholder."
34
-
35
- @tool
36
- def get_table_schema(table_name: str = "positions") -> str:
37
- """
38
- [PLACEHOLDER] Returns the schema for the 'positions' table.
39
- Please create sql_tools.py to implement this.
40
- """
41
- return "report_date DATE, portfolio_id VARCHAR, sector VARCHAR, market_value_usd DOUBLE"
42
-
43
-
44
- # --- Agent Tools ---
45
- # These tools perform analysis on data *after* it has been fetched.
46
-
47
- @tool
48
- def calculate_summary_statistics_from_data(data_string: str, column: str) -> str:
49
- """
50
- Calculates summary statistics (mean, median, std, min, max) for a specific
51
- 'column' from a 'data_string'.
52
- 'data_string' should be the string output from the `run_duckdb_query` tool.
53
- """
54
- try:
55
- # Convert the string data back into a DataFrame
56
- data_df = pd.read_csv(StringIO(data_string.strip()), delim_whitespace=True, header=0)
57
-
58
- # HACK: The string output might have an extra index column, let's find the real columns
59
- if column not in data_df.columns:
60
- # Try reading again, assuming first column is an unnamed index
61
- data_df = pd.read_csv(StringIO(data_string.strip()), delim_whitespace=True, header=0, index_col=0)
62
- if column not in data_df.columns:
63
- return f"Error: Column '{column}' not found in data."
64
-
65
- stats = {
66
- "column": column,
67
- "mean": data_df[column].mean(),
68
- "median": data_df[column].median(),
69
- "std_dev": data_df[column].std(),
70
- "min": data_df[column].min(),
71
- "max": data_df[column].max(),
72
- "count": data_df[column].count()
73
- }
74
- return str(stats)
75
- except Exception as e:
76
- return f"Error in calculate_summary_statistics: {e}. Data input was: '{data_string[:200]}...'"
77
-
78
- @tool
79
- def perform_arima_forecast_from_data(data_string: str, time_column: str, value_column: str, forecast_periods: int) -> str:
80
- """
81
- Performs an ARIMA(1,1,1) forecast on a 'data_string'.
82
- 'data_string': The string output from `run_duckdb_query`.
83
- 'time_column': The name of the date/time column in the data.
84
- 'value_column': The name of the numerical column to forecast.
85
- 'forecast_periods': The number of periods (e.g., days) to forecast.
86
-
87
- The data MUST be ordered by the time_column before being passed to this tool.
88
- """
89
- try:
90
- # Convert the string data back into a DataFrame
91
- data_df = pd.read_csv(StringIO(data_string.strip()), delim_whitespace=True, header=0)
92
-
93
- # HACK: The string output might have an extra index column
94
- if time_column not in data_df.columns:
95
- data_df = pd.read_csv(StringIO(data_string.strip()), delim_whitespace=True, header=0, index_col=0)
96
- if time_column not in data_df.columns:
97
- return f"Error: Time column '{time_column}' not found in data."
98
-
99
- if value_column not in data_df.columns:
100
- return f"Error: Value column '{value_column}' not found in data."
101
-
102
- if data_df.empty:
103
- return "Error: Query returned no data."
104
-
105
- # Prepare data for statsmodels
106
- data_df[time_column] = pd.to_datetime(data_df[time_column])
107
- data_df = data_df.set_index(time_column)
108
- data_df = data_df.asfreq('D') # Ensure daily frequency, fill gaps if any
109
- data_df[value_column] = data_df[value_column].fillna(method='ffill')
110
-
111
- model = sm.tsa.ARIMA(data_df[value_column], order=(1, 1, 1))
112
- results = model.fit()
113
- forecast = results.forecast(steps=forecast_periods)
114
-
115
- forecast_df = pd.DataFrame({
116
- 'date': forecast.index.strftime('%Y-%m-%d'),
117
- 'forecasted_value': forecast.values
118
- })
119
-
120
- return f"Forecast successful. Last historical value was {data_df[value_column].iloc[-1]:.2f}.\nForecast:\n{forecast_df.to_string()}"
121
-
122
- except Exception as e:
123
- return f"Error in perform_arima_forecast: {e}. Data input was: '{data_string[:200]}...'"
124
-
125
- # --- Main Agent and UI Setup ---
126
-
127
- # Check for the GROQ_API_KEY in Hugging Face Space Secrets
128
- if "GROQ_API_KEY" not in os.environ:
129
- print("GROQ_API_KEY not found in secrets!")
130
- def missing_key_error(message, history):
131
- return "Error: `GROQ_API_KEY` is not set in this Space's Secrets. Please add it to use the app."
132
-
133
- gr.ChatInterface(
134
- missing_key_error,
135
- title="Agentic Portfolio Analyst",
136
- description="Error: GROQ_API_KEY secret is missing."
137
- ).launch()
138
-
139
- else:
140
- print("GROQ_API_KEY found. Initializing agent...")
141
- llm = ChatGroq(model_name="llama-3.3-70b-versatile")
142
-
143
- # 2. Collect all our tools (imported and local)
144
- tools = [
145
- run_duckdb_query,
146
- get_table_schema,
147
- calculate_summary_statistics_from_data,
148
- perform_arima_forecast_from_data
149
- ]
150
-
151
- # 3. Create the Agent Prompt
152
- system_prompt = """
153
- You are an expert portfolio analyst. You have access to SQL tools and analysis tools.
154
-
155
- Your logic MUST follow these steps:
156
- 1. Use `get_table_schema` to understand the data.
157
- 2. Use `run_duckdb_query` to fetch the raw data you need.
158
- 3. If analysis (statistics or forecasting) is needed, take the string output
159
- from `run_duckdb_query` and pass it *directly* to either
160
- `calculate_summary_statistics_from_data` or `perform_arima_forecast_from_data`.
161
-
162
- Example for forecasting:
163
- 1. Call `run_duckdb_query("SELECT report_date, SUM(market_value_usd) AS total_value FROM positions WHERE sector = 'Tech' GROUP BY report_date ORDER BY report_date")`.
164
- 2. Get the result string: " report_date total_value \n 2024-01-01 100000.0 \n 2024-01-02 100500.0 \n ..."
165
- 3. Call `perform_arima_forecast_from_data(data_string=" report_date total_value \n 2024-01-01 100000.0 \n ...", time_column="report_date", value_column="total_value", forecast_periods=30)`.
166
-
167
- Answer the user's request based on the final tool output.
168
- """
169
-
170
- prompt = ChatPromptTemplate.from_messages(
171
- [
172
- SystemMessage(content=system_prompt),
173
- ("placeholder", "{chat_history}"),
174
- ("human", "{input}"),
175
- ("placeholder", "{agent_scratchpad}"),
176
- ]
177
- )
178
-
179
- # 4. Create the Agent
180
- agent = create_tool_calling_agent(llm, tools, prompt)
181
-
182
- # 5. Create the Agent Executor
183
- agent_executor = AgentExecutor(
184
- agent=agent,
185
- tools=tools,
186
- verbose=True
187
- )
188
-
189
- # 6. Define the function for Gradio
190
- def run_agent(message, history):
191
- chat_history = []
192
- for human_msg, ai_msg in history:
193
- chat_history.append(("human", human_msg))
194
- chat_history.append(("ai", ai_msg))
195
-
196
- try:
197
- response = agent_executor.invoke({
198
- "input": message,
199
- "chat_history": chat_history
200
- })
201
- return response["output"]
202
- except Exception as e:
203
- return f"An error occurred: {e}"
204
-
205
- # 7. Launch the Gradio App
206
- gr.ChatInterface(
207
- run_agent,
208
- title="Agentic Portfolio Analyst",
209
- description="Ask me questions about your portfolio. (This app uses imported SQL tools).",
210
- examples=[
211
- "What is the schema of the positions table?",
212
- "What's the total market value by sector on the last available date?",
213
- "Give me summary statistics for the 'Tech' sector's market value from portfolio P-123. Use the 'market_value_usd' column for stats.",
214
- "What is the 30-day forecast for the total market value of portfolio P-123? Use 'total_value' for the forecast value column."
215
- ]
216
- ).launch()
217
-
 
 
 
 
1
  import os
2
+ up = res.shocks.loc[res.shocks['bucket']==res.shocks['bucket'].unique()[0], 'dPV_up_100bp'].sum()
3
+ dn = res.shocks.loc[res.shocks['bucket']==res.shocks['bucket'].unique()[0], 'dPV_dn_100bp'].sum()
4
+ # Better: show net across buckets
5
+ net_up = res.shocks['dPV_up_100bp'].sum()
6
+ net_dn = res.shocks['dPV_dn_100bp'].sum()
7
+ y -= 2*mm
8
+ line(f"+100bp net ΔPV: {net_up:,.0f} LKR | -100bp net ΔPV: {net_dn:,.0f} LKR")
9
+ c.showPage()
10
+ c.save()
11
+ return out
12
+
13
+
14
+ # ---------- Gradio UI ----------
15
+
16
+
17
+ def run_dashboard() -> Tuple[str, float, float, float, Any, Any, Any, Any, Any, Any, Any]:
18
+ conn = connect_md()
19
+ res = fetch_all(conn)
20
+ fig = plot_ladder(res.ladder)
21
+ excel_path = export_excel(res)
22
+ pdf_path = export_pdf(res)
23
+ return (
24
+ res.as_of_date,
25
+ res.assets_t1,
26
+ res.sof_t1,
27
+ res.net_gap_t1,
28
+ fig,
29
+ res.t1_by_month,
30
+ res.t1_by_segment,
31
+ res.t1_by_ccy,
32
+ res.irr,
33
+ res.shocks,
34
+ str(excel_path),
35
+ str(pdf_path),
36
+ )
37
+
38
+
39
+ with gr.Blocks(title=APP_TITLE) as demo:
40
+ gr.Markdown(f"# {APP_TITLE}\n*Source:* `my_db.main.masterdataset_v` → `positions_v` | *Sign:* Assets=+ SoF=–")
41
+ with gr.Row():
42
+ btn = gr.Button("🔄 Refresh", variant="primary")
43
+ with gr.Row():
44
+ as_of = gr.Textbox(label="As of date", interactive=False)
45
+ with gr.Row():
46
+ k1 = gr.Number(label="Assets T+1 (LKR)", precision=0)
47
+ k2 = gr.Number(label="SoF T+1 (LKR)", precision=0)
48
+ k3 = gr.Number(label="Net Gap T+1 (LKR)", precision=0)
49
+ chart = gr.Plot(label="Maturity Ladder")
50
+ with gr.Row():
51
+ t1m = gr.Dataframe(label="T+1 by Tenor (months)")
52
+ t1s = gr.Dataframe(label="T+1 by Segment")
53
+ t1c = gr.Dataframe(label="T+1 by Currency")
54
+ irr = gr.Dataframe(label="Interest-Rate Risk (bucketed)")
55
+ shocks = gr.Dataframe(label="Parallel Shock ±100bp (bucketed)")
56
+
57
+
58
+ with gr.Row():
59
+ excel_file = gr.File(label="Excel export", interactive=False)
60
+ pdf_file = gr.File(label="PDF export", interactive=False)
61
+
62
+
63
+ btn.click(fn=run_dashboard, outputs=[as_of, k1, k2, k3, chart, t1m, t1s, t1c, irr, shocks, excel_file, pdf_file])
64
+
65
+
66
+ if __name__ == "__main__":
67
+ demo.launch()