JC321 commited on
Commit
00da01d
·
verified ·
1 Parent(s): c583058

Upload 3 files

Browse files
Files changed (3) hide show
  1. edgar_client.py +51 -15
  2. financial_analyzer.py +15 -8
  3. mcp_server_sse.py +3 -3
edgar_client.py CHANGED
@@ -273,9 +273,17 @@ class EdgarDataClient:
273
  year = int(period)
274
  quarter = None
275
 
 
 
 
 
 
 
 
 
276
  # Get company filings to find accession number and primary document
277
  filings = self.get_company_filings(cik, form_types=target_forms)
278
- filings_map = {} # Map: form -> {accession_number, primary_document, filing_date}
279
 
280
  # Build filing map for quick lookup
281
  for filing in filings:
@@ -289,14 +297,16 @@ class EdgarDataClient:
289
  file_year = int(filing_date[:4]) if len(filing_date) >= 4 else 0
290
 
291
  # Store filing if it matches the period year
292
- if file_year == year:
 
293
  key = f"{form_type}_{file_year}"
294
  if key not in filings_map:
295
  filings_map[key] = {
296
  "accession_number": accession_number,
297
  "primary_document": primary_document,
298
  "form_type": form_type,
299
- "filing_date": filing_date
 
300
  }
301
 
302
  # Iterate through each financial metric
@@ -304,15 +314,26 @@ class EdgarDataClient:
304
  # Support multiple possible tags
305
  for metric_tag in metric_tags:
306
  # Search both US-GAAP and IFRS tags
 
307
  metric_data = None
308
  data_source = None
309
 
310
- if metric_tag in us_gaap:
311
- metric_data = us_gaap[metric_tag]
312
- data_source = "us-gaap"
313
- elif metric_tag in ifrs_full:
314
- metric_data = ifrs_full[metric_tag]
315
- data_source = "ifrs-full"
 
 
 
 
 
 
 
 
 
 
316
 
317
  if metric_data:
318
  units = metric_data.get("units", {})
@@ -368,11 +389,16 @@ class EdgarDataClient:
368
  # Strategy 3: Allow fy to differ by 1 year (fiscal year vs calendar year mismatch)
369
  elif not matched_entry and fy > 0 and abs(fy - year) <= 1 and (fp == "FY" or fp == "" or not fp):
370
  matched_entry = entry
371
- # Strategy 4: Match by frame field for 20-F
372
- elif not matched_entry and form == "20-F" and "frame" in entry:
373
  frame = entry.get("frame", "")
374
- if f"CY{year}" in frame or str(year) in end_date:
375
- matched_entry = entry
 
 
 
 
 
376
 
377
  # If quarterly data not found, try finding from annual report (fallback strategy)
378
  if not matched_entry and quarter and target_forms_annual:
@@ -394,10 +420,20 @@ class EdgarDataClient:
394
  # Get form and accession info
395
  form_type = matched_entry.get("form", "")
396
  accn_from_facts = matched_entry.get('accn', '').replace('-', '')
 
 
397
 
398
  # Try to get accession_number and primary_document from filings
399
- filing_key = f"{form_type}_{year}"
400
- filing_info = filings_map.get(filing_key)
 
 
 
 
 
 
 
 
401
 
402
  if filing_info:
403
  # Use filing info from get_company_filings
 
273
  year = int(period)
274
  quarter = None
275
 
276
+ # Detect if company uses 20-F (foreign filer)
277
+ is_20f_filer = False
278
+ all_filings = self.get_company_filings(cik)
279
+ if all_filings:
280
+ form_types_used = set(f.get('form_type', '') for f in all_filings[:20])
281
+ if '20-F' in form_types_used and '10-K' not in form_types_used:
282
+ is_20f_filer = True
283
+
284
  # Get company filings to find accession number and primary document
285
  filings = self.get_company_filings(cik, form_types=target_forms)
286
+ filings_map = {} # Map: form_year -> {accession_number, primary_document, filing_date, form_type}
287
 
288
  # Build filing map for quick lookup
289
  for filing in filings:
 
297
  file_year = int(filing_date[:4]) if len(filing_date) >= 4 else 0
298
 
299
  # Store filing if it matches the period year
300
+ # For 20-F, also check year-1 (fiscal year may differ from filing year)
301
+ if file_year == year or (is_20f_filer and form_type == '20-F' and file_year in [year - 1, year + 1]):
302
  key = f"{form_type}_{file_year}"
303
  if key not in filings_map:
304
  filings_map[key] = {
305
  "accession_number": accession_number,
306
  "primary_document": primary_document,
307
  "form_type": form_type,
308
+ "filing_date": filing_date,
309
+ "file_year": file_year
310
  }
311
 
312
  # Iterate through each financial metric
 
314
  # Support multiple possible tags
315
  for metric_tag in metric_tags:
316
  # Search both US-GAAP and IFRS tags
317
+ # For 20-F filers, prioritize IFRS
318
  metric_data = None
319
  data_source = None
320
 
321
+ if is_20f_filer:
322
+ # Check IFRS first for 20-F filers
323
+ if metric_tag in ifrs_full:
324
+ metric_data = ifrs_full[metric_tag]
325
+ data_source = "ifrs-full"
326
+ elif metric_tag in us_gaap:
327
+ metric_data = us_gaap[metric_tag]
328
+ data_source = "us-gaap"
329
+ else:
330
+ # Check US-GAAP first for 10-K filers
331
+ if metric_tag in us_gaap:
332
+ metric_data = us_gaap[metric_tag]
333
+ data_source = "us-gaap"
334
+ elif metric_tag in ifrs_full:
335
+ metric_data = ifrs_full[metric_tag]
336
+ data_source = "ifrs-full"
337
 
338
  if metric_data:
339
  units = metric_data.get("units", {})
 
389
  # Strategy 3: Allow fy to differ by 1 year (fiscal year vs calendar year mismatch)
390
  elif not matched_entry and fy > 0 and abs(fy - year) <= 1 and (fp == "FY" or fp == "" or not fp):
391
  matched_entry = entry
392
+ # Strategy 4: Enhanced matching for 20-F - check frame field and end date
393
+ elif not matched_entry and form == "20-F":
394
  frame = entry.get("frame", "")
395
+ # Match if CY{year} in frame OR end date contains year OR fiscal year within range
396
+ if (f"CY{year}" in frame or
397
+ (str(year) in end_date and len(end_date) >= 4 and end_date[:4] == str(year)) or
398
+ (fy > 0 and abs(fy - year) <= 1)):
399
+ # Additional check: prefer entries with FY period
400
+ if fp == "FY" or fp == "" or not fp:
401
+ matched_entry = entry
402
 
403
  # If quarterly data not found, try finding from annual report (fallback strategy)
404
  if not matched_entry and quarter and target_forms_annual:
 
420
  # Get form and accession info
421
  form_type = matched_entry.get("form", "")
422
  accn_from_facts = matched_entry.get('accn', '').replace('-', '')
423
+ filed_date = matched_entry.get('filed', '')
424
+ filed_year = int(filed_date[:4]) if filed_date and len(filed_date) >= 4 else year
425
 
426
  # Try to get accession_number and primary_document from filings
427
+ # For 20-F, try multiple year keys since filing year may differ
428
+ filing_info = None
429
+ possible_keys = [f"{form_type}_{year}"]
430
+ if form_type == "20-F":
431
+ possible_keys.extend([f"20-F_{filed_year}", f"20-F_{year-1}", f"20-F_{year+1}"])
432
+
433
+ for filing_key in possible_keys:
434
+ if filing_key in filings_map:
435
+ filing_info = filings_map[filing_key]
436
+ break
437
 
438
  if filing_info:
439
  # Use filing info from get_company_filings
financial_analyzer.py CHANGED
@@ -86,6 +86,10 @@ class FinancialAnalyzer:
86
  if not all_annual_filings:
87
  return []
88
 
 
 
 
 
89
  # Step 2: Extract filing years from annual reports
90
  # Use filing_date to determine the years we should query
91
  filing_year_map = {} # Map: filing_year -> list of filings
@@ -147,6 +151,7 @@ class FinancialAnalyzer:
147
 
148
  # Step 5: Generate period list for target years
149
  # For each year: FY -> Q4 -> Q3 -> Q2 -> Q1 (descending order)
 
150
  periods = []
151
  for file_year in target_years:
152
  # Try to get fiscal year from mapping, otherwise use filing year
@@ -160,14 +165,16 @@ class FinancialAnalyzer:
160
  'filing_year': file_year
161
  })
162
 
163
- # Then add quarterly data in descending order: Q4, Q3, Q2, Q1
164
- for quarter in range(4, 0, -1):
165
- periods.append({
166
- 'period': f"{fiscal_year}Q{quarter}",
167
- 'type': 'quarterly',
168
- 'fiscal_year': fiscal_year,
169
- 'filing_year': file_year
170
- })
 
 
171
 
172
  # Step 6: Get financial data for each period
173
  for idx, period_info in enumerate(periods):
 
86
  if not all_annual_filings:
87
  return []
88
 
89
+ # Detect if company is a 20-F filer (foreign company)
90
+ is_20f_filer = len(filings_20f) > 0 and len(filings_10k) == 0
91
+ has_quarterly = False # 20-F filers typically don't have quarterly reports
92
+
93
  # Step 2: Extract filing years from annual reports
94
  # Use filing_date to determine the years we should query
95
  filing_year_map = {} # Map: filing_year -> list of filings
 
151
 
152
  # Step 5: Generate period list for target years
153
  # For each year: FY -> Q4 -> Q3 -> Q2 -> Q1 (descending order)
154
+ # For 20-F filers: only FY (no quarterly data)
155
  periods = []
156
  for file_year in target_years:
157
  # Try to get fiscal year from mapping, otherwise use filing year
 
165
  'filing_year': file_year
166
  })
167
 
168
+ # Only add quarterly data for 10-K filers (not for 20-F filers)
169
+ if not is_20f_filer:
170
+ # Then add quarterly data in descending order: Q4, Q3, Q2, Q1
171
+ for quarter in range(4, 0, -1):
172
+ periods.append({
173
+ 'period': f"{fiscal_year}Q{quarter}",
174
+ 'type': 'quarterly',
175
+ 'fiscal_year': fiscal_year,
176
+ 'filing_year': file_year
177
+ })
178
 
179
  # Step 6: Get financial data for each period
180
  for idx, period_info in enumerate(periods):
mcp_server_sse.py CHANGED
@@ -22,7 +22,7 @@ import sys
22
  app = FastAPI(
23
  title="SEC Financial Report MCP Server",
24
  description="Model Context Protocol Server for SEC EDGAR Financial Data",
25
- version="2.0.0"
26
  )
27
 
28
  # Server startup time for monitoring
@@ -374,7 +374,7 @@ async def handle_mcp_message(request: MCPRequest):
374
  },
375
  "serverInfo": {
376
  "name": "sec-financial-data",
377
- "version": "2.0.0"
378
  }
379
  }
380
  ).dict()
@@ -619,7 +619,7 @@ async def health_check():
619
  return {
620
  "status": "healthy",
621
  "server": "sec-financial-data",
622
- "version": "2.0.0",
623
  "protocol": "MCP",
624
  "transport": "SSE",
625
  "tools_count": len(MCP_TOOLS),
 
22
  app = FastAPI(
23
  title="SEC Financial Report MCP Server",
24
  description="Model Context Protocol Server for SEC EDGAR Financial Data",
25
+ version="2.1.0"
26
  )
27
 
28
  # Server startup time for monitoring
 
374
  },
375
  "serverInfo": {
376
  "name": "sec-financial-data",
377
+ "version": "2.1.0"
378
  }
379
  }
380
  ).dict()
 
619
  return {
620
  "status": "healthy",
621
  "server": "sec-financial-data",
622
+ "version": "2.1.0",
623
  "protocol": "MCP",
624
  "transport": "SSE",
625
  "tools_count": len(MCP_TOOLS),