- 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
158 lines
7.1 KiB
Python
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()
|