"""Property-based tests for v3 macro and competitive layers. Validates: - Property 13: Noisy-OR normalized exposure is bounded in [0, 1] - Property 14: Competitive LLR is clamped to [-1.25, 1.25] - Property 15: Graph attenuation is zero beyond max distance Requirements: 9.1, 9.2, 10.3, 10.4, 10.5 """ from __future__ import annotations from hypothesis import given, settings from hypothesis import strategies as st from services.aggregation.interpolation import ( compute_normalized_macro_exposure, ) from services.aggregation.signal_propagation import ( compute_competitive_llr, compute_shrunk_correlation, ) # --------------------------------------------------------------------------- # Strategies # --------------------------------------------------------------------------- unit_floats = st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False) llr_source_values = st.floats(min_value=-5.0, max_value=5.0, allow_nan=False, allow_infinity=False) distances_valid = st.integers(min_value=1, max_value=3) distances_beyond = st.integers(min_value=4, max_value=10) # --------------------------------------------------------------------------- # Feature: math-core-v3-engine, Property 13: Noisy-OR normalized exposure # is bounded in [0, 1] # --------------------------------------------------------------------------- # **Validates: Requirements 9.1, 9.2** @settings(max_examples=100) @given( geo=unit_floats, supply=unit_floats, commodity=unit_floats, sector=unit_floats, ) def test_property_13_noisy_or_exposure_bounded( geo: float, supply: float, commodity: float, sector: float, ) -> None: """Property 13: Noisy-OR normalized exposure is bounded in [0, 1]. For any overlap values O_k in [0, 1] for each dimension (geo, supply, commodity, sector) with fixed positive weights, the normalized macro exposure E_macro SHALL be in [0.0, 1.0], reaching exactly 1.0 when all O_k = 1.0. (Use tier="regional" with dampener=1.0 for this property test) """ overlaps = { "geo": geo, "supply": supply, "commodity": commodity, "sector": sector, } e_macro = compute_normalized_macro_exposure(overlaps, tier="regional") # Must be bounded in [0, 1] with regional dampener = 1.0 assert 0.0 <= e_macro <= 1.0, ( f"E_macro={e_macro} out of bounds [0, 1] for overlaps={overlaps}" ) @settings(max_examples=1) @given(st.just(None)) def test_property_13_max_exposure_is_one(_: None) -> None: """Property 13 (edge): E_macro reaches exactly 1.0 when all O_k = 1.0.""" overlaps = {"geo": 1.0, "supply": 1.0, "commodity": 1.0, "sector": 1.0} e_macro = compute_normalized_macro_exposure(overlaps, tier="regional") assert e_macro == 1.0, f"Expected 1.0 when all overlaps=1.0, got {e_macro}" # --------------------------------------------------------------------------- # Feature: math-core-v3-engine, Property 14: Competitive LLR is clamped # to [-1.25, 1.25] # --------------------------------------------------------------------------- # **Validates: Requirements 10.3, 10.4, 10.5** @settings(max_examples=100) @given( llr_source=llr_source_values, rho_rolling=unit_floats, n_observations=st.integers(min_value=1, max_value=500), same_sector=st.booleans(), d_network=distances_valid, pattern_confidence=unit_floats, ) def test_property_14_competitive_llr_clamped( llr_source: float, rho_rolling: float, n_observations: int, same_sector: bool, d_network: int, pattern_confidence: float, ) -> None: """Property 14: Competitive LLR is clamped to [-1.25, 1.25]. For any source LLR, shrunk correlation (non-negative), graph distance (1-3), and pattern confidence in [0,1], the computed competitive LLR SHALL be in [-1.25, 1.25]. """ rho_effective = compute_shrunk_correlation( rho_rolling=rho_rolling, n_observations=n_observations, same_sector=same_sector, ) # rho_effective should be non-negative per definition assert rho_effective >= 0.0, f"rho_effective={rho_effective} is negative" llr_competitive = compute_competitive_llr( llr_source=llr_source, rho_effective=rho_effective, d_network=d_network, pattern_confidence=pattern_confidence, ) assert -1.25 <= llr_competitive <= 1.25, ( f"Competitive LLR={llr_competitive} out of bounds [-1.25, 1.25] " f"for llr_source={llr_source}, rho_effective={rho_effective}, " f"d_network={d_network}, pattern_confidence={pattern_confidence}" ) # --------------------------------------------------------------------------- # Feature: math-core-v3-engine, Property 15: Graph attenuation is zero # beyond max distance # --------------------------------------------------------------------------- # **Validates: Requirements 10.4, 10.5** @settings(max_examples=100) @given( llr_source=llr_source_values, rho_effective=unit_floats, d_network=distances_beyond, pattern_confidence=unit_floats, ) def test_property_15_zero_attenuation_beyond_max_distance( llr_source: float, rho_effective: float, d_network: int, pattern_confidence: float, ) -> None: """Property 15: Graph attenuation is zero beyond max distance. For any inputs where graph distance > 3, the computed attenuation SHALL be 0.0, producing zero competitive LLR regardless of other parameters. """ llr_competitive = compute_competitive_llr( llr_source=llr_source, rho_effective=rho_effective, d_network=d_network, pattern_confidence=pattern_confidence, ) assert llr_competitive == 0.0, ( f"Expected 0.0 for d_network={d_network} > 3, got {llr_competitive}" )