spb/qwhpi Public
QHPI — Quebec Housing Price Index: quality-adjusted, hierarchically pooled housing price indexes.
Python 63.9%
TypeScript 25.4%
CSS 5.5%
TeX 3.5%
SQL 0.8%
Makefile 0.5%
Dockerfile 0.5%
1# =============================================================================2# QWHPI — Quebec Weekly Housing Price Index3# Author : Simon-Pierre Boucher4# Contact : contact@spboucher.ai5# File : engine/tests/test_state_space.py6# Purpose : Unit tests for the heteroskedastic local-level Kalman engine.7# =============================================================================8"""State-space unit tests."""910from __future__ import annotations1112import sys13from pathlib import Path1415sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))1617import numpy as np1819from qwhpi.state_space import fit_local_level2021RNG = np.random.default_rng(0)222324def test_recovers_constant_signal():25 T, n = 150, 3026 y = 0.05 + RNG.normal(0, 0.2 / np.sqrt(n), T)27 fit = fit_local_level(y, np.full(T, n), sigma2=0.04)28 assert abs(fit.smoothed[T // 2] - 0.05) < 0.0229 assert fit.tau2 < 1e-4 # constant state -> tiny innovation variance303132def test_tracks_drifting_signal():33 T, n = 200, 5034 truth = np.cumsum(RNG.normal(0, 0.01, T))35 y = truth + RNG.normal(0, 0.3 / np.sqrt(n), T)36 fit = fit_local_level(y, np.full(T, n), sigma2=0.09)37 rmse = np.sqrt(np.mean((fit.smoothed - truth) ** 2))38 assert rmse < 0.02394041def test_empty_weeks_are_predicted_not_dropped():42 T = 10043 n = np.full(T, 20.0)44 n[40:60] = 0 # 20-week gap45 truth = np.cumsum(RNG.normal(0, 0.02, T)) # real innovation -> tau2 > 046 y = np.where(n > 0, truth + RNG.normal(0, 0.05, T), np.nan)47 fit = fit_local_level(y, n, sigma2=0.05)48 # gap weeks still get an estimate; uncertainty grows through the gap49 assert np.all(np.isfinite(fit.smoothed))50 assert fit.filtered_var[59] > fit.filtered_var[41] > fit.filtered_var[39]51 assert fit.gain[50] == 0.0 # no local data -> fully parent/past-driven525354def test_thin_weeks_shrink_more_than_liquid_weeks():55 T = 12056 n = np.full(T, 2.0)57 n[:60] = 200.058 y = RNG.normal(0, 0.05, T)59 fit = fit_local_level(y, n, sigma2=0.09)60 assert fit.gain[:60].mean() > fit.gain[60:].mean()61