Viney Claude Sonnet 5 commited on
Commit
273c984
·
1 Parent(s): d65dc97

fix: preserve None for masked fields in financials chart series instead of coercing to 0

Browse files

Chart builders (_chart_margins, _chart_cash, _chart_balance,
_chart_capital_allocation) used (_safe(...) or 0) for fields masked
by the CHECK_REQUIRED field-level disclosure, rendering a fabricated
0% margin / $0B FCF instead of a gap in the series. Also reword
_masked_quality_message from the now-inaccurate "not VERIFIED" to
"no citable SEC lineage", since CHECK_REQUIRED rows are displayed
(partially) rather than hidden entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Files changed (2) hide show
  1. dashboard/financials.py +23 -12
  2. tests/test_financials.py +48 -0
dashboard/financials.py CHANGED
@@ -156,7 +156,7 @@ def _masked_quality_message(rows: list[dict]) -> str:
156
  details.append(f"{period}: {status}{warning_text}")
157
  suffix = f"; +{len(rows) - 5} more" if len(rows) > 5 else ""
158
  return (
159
- f"{len(rows)} period(s) masked because period lineage is not VERIFIED: "
160
  + "; ".join(details)
161
  + suffix
162
  )
@@ -392,8 +392,10 @@ def _chart_revenue(labels: list, data: list) -> None:
392
  def _chart_margins(labels: list, data: list) -> None:
393
  import plotly.graph_objects as go
394
 
395
- gross = [(_safe(r.get("gross_margin")) or 0) * 100 for r in data]
396
- op = [(_safe(r.get("operating_margin")) or 0) * 100 for r in data]
 
 
397
  eps = [_safe(r.get("eps")) for r in data]
398
 
399
  fig = go.Figure()
@@ -427,10 +429,14 @@ def _chart_margins(labels: list, data: list) -> None:
427
  def _chart_cash(labels: list, data: list) -> None:
428
  import plotly.graph_objects as go
429
 
430
- fcf = [(_safe(r.get("free_cash_flow")) or 0) / 1e9 for r in data]
431
- capex = [(_safe(r.get("capex")) or 0) / 1e9 for r in data]
432
- buybacks = [(_safe(r.get("buybacks")) or 0) / 1e9 for r in data]
433
- dividends = [(_safe(r.get("dividends_paid")) or 0) / 1e9 for r in data]
 
 
 
 
434
 
435
  fig = go.Figure()
436
  fig.add_trace(go.Bar(name="Free Cash Flow", x=labels, y=fcf,
@@ -462,8 +468,10 @@ def _chart_cash(labels: list, data: list) -> None:
462
  def _chart_balance(labels: list, data: list) -> None:
463
  import plotly.graph_objects as go
464
 
465
- debt = [(_safe(r.get("total_debt")) or 0) / 1e9 for r in data]
466
- equity = [(_safe(r.get("stockholders_equity")) or 0) / 1e9 for r in data]
 
 
467
  de = []
468
  for r in data:
469
  d = _safe(r.get("total_debt"))
@@ -507,9 +515,12 @@ def _chart_capital_allocation(labels: list, data: list) -> None:
507
  import plotly.graph_objects as go
508
  from plotly.subplots import make_subplots
509
 
510
- capex = [(_safe(r.get("capex")) or 0) / 1e9 for r in data]
511
- dividends = [(_safe(r.get("dividends_paid")) or 0) / 1e9 for r in data]
512
- buybacks = [(_safe(r.get("buybacks")) or 0) / 1e9 for r in data]
 
 
 
513
  fcf = [_safe(r.get("free_cash_flow")) for r in data]
514
  fcf_b = [v / 1e9 if v is not None else None for v in fcf]
515
 
 
156
  details.append(f"{period}: {status}{warning_text}")
157
  suffix = f"; +{len(rows) - 5} more" if len(rows) > 5 else ""
158
  return (
159
+ f"{len(rows)} period(s) masked because no citable SEC lineage is available: "
160
  + "; ".join(details)
161
  + suffix
162
  )
 
392
  def _chart_margins(labels: list, data: list) -> None:
393
  import plotly.graph_objects as go
394
 
395
+ gross_values = [_safe(r.get("gross_margin")) for r in data]
396
+ op_values = [_safe(r.get("operating_margin")) for r in data]
397
+ gross = [v * 100 if v is not None else None for v in gross_values]
398
+ op = [v * 100 if v is not None else None for v in op_values]
399
  eps = [_safe(r.get("eps")) for r in data]
400
 
401
  fig = go.Figure()
 
429
  def _chart_cash(labels: list, data: list) -> None:
430
  import plotly.graph_objects as go
431
 
432
+ fcf_values = [_safe(r.get("free_cash_flow")) for r in data]
433
+ capex_values = [_safe(r.get("capex")) for r in data]
434
+ buyback_values = [_safe(r.get("buybacks")) for r in data]
435
+ dividend_values = [_safe(r.get("dividends_paid")) for r in data]
436
+ fcf = [v / 1e9 if v is not None else None for v in fcf_values]
437
+ capex = [v / 1e9 if v is not None else None for v in capex_values]
438
+ buybacks = [v / 1e9 if v is not None else None for v in buyback_values]
439
+ dividends = [v / 1e9 if v is not None else None for v in dividend_values]
440
 
441
  fig = go.Figure()
442
  fig.add_trace(go.Bar(name="Free Cash Flow", x=labels, y=fcf,
 
468
  def _chart_balance(labels: list, data: list) -> None:
469
  import plotly.graph_objects as go
470
 
471
+ debt_values = [_safe(r.get("total_debt")) for r in data]
472
+ equity_values = [_safe(r.get("stockholders_equity")) for r in data]
473
+ debt = [v / 1e9 if v is not None else None for v in debt_values]
474
+ equity = [v / 1e9 if v is not None else None for v in equity_values]
475
  de = []
476
  for r in data:
477
  d = _safe(r.get("total_debt"))
 
515
  import plotly.graph_objects as go
516
  from plotly.subplots import make_subplots
517
 
518
+ capex_values = [_safe(r.get("capex")) for r in data]
519
+ dividend_values = [_safe(r.get("dividends_paid")) for r in data]
520
+ buyback_values = [_safe(r.get("buybacks")) for r in data]
521
+ capex = [v / 1e9 if v is not None else None for v in capex_values]
522
+ dividends = [v / 1e9 if v is not None else None for v in dividend_values]
523
+ buybacks = [v / 1e9 if v is not None else None for v in buyback_values]
524
  fcf = [_safe(r.get("free_cash_flow")) for r in data]
525
  fcf_b = [v / 1e9 if v is not None else None for v in fcf]
526
 
tests/test_financials.py CHANGED
@@ -7,6 +7,8 @@ Each company has a different fiscal year end month:
7
  NVDA — January (fy_end_month=1)
8
  """
9
  from __future__ import annotations
 
 
10
  import pytest
11
 
12
 
@@ -225,3 +227,49 @@ class TestFinancialDataQuality:
225
  assert "FY2024: LEGACY_UNVERIFIED" in message
226
  assert "Q22025" not in message
227
  assert "revenue:duration_mismatch" not in message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  NVDA — January (fy_end_month=1)
8
  """
9
  from __future__ import annotations
10
+ from unittest.mock import patch
11
+
12
  import pytest
13
 
14
 
 
227
  assert "FY2024: LEGACY_UNVERIFIED" in message
228
  assert "Q22025" not in message
229
  assert "revenue:duration_mismatch" not in message
230
+ assert "no citable SEC lineage" in message
231
+ assert "not VERIFIED" not in message
232
+
233
+ @patch("dashboard.financials.st.plotly_chart")
234
+ def test_chart_margins_preserves_masked_gross_margin_as_none(
235
+ self, mock_plotly_chart
236
+ ):
237
+ from dashboard.financials import _chart_margins, _display_row
238
+
239
+ row = self._row(
240
+ "Q22025", "2025-08-01", "10-Q", "CHECK_REQUIRED",
241
+ ["gross_margin:fallback_non_sec"],
242
+ )
243
+ displayed = _display_row(row)
244
+
245
+ assert displayed is not None
246
+ assert displayed["gross_margin"] is None
247
+ assert displayed["revenue"] == 10_000_000_000.0
248
+ assert displayed["eps"] == 2.5
249
+
250
+ _chart_margins(["Q2 FY25"], [displayed])
251
+
252
+ margins_figure = mock_plotly_chart.call_args_list[0].args[0]
253
+ assert list(margins_figure.data[0].y) == [None]
254
+
255
+ @patch("dashboard.financials.st.plotly_chart")
256
+ def test_chart_cash_preserves_masked_free_cash_flow_as_none(
257
+ self, mock_plotly_chart
258
+ ):
259
+ from dashboard.financials import _chart_cash, _display_row
260
+
261
+ row = self._row(
262
+ "Q22025", "2025-08-01", "10-Q", "CHECK_REQUIRED",
263
+ ["free_cash_flow:fallback_non_sec"],
264
+ )
265
+ displayed = _display_row(row)
266
+
267
+ assert displayed is not None
268
+ assert displayed["free_cash_flow"] is None
269
+ assert displayed["revenue"] == 10_000_000_000.0
270
+ assert displayed["eps"] == 2.5
271
+
272
+ _chart_cash(["Q2 FY25"], [displayed])
273
+
274
+ cash_figure = mock_plotly_chart.call_args_list[0].args[0]
275
+ assert list(cash_figure.data[0].y) == [None]