Spaces:
Sleeping
Sleeping
Fix NoneType error for companies with incomplete SEC data
Browse filesAdd defensive null checks to get_latest_value() and calculate_growth()
to handle cases where SEC EDGAR returns incomplete data for smaller
companies (e.g., VBNK). The us-gaap namespace may be missing or contain
incomplete concept data.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
mcp-servers/financials-basket/server.py
CHANGED
|
@@ -102,30 +102,45 @@ def get_latest_value(facts: dict, concept: str, unit: str = "USD") -> Optional[d
|
|
| 102 |
Extract latest value for a concept from company facts.
|
| 103 |
Returns dict with value, period end date, and fiscal year.
|
| 104 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
try:
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
-
|
|
|
|
| 110 |
return None
|
| 111 |
|
| 112 |
# Filter for annual (10-K) filings and get most recent
|
| 113 |
-
annual_facts = [f for f in units if f.get("form") == "10-K"]
|
|
|
|
|
|
|
|
|
|
| 114 |
if not annual_facts:
|
| 115 |
-
|
| 116 |
|
| 117 |
# Sort by end date descending
|
| 118 |
annual_facts.sort(key=lambda x: x.get("end", ""), reverse=True)
|
| 119 |
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
}
|
| 128 |
-
return None
|
| 129 |
except Exception as e:
|
| 130 |
logger.error(f"Error extracting {concept}: {e}")
|
| 131 |
return None
|
|
@@ -133,18 +148,35 @@ def get_latest_value(facts: dict, concept: str, unit: str = "USD") -> Optional[d
|
|
| 133 |
|
| 134 |
def calculate_growth(facts: dict, concept: str, years: int = 3) -> Optional[float]:
|
| 135 |
"""Calculate CAGR for a concept over specified years."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
try:
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
-
annual_facts = [f for f in units if f.get("form") == "10-K"]
|
| 141 |
annual_facts.sort(key=lambda x: x.get("end", ""), reverse=True)
|
| 142 |
|
| 143 |
if len(annual_facts) < years + 1:
|
| 144 |
return None
|
| 145 |
|
| 146 |
-
latest_val = annual_facts[0].get("val", 0)
|
| 147 |
-
older_val = annual_facts[years].get("val", 0)
|
| 148 |
|
| 149 |
if older_val <= 0 or latest_val <= 0:
|
| 150 |
return None
|
|
|
|
| 102 |
Extract latest value for a concept from company facts.
|
| 103 |
Returns dict with value, period end date, and fiscal year.
|
| 104 |
"""
|
| 105 |
+
# Defensive check for None or invalid facts
|
| 106 |
+
if not facts or not isinstance(facts, dict):
|
| 107 |
+
return None
|
| 108 |
+
|
| 109 |
try:
|
| 110 |
+
us_gaap = facts.get("us-gaap")
|
| 111 |
+
if not us_gaap or not isinstance(us_gaap, dict):
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
concept_data = us_gaap.get(concept)
|
| 115 |
+
if not concept_data or not isinstance(concept_data, dict):
|
| 116 |
+
return None
|
| 117 |
+
|
| 118 |
+
units_data = concept_data.get("units")
|
| 119 |
+
if not units_data or not isinstance(units_data, dict):
|
| 120 |
+
return None
|
| 121 |
|
| 122 |
+
units = units_data.get(unit, [])
|
| 123 |
+
if not units or not isinstance(units, list):
|
| 124 |
return None
|
| 125 |
|
| 126 |
# Filter for annual (10-K) filings and get most recent
|
| 127 |
+
annual_facts = [f for f in units if isinstance(f, dict) and f.get("form") == "10-K"]
|
| 128 |
+
if not annual_facts:
|
| 129 |
+
annual_facts = [f for f in units if isinstance(f, dict)] # Fallback to all if no 10-K
|
| 130 |
+
|
| 131 |
if not annual_facts:
|
| 132 |
+
return None
|
| 133 |
|
| 134 |
# Sort by end date descending
|
| 135 |
annual_facts.sort(key=lambda x: x.get("end", ""), reverse=True)
|
| 136 |
|
| 137 |
+
latest = annual_facts[0]
|
| 138 |
+
return {
|
| 139 |
+
"value": latest.get("val"),
|
| 140 |
+
"end_date": latest.get("end"),
|
| 141 |
+
"fiscal_year": latest.get("fy"),
|
| 142 |
+
"form": latest.get("form")
|
| 143 |
+
}
|
|
|
|
|
|
|
| 144 |
except Exception as e:
|
| 145 |
logger.error(f"Error extracting {concept}: {e}")
|
| 146 |
return None
|
|
|
|
| 148 |
|
| 149 |
def calculate_growth(facts: dict, concept: str, years: int = 3) -> Optional[float]:
|
| 150 |
"""Calculate CAGR for a concept over specified years."""
|
| 151 |
+
# Defensive check for None or invalid facts
|
| 152 |
+
if not facts or not isinstance(facts, dict):
|
| 153 |
+
return None
|
| 154 |
+
|
| 155 |
try:
|
| 156 |
+
us_gaap = facts.get("us-gaap")
|
| 157 |
+
if not us_gaap or not isinstance(us_gaap, dict):
|
| 158 |
+
return None
|
| 159 |
+
|
| 160 |
+
concept_data = us_gaap.get(concept)
|
| 161 |
+
if not concept_data or not isinstance(concept_data, dict):
|
| 162 |
+
return None
|
| 163 |
+
|
| 164 |
+
units_data = concept_data.get("units")
|
| 165 |
+
if not units_data or not isinstance(units_data, dict):
|
| 166 |
+
return None
|
| 167 |
+
|
| 168 |
+
units = units_data.get("USD", [])
|
| 169 |
+
if not units or not isinstance(units, list):
|
| 170 |
+
return None
|
| 171 |
|
| 172 |
+
annual_facts = [f for f in units if isinstance(f, dict) and f.get("form") == "10-K"]
|
| 173 |
annual_facts.sort(key=lambda x: x.get("end", ""), reverse=True)
|
| 174 |
|
| 175 |
if len(annual_facts) < years + 1:
|
| 176 |
return None
|
| 177 |
|
| 178 |
+
latest_val = annual_facts[0].get("val", 0) or 0
|
| 179 |
+
older_val = annual_facts[years].get("val", 0) or 0
|
| 180 |
|
| 181 |
if older_val <= 0 or latest_val <= 0:
|
| 182 |
return None
|