JC321 commited on
Commit
fa4c2b2
·
verified ·
1 Parent(s): 98e3256

Upload 10 files

Browse files
Files changed (5) hide show
  1. API_ENHANCEMENTS.md +260 -0
  2. README.md +6 -0
  3. financial_analyzer.py +248 -0
  4. mcp_client_examples.json +129 -0
  5. mcp_server.py +134 -1
API_ENHANCEMENTS.md ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # API Enhancements Summary
2
+
3
+ ## Overview
4
+
5
+ The MCP Server has been enhanced to include all important methods from `FinancialAnalyzer`, providing both basic and advanced financial data analysis capabilities.
6
+
7
+ ## Total API Endpoints: 9
8
+
9
+ ### Basic Endpoints (6) - Using `EdgarDataClient`
10
+
11
+ 1. **POST /api/search_company**
12
+ - Search company by name
13
+ - Returns: CIK, name, ticker
14
+
15
+ 2. **POST /api/get_company_info**
16
+ - Get detailed company information
17
+ - Returns: Full company details with SIC
18
+
19
+ 3. **POST /api/get_company_filings**
20
+ - Get company SEC filings
21
+ - Supports: 10-K, 10-Q, 20-F, etc.
22
+
23
+ 4. **POST /api/get_company_facts**
24
+ - Get complete XBRL financial facts
25
+ - Returns: Raw financial data
26
+
27
+ 5. **POST /api/get_financial_data**
28
+ - Get financial data for specific period
29
+ - Supports: Annual (YYYY) and Quarterly (YYYYQX)
30
+
31
+ 6. **GET /health**
32
+ - Health check endpoint
33
+ - Returns: Server status
34
+
35
+ ### Advanced Endpoints (3) - Using `FinancialAnalyzer` ⭐
36
+
37
+ #### 7. POST /api/advanced_search
38
+ **Purpose**: Intelligent company search supporting both name and CIK
39
+
40
+ **From**: `FinancialAnalyzer.search_company()`
41
+
42
+ **Request**:
43
+ ```json
44
+ {
45
+ "company_input": "Apple" // or "0000320193"
46
+ }
47
+ ```
48
+
49
+ **Features**:
50
+ - Auto-detects input type (name vs CIK)
51
+ - Returns complete company information
52
+ - More intelligent than basic search
53
+
54
+ **Use Case**: When you don't know if user input is name or CIK
55
+
56
+ ---
57
+
58
+ #### 8. POST /api/extract_financial_metrics
59
+ **Purpose**: Extract multi-year financial metrics (annual + quarterly)
60
+
61
+ **From**: `FinancialAnalyzer.extract_financial_metrics()`
62
+
63
+ **Request**:
64
+ ```json
65
+ {
66
+ "cik": "0001045810",
67
+ "years": 3
68
+ }
69
+ ```
70
+
71
+ **Response**:
72
+ ```json
73
+ {
74
+ "metrics": [
75
+ {
76
+ "period": "2024",
77
+ "total_revenue": 60922000000,
78
+ "net_income": 29760000000,
79
+ "earnings_per_share": 12.04,
80
+ ...
81
+ },
82
+ {
83
+ "period": "2024Q4",
84
+ ...
85
+ },
86
+ {
87
+ "period": "2024Q3",
88
+ ...
89
+ }
90
+ ],
91
+ "count": 15
92
+ }
93
+ ```
94
+
95
+ **Features**:
96
+ - Returns both annual and quarterly data
97
+ - Automatically formats data
98
+ - Sorted by period (newest first)
99
+ - Configurable years (1-10)
100
+
101
+ **Use Cases**:
102
+ - Historical trend analysis
103
+ - Multi-period comparison
104
+ - Financial modeling
105
+ - Data visualization
106
+
107
+ ---
108
+
109
+ #### 9. POST /api/get_latest_financial_data
110
+ **Purpose**: Get the most recent annual financial data
111
+
112
+ **From**: `FinancialAnalyzer.get_latest_financial_data()`
113
+
114
+ **Request**:
115
+ ```json
116
+ {
117
+ "cik": "0001045810"
118
+ }
119
+ ```
120
+
121
+ **Response**:
122
+ ```json
123
+ {
124
+ "period": "2024",
125
+ "total_revenue": 60922000000,
126
+ "net_income": 29760000000,
127
+ "earnings_per_share": 12.04,
128
+ "operating_expenses": 11822000000,
129
+ "operating_cash_flow": 28091000000,
130
+ "source_url": "https://www.sec.gov/...",
131
+ "source_form": "10-K"
132
+ }
133
+ ```
134
+
135
+ **Features**:
136
+ - Automatically finds latest fiscal year
137
+ - No need to know the year
138
+ - Returns most recent 10-K or 20-F data
139
+
140
+ **Use Cases**:
141
+ - Quick company snapshot
142
+ - Current financial position
143
+ - Latest performance metrics
144
+
145
+ ---
146
+
147
+ ## Key Benefits
148
+
149
+ ### 1. **Flexibility**
150
+ - Basic endpoints for precise queries
151
+ - Advanced endpoints for intelligent analysis
152
+
153
+ ### 2. **Efficiency**
154
+ - `extract_financial_metrics` - Get multiple years in one call
155
+ - `get_latest_financial_data` - No need to determine fiscal year
156
+ - `advanced_search` - Flexible input handling
157
+
158
+ ### 3. **Complete Coverage**
159
+ All important `FinancialAnalyzer` methods are now exposed:
160
+ - ✅ `search_company()` → `/api/advanced_search`
161
+ - ✅ `extract_financial_metrics()` → `/api/extract_financial_metrics`
162
+ - ✅ `get_latest_financial_data()` → `/api/get_latest_financial_data`
163
+
164
+ ### 4. **Data Quality**
165
+ - Automatic data formatting
166
+ - Consistent response structure
167
+ - Comprehensive error handling
168
+
169
+ ## Comparison: Basic vs Advanced
170
+
171
+ | Feature | Basic Endpoint | Advanced Endpoint |
172
+ |---------|---------------|-------------------|
173
+ | Company Search | `/api/search_company` | `/api/advanced_search` |
174
+ | Input Type | Name only | Name or CIK |
175
+ | Return Data | Basic (CIK, name, ticker) | Complete (includes SIC) |
176
+ | | | |
177
+ | Financial Data | `/api/get_financial_data` | `/api/extract_financial_metrics` |
178
+ | Period | Single period | Multiple years |
179
+ | Data Type | Annual OR quarterly | Annual AND quarterly |
180
+ | Request Count | 1 per period | 1 for all periods |
181
+ | | | |
182
+ | Latest Data | `/api/get_financial_data` | `/api/get_latest_financial_data` |
183
+ | Year Required | Yes (must specify) | No (auto-detects) |
184
+ | Convenience | Manual | Automatic |
185
+
186
+ ## Usage Examples
187
+
188
+ ### Example 1: Quick Company Analysis
189
+
190
+ ```python
191
+ import requests
192
+
193
+ BASE_URL = "https://YOUR_SPACE.hf.space"
194
+
195
+ # Step 1: Advanced search (works with name or CIK)
196
+ response = requests.post(
197
+ f"{BASE_URL}/api/advanced_search",
198
+ json={"company_input": "NVIDIA"}
199
+ )
200
+ company = response.json()
201
+ cik = company['cik']
202
+
203
+ # Step 2: Get latest data (no year needed!)
204
+ response = requests.post(
205
+ f"{BASE_URL}/api/get_latest_financial_data",
206
+ json={"cik": cik}
207
+ )
208
+ latest = response.json()
209
+ print(f"Latest FY{latest['period']} Revenue: ${latest['total_revenue']:,.0f}")
210
+ ```
211
+
212
+ ### Example 2: Multi-Year Trend Analysis
213
+
214
+ ```python
215
+ # Get 5 years of data in one call
216
+ response = requests.post(
217
+ f"{BASE_URL}/api/extract_financial_metrics",
218
+ json={"cik": cik, "years": 5}
219
+ )
220
+ data = response.json()
221
+
222
+ # Analyze trends
223
+ import pandas as pd
224
+ df = pd.DataFrame(data['metrics'])
225
+ annual_data = df[~df['period'].str.contains('Q')] # Filter annual only
226
+ print(annual_data[['period', 'total_revenue', 'net_income']])
227
+ ```
228
+
229
+ ### Example 3: Flexible Search
230
+
231
+ ```python
232
+ # Works with either input type
233
+ def search_company(input_str):
234
+ response = requests.post(
235
+ f"{BASE_URL}/api/advanced_search",
236
+ json={"company_input": input_str}
237
+ )
238
+ return response.json()
239
+
240
+ # Both work!
241
+ result1 = search_company("Apple") # By name
242
+ result2 = search_company("0000320193") # By CIK
243
+ ```
244
+
245
+ ## Documentation
246
+
247
+ - **Complete API Examples**: See `mcp_client_examples.json`
248
+ - **Interactive Testing**: Visit `/docs` (Swagger UI)
249
+ - **Detailed Usage**: See `API_USAGE.md`
250
+
251
+ ## Summary
252
+
253
+ The MCP Server now provides **9 comprehensive API endpoints** covering:
254
+ - ✅ Basic data retrieval (EdgarDataClient)
255
+ - ✅ Advanced analysis (FinancialAnalyzer)
256
+ - ✅ Flexible search options
257
+ - ✅ Multi-year data extraction
258
+ - ✅ Automatic latest data retrieval
259
+
260
+ All important functionality from both `edgar_client.py` and `financial_analyzer.py` is now accessible via RESTful APIs! 🎉
README.md CHANGED
@@ -42,6 +42,7 @@ Once deployed, visit `/docs` for interactive Swagger UI documentation or `/redoc
42
 
43
  ## API Endpoints
44
 
 
45
  - `POST /api/search_company` - Search company by name
46
  - `POST /api/get_company_info` - Get company information
47
  - `POST /api/get_company_filings` - Get company filings
@@ -49,6 +50,11 @@ Once deployed, visit `/docs` for interactive Swagger UI documentation or `/redoc
49
  - `POST /api/get_financial_data` - Get financial data for period
50
  - `GET /health` - Health check
51
 
 
 
 
 
 
52
  ## Usage Example
53
 
54
  ```python
 
42
 
43
  ## API Endpoints
44
 
45
+ ### Basic Endpoints
46
  - `POST /api/search_company` - Search company by name
47
  - `POST /api/get_company_info` - Get company information
48
  - `POST /api/get_company_filings` - Get company filings
 
50
  - `POST /api/get_financial_data` - Get financial data for period
51
  - `GET /health` - Health check
52
 
53
+ ### Advanced Endpoints (FinancialAnalyzer)
54
+ - `POST /api/advanced_search` - Advanced search (supports name or CIK)
55
+ - `POST /api/extract_financial_metrics` - Extract multi-year metrics
56
+ - `POST /api/get_latest_financial_data` - Get latest annual data
57
+
58
  ## Usage Example
59
 
60
  ```python
financial_analyzer.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Financial Data Analysis Module"""
2
+
3
+ from edgar_client import EdgarDataClient
4
+ from datetime import datetime
5
+ import json
6
+
7
+
8
+ class FinancialAnalyzer:
9
+ def __init__(self, user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"):
10
+ """
11
+ 初始化财务分析器
12
+
13
+ Args:
14
+ user_agent (str): 用户代理字符串,用于识别请求来源
15
+ """
16
+ self.edgar_client = EdgarDataClient(user_agent)
17
+
18
+ def search_company(self, company_input):
19
+ """
20
+ 搜索公司信息(通过名称或CIK)
21
+
22
+ Args:
23
+ company_input (str): 公司名称或CIK
24
+
25
+ Returns:
26
+ dict: 公司信息
27
+ """
28
+ # 如果输入是数字,假设它是CIK
29
+ if company_input.isdigit() and len(company_input) >= 8:
30
+ # 获取公司信息
31
+ company_info = self.edgar_client.get_company_info(company_input)
32
+ if company_info:
33
+ return company_info
34
+ else:
35
+ return {"error": "未找到指定CIK的公司"}
36
+ else:
37
+ # 通过名称搜索公司
38
+ company = self.edgar_client.search_company_by_name(company_input)
39
+ if company:
40
+ # 获取详细信息
41
+ company_info = self.edgar_client.get_company_info(company['cik'])
42
+ if company_info:
43
+ return company_info
44
+ else:
45
+ # 如果无法获取详细信息,返回基本信息
46
+ return {
47
+ "cik": company['cik'],
48
+ "name": company['name'],
49
+ "tickers": [company['ticker']] if company['ticker'] else []
50
+ }
51
+ else:
52
+ return {"error": "未找到匹配的公司"}
53
+
54
+ def get_company_filings_list(self, cik, form_types=['10-K', '10-Q']):
55
+ """
56
+ 获取公司财报列表
57
+
58
+ Args:
59
+ cik (str): 公司CIK
60
+ form_types (list): 财报类型列表
61
+
62
+ Returns:
63
+ list: 财报列表
64
+ """
65
+ filings = self.edgar_client.get_company_filings(cik, form_types)
66
+ return filings
67
+
68
+ def extract_financial_metrics(self, cik, years=3):
69
+ """
70
+ 提取指定年数的财务指标
71
+
72
+ Args:
73
+ cik (str): 公司CIK
74
+ years (int): 要提取的年数,默认为3年
75
+
76
+ Returns:
77
+ list: 财务数据列表
78
+ """
79
+ # 直接从company facts中获取所有可用的财年数据
80
+ # 这样可以避免filing date和fiscal year不匹配的问题
81
+ financial_data = []
82
+
83
+ # 获取company facts以确定可用的财年
84
+ facts = self.edgar_client.get_company_facts(cik)
85
+ if not facts:
86
+ return []
87
+
88
+ # 从facts中提取所有可用的财年
89
+ available_years = set()
90
+
91
+ # 检查US-GAAP和IFRS数据源
92
+ for data_source in ["us-gaap", "ifrs-full"]:
93
+ if data_source in facts.get("facts", {}):
94
+ source_data = facts["facts"][data_source]
95
+
96
+ # 查找Revenue标签以确定可用年份
97
+ revenue_tags = ["Revenues", "RevenueFromContractWithCustomerExcludingAssessedTax",
98
+ "Revenue", "RevenueFromContractWithCustomer"]
99
+
100
+ for tag in revenue_tags:
101
+ if tag in source_data:
102
+ units = source_data[tag].get("units", {})
103
+ if "USD" in units:
104
+ for entry in units["USD"]:
105
+ # 只考虑年度报告(10-K或20-F)
106
+ if entry.get("form") in ["10-K", "20-F"]:
107
+ # 优先使用fy字段(财政年度)
108
+ fy = entry.get("fy", 0)
109
+ if fy > 0:
110
+ available_years.add(fy)
111
+ # 如果没有fy字段,从end date提取年份作为备选
112
+ # 注意:对于财年不等于日历年的公司,这可能不准确
113
+ elif not fy:
114
+ end_date = entry.get("end", "")
115
+ if end_date and len(end_date) >= 4:
116
+ year = int(end_date[:4])
117
+ available_years.add(year)
118
+ break
119
+ if available_years:
120
+ break
121
+
122
+ if not available_years:
123
+ # 如果无法从facts获取,回退到使用filing date
124
+ filings_10k = self.edgar_client.get_company_filings(cik, ['10-K'])
125
+ filings_20f = self.edgar_client.get_company_filings(cik, ['20-F'])
126
+ filings = filings_10k + filings_20f
127
+
128
+ if not filings:
129
+ return []
130
+
131
+ # 使用filing date作为参考
132
+ latest_filing_year = None
133
+ for filing in filings:
134
+ if 'filing_date' in filing and filing['filing_date']:
135
+ try:
136
+ filing_year = int(filing['filing_date'][:4])
137
+ if latest_filing_year is None or filing_year > latest_filing_year:
138
+ latest_filing_year = filing_year
139
+ except ValueError:
140
+ continue
141
+
142
+ if latest_filing_year is None:
143
+ return []
144
+
145
+ # 生成年份列表
146
+ for i in range(years * 2): # 扩大范围以捕获更多数据
147
+ available_years.add(latest_filing_year - i)
148
+
149
+ # 按年份降序排列
150
+ sorted_years = sorted(available_years, reverse=True)
151
+
152
+ # 生成期间列表(年度和季度)
153
+ periods = []
154
+ for year in sorted_years[:years]:
155
+ # 添加年度数据
156
+ periods.append(str(year))
157
+ # 添加季度数据,按Q4、Q3、Q2、Q1顺序
158
+ for quarter in range(4, 0, -1):
159
+ periods.append(f"{year}Q{quarter}")
160
+
161
+ # 获取每个期间的财务数据
162
+ for period in periods:
163
+ data = self.edgar_client.get_financial_data_for_period(cik, period)
164
+ # 即使没有完整数据也添加,避免N/A情况
165
+ if data: # 只要period字段存在就添加
166
+ financial_data.append(data)
167
+
168
+ return financial_data
169
+
170
+ def get_latest_financial_data(self, cik):
171
+ """
172
+ 获取最新财务数据
173
+
174
+ Args:
175
+ cik (str): 公司CIK
176
+
177
+ Returns:
178
+ dict: 最新财务数据
179
+ """
180
+ # 获取最近的财报年份(支持10-K和20-F)
181
+ filings_10k = self.edgar_client.get_company_filings(cik, ['10-K'])
182
+ filings_20f = self.edgar_client.get_company_filings(cik, ['20-F'])
183
+ filings = filings_10k + filings_20f
184
+
185
+ if not filings:
186
+ return {}
187
+
188
+ # 获取最新的财报年份
189
+ latest_filing_year = None
190
+ for filing in filings:
191
+ if 'filing_date' in filing and filing['filing_date']:
192
+ try:
193
+ filing_year = int(filing['filing_date'][:4])
194
+ if latest_filing_year is None or filing_year > latest_filing_year:
195
+ latest_filing_year = filing_year
196
+ except ValueError:
197
+ continue
198
+
199
+ if latest_filing_year is None:
200
+ return {}
201
+
202
+ # 获取最新年份的财务数据
203
+ return self.edgar_client.get_financial_data_for_period(cik, str(latest_filing_year))
204
+
205
+ def format_financial_data(self, financial_data):
206
+ """
207
+ 格式化财务数据以便显示
208
+
209
+ Args:
210
+ financial_data (dict or list): 财务数据
211
+
212
+ Returns:
213
+ dict or list: 格式化后的财务数据
214
+ """
215
+ if isinstance(financial_data, list):
216
+ formatted_data = []
217
+ for data in financial_data:
218
+ formatted_data.append(self._format_single_financial_data(data))
219
+ return formatted_data
220
+ else:
221
+ return self._format_single_financial_data(financial_data)
222
+
223
+ def _format_single_financial_data(self, data):
224
+ """
225
+ 格式化单个财务数据条目
226
+
227
+ Args:
228
+ data (dict): 财务数据
229
+
230
+ Returns:
231
+ dict: 格式化后的财务数据
232
+ """
233
+ formatted = data.copy()
234
+
235
+ # 确保所有关键字段都存在,即使为None
236
+ key_fields = ['total_revenue', 'net_income', 'earnings_per_share', 'operating_expenses', 'operating_cash_flow', 'source_url', 'source_form']
237
+ for key in key_fields:
238
+ if key not in formatted:
239
+ formatted[key] = None
240
+
241
+ # 不再进行单位转换,保持原始数值
242
+ # 格式化EPS,保留两位小数
243
+ if 'earnings_per_share' in formatted and isinstance(formatted['earnings_per_share'], (int, float)):
244
+ formatted['earnings_per_share'] = round(formatted['earnings_per_share'], 2)
245
+
246
+ return formatted
247
+
248
+
mcp_client_examples.json CHANGED
@@ -331,6 +331,135 @@
331
  "Use for monitoring and health checks",
332
  "Always returns 200 if server is running"
333
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  }
335
  },
336
 
 
331
  "Use for monitoring and health checks",
332
  "Always returns 200 if server is running"
333
  ]
334
+ },
335
+
336
+ "8_advanced_search": {
337
+ "name": "Advanced Company Search",
338
+ "endpoint": "/api/advanced_search",
339
+ "method": "POST",
340
+ "description": "Advanced search supporting both company name and CIK (uses FinancialAnalyzer.search_company)",
341
+
342
+ "request_by_name": {
343
+ "company_input": "Apple"
344
+ },
345
+
346
+ "request_by_cik": {
347
+ "company_input": "0000320193"
348
+ },
349
+
350
+ "response_success": {
351
+ "cik": "0000320193",
352
+ "name": "Apple Inc.",
353
+ "tickers": ["AAPL"],
354
+ "sic": "3571",
355
+ "sic_description": "Electronic Computers",
356
+ "error": null
357
+ },
358
+
359
+ "curl_example": "curl -X POST 'https://YOUR_SPACE.hf.space/api/advanced_search' -H 'Content-Type: application/json' -d '{\"company_input\": \"Apple\"}'" ,
360
+
361
+ "python_example": "import requests\n\n# Search by name\nresponse = requests.post(\n 'https://YOUR_SPACE.hf.space/api/advanced_search',\n json={'company_input': 'Apple'}\n)\nprint(response.json())\n\n# Search by CIK\nresponse = requests.post(\n 'https://YOUR_SPACE.hf.space/api/advanced_search',\n json={'company_input': '0000320193'}\n)\nprint(response.json())",
362
+
363
+ "javascript_example": "// Flexible search - works with both name and CIK\nfetch('https://YOUR_SPACE.hf.space/api/advanced_search', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({company_input: 'Apple'})\n}).then(r => r.json()).then(console.log)",
364
+
365
+ "notes": [
366
+ "Automatically detects if input is CIK (numeric, 8+ digits) or company name",
367
+ "Returns full company information including SIC",
368
+ "More intelligent than basic search_company endpoint"
369
+ ]
370
+ },
371
+
372
+ "9_extract_financial_metrics": {
373
+ "name": "Extract Multi-Year Financial Metrics",
374
+ "endpoint": "/api/extract_financial_metrics",
375
+ "method": "POST",
376
+ "description": "Extract financial metrics for multiple years (both annual and quarterly data)",
377
+
378
+ "request_example": {
379
+ "cik": "0001045810",
380
+ "years": 3
381
+ },
382
+
383
+ "response_success": {
384
+ "metrics": [
385
+ {
386
+ "period": "2024",
387
+ "total_revenue": 60922000000,
388
+ "net_income": 29760000000,
389
+ "earnings_per_share": 12.04,
390
+ "operating_expenses": 11822000000,
391
+ "operating_cash_flow": 28091000000,
392
+ "source_form": "10-K",
393
+ "data_source": "us-gaap"
394
+ },
395
+ {
396
+ "period": "2024Q4",
397
+ "total_revenue": 35082000000,
398
+ "net_income": 19309000000,
399
+ "earnings_per_share": 7.81,
400
+ "source_form": "10-Q"
401
+ },
402
+ {
403
+ "period": "2024Q3",
404
+ "total_revenue": 30040000000,
405
+ "net_income": 16599000000,
406
+ "earnings_per_share": 6.70,
407
+ "source_form": "10-Q"
408
+ }
409
+ ],
410
+ "count": 3,
411
+ "error": null
412
+ },
413
+
414
+ "curl_example": "curl -X POST 'https://YOUR_SPACE.hf.space/api/extract_financial_metrics' -H 'Content-Type: application/json' -d '{\"cik\": \"0001045810\", \"years\": 3}'",
415
+
416
+ "python_example": "import requests\nimport pandas as pd\n\nresponse = requests.post(\n 'https://YOUR_SPACE.hf.space/api/extract_financial_metrics',\n json={'cik': '0001045810', 'years': 3}\n)\ndata = response.json()\n\n# Convert to DataFrame for easy analysis\ndf = pd.DataFrame(data['metrics'])\nprint(f\"Retrieved {data['count']} periods\")\nprint(df[['period', 'total_revenue', 'net_income', 'earnings_per_share']])",
417
+
418
+ "javascript_example": "fetch('https://YOUR_SPACE.hf.space/api/extract_financial_metrics', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({cik: '0001045810', years: 3})\n}).then(r => r.json()).then(data => {\n console.log(`Found ${data.count} periods`);\n data.metrics.forEach(m => {\n console.log(`${m.period}: Revenue $${(m.total_revenue/1e9).toFixed(2)}B`);\n });\n})",
419
+
420
+ "notes": [
421
+ "Returns both annual (e.g., '2024') and quarterly (e.g., '2024Q3') data",
422
+ "Data is automatically formatted and sorted by period",
423
+ "Years parameter: min=1, max=10, default=3",
424
+ "Very useful for trend analysis and historical comparison"
425
+ ]
426
+ },
427
+
428
+ "10_get_latest_financial_data": {
429
+ "name": "Get Latest Financial Data",
430
+ "endpoint": "/api/get_latest_financial_data",
431
+ "method": "POST",
432
+ "description": "Get the most recent annual financial data for a company",
433
+
434
+ "request_example": {
435
+ "cik": "0001045810"
436
+ },
437
+
438
+ "response_success": {
439
+ "period": "2024",
440
+ "total_revenue": 60922000000,
441
+ "net_income": 29760000000,
442
+ "earnings_per_share": 12.04,
443
+ "operating_expenses": 11822000000,
444
+ "operating_cash_flow": 28091000000,
445
+ "source_url": "https://www.sec.gov/Archives/edgar/data/1045810/000104581024000029",
446
+ "source_form": "10-K",
447
+ "data_source": "us-gaap",
448
+ "error": null
449
+ },
450
+
451
+ "curl_example": "curl -X POST 'https://YOUR_SPACE.hf.space/api/get_latest_financial_data' -H 'Content-Type: application/json' -d '{\"cik\": \"0001045810\"}' ",
452
+
453
+ "python_example": "import requests\n\nresponse = requests.post(\n 'https://YOUR_SPACE.hf.space/api/get_latest_financial_data',\n json={'cik': '0001045810'}\n)\ndata = response.json()\n\nif not data.get('error'):\n print(f\"Latest Data: FY{data['period']}\")\n print(f\"Revenue: ${data['total_revenue']:,.0f}\")\n print(f\"Net Income: ${data['net_income']:,.0f}\")\n print(f\"EPS: ${data['earnings_per_share']:.2f}\")\n print(f\"Source: {data['source_form']}\")\nelse:\n print(f\"Error: {data['error']}\")",
454
+
455
+ "javascript_example": "fetch('https://YOUR_SPACE.hf.space/api/get_latest_financial_data', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({cik: '0001045810'})\n}).then(r => r.json()).then(data => {\n if (!data.error) {\n console.log(`Latest FY${data.period}`);\n console.log(`Revenue: $${(data.total_revenue/1e9).toFixed(2)}B`);\n console.log(`Net Income: $${(data.net_income/1e9).toFixed(2)}B`);\n }\n})",
456
+
457
+ "notes": [
458
+ "Automatically finds and returns the most recent annual report data",
459
+ "Saves you from having to manually determine the latest year",
460
+ "Returns data from most recent 10-K or 20-F filing",
461
+ "Ideal for getting current snapshot without knowing fiscal year"
462
+ ]
463
  }
464
  },
465
 
mcp_server.py CHANGED
@@ -8,6 +8,7 @@ from fastapi.middleware.cors import CORSMiddleware
8
  from pydantic import BaseModel, Field
9
  from typing import Optional, List, Dict, Any
10
  from edgar_client import EdgarDataClient
 
11
  import uvicorn
12
 
13
  # Initialize FastAPI app
@@ -31,6 +32,11 @@ edgar_client = EdgarDataClient(
31
  user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
32
  )
33
 
 
 
 
 
 
34
 
35
  # Request/Response Models
36
  class UserAgentRequest(BaseModel):
@@ -68,6 +74,19 @@ class FinancialDataRequest(BaseModel):
68
  )
69
 
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  class CompanySearchResponse(BaseModel):
72
  cik: Optional[str] = None
73
  name: Optional[str] = None
@@ -115,6 +134,12 @@ class FinancialDataResponse(BaseModel):
115
  error: Optional[str] = None
116
 
117
 
 
 
 
 
 
 
118
  # API Endpoints
119
  @app.get("/")
120
  async def root():
@@ -126,10 +151,13 @@ async def root():
126
  "endpoints": {
127
  "health": "/health",
128
  "search_company": "/api/search_company",
 
129
  "get_company_info": "/api/get_company_info",
130
  "get_company_filings": "/api/get_company_filings",
131
  "get_company_facts": "/api/get_company_facts",
132
- "get_financial_data": "/api/get_financial_data"
 
 
133
  },
134
  "user_agent": "Juntao Peng (jtyxabc@gmail.com)"
135
  }
@@ -302,6 +330,111 @@ async def get_financial_data_for_period(request: FinancialDataRequest):
302
  raise HTTPException(status_code=500, detail=str(e))
303
 
304
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  if __name__ == "__main__":
306
  # Run the server
307
  uvicorn.run(
 
8
  from pydantic import BaseModel, Field
9
  from typing import Optional, List, Dict, Any
10
  from edgar_client import EdgarDataClient
11
+ from financial_analyzer import FinancialAnalyzer
12
  import uvicorn
13
 
14
  # Initialize FastAPI app
 
32
  user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
33
  )
34
 
35
+ # Initialize Financial Analyzer
36
+ financial_analyzer = FinancialAnalyzer(
37
+ user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
38
+ )
39
+
40
 
41
  # Request/Response Models
42
  class UserAgentRequest(BaseModel):
 
74
  )
75
 
76
 
77
+ class AdvancedSearchRequest(BaseModel):
78
+ company_input: str = Field(..., description="Company name or CIK code")
79
+
80
+
81
+ class ExtractMetricsRequest(BaseModel):
82
+ cik: str = Field(..., description="Company CIK code")
83
+ years: int = Field(default=3, description="Number of years to extract", ge=1, le=10)
84
+
85
+
86
+ class LatestDataRequest(BaseModel):
87
+ cik: str = Field(..., description="Company CIK code")
88
+
89
+
90
  class CompanySearchResponse(BaseModel):
91
  cik: Optional[str] = None
92
  name: Optional[str] = None
 
134
  error: Optional[str] = None
135
 
136
 
137
+ class MetricsListResponse(BaseModel):
138
+ metrics: List[Dict[str, Any]] = []
139
+ count: int = 0
140
+ error: Optional[str] = None
141
+
142
+
143
  # API Endpoints
144
  @app.get("/")
145
  async def root():
 
151
  "endpoints": {
152
  "health": "/health",
153
  "search_company": "/api/search_company",
154
+ "advanced_search": "/api/advanced_search",
155
  "get_company_info": "/api/get_company_info",
156
  "get_company_filings": "/api/get_company_filings",
157
  "get_company_facts": "/api/get_company_facts",
158
+ "get_financial_data": "/api/get_financial_data",
159
+ "extract_financial_metrics": "/api/extract_financial_metrics",
160
+ "get_latest_financial_data": "/api/get_latest_financial_data"
161
  },
162
  "user_agent": "Juntao Peng (jtyxabc@gmail.com)"
163
  }
 
330
  raise HTTPException(status_code=500, detail=str(e))
331
 
332
 
333
+ @app.post("/api/advanced_search", response_model=CompanyInfoResponse)
334
+ async def advanced_search_company(request: AdvancedSearchRequest):
335
+ """
336
+ Advanced company search (supports both company name and CIK)
337
+ Uses FinancialAnalyzer.search_company() method
338
+
339
+ Args:
340
+ company_input (str): Company name or CIK code
341
+
342
+ Returns:
343
+ dict: Company information
344
+ """
345
+ try:
346
+ result = financial_analyzer.search_company(request.company_input)
347
+
348
+ if result.get("error"):
349
+ return CompanyInfoResponse(error=result["error"])
350
+
351
+ return CompanyInfoResponse(
352
+ cik=result.get("cik"),
353
+ name=result.get("name"),
354
+ tickers=result.get("tickers"),
355
+ sic=result.get("sic"),
356
+ sic_description=result.get("sic_description")
357
+ )
358
+ except Exception as e:
359
+ raise HTTPException(status_code=500, detail=str(e))
360
+
361
+
362
+ @app.post("/api/extract_financial_metrics", response_model=MetricsListResponse)
363
+ async def extract_financial_metrics(request: ExtractMetricsRequest):
364
+ """
365
+ Extract financial metrics for multiple years (annual and quarterly)
366
+ Uses FinancialAnalyzer.extract_financial_metrics() method
367
+
368
+ Args:
369
+ cik (str): Company CIK code
370
+ years (int): Number of years to extract (default: 3, max: 10)
371
+
372
+ Returns:
373
+ dict: List of financial metrics for multiple periods
374
+ """
375
+ try:
376
+ metrics = financial_analyzer.extract_financial_metrics(request.cik, request.years)
377
+
378
+ if not metrics:
379
+ return MetricsListResponse(
380
+ metrics=[],
381
+ count=0,
382
+ error=f"No financial metrics found for CIK: {request.cik}"
383
+ )
384
+
385
+ # Format the metrics
386
+ formatted_metrics = financial_analyzer.format_financial_data(metrics)
387
+
388
+ return MetricsListResponse(
389
+ metrics=formatted_metrics,
390
+ count=len(formatted_metrics)
391
+ )
392
+ except Exception as e:
393
+ raise HTTPException(status_code=500, detail=str(e))
394
+
395
+
396
+ @app.post("/api/get_latest_financial_data", response_model=FinancialDataResponse)
397
+ async def get_latest_financial_data(request: LatestDataRequest):
398
+ """
399
+ Get the latest financial data for a company
400
+ Uses FinancialAnalyzer.get_latest_financial_data() method
401
+
402
+ Args:
403
+ cik (str): Company CIK code
404
+
405
+ Returns:
406
+ dict: Latest financial data
407
+ """
408
+ try:
409
+ result = financial_analyzer.get_latest_financial_data(request.cik)
410
+
411
+ if not result or "period" not in result:
412
+ return FinancialDataResponse(
413
+ error=f"No latest financial data found for CIK: {request.cik}"
414
+ )
415
+
416
+ # Collect additional details
417
+ additional_details = {}
418
+ for key, value in result.items():
419
+ if key.endswith("_details"):
420
+ additional_details[key] = value
421
+
422
+ return FinancialDataResponse(
423
+ period=result.get("period"),
424
+ total_revenue=result.get("total_revenue"),
425
+ net_income=result.get("net_income"),
426
+ earnings_per_share=result.get("earnings_per_share"),
427
+ operating_expenses=result.get("operating_expenses"),
428
+ operating_cash_flow=result.get("operating_cash_flow"),
429
+ source_url=result.get("source_url"),
430
+ source_form=result.get("source_form"),
431
+ data_source=result.get("data_source"),
432
+ additional_details=additional_details if additional_details else None
433
+ )
434
+ except Exception as e:
435
+ raise HTTPException(status_code=500, detail=str(e))
436
+
437
+
438
  if __name__ == "__main__":
439
  # Run the server
440
  uvicorn.run(