Files
set50-system/backend/tests/test_te_thailand.py
Kunthawat Greethong fcc0da9c8d feat(factor): te_thailand rate/credit/retail/property/confidence + thai_trade external sector; fix sign inversion on bearish factors
- add te_thailand collector (TradingEconomics) -> 8 factors: interest rate,
  business loan growth, consumer credit, household debt/GDP, retail sales YoY,
  consumer confidence, residential property prices, business confidence;
  feed banks/retail/consumer_staples/nonbank_finance/property/telecom/healthcare
- add thai_trade collector (TradingEconomics external sector) -> exports/
  imports/current-account factors (concurrent in-tree work, verified green)
- fix sign inversion: theme weights were negative on sign:-1 factors (NPL,
  inflation, unemployment) so higher NPL/inflation RAISED scores; direction now
  lives only in factor sign, theme weights positive (regression-locked)
- tests: te_thailand parse+direction, value-key resolution contract, dashboard
  8-sources, scheduler vintage counts; suite 362 OK
2026-08-29 09:18:55 +07:00

158 lines
7.1 KiB
Python

"""Tests for the Thailand rates/credit/retail/confidence collector + themes."""
from __future__ import annotations
import unittest
from app import te_thailand, themes
from app.te_thailand import ThaiFactorsSnapshot, parse_te_thailand_html
def _row(label, value, prev, unit, period):
return (f"<tr><td>{label}</td><td>{value}</td><td>{prev}</td>"
f"<td>{unit}</td><td>{period}</td></tr>")
def _table(*rows):
return f"<table>{''.join(rows)}</table>"
_IR_HTML = _table(
_row("Interest Rate", "1.00", "1.00", "percent", "Aug 2026"),
_row("Loans to Non Financial Corporations", "10553097.00", "10488479.00", "THB Million", "Jun 2026"),
_row("Banks Balance Sheet", "40475793.00", "40559803.00", "THB Million", "Jun 2026"),
)
_CC_HTML = _table(
_row("Consumer Confidence", "51.80", "50.70", "points", "Jul 2026"),
_row("Retail Sales YoY", "-14.50", "-20.00", "percent", "May 2026"),
_row("Consumer Credit", "5285734.00", "5296795.00", "THB Million", "Jun 2025"),
_row("Households Debt to GDP", "87.50", "87.30", "percent of GDP", "Dec 2025"),
_row("Consumer Spending", "1771591.00", "1730973.00", "THB Million", "Jun 2026"),
)
_HOUSING_HTML = _table(
_row("Housing Index", "162.70", "162.40", "points", "Jun 2026"),
_row("Residential Property Prices", "1.26", "0.63", "Percent", "Mar 2026"),
_row("Housing Starts", "4093.00", "6520.00", "units", "Apr 2026"),
)
_BIZCONF_HTML = _table(
_row("Business Confidence", "46.70", "46.10", "points", "Jul 2026"),
_row("Leading Economic Index", "166.19", "163.56", "points", "Jun 2026"),
)
class TeThailandParseTest(unittest.TestCase):
def test_parses_all_indicators(self):
snap = parse_te_thailand_html(_IR_HTML, _CC_HTML, _HOUSING_HTML, _BIZCONF_HTML)
self.assertIsInstance(snap, ThaiFactorsSnapshot)
self.assertEqual(snap.interest_rate_pct, 1.0)
self.assertEqual(snap.loans_to_fin_corp, 10553097.0)
self.assertEqual(snap.consumer_credit_thbmn, 5285734.0)
self.assertEqual(snap.household_debt_gdp_pct, 87.5)
self.assertEqual(snap.retail_sales_yoy, -14.5)
self.assertEqual(snap.consumer_confidence, 51.8)
self.assertEqual(snap.consumer_spending, 1771591.0)
self.assertEqual(snap.property_prices_yoy, 1.26)
self.assertEqual(snap.business_confidence, 46.7)
self.assertEqual(snap.periods["interest_rate_pct"], "Aug 2026")
self.assertEqual(snap.periods["property_prices_yoy"], "Mar 2026")
def test_to_dict_full(self):
d = parse_te_thailand_html(_IR_HTML, _CC_HTML, _HOUSING_HTML, _BIZCONF_HTML).to_dict()
self.assertIn("interest_rate_pct", d)
self.assertIn("retail_sales_yoy", d)
self.assertIn("property_prices_yoy", d)
self.assertIn("business_confidence", d)
self.assertIn("source", d)
def test_missing_values_raise(self):
# neither page yields a usable series -> collector fails loudly
empty = "<table><tr><td>x</td><td>1</td></tr></table>"
with self.assertRaises(te_thailand.ThaiFactorsError):
parse_te_thailand_html(empty, empty)
def test_every_factor_value_key_resolves(self):
from app import factors
keys = set(ThaiFactorsSnapshot().to_dict().keys())
for fkey, fact in factors.FACTORS.items():
if fact.get("fetch") != "te_thailand":
continue
self.assertIn(
fact.get("value_key"), keys,
f"factor {fkey!r} value_key not emitted by te_thailand",
)
class TeThailandThemeDirectionTest(unittest.TestCase):
"""New factors must move theme surprises the intended direction (sign fix)."""
@staticmethod
def _fetched():
base = {
"macro_thai": {
"private_consumption_yoy": 4.9, "private_investment_yoy": 18.1,
"headline_inflation_yoy": 1.95, "core_inflation_yoy": 1.0,
"unemployment_pct": 1.0, "manufacturing_yoy": -3.1,
"tourists_ytd_mn": 16.2,
},
"auto_credit": {"new_car_sales_yoy": 20.07, "vehicle_production": 117383.0,
"auto_exports": 81526.0},
"auto_npl": {"pct_of_npls": 3.0},
"bank_npl": {"pct_of_npls": 1.0},
"energy_thai": {"quarterly": {"Q1/2026": {"net_profit": 19481.0, "sales": 114809.0}}},
"thai_trade": {"current_account_usdm": 500.0, "exports_usdm": 34000.0,
"imports_usdm": 38000.0},
# te_thailand factors — neutral-ish values
"te_thailand": {
"interest_rate_pct": 1.5,
"loans_to_fin_corp": 10000000.0,
"consumer_credit_thbmn": 5000000.0,
"household_debt_gdp_pct": 85.0,
"retail_sales_yoy": 0.0,
"consumer_confidence": 50.0,
"property_prices_yoy": 0.0,
"business_confidence": 50.0,
},
}
return base
def _surprises(self, te):
d = self._fetched()
d["te_thailand"] = te
return themes.compute_theme_surprises(d)
def test_higher_retail_sales_raises_retail(self):
low = self._surprises({**self._fetched()["te_thailand"], "retail_sales_yoy": -15.0})
high = self._surprises({**self._fetched()["te_thailand"], "retail_sales_yoy": 8.0})
self.assertGreater(high["retail"], low["retail"])
def test_higher_rate_and_loans_raise_banks(self):
low = self._surprises({**self._fetched()["te_thailand"], "interest_rate_pct": 0.5,
"loans_to_fin_corp": 8500000.0})
high = self._surprises({**self._fetched()["te_thailand"], "interest_rate_pct": 2.5,
"loans_to_fin_corp": 12000000.0})
self.assertGreater(high["banks"], low["banks"])
def test_higher_household_debt_lowers_nonbank(self):
low = self._surprises({**self._fetched()["te_thailand"], "household_debt_gdp_pct": 80.0})
high = self._surprises({**self._fetched()["te_thailand"], "household_debt_gdp_pct": 92.0})
self.assertLess(high["nonbank_finance"], low["nonbank_finance"])
def test_higher_consumer_confidence_raises_retail(self):
low = self._surprises({**self._fetched()["te_thailand"], "consumer_confidence": 42.0})
high = self._surprises({**self._fetched()["te_thailand"], "consumer_confidence": 60.0})
self.assertGreater(high["retail"], low["retail"])
def test_higher_property_prices_raise_property(self):
low = self._surprises({**self._fetched()["te_thailand"], "property_prices_yoy": -4.0})
high = self._surprises({**self._fetched()["te_thailand"], "property_prices_yoy": 4.0})
self.assertGreater(high["property"], low["property"])
def test_higher_business_confidence_raises_telecom(self):
low = self._surprises({**self._fetched()["te_thailand"], "business_confidence": 42.0})
high = self._surprises({**self._fetched()["te_thailand"], "business_confidence": 58.0})
self.assertGreater(high["telecom_it"], low["telecom_it"])
if __name__ == "__main__":
unittest.main()