vn6295337 Claude Opus 4.5 commited on
Commit
33d2228
·
1 Parent(s): 533590e

Fix: Track actual data source (SEC vs Yahoo) for fundamentals metrics

Browse files

Backend: _extract_metrics_from_raw_data() now tracks which source
(sec_edgar or yahoo_finance) each metric came from via new data_source field.

Frontend: inferDataSource() uses explicit data_source if available,
with fallback to form-based inference for backward compatibility.

This ensures SEC EDGAR data displays correctly even when form field is missing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

frontend/src/components/MCPDataPanel.tsx CHANGED
@@ -97,10 +97,14 @@ function formatValue(value: string | number): string {
97
  }
98
 
99
  // Infer data source from category and metric
100
- function inferDataSource(category: string, metric: string, form?: string): string {
101
  const lowerMetric = metric.toLowerCase()
102
 
103
  if (category === 'fundamentals') {
 
 
 
 
104
  return form ? 'SEC EDGAR' : 'Yahoo Finance'
105
  }
106
  if (category === 'valuation') return 'Yahoo Finance'
@@ -161,6 +165,7 @@ export function MCPDataPanel({ metrics, rawData, companyName, ticker, exchange,
161
  fiscalPeriod?: string | null
162
  endDate?: string
163
  form?: string
 
164
  }>> = {
165
  fundamentals: [],
166
  valuation: [],
@@ -180,7 +185,8 @@ export function MCPDataPanel({ metrics, rawData, companyName, ticker, exchange,
180
  value: m.value,
181
  fiscalPeriod,
182
  endDate: m.end_date,
183
- form: m.form
 
184
  })
185
  }
186
  }
@@ -207,7 +213,7 @@ export function MCPDataPanel({ metrics, rawData, companyName, ticker, exchange,
207
  value: formatValue(m.value),
208
  dataType: inferDataType(m.form, m.metric),
209
  asOf: m.endDate || '-',
210
- source: inferDataSource(cat, m.metric, m.form),
211
  category: cat.charAt(0).toUpperCase() + cat.slice(1)
212
  })
213
  }
 
97
  }
98
 
99
  // Infer data source from category and metric
100
+ function inferDataSource(category: string, metric: string, form?: string, dataSource?: string): string {
101
  const lowerMetric = metric.toLowerCase()
102
 
103
  if (category === 'fundamentals') {
104
+ // Use explicit data_source if provided, otherwise fall back to form-based inference
105
+ if (dataSource === 'sec_edgar') return 'SEC EDGAR'
106
+ if (dataSource === 'yahoo_finance') return 'Yahoo Finance'
107
+ // Legacy fallback: infer from form field
108
  return form ? 'SEC EDGAR' : 'Yahoo Finance'
109
  }
110
  if (category === 'valuation') return 'Yahoo Finance'
 
165
  fiscalPeriod?: string | null
166
  endDate?: string
167
  form?: string
168
+ dataSource?: string
169
  }>> = {
170
  fundamentals: [],
171
  valuation: [],
 
185
  value: m.value,
186
  fiscalPeriod,
187
  endDate: m.end_date,
188
+ form: m.form,
189
+ dataSource: m.data_source
190
  })
191
  }
192
  }
 
213
  value: formatValue(m.value),
214
  dataType: inferDataType(m.form, m.metric),
215
  asOf: m.endDate || '-',
216
+ source: inferDataSource(cat, m.metric, m.form, m.dataSource),
217
  category: cat.charAt(0).toUpperCase() + cat.slice(1)
218
  })
219
  }
frontend/src/lib/api.ts CHANGED
@@ -34,6 +34,7 @@ export interface MetricEntry {
34
  end_date?: string // Fiscal period end date, e.g., "2023-09-30"
35
  fiscal_year?: number // Fiscal year number, e.g., 2023
36
  form?: string // SEC form type: "10-K" (annual) or "10-Q" (quarterly)
 
37
  }
38
 
39
  // MCP status for each server (partial = some data but with errors)
 
34
  end_date?: string // Fiscal period end date, e.g., "2023-09-30"
35
  fiscal_year?: number // Fiscal year number, e.g., 2023
36
  form?: string // SEC form type: "10-K" (annual) or "10-Q" (quarterly)
37
+ data_source?: string // Actual data source: "sec_edgar" or "yahoo_finance"
38
  }
39
 
40
  // MCP status for each server (partial = some data but with errors)
src/services/workflow_store.py CHANGED
@@ -130,31 +130,46 @@ def _extract_metrics_from_raw_data(raw_data: dict) -> list:
130
 
131
  for metric_name in fin_metrics:
132
  # Prefer SEC EDGAR data, fall back to Yahoo Finance
133
- metric_data = sec_data.get(metric_name) or yf_fund.get(metric_name)
134
- if metric_data is not None:
135
- entry = {
136
- "timestamp": timestamp,
137
- "source": "fundamentals",
138
- "metric": metric_name,
139
- }
140
- if isinstance(metric_data, dict):
141
- entry["value"] = metric_data.get("value")
142
- if metric_data.get("end_date"):
143
- entry["end_date"] = metric_data["end_date"]
144
- if metric_data.get("fiscal_year"):
145
- entry["fiscal_year"] = metric_data["fiscal_year"]
146
- if metric_data.get("form"):
147
- entry["form"] = metric_data["form"]
148
- else:
149
- entry["value"] = metric_data
150
 
151
- if entry.get("value") is not None:
152
- metrics.append(entry)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
  # Extract valuation metrics from Yahoo Finance
155
  val_all = multi_source.get("valuation_all", {})
156
  yf_val = val_all.get("yahoo_finance", {}).get("data", {})
157
 
 
 
 
158
  val_metrics = [
159
  "market_cap", "enterprise_value", "trailing_pe", "forward_pe",
160
  "pb_ratio", "ps_ratio", "trailing_peg", "price_to_fcf",
@@ -164,14 +179,27 @@ def _extract_metrics_from_raw_data(raw_data: dict) -> list:
164
  for metric_name in val_metrics:
165
  metric_data = yf_val.get(metric_name)
166
  if metric_data is not None:
167
- value = metric_data.get("value") if isinstance(metric_data, dict) else metric_data
168
- if value is not None:
169
- metrics.append({
170
- "timestamp": timestamp,
171
- "source": "valuation",
172
- "metric": metric_name,
173
- "value": value
174
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
  # Extract volatility metrics
177
  vol_all = multi_source.get("volatility_all", {})
@@ -193,17 +221,32 @@ def _extract_metrics_from_raw_data(raw_data: dict) -> list:
193
  metrics.append(entry)
194
 
195
  # Beta and volatility from Yahoo Finance
 
 
 
196
  for vol_metric in ["beta", "historical_volatility", "implied_volatility"]:
197
  metric_data = yf_vol.get(vol_metric)
198
  if metric_data is not None:
199
- value = metric_data.get("value") if isinstance(metric_data, dict) else metric_data
200
- if value is not None:
201
- metrics.append({
202
- "timestamp": timestamp,
203
- "source": "volatility",
204
- "metric": vol_metric,
205
- "value": value
206
- })
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
  # Extract macro indicators
209
  macro_all = multi_source.get("macro_all", {})
 
130
 
131
  for metric_name in fin_metrics:
132
  # Prefer SEC EDGAR data, fall back to Yahoo Finance
133
+ # Track which source the data actually came from
134
+ sec_metric = sec_data.get(metric_name)
135
+ yf_metric = yf_fund.get(metric_name)
136
+
137
+ if sec_metric is not None:
138
+ metric_data = sec_metric
139
+ actual_source = "sec_edgar"
140
+ elif yf_metric is not None:
141
+ metric_data = yf_metric
142
+ actual_source = "yahoo_finance"
143
+ else:
144
+ continue
 
 
 
 
 
145
 
146
+ entry = {
147
+ "timestamp": timestamp,
148
+ "source": "fundamentals",
149
+ "metric": metric_name,
150
+ "data_source": actual_source, # Track actual data source for frontend
151
+ }
152
+ if isinstance(metric_data, dict):
153
+ entry["value"] = metric_data.get("value")
154
+ if metric_data.get("end_date"):
155
+ entry["end_date"] = metric_data["end_date"]
156
+ if metric_data.get("fiscal_year"):
157
+ entry["fiscal_year"] = metric_data["fiscal_year"]
158
+ if metric_data.get("form"):
159
+ entry["form"] = metric_data["form"]
160
+ else:
161
+ entry["value"] = metric_data
162
+
163
+ if entry.get("value") is not None:
164
+ metrics.append(entry)
165
 
166
  # Extract valuation metrics from Yahoo Finance
167
  val_all = multi_source.get("valuation_all", {})
168
  yf_val = val_all.get("yahoo_finance", {}).get("data", {})
169
 
170
+ # Get valuation fetch date if available (point-in-time data)
171
+ val_fetch_date = yf_val.get("_fetch_date") or yf_val.get("fetch_date")
172
+
173
  val_metrics = [
174
  "market_cap", "enterprise_value", "trailing_pe", "forward_pe",
175
  "pb_ratio", "ps_ratio", "trailing_peg", "price_to_fcf",
 
179
  for metric_name in val_metrics:
180
  metric_data = yf_val.get(metric_name)
181
  if metric_data is not None:
182
+ entry = {
183
+ "timestamp": timestamp,
184
+ "source": "valuation",
185
+ "metric": metric_name,
186
+ }
187
+ if isinstance(metric_data, dict):
188
+ entry["value"] = metric_data.get("value")
189
+ # Extract date if available in metric data
190
+ if metric_data.get("date"):
191
+ entry["end_date"] = metric_data["date"]
192
+ elif metric_data.get("end_date"):
193
+ entry["end_date"] = metric_data["end_date"]
194
+ elif val_fetch_date:
195
+ entry["end_date"] = val_fetch_date
196
+ else:
197
+ entry["value"] = metric_data
198
+ if val_fetch_date:
199
+ entry["end_date"] = val_fetch_date
200
+
201
+ if entry.get("value") is not None:
202
+ metrics.append(entry)
203
 
204
  # Extract volatility metrics
205
  vol_all = multi_source.get("volatility_all", {})
 
221
  metrics.append(entry)
222
 
223
  # Beta and volatility from Yahoo Finance
224
+ # Get volatility fetch date if available
225
+ vol_fetch_date = yf_vol.get("_fetch_date") or yf_vol.get("fetch_date")
226
+
227
  for vol_metric in ["beta", "historical_volatility", "implied_volatility"]:
228
  metric_data = yf_vol.get(vol_metric)
229
  if metric_data is not None:
230
+ entry = {
231
+ "timestamp": timestamp,
232
+ "source": "volatility",
233
+ "metric": vol_metric,
234
+ }
235
+ if isinstance(metric_data, dict):
236
+ entry["value"] = metric_data.get("value")
237
+ if metric_data.get("date"):
238
+ entry["end_date"] = metric_data["date"]
239
+ elif metric_data.get("end_date"):
240
+ entry["end_date"] = metric_data["end_date"]
241
+ elif vol_fetch_date:
242
+ entry["end_date"] = vol_fetch_date
243
+ else:
244
+ entry["value"] = metric_data
245
+ if vol_fetch_date:
246
+ entry["end_date"] = vol_fetch_date
247
+
248
+ if entry.get("value") is not None:
249
+ metrics.append(entry)
250
 
251
  # Extract macro indicators
252
  macro_all = multi_source.get("macro_all", {})