JC321 commited on
Commit
66e07c5
·
verified ·
1 Parent(s): aefa151

Upload 2 files

Browse files
EasyReportDataMCP/edgar_client.py CHANGED
@@ -3,6 +3,7 @@
3
  import requests
4
  from requests.adapters import HTTPAdapter
5
  from urllib3.util.retry import Retry
 
6
  try:
7
  from sec_edgar_api.EdgarClient import EdgarClient
8
  except ImportError:
@@ -15,6 +16,9 @@ from datetime import datetime, timedelta
15
  import re
16
  import difflib
17
 
 
 
 
18
 
19
  class EdgarDataClient:
20
  # Class-level cache for company_tickers.json (shared across instances)
@@ -65,9 +69,9 @@ class EdgarDataClient:
65
  # Configure requests session with connection pooling
66
  self.session = requests.Session()
67
 
68
- # Configure retry strategy
69
  retry_strategy = Retry(
70
- total=3,
71
  backoff_factor=1,
72
  status_forcelist=[429, 500, 502, 503, 504],
73
  allowed_methods=["HEAD", "GET", "OPTIONS"]
@@ -83,8 +87,8 @@ class EdgarDataClient:
83
  self.session.mount("http://", adapter)
84
  self.session.mount("https://", adapter)
85
 
86
- # Set default timeout
87
- self.timeout = 30 # 30 seconds timeout
88
 
89
  # Initialize sec_edgar_api client with timeout wrapper
90
  if EdgarClient:
@@ -393,15 +397,25 @@ class EdgarDataClient:
393
  print("sec_edgar_api library not installed")
394
  return []
395
 
 
 
 
396
  # Convert list to tuple for caching (lists are not hashable)
397
  if form_types and isinstance(form_types, list):
398
  form_types = tuple(form_types)
399
 
400
  try:
401
  self._rate_limit()
 
 
 
402
  # Get company submissions (now has timeout protection)
403
  submissions = self.edgar.get_submissions(cik=cik)
404
 
 
 
 
 
405
  # Extract filing information
406
  filings = []
407
  recent = submissions.get("filings", {}).get("recent", {})
@@ -433,6 +447,11 @@ class EdgarDataClient:
433
 
434
  filings.append(filing)
435
 
 
 
 
 
 
436
  return filings
437
  except TimeoutError as e:
438
  print(f"Timeout getting company filings for CIK {cik}: {e}")
@@ -544,16 +563,17 @@ class EdgarDataClient:
544
  # Extract year from filing_date (format: YYYY-MM-DD)
545
  file_year = int(filing_date[:4]) if len(filing_date) >= 4 else 0
546
 
547
- # Store filing if it matches the period year
548
- if file_year == year:
549
- key = f"{form_type}_{file_year}"
550
- if key not in filings_map:
551
- filings_map[key] = {
552
- "accession_number": accession_number,
553
- "primary_document": primary_document,
554
- "form_type": form_type,
555
- "filing_date": filing_date
556
- }
 
557
 
558
  # Iterate through each financial metric
559
  for metric_key, metric_tags in financial_metrics.items():
@@ -650,11 +670,36 @@ class EdgarDataClient:
650
  # Get form and accession info
651
  form_type = matched_entry.get("form", "")
652
  accn_from_facts = matched_entry.get('accn', '').replace('-', '')
 
653
 
654
- # Try to get accession_number and primary_document from filings
 
 
 
655
  filing_key = f"{form_type}_{year}"
656
  filing_info = filings_map.get(filing_key)
657
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
658
  if filing_info:
659
  # Use filing info from get_company_filings
660
  accession_number = filing_info["accession_number"].replace('-', '')
 
3
  import requests
4
  from requests.adapters import HTTPAdapter
5
  from urllib3.util.retry import Retry
6
+ import urllib3
7
  try:
8
  from sec_edgar_api.EdgarClient import EdgarClient
9
  except ImportError:
 
16
  import re
17
  import difflib
18
 
19
+ # Disable SSL warnings for better compatibility
20
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
21
+
22
 
23
  class EdgarDataClient:
24
  # Class-level cache for company_tickers.json (shared across instances)
 
69
  # Configure requests session with connection pooling
70
  self.session = requests.Session()
71
 
72
+ # Configure retry strategy with enhanced retries for stability
73
  retry_strategy = Retry(
74
+ total=5, # Increased from 3 to 5 for better reliability
75
  backoff_factor=1,
76
  status_forcelist=[429, 500, 502, 503, 504],
77
  allowed_methods=["HEAD", "GET", "OPTIONS"]
 
87
  self.session.mount("http://", adapter)
88
  self.session.mount("https://", adapter)
89
 
90
+ # Set default timeout with connection and read timeouts
91
+ self.timeout = (10, 30) # (connect timeout, read timeout)
92
 
93
  # Initialize sec_edgar_api client with timeout wrapper
94
  if EdgarClient:
 
397
  print("sec_edgar_api library not installed")
398
  return []
399
 
400
+ # ✅ 添加调试日志
401
+ print(f"[DEBUG] get_company_filings called with CIK: {cik}, form_types: {form_types}")
402
+
403
  # Convert list to tuple for caching (lists are not hashable)
404
  if form_types and isinstance(form_types, list):
405
  form_types = tuple(form_types)
406
 
407
  try:
408
  self._rate_limit()
409
+ # ✅ 调试: 打印实际调用SEC API的CIK
410
+ print(f"[DEBUG] Calling SEC API get_submissions with CIK: {cik}")
411
+
412
  # Get company submissions (now has timeout protection)
413
  submissions = self.edgar.get_submissions(cik=cik)
414
 
415
+ # ✅ 调试: 打印返回的基本信息
416
+ print(f"[DEBUG] Got submissions for: {submissions.get('name', 'Unknown')}")
417
+ print(f"[DEBUG] Available form types in recent filings: {set(submissions.get('filings', {}).get('recent', {}).get('form', [])[:20])}")
418
+
419
  # Extract filing information
420
  filings = []
421
  recent = submissions.get("filings", {}).get("recent", {})
 
447
 
448
  filings.append(filing)
449
 
450
+ # ✅ 调试: 打印过滤后的结果
451
+ print(f"[DEBUG] After filtering, found {len(filings)} filings matching form_types: {form_types}")
452
+ if len(filings) > 0:
453
+ print(f"[DEBUG] First filing: {filings[0]}")
454
+
455
  return filings
456
  except TimeoutError as e:
457
  print(f"Timeout getting company filings for CIK {cik}: {e}")
 
563
  # Extract year from filing_date (format: YYYY-MM-DD)
564
  file_year = int(filing_date[:4]) if len(filing_date) >= 4 else 0
565
 
566
+ # FIXED: Remove year filter to keep all filings
567
+ # 20-F forms are often filed in the year after the fiscal year
568
+ # We'll match them later using fiscal year (fy) and filed date
569
+ key = f"{form_type}_{file_year}"
570
+ if key not in filings_map:
571
+ filings_map[key] = {
572
+ "accession_number": accession_number,
573
+ "primary_document": primary_document,
574
+ "form_type": form_type,
575
+ "filing_date": filing_date
576
+ }
577
 
578
  # Iterate through each financial metric
579
  for metric_key, metric_tags in financial_metrics.items():
 
670
  # Get form and accession info
671
  form_type = matched_entry.get("form", "")
672
  accn_from_facts = matched_entry.get('accn', '').replace('-', '')
673
+ filed_date = matched_entry.get('filed', '')
674
 
675
+ # ENHANCED: Multi-strategy filing lookup for 20-F and cross-year submissions
676
+ filing_info = None
677
+
678
+ # Strategy 1: Try matching by fiscal year (original logic)
679
  filing_key = f"{form_type}_{year}"
680
  filing_info = filings_map.get(filing_key)
681
 
682
+ # Strategy 2: Try matching by filed year (for 20-F filed in next year)
683
+ if not filing_info and filed_date:
684
+ filed_year = int(filed_date[:4]) if len(filed_date) >= 4 else 0
685
+ if filed_year > 0:
686
+ filing_key = f"{form_type}_{filed_year}"
687
+ filing_info = filings_map.get(filing_key)
688
+
689
+ # Strategy 3: Try fiscal year + 1 (common for 20-F)
690
+ if not filing_info:
691
+ filing_key = f"{form_type}_{year + 1}"
692
+ filing_info = filings_map.get(filing_key)
693
+
694
+ # Strategy 4: Search all filings with matching form type and accession
695
+ if not filing_info and accn_from_facts:
696
+ for key, finfo in filings_map.items():
697
+ if finfo["form_type"] == form_type:
698
+ filing_accn = finfo["accession_number"].replace('-', '')
699
+ if filing_accn == accn_from_facts:
700
+ filing_info = finfo
701
+ break
702
+
703
  if filing_info:
704
  # Use filing info from get_company_filings
705
  accession_number = filing_info["accession_number"].replace('-', '')
EasyReportDataMCP/mcp_server_fastmcp.py CHANGED
@@ -213,15 +213,29 @@ def extract_financial_metrics(cik: str, years: int = 3) -> dict:
213
  if years < 1 or years > 10:
214
  return {"error": "Years parameter must be between 1 and 10"}
215
 
 
 
 
216
  # Check if company has filings (use tuple for caching)
217
  filings_10k = edgar_client.get_company_filings(cik, ('10-K',))
218
  filings_20f = edgar_client.get_company_filings(cik, ('20-F',))
 
 
 
 
 
 
 
 
219
  total_filings = len(filings_10k) + len(filings_20f)
220
 
221
  if total_filings == 0:
 
222
  return {
223
- "error": f"No annual filings found for CIK: {cik}",
224
- "suggestion": "Please check if the CIK is correct"
 
 
225
  }
226
 
227
  # Extract metrics
@@ -302,7 +316,13 @@ def get_latest_financial_data(cik: str) -> dict:
302
  if result and "period" in result:
303
  return result
304
  else:
305
- return {"error": f"No latest financial data found for CIK: {cik}"}
 
 
 
 
 
 
306
 
307
 
308
  @mcp.tool()
@@ -350,20 +370,13 @@ def advanced_search_company(company_input: str) -> dict:
350
  if __name__ == "__main__":
351
  import os
352
 
353
- # Set port from environment (HF Space sets PORT=7860)
354
- port = int(os.getenv("PORT", "7860"))
355
  host = os.getenv("HOST", "0.0.0.0")
356
 
357
- # Monkeypatch uvicorn.Config to use our port
358
- import uvicorn
359
- original_config_init = uvicorn.Config.__init__
360
-
361
- def patched_init(self, *args, **kwargs):
362
- kwargs['host'] = host
363
- kwargs['port'] = port
364
- return original_config_init(self, *args, **kwargs)
365
-
366
- uvicorn.Config.__init__ = patched_init
367
 
368
- # Run FastMCP with HTTP transport (stateless mode)
369
- mcp.run(transport="http")
 
213
  if years < 1 or years > 10:
214
  return {"error": "Years parameter must be between 1 and 10"}
215
 
216
+ # ✅ 添加调试日志,查看实际CIK格式和查询结果
217
+ print(f"[DEBUG] extract_financial_metrics called with CIK: {cik}, type: {type(cik)}, years: {years}")
218
+
219
  # Check if company has filings (use tuple for caching)
220
  filings_10k = edgar_client.get_company_filings(cik, ('10-K',))
221
  filings_20f = edgar_client.get_company_filings(cik, ('20-F',))
222
+
223
+ # ✅ 打印查询结果
224
+ print(f"[DEBUG] Found {len(filings_10k)} 10-K filings, {len(filings_20f)} 20-F filings")
225
+ if len(filings_10k) > 0:
226
+ print(f"[DEBUG] Latest 10-K: {filings_10k[0]}")
227
+ if len(filings_20f) > 0:
228
+ print(f"[DEBUG] Latest 20-F: {filings_20f[0]}")
229
+
230
  total_filings = len(filings_10k) + len(filings_20f)
231
 
232
  if total_filings == 0:
233
+ # ✅ 提供更详细的错误信息,帮助用户了解问题
234
  return {
235
+ "error": f"No 10-K or 20-F annual filings found for CIK: {cik}",
236
+ "suggestion": "This company might not have filed 10-K or 20-F forms yet. Please verify the CIK is correct.",
237
+ "note": "Some companies may use different filing forms or may be newly listed.",
238
+ "cik": cik
239
  }
240
 
241
  # Extract metrics
 
316
  if result and "period" in result:
317
  return result
318
  else:
319
+ # 提供更有帮助的错误信息
320
+ return {
321
+ "error": f"No latest financial data found for CIK: {cik}",
322
+ "suggestion": "The company may not have recent 10-K or 20-F filings, or the data format may not be supported.",
323
+ "note": "Try using a different CIK or check if the company has filed recent annual reports.",
324
+ "cik": cik
325
+ }
326
 
327
 
328
  @mcp.tool()
 
370
  if __name__ == "__main__":
371
  import os
372
 
373
+ # Set port to 7861 for EasyReportDataMCP (MarketandStockMCP uses 7870)
374
+ port = int(os.getenv("PORT", "7861"))
375
  host = os.getenv("HOST", "0.0.0.0")
376
 
377
+ print("▶️ Starting EasyReportDataMCP Server...")
378
+ print(f"📡 MCP server will listen on {host}:{port}")
379
+ print("✅ Available tools: advanced_search_company, get_latest_financial_data, extract_financial_metrics")
 
 
 
 
 
 
 
380
 
381
+ # Run FastMCP with SSE transport (Server-Sent Events for chat_direct.py compatibility)
382
+ mcp.run(transport="sse", port=port)