Spaces:
Sleeping
Sleeping
fix: Handle 'sources' wrapper in fundamentals data structure
Browse filesThe Researcher-Agent returns fundamentals wrapped in a "sources" key:
{"sources": {"sec_edgar": {"data": {...}}, "yahoo_finance": {...}}}
But analyzer expected flat structure:
{"sec_edgar": {"data": {...}}, "yahoo_finance": {...}}
This caused fundamentals (revenue, net_income, margins, etc.) to be
silently missing from SWOT analysis.
Fixed in 3 places:
- _extract_company_profile() - for company profile extraction
- _generate_data_report() - for Data Report tables
- _extract_key_metrics() - for metric reference table
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- src/nodes/analyzer.py +29 -9
src/nodes/analyzer.py
CHANGED
|
@@ -159,7 +159,12 @@ def _extract_company_profile(raw_data: str) -> dict:
|
|
| 159 |
profile = {}
|
| 160 |
|
| 161 |
# Try SEC EDGAR for business address (most authoritative)
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
sec_profile = sec_data.get("company_info", {}) or sec_data.get("profile", {})
|
| 164 |
|
| 165 |
if sec_profile:
|
|
@@ -177,7 +182,11 @@ def _extract_company_profile(raw_data: str) -> dict:
|
|
| 177 |
yf_profile = yf_val.get("profile", {})
|
| 178 |
|
| 179 |
if not yf_profile:
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
yf_profile = yf_fund.get("profile", {})
|
| 182 |
|
| 183 |
if yf_profile:
|
|
@@ -337,8 +346,13 @@ def _generate_data_report(raw_data: str, is_financial: bool = False) -> str:
|
|
| 337 |
|
| 338 |
# ========== FINANCIALS ==========
|
| 339 |
fin_all = multi_source.get("fundamentals_all", {})
|
| 340 |
-
|
| 341 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
|
| 343 |
if sec_data or yf_data:
|
| 344 |
lines.append("## Financials")
|
|
@@ -606,15 +620,21 @@ def _extract_key_metrics(raw_data: str) -> dict:
|
|
| 606 |
}
|
| 607 |
|
| 608 |
# Extract fundamentals with temporal data
|
| 609 |
-
# Structure
|
| 610 |
-
#
|
|
|
|
| 611 |
fin = metrics.get("fundamentals", {})
|
| 612 |
if not fin or "error" in fin:
|
| 613 |
fin = data.get("multi_source", {}).get("fundamentals_all", {})
|
| 614 |
if fin and "error" not in fin:
|
| 615 |
-
#
|
| 616 |
-
|
| 617 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
# Merge with SEC as primary
|
| 619 |
fin_data = {**yf_data, **sec_data} # SEC overwrites YF where both exist
|
| 620 |
extracted["fundamentals"] = {
|
|
|
|
| 159 |
profile = {}
|
| 160 |
|
| 161 |
# Try SEC EDGAR for business address (most authoritative)
|
| 162 |
+
# Handle both structures (with and without "sources" wrapper)
|
| 163 |
+
fin_all = multi_source.get("fundamentals_all", {})
|
| 164 |
+
if "sources" in fin_all:
|
| 165 |
+
sec_data = fin_all.get("sources", {}).get("sec_edgar", {}).get("data", {})
|
| 166 |
+
else:
|
| 167 |
+
sec_data = fin_all.get("sec_edgar", {}).get("data", {})
|
| 168 |
sec_profile = sec_data.get("company_info", {}) or sec_data.get("profile", {})
|
| 169 |
|
| 170 |
if sec_profile:
|
|
|
|
| 182 |
yf_profile = yf_val.get("profile", {})
|
| 183 |
|
| 184 |
if not yf_profile:
|
| 185 |
+
# Handle both structures (with and without "sources" wrapper)
|
| 186 |
+
if "sources" in fin_all:
|
| 187 |
+
yf_fund = fin_all.get("sources", {}).get("yahoo_finance", {}).get("data", {})
|
| 188 |
+
else:
|
| 189 |
+
yf_fund = fin_all.get("yahoo_finance", {}).get("data", {})
|
| 190 |
yf_profile = yf_fund.get("profile", {})
|
| 191 |
|
| 192 |
if yf_profile:
|
|
|
|
| 346 |
|
| 347 |
# ========== FINANCIALS ==========
|
| 348 |
fin_all = multi_source.get("fundamentals_all", {})
|
| 349 |
+
# Handle both structures (with and without "sources" wrapper)
|
| 350 |
+
if "sources" in fin_all:
|
| 351 |
+
sec_data = fin_all.get("sources", {}).get("sec_edgar", {}).get("data", {})
|
| 352 |
+
yf_data = fin_all.get("sources", {}).get("yahoo_finance", {}).get("data", {})
|
| 353 |
+
else:
|
| 354 |
+
sec_data = fin_all.get("sec_edgar", {}).get("data", {})
|
| 355 |
+
yf_data = fin_all.get("yahoo_finance", {}).get("data", {})
|
| 356 |
|
| 357 |
if sec_data or yf_data:
|
| 358 |
lines.append("## Financials")
|
|
|
|
| 620 |
}
|
| 621 |
|
| 622 |
# Extract fundamentals with temporal data
|
| 623 |
+
# Structure varies:
|
| 624 |
+
# - Old: {"sec_edgar": {"data": {...}}, "yahoo_finance": {"data": {...}}}
|
| 625 |
+
# - New: {"sources": {"sec_edgar": {"data": {...}}, "yahoo_finance": {"data": {...}}}}
|
| 626 |
fin = metrics.get("fundamentals", {})
|
| 627 |
if not fin or "error" in fin:
|
| 628 |
fin = data.get("multi_source", {}).get("fundamentals_all", {})
|
| 629 |
if fin and "error" not in fin:
|
| 630 |
+
# Handle both structures (with and without "sources" wrapper)
|
| 631 |
+
if "sources" in fin:
|
| 632 |
+
sources = fin.get("sources", {})
|
| 633 |
+
sec_data = sources.get("sec_edgar", {}).get("data", {})
|
| 634 |
+
yf_data = sources.get("yahoo_finance", {}).get("data", {})
|
| 635 |
+
else:
|
| 636 |
+
sec_data = fin.get("sec_edgar", {}).get("data", {})
|
| 637 |
+
yf_data = fin.get("yahoo_finance", {}).get("data", {})
|
| 638 |
# Merge with SEC as primary
|
| 639 |
fin_data = {**yf_data, **sec_data} # SEC overwrites YF where both exist
|
| 640 |
extracted["fundamentals"] = {
|