Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- edgar_client.py +30 -9
- financial_analyzer.py +12 -0
edgar_client.py
CHANGED
|
@@ -669,9 +669,6 @@ class EdgarDataClient:
|
|
| 669 |
"operating_cash_flow": ["NetCashProvidedByUsedInOperatingActivities", "NetCashProvidedUsedInOperatingActivities", "NetCashFlowsFromUsedInOperatingActivities", "CashFlowsFromUsedInOperatingActivities"],
|
| 670 |
}
|
| 671 |
|
| 672 |
-
# Store result
|
| 673 |
-
result = {"period": period}
|
| 674 |
-
|
| 675 |
# Determine target form types to search
|
| 676 |
if 'Q' in period:
|
| 677 |
# Quarterly data, mainly search 10-Q (20-F usually doesn't have quarterly reports)
|
|
@@ -686,6 +683,17 @@ class EdgarDataClient:
|
|
| 686 |
year = int(period)
|
| 687 |
quarter = None
|
| 688 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
# Detect if company uses 20-F (foreign filer)
|
| 690 |
is_20f_filer = False
|
| 691 |
all_filings = self.get_company_filings(cik)
|
|
@@ -693,6 +701,8 @@ class EdgarDataClient:
|
|
| 693 |
form_types_used = set(f.get('form_type', '') for f in all_filings[:20])
|
| 694 |
if '20-F' in form_types_used and '10-K' not in form_types_used:
|
| 695 |
is_20f_filer = True
|
|
|
|
|
|
|
| 696 |
|
| 697 |
# Get company filings to find accession number and primary document
|
| 698 |
filings = self.get_company_filings(cik, form_types=target_forms)
|
|
@@ -855,17 +865,28 @@ class EdgarDataClient:
|
|
| 855 |
|
| 856 |
# Generate complete source URL
|
| 857 |
if primary_document:
|
| 858 |
-
|
| 859 |
else:
|
| 860 |
-
|
| 861 |
else:
|
| 862 |
# Fallback to company browse page if filing not found
|
| 863 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 864 |
|
| 865 |
-
|
| 866 |
-
result["
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 867 |
|
| 868 |
-
# Add detailed information
|
| 869 |
result[f"{metric_key}_details"] = {
|
| 870 |
"tag": metric_tag,
|
| 871 |
"form": matched_entry.get("form", ""),
|
|
|
|
| 669 |
"operating_cash_flow": ["NetCashProvidedByUsedInOperatingActivities", "NetCashProvidedUsedInOperatingActivities", "NetCashFlowsFromUsedInOperatingActivities", "CashFlowsFromUsedInOperatingActivities"],
|
| 670 |
}
|
| 671 |
|
|
|
|
|
|
|
|
|
|
| 672 |
# Determine target form types to search
|
| 673 |
if 'Q' in period:
|
| 674 |
# Quarterly data, mainly search 10-Q (20-F usually doesn't have quarterly reports)
|
|
|
|
| 683 |
year = int(period)
|
| 684 |
quarter = None
|
| 685 |
|
| 686 |
+
# Store result with consolidated meta and sources (added for de-duplication)
|
| 687 |
+
result = {
|
| 688 |
+
"period": period,
|
| 689 |
+
"meta": {
|
| 690 |
+
"year": year,
|
| 691 |
+
"quarter": quarter,
|
| 692 |
+
"is_20f_filer": False # will set below
|
| 693 |
+
},
|
| 694 |
+
"sources": {}
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
# Detect if company uses 20-F (foreign filer)
|
| 698 |
is_20f_filer = False
|
| 699 |
all_filings = self.get_company_filings(cik)
|
|
|
|
| 701 |
form_types_used = set(f.get('form_type', '') for f in all_filings[:20])
|
| 702 |
if '20-F' in form_types_used and '10-K' not in form_types_used:
|
| 703 |
is_20f_filer = True
|
| 704 |
+
# Reflect in meta
|
| 705 |
+
result["meta"]["is_20f_filer"] = is_20f_filer
|
| 706 |
|
| 707 |
# Get company filings to find accession number and primary document
|
| 708 |
filings = self.get_company_filings(cik, form_types=target_forms)
|
|
|
|
| 865 |
|
| 866 |
# Generate complete source URL
|
| 867 |
if primary_document:
|
| 868 |
+
url = f"https://www.sec.gov/Archives/edgar/data/{cik}/{accession_number}/{primary_document}"
|
| 869 |
else:
|
| 870 |
+
url = f"https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK={cik}&type={form_type}&dateb=&owner=exclude&count=100"
|
| 871 |
else:
|
| 872 |
# Fallback to company browse page if filing not found
|
| 873 |
+
url = f"https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK={cik}&type={form_type}&dateb=&owner=exclude&count=100"
|
| 874 |
+
|
| 875 |
+
# Backward compatible: only set once to avoid later overwrites
|
| 876 |
+
if "source_url" not in result:
|
| 877 |
+
result["source_url"] = url
|
| 878 |
+
result["source_form"] = form_type
|
| 879 |
+
result["data_source"] = data_source
|
| 880 |
|
| 881 |
+
# Consolidated per-metric source info
|
| 882 |
+
result["sources"][metric_key] = {
|
| 883 |
+
"url": url,
|
| 884 |
+
"form": form_type,
|
| 885 |
+
"data_source": data_source,
|
| 886 |
+
"filed": matched_entry.get("filed", "")
|
| 887 |
+
}
|
| 888 |
|
| 889 |
+
# Add detailed information (kept for compatibility)
|
| 890 |
result[f"{metric_key}_details"] = {
|
| 891 |
"tag": metric_tag,
|
| 892 |
"form": matched_entry.get("form", ""),
|
financial_analyzer.py
CHANGED
|
@@ -75,6 +75,10 @@ class FinancialAnalyzer:
|
|
| 75 |
# Fast path succeeded, now get detailed info
|
| 76 |
company_info = self.edgar_client.get_company_info(cik)
|
| 77 |
if company_info:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
return company_info
|
| 79 |
else:
|
| 80 |
# Fallback to basic info if detailed fetch fails
|
|
@@ -82,6 +86,7 @@ class FinancialAnalyzer:
|
|
| 82 |
"cik": basic_info['cik'],
|
| 83 |
"name": basic_info['name'],
|
| 84 |
"tickers": [basic_info['ticker']] if basic_info.get('ticker') else [],
|
|
|
|
| 85 |
"_source": "basic_cik_lookup"
|
| 86 |
}
|
| 87 |
else:
|
|
@@ -106,6 +111,7 @@ class FinancialAnalyzer:
|
|
| 106 |
"cik": basic_info['cik'],
|
| 107 |
"name": basic_info['name'],
|
| 108 |
"tickers": [basic_info['ticker']] if basic_info.get('ticker') else [],
|
|
|
|
| 109 |
"ein": None, # Not available in basic search
|
| 110 |
"fiscal_year_end": None, # Not available in basic search
|
| 111 |
"sic_description": None, # Not available in basic search
|
|
@@ -128,6 +134,7 @@ class FinancialAnalyzer:
|
|
| 128 |
"cik": basic_info['cik'],
|
| 129 |
"name": basic_info['name'],
|
| 130 |
"tickers": [basic_info['ticker']] if basic_info.get('ticker') else [],
|
|
|
|
| 131 |
"ein": None,
|
| 132 |
"fiscal_year_end": None,
|
| 133 |
"sic_description": None,
|
|
@@ -139,6 +146,10 @@ class FinancialAnalyzer:
|
|
| 139 |
company_info = self.edgar_client.get_company_info(basic_info['cik'])
|
| 140 |
|
| 141 |
if company_info:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
return company_info
|
| 143 |
else:
|
| 144 |
# Fallback to basic info if detailed fetch fails
|
|
@@ -146,6 +157,7 @@ class FinancialAnalyzer:
|
|
| 146 |
"cik": basic_info['cik'],
|
| 147 |
"name": basic_info['name'],
|
| 148 |
"tickers": [basic_info['ticker']] if basic_info.get('ticker') else [],
|
|
|
|
| 149 |
"_source": "basic_search_fallback"
|
| 150 |
}
|
| 151 |
|
|
|
|
| 75 |
# Fast path succeeded, now get detailed info
|
| 76 |
company_info = self.edgar_client.get_company_info(cik)
|
| 77 |
if company_info:
|
| 78 |
+
# Ensure 'ticker' exists alongside 'tickers' for compatibility
|
| 79 |
+
if "ticker" not in company_info:
|
| 80 |
+
tks = company_info.get("tickers") or []
|
| 81 |
+
company_info["ticker"] = tks[0] if tks else None
|
| 82 |
return company_info
|
| 83 |
else:
|
| 84 |
# Fallback to basic info if detailed fetch fails
|
|
|
|
| 86 |
"cik": basic_info['cik'],
|
| 87 |
"name": basic_info['name'],
|
| 88 |
"tickers": [basic_info['ticker']] if basic_info.get('ticker') else [],
|
| 89 |
+
"ticker": basic_info.get('ticker'),
|
| 90 |
"_source": "basic_cik_lookup"
|
| 91 |
}
|
| 92 |
else:
|
|
|
|
| 111 |
"cik": basic_info['cik'],
|
| 112 |
"name": basic_info['name'],
|
| 113 |
"tickers": [basic_info['ticker']] if basic_info.get('ticker') else [],
|
| 114 |
+
"ticker": basic_info.get('ticker'),
|
| 115 |
"ein": None, # Not available in basic search
|
| 116 |
"fiscal_year_end": None, # Not available in basic search
|
| 117 |
"sic_description": None, # Not available in basic search
|
|
|
|
| 134 |
"cik": basic_info['cik'],
|
| 135 |
"name": basic_info['name'],
|
| 136 |
"tickers": [basic_info['ticker']] if basic_info.get('ticker') else [],
|
| 137 |
+
"ticker": basic_info.get('ticker'),
|
| 138 |
"ein": None,
|
| 139 |
"fiscal_year_end": None,
|
| 140 |
"sic_description": None,
|
|
|
|
| 146 |
company_info = self.edgar_client.get_company_info(basic_info['cik'])
|
| 147 |
|
| 148 |
if company_info:
|
| 149 |
+
# Ensure 'ticker' exists alongside 'tickers' for compatibility
|
| 150 |
+
if "ticker" not in company_info:
|
| 151 |
+
tks = company_info.get("tickers") or []
|
| 152 |
+
company_info["ticker"] = tks[0] if tks else None
|
| 153 |
return company_info
|
| 154 |
else:
|
| 155 |
# Fallback to basic info if detailed fetch fails
|
|
|
|
| 157 |
"cik": basic_info['cik'],
|
| 158 |
"name": basic_info['name'],
|
| 159 |
"tickers": [basic_info['ticker']] if basic_info.get('ticker') else [],
|
| 160 |
+
"ticker": basic_info.get('ticker'),
|
| 161 |
"_source": "basic_search_fallback"
|
| 162 |
}
|
| 163 |
|