yufengchen commited on
Commit
bce1ccf
·
verified ·
1 Parent(s): ad9d5c8

Initial Commit

Browse files
Files changed (3) hide show
  1. README.md +14 -14
  2. app.py +271 -0
  3. requirements.txt +7 -0
README.md CHANGED
@@ -1,14 +1,14 @@
1
- ---
2
- title: SEC10QInsight
3
- emoji: 🏢
4
- colorFrom: pink
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 5.33.1
8
- app_file: app.py
9
- pinned: false
10
- license: cc-by-nc-sa-4.0
11
- short_description: SEC 10-Q data explorer with AI insights
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ ---
2
+ title: SEC10QInsight
3
+ emoji:
4
+ colorFrom: purple
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 5.33.0
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ short_description: SEC 10-Q data explorer with AI insights
12
+ tags:
13
+ - mcp-server-track
14
+ ---
app.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from random import randint, random
2
+ import gradio as gr
3
+ import pandas as pd
4
+ import requests
5
+ import os
6
+ import json
7
+ from openai import OpenAI
8
+ import matplotlib.pyplot as plt
9
+
10
+ # Flag to indicate MCP server mode
11
+ mcp_server = True
12
+
13
+ # SEC API settings
14
+ SEC_API_URL = "https://data.sec.gov/api/xbrl/companyfacts/CIK{}.json"
15
+ USER_AGENT = os.environ.get("USER_AGENT", "Your Name your.email@example.com")
16
+
17
+ # Sample CIK list
18
+ CIK_OPTIONS = {
19
+ "Tesla (TSLA)": "0001318605",
20
+ "Apple (AAPL)": "0000320193",
21
+ "Microsoft (MSFT)": "0000789019"
22
+ }
23
+
24
+ # SambaNova API settings
25
+ SAMBANOVA_API_URL = "https://api.cloud.sambanova.ai/v1/chat/completions"
26
+ SAMBANOVA_API_KEY = os.environ.get("SAMBANOVA_API_KEY") # Set in your environment
27
+
28
+ def fetch_comprehensive_income_net_of_tax(cik):
29
+ """
30
+ Fetch 'ComprehensiveIncomeNetOfTax' USD values from SEC 10-Q filings for a given CIK.
31
+
32
+ Args:
33
+ cik (str): Central Index Key (CIK) of the company.
34
+
35
+ Returns:
36
+ pd.DataFrame: DataFrame of values and metadata for 'ComprehensiveIncomeNetOfTax'.
37
+ """
38
+ headers = {"User-Agent": USER_AGENT}
39
+ url = SEC_API_URL.format(cik)
40
+
41
+ print(f"Fetching data from SEC API for CIK: {cik} at URL: {url}")
42
+
43
+ try:
44
+ response = requests.get(url, headers=headers)
45
+ data = response.json()
46
+
47
+ # Navigate directly to the desired metric
48
+ item_data = data.get("facts", {}).get("us-gaap", {}).get("ComprehensiveIncomeNetOfTax", {})
49
+ usd_entries = item_data.get("units", {}).get("USD", [])
50
+
51
+ # print(f'usd_entries: {usd_entries}')
52
+
53
+ filtered_entries = [
54
+ {
55
+ # "Metric": "ComprehensiveIncomeNetOfTax",
56
+ "Frame": entry.get("frame"),
57
+ "Value": entry.get("val"),
58
+ "Period": f"{entry.get('fy')}{entry.get('fp')}",
59
+ "Form": entry.get("form"),
60
+ "Filed": entry.get("filed")
61
+ }
62
+ for entry in usd_entries
63
+ if entry.get("form") == "10-Q" and entry.get("frame")
64
+ ]
65
+
66
+ # print(f'filtered_entries: {filtered_entries}')
67
+
68
+ return pd.DataFrame(filtered_entries)
69
+
70
+ except requests.RequestException as e:
71
+ print(f"Error fetching SEC data: {e}")
72
+ return pd.DataFrame({"Error": [str(e)]})
73
+
74
+
75
+ # Generate response using SambaNova
76
+ def get_sambanova_response(query, data):
77
+ context = f"SEC data: {json.dumps(data.to_dict() if not data.empty and 'Error' not in data.columns else {})}. User query: {query}"
78
+ # headers = {
79
+ # "Authorization": f"Bearer {SAMBANOVA_API_KEY}",
80
+ # "Content-Type": "application/json"
81
+ # }
82
+ # payload = {
83
+ # "model": "sambanova-chat", # Replace with actual model name if different
84
+ # "messages": [
85
+ # {
86
+ # "role": "system",
87
+ # "content": "You are a financial data assistant. Provide concise answers based on SEC data, including trends or summaries where applicable."
88
+ # },
89
+ # {"role": "user", "content": context}
90
+ # ],
91
+ # "max_tokens": 2000
92
+ # }
93
+
94
+ messages = [
95
+ {
96
+ "role": "system",
97
+ "content": "You are a financial data assistant. Provide concise answers based on SEC data, including trends or summaries where applicable."
98
+ },
99
+ {"role": "user", "content": context}
100
+ ]
101
+
102
+ # print(f"Sending request to SambaNova API with payload: {json.dumps(payload, indent=2)}")
103
+ # print(f"Using headers: {headers}")
104
+ # print(f'Context: {context}')
105
+
106
+ try:
107
+ sambanova_client = OpenAI(
108
+ api_key = SAMBANOVA_API_KEY,
109
+ base_url = "https://api.sambanova.ai/v1",
110
+ )
111
+
112
+ response = sambanova_client.chat.completions.create(
113
+ model = "Llama-4-Maverick-17B-128E-Instruct",
114
+ messages = messages,
115
+ temperature = 0.1,
116
+ top_p = 0.1,
117
+ )
118
+
119
+ # print(f"SambaNova response: {response}")
120
+
121
+ # response = requests.post(SAMBANOVA_API_URL, headers=headers, json=payload)
122
+ # result = response.json()
123
+ # return result["choices"][0]["message"]["content"]
124
+ return response.choices[0].message.content
125
+ except Exception as e:
126
+ return f"Error: {str(e)}"
127
+
128
+ # Method to visualize numerical data
129
+ def visualize_data(data):
130
+ """
131
+ Generate a line plot using matplotlib and use 'Frame' as the x-axis.
132
+
133
+ Args:
134
+ data (pd.DataFrame): DataFrame containing 'Value' and 'Frame' columns.
135
+
136
+ Returns:
137
+ tuple: Gradio Plot object and visibility flag.
138
+ """
139
+ if data.empty or "Error" in data.columns:
140
+ return gr.update(value="No data to visualize"), False
141
+
142
+ df = data.copy()
143
+ if "Value" not in df.columns or "Frame" not in df.columns:
144
+ return gr.update(value="Missing 'Value' or 'Frame' in data"), False
145
+
146
+ df["Value"] = pd.to_numeric(df["Value"], errors="coerce")
147
+ df = df[df["Value"].notna() & df["Frame"].notna()]
148
+
149
+ if df.empty:
150
+ return gr.update(value="No valid data to plot"), False
151
+
152
+ # Sort frames in lexical order
153
+ df_sorted = df.sort_values(by="Frame")
154
+ x = df_sorted["Frame"]
155
+ y = df_sorted["Value"]
156
+
157
+ return {
158
+ "plot_data": {
159
+ "Frame": x,
160
+ "Value": y
161
+ },
162
+ "plot_visible": True
163
+ }
164
+
165
+ # MCP server endpoint to handle queries
166
+ def mcp_query(query_data):
167
+ query = query_data.get("query", "")
168
+ cik_name = query_data.get("cik_name", "Apple (AAPL)")
169
+ cik = CIK_OPTIONS.get(cik_name)
170
+
171
+ print(f"Received query: {query} for CIK: {cik}")
172
+
173
+ if not cik or not query:
174
+ raise HTTPException(status_code=400, detail="Invalid CIK or query")
175
+
176
+ df = fetch_comprehensive_income_net_of_tax(cik)
177
+
178
+ # print(f"Fetched data for CIK {cik}:\n {df}")
179
+
180
+ if df.empty or "Error" in df.columns:
181
+ raise HTTPException(status_code=500, detail="Error fetching data")
182
+
183
+ response = get_sambanova_response(query, df)
184
+
185
+ # print(f"SambaNova response: {response}")
186
+
187
+ v_data = visualize_data(df)
188
+
189
+ return {
190
+ "response": response,
191
+ "data": df.to_dict() if not df.empty else {},
192
+ "plot_data": v_data.get("plot_data", {}),
193
+ "plot_visible": v_data.get("plot_visible", False),
194
+ }
195
+
196
+ def process_interface(cik_name, query):
197
+ if not query.strip():
198
+ return "❌ Please enter a query.", gr.update(value=None), gr.update(value="No plot", visible=False)
199
+
200
+ result = mcp_query({"query": query, "cik_name": cik_name})
201
+ # print(f"Processing interface with result: {result}")
202
+
203
+ if "error" in result:
204
+ return result["error"], gr.update(value=None), gr.update(value="Error", visible=False)
205
+
206
+ df = pd.DataFrame(result.get("data", {})) if result.get("data") else pd.DataFrame()
207
+
208
+ # Plot using matplotlib
209
+ if result["plot_visible"] and result.get("plot_data"):
210
+ plot_df = pd.DataFrame(result["plot_data"])
211
+ fig, ax = plt.subplots(figsize=(12, 4))
212
+ ax.plot(plot_df["Frame"], plot_df["Value"], marker="o")
213
+ ax.set_title("Trend Over Time")
214
+ ax.set_xlabel("Frame")
215
+ ax.set_ylabel("Value")
216
+ ax.grid(True)
217
+ # Rotate + reduce number of ticks
218
+ ax.set_xticks(ax.get_xticks()[::2]) # Show every 4th tick
219
+ plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
220
+
221
+ plt.subplots_adjust(top=0.85) # ✅ Fix top overlap
222
+
223
+ plt.tight_layout()
224
+ plot = gr.Plot(fig)
225
+ else:
226
+ plot = gr.update(value=None, visible=False)
227
+
228
+ return result["response"], df, plot
229
+
230
+ # Gradio UI
231
+ with gr.Blocks() as demo:
232
+ gr.Markdown("# SEC Data Query Interface")
233
+
234
+ with gr.Row(): # ✅ Your preferred layout
235
+ cik_dropdown = gr.Dropdown(
236
+ choices=["Apple (AAPL)", "Tesla (TSLA)", "Microsoft (MSFT)"],
237
+ value="Apple (AAPL)",
238
+ label="Select Company"
239
+ )
240
+ query_input = gr.Textbox(
241
+ label="Enter your query (e.g., 'Show trends')",
242
+ lines=1,
243
+ value="Show trends"
244
+ )
245
+
246
+ with gr.Row():
247
+ submit_button = gr.Button("Submit")
248
+
249
+ with gr.Row():
250
+ gr.Markdown("### 📝 Response")
251
+ with gr.Row():
252
+ output_text = gr.Textbox(interactive=False)
253
+
254
+ with gr.Row():
255
+ gr.Markdown("### 📈 Visualization")
256
+ with gr.Row():
257
+ output_plot = gr.Plot(label=".", visible=True)
258
+
259
+ with gr.Row():
260
+ gr.Markdown("### 📊 Financial Metrics")
261
+ with gr.Row():
262
+ output_table = gr.DataFrame()
263
+
264
+ submit_button.click(
265
+ fn=process_interface,
266
+ inputs=[cik_dropdown, query_input],
267
+ outputs=[output_text, output_table, output_plot]
268
+ )
269
+
270
+ if __name__ == "__main__":
271
+ demo.launch(mcp_server=True)
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio[mcp]
2
+ gradio
3
+ pandas
4
+ matplotlib
5
+ requests
6
+ openai
7
+ fastapi