[verified] Add per-symbol analysis breakdown endpoint + per-theme narrative
- themes.symbol_breakdown(): transparent scoring derivation (theme_score, siamchart_score components, combined = 0.6*theme + 0.4*siamchart npolut) - GET /api/v1/symbols/<symbol>: themes + theme surprise contributions + fundamentals + price + weights (ข้อ 7) - dashboard.py _theme_narrative(): long-form Thai explanation of each theme's analysis outcome + implication for its stocks (ข้อ 5) - 2 tests; full suite OK; live verified (AOT: combined 0.107 = 0.6*0.571 + 0.4*(-0.588))
This commit is contained in:
@@ -70,3 +70,36 @@ class ThemesTest(unittest.TestCase):
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
class SymbolBreakdownTest(unittest.TestCase):
|
||||
def test_breakdown_shows_components(self):
|
||||
factor_view = {
|
||||
"factors": [
|
||||
{"symbol": "AOT", "eps_growth_yoy": 10.0, "dividend_yield": 2.0,
|
||||
"pe": 20.0, "eps": 5.0, "pbv": 2.0, "roe": 15.0, "is_dividend": True,
|
||||
"company_name": "Airports"},
|
||||
]
|
||||
}
|
||||
theme_surprises = {"tourism": 0.57, "auto_credit": 0.81, "refining_energy": 1.62}
|
||||
from app import themes
|
||||
d = themes.symbol_breakdown("AOT", factor_view=factor_view, theme_surprises=theme_surprises,
|
||||
latest_price=67.0, price_date="2026-08-21")
|
||||
self.assertEqual(d["symbol"], "AOT")
|
||||
self.assertEqual(d["themes"], ["tourism"]) # AOT in tourism map
|
||||
self.assertEqual(d["theme_contributions"][0]["surprise"], 0.57)
|
||||
self.assertIn("siamchart_components", d)
|
||||
self.assertEqual(d["weights"], {"theme": 0.6, "siamchart": 0.4})
|
||||
self.assertIsInstance(d["combined_score"], float)
|
||||
self.assertEqual(d["price"]["latest"], 67.0)
|
||||
self.assertIn("fundamentals", d)
|
||||
|
||||
def test_breakdown_symbol_without_theme(self):
|
||||
factor_view = {"factors": [{"symbol": "BANPU", "eps_growth_yoy": -2.0,
|
||||
"dividend_yield": 0.0, "is_dividend": False,
|
||||
"company_name": "BANPU"}]}
|
||||
from app import themes
|
||||
d = themes.symbol_breakdown("BANPU", factor_view=factor_view, theme_surprises={})
|
||||
self.assertEqual(d["theme_score"], 0.0)
|
||||
self.assertEqual(d["themes"], ["refining_energy"]) # BANPU in energy map
|
||||
self.assertEqual(d["theme_contributions"], []) # but no surprise set -> 0
|
||||
|
||||
Reference in New Issue
Block a user