JC321 commited on
Commit
c0bdef3
·
verified ·
1 Parent(s): 4c8f220

Upload 3 files

Browse files
Files changed (2) hide show
  1. edgar_client.py +8 -4
  2. financial_analyzer.py +60 -49
edgar_client.py CHANGED
@@ -303,18 +303,22 @@ class EdgarDataClient:
303
  else:
304
  matched_entry = entry
305
  else:
306
- # Annual data match - prioritize FY field match
 
307
  if fy == year and (fp == "FY" or fp == "" or not fp):
308
- # If already matched, compare end date, choose the latest (most recent fiscal year end date)
309
  if matched_entry:
310
  if entry.get("end", "") > matched_entry.get("end", ""):
311
  matched_entry = entry
312
  else:
313
  matched_entry = entry
314
- # Alternative: match end date year (only when no FY match)
315
  elif not matched_entry and entry_year == year and (fp == "FY" or fp == "" or not fp):
316
  matched_entry = entry
317
- # Special handling for 20-F: some 20-F don't have FY marker, match via frame field
 
 
 
318
  elif not matched_entry and form == "20-F" and "frame" in entry:
319
  frame = entry.get("frame", "")
320
  if f"CY{year}" in frame or str(year) in end_date:
 
303
  else:
304
  matched_entry = entry
305
  else:
306
+ # Annual data match - prioritize fiscal year (fy) field
307
+ # Strategy 1: Exact match by fiscal year
308
  if fy == year and (fp == "FY" or fp == "" or not fp):
309
+ # If already matched, compare end date, choose the latest
310
  if matched_entry:
311
  if entry.get("end", "") > matched_entry.get("end", ""):
312
  matched_entry = entry
313
  else:
314
  matched_entry = entry
315
+ # Strategy 2: Match by end date year (when fy not available or doesn't match)
316
  elif not matched_entry and entry_year == year and (fp == "FY" or fp == "" or not fp):
317
  matched_entry = entry
318
+ # Strategy 3: Allow fy to differ by 1 year (fiscal year vs calendar year mismatch)
319
+ elif not matched_entry and fy > 0 and abs(fy - year) <= 1 and (fp == "FY" or fp == "" or not fp):
320
+ matched_entry = entry
321
+ # Strategy 4: Match by frame field for 20-F
322
  elif not matched_entry and form == "20-F" and "frame" in entry:
323
  frame = entry.get("frame", "")
324
  if f"CY{year}" in frame or str(year) in end_date:
financial_analyzer.py CHANGED
@@ -76,24 +76,34 @@ class FinancialAnalyzer:
76
  Returns:
77
  list: List of financial data
78
  """
79
- # Get all available fiscal years directly from company facts
80
- # This avoids mismatches between filing date and fiscal year
81
  financial_data = []
82
 
83
- # Get company facts to determine available fiscal years
 
 
 
 
 
 
 
 
 
 
84
  facts = self.edgar_client.get_company_facts(cik)
85
  if not facts:
86
  return []
87
 
88
- # Extract all available fiscal years from facts
89
- available_years = set()
 
 
90
 
91
  # Check US-GAAP and IFRS data sources
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
- # Look for Revenue tags to determine available years
97
  revenue_tags = ["Revenues", "RevenueFromContractWithCustomerExcludingAssessedTax",
98
  "Revenue", "RevenueFromContractWithCustomer"]
99
 
@@ -102,67 +112,68 @@ class FinancialAnalyzer:
102
  units = source_data[tag].get("units", {})
103
  if "USD" in units:
104
  for entry in units["USD"]:
105
- # Only consider annual reports (10-K or 20-F)
106
- if entry.get("form") in ["10-K", "20-F"]:
107
- # Prioritize using fy field (fiscal year)
108
- fy = entry.get("fy", 0)
109
- if fy > 0:
110
- available_years.add(fy)
111
- # If no fy field, extract year from end date as fallback
112
- # Note: This may be inaccurate for companies whose fiscal year differs from calendar year
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
- # If unable to get from facts, fall back to using 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
- # Use filing date as reference
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
- # Generate year list
146
- for i in range(years * 2): # Expand range to capture more data
147
- available_years.add(latest_filing_year - i)
148
-
149
- # Sort years in descending order
150
- sorted_years = sorted(available_years, reverse=True)
151
 
152
- # Generate period list (annual and quarterly)
153
  periods = []
154
- for year in sorted_years[:years]:
155
- # Add annual data
156
- periods.append(str(year))
157
  # Add quarterly data in order Q4, Q3, Q2, Q1
158
  for quarter in range(4, 0, -1):
159
  periods.append(f"{year}Q{quarter}")
160
 
161
- # Get financial data for each period
162
  for period in periods:
163
- data = self.edgar_client.get_financial_data_for_period(cik, period)
164
- # Add even if incomplete data, to avoid N/A situations
165
- if data: # Add as long as period field exists
 
 
 
 
 
 
 
 
 
 
166
  financial_data.append(data)
167
 
168
  return financial_data
 
76
  Returns:
77
  list: List of financial data
78
  """
 
 
79
  financial_data = []
80
 
81
+ # Step 1: Get company filings to see what forms were filed
82
+ filings_10k = self.edgar_client.get_company_filings(cik, ['10-K'])
83
+ filings_20f = self.edgar_client.get_company_filings(cik, ['20-F'])
84
+ filings_10q = self.edgar_client.get_company_filings(cik, ['10-Q'])
85
+
86
+ all_filings = filings_10k + filings_20f + filings_10q
87
+
88
+ if not all_filings:
89
+ return []
90
+
91
+ # Step 2: Get company facts to extract fiscal years from filings
92
  facts = self.edgar_client.get_company_facts(cik)
93
  if not facts:
94
  return []
95
 
96
+ # Step 3: Extract fiscal years from the facts data based on actual filings
97
+ # Build a map of accession_number -> fiscal_year
98
+ accession_to_fy = {}
99
+ fiscal_years = set()
100
 
101
  # Check US-GAAP and IFRS data sources
102
  for data_source in ["us-gaap", "ifrs-full"]:
103
  if data_source in facts.get("facts", {}):
104
  source_data = facts["facts"][data_source]
105
 
106
+ # Look for Revenue tags to map accession numbers to fiscal years
107
  revenue_tags = ["Revenues", "RevenueFromContractWithCustomerExcludingAssessedTax",
108
  "Revenue", "RevenueFromContractWithCustomer"]
109
 
 
112
  units = source_data[tag].get("units", {})
113
  if "USD" in units:
114
  for entry in units["USD"]:
115
+ form = entry.get("form", "")
116
+ fy = entry.get("fy", 0)
117
+ accn = entry.get("accn", "")
118
+ fp = entry.get("fp", "")
119
+
120
+ # Only consider annual reports for fiscal year determination
121
+ if form in ["10-K", "20-F"] and fy > 0 and (fp == "FY" or not fp):
122
+ # Store the mapping
123
+ if accn:
124
+ accession_to_fy[accn] = fy
125
+ fiscal_years.add(fy)
 
 
126
  break
127
+ if fiscal_years:
128
  break
129
 
130
+ # Step 4: If we have fiscal years from facts, use them
131
+ # Otherwise, fall back to filing dates
132
+ if fiscal_years:
133
+ # Sort fiscal years in descending order and take the most recent N years
134
+ sorted_fiscal_years = sorted(fiscal_years, reverse=True)
135
+ target_years = sorted_fiscal_years[:years]
136
+ else:
137
+ # Fallback: use filing dates
138
+ filing_years = set()
139
+ for filing in all_filings:
 
 
140
  if 'filing_date' in filing and filing['filing_date']:
141
  try:
142
  filing_year = int(filing['filing_date'][:4])
143
+ filing_years.add(filing_year)
 
144
  except ValueError:
145
  continue
146
 
147
+ if not filing_years:
148
  return []
149
 
150
+ sorted_years = sorted(filing_years, reverse=True)
151
+ target_years = sorted_years[:years]
 
 
 
 
152
 
153
+ # Step 5: Generate period list (annual and quarterly) for target years
154
  periods = []
155
+ for year in target_years:
156
+ # Add annual data (use FY prefix to indicate fiscal year)
157
+ periods.append(f"FY{year}")
158
  # Add quarterly data in order Q4, Q3, Q2, Q1
159
  for quarter in range(4, 0, -1):
160
  periods.append(f"{year}Q{quarter}")
161
 
162
+ # Step 6: Get financial data for each period
163
  for period in periods:
164
+ # Extract year from period (handle both "FY2024" and "2024Q1" formats)
165
+ if period.startswith("FY"):
166
+ year_str = period[2:] # Remove "FY" prefix
167
+ else:
168
+ year_str = period.split('Q')[0] if 'Q' in period else period
169
+
170
+ data = self.edgar_client.get_financial_data_for_period(cik, year_str if not period.startswith("FY") else year_str)
171
+
172
+ # Add period info to result
173
+ if data and "period" in data:
174
+ # For annual data, use "FY" prefix if it's fiscal year based
175
+ if 'Q' not in period and period.startswith("FY"):
176
+ data["period"] = f"FY{data['period']}"
177
  financial_data.append(data)
178
 
179
  return financial_data