summaryrefslogtreecommitdiff
path: root/backend/tests/test_api.py
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-18 01:25:05 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-18 01:25:05 -0700
commit23fcb2087473d13fa0bcc9adf3f0e10039f249fb (patch)
tree1cc46dcbb36f7a3ab41a1478fa72655bb48c0e55 /backend/tests/test_api.py
parent8a7dff97216fd301c7f3c4f20bebec917451d911 (diff)
feat: add valuation math helpers and VALUATION_CACHE
Diffstat (limited to 'backend/tests/test_api.py')
-rw-r--r--backend/tests/test_api.py57
1 files changed, 57 insertions, 0 deletions
diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py
index 395bc12..a5c4eb5 100644
--- a/backend/tests/test_api.py
+++ b/backend/tests/test_api.py
@@ -18,6 +18,7 @@ def clear_service_caches() -> None:
data_service.SHARES_CACHE.clear()
data_service.RATIO_CACHE.clear()
data_service.FINANCIALS_CACHE.clear()
+ data_service.VALUATION_CACHE.clear()
def quarterly_frame(rows: dict[str, list[float]]) -> pd.DataFrame:
@@ -627,3 +628,59 @@ def test_valuation_schema_structure() -> None:
assert resp.dcf.intrinsic_value_per_share == 182.0
assert resp.ev_ebitda.multiple_used == 20.0
assert resp.ev_revenue.available is False
+
+
+def test_build_fcf_series_happy_path() -> None:
+ cf = annual_frame({
+ "Operating Cash Flow": [100.0, 90.0, 80.0, 70.0],
+ "Capital Expenditure": [-10.0, -9.0, -8.0, -7.0],
+ })
+ result = data_service._build_fcf_series(cf)
+ assert result is not None
+ assert len(result) == 4
+ # most recent year FCF = 100 + (-10) = 90
+ assert result.iloc[-1] == 90.0
+
+
+def test_build_fcf_series_empty_df() -> None:
+ result = data_service._build_fcf_series(pd.DataFrame())
+ assert result is None
+
+
+def test_build_fcf_series_missing_capex() -> None:
+ cf = annual_frame({"Operating Cash Flow": [100.0, 90.0, 80.0, 70.0]})
+ result = data_service._build_fcf_series(cf)
+ assert result is None
+
+
+def test_build_multiple_result_empty() -> None:
+ result = data_service._build_multiple_result({})
+ assert result == {"available": False}
+
+
+def test_build_multiple_result_valid() -> None:
+ raw = {
+ "implied_price_per_share": 178.0,
+ "implied_ev": 1_000.0,
+ "equity_value": 900.0,
+ "net_debt": 100.0,
+ "target_multiple_used": 20.0,
+ }
+ result = data_service._build_multiple_result(raw)
+ assert result["available"] is True
+ assert result["implied_price_per_share"] == 178.0
+ assert result["multiple_used"] == 20.0
+
+
+def test_dcf_capped_growth_rate_caps_extremes() -> None:
+ # growth of 200% should be capped at 50%
+ series = pd.Series([10.0, 30.0], index=pd.to_datetime(["2022", "2023"]))
+ result = data_service._dcf_capped_growth_rate(series)
+ assert result == 0.50
+
+
+def test_dcf_capped_growth_rate_skips_sign_flip() -> None:
+ # negative to positive is a sign flip — should skip and return None (no usable periods)
+ series = pd.Series([-10.0, 20.0], index=pd.to_datetime(["2022", "2023"]))
+ result = data_service._dcf_capped_growth_rate(series)
+ assert result is None