from ucml import pricepaths as pp from ucml import config as C H = 3600.0 def test_transient_removed(): # A -> B (6h) -> A : B is a transient, path collapses to a single level A ts = [0.0, 10 * H, 16 * H] pr = [100.0, 110.0, 100.0] s = pp.sustain(ts, pr, last_seen=100 * H) assert [p for _, p in s.levels] == [100.0] assert s.n_transients == 1 def test_sustained_change_kept(): ts = [0.0, 10 * H, 10 * H + 3 * 86400] pr = [100.0, 110.0, 100.0] s = pp.sustain(ts, pr, last_seen=20 * 86400) assert [p for _, p in s.levels] == [100.0, 110.0, 100.0] assert s.n_transients == 0 def test_null_dropped_and_counted(): s = pp.sustain([0.0, H, 2 * H], [20000.0, None, 19000.0], last_seen=10 * 86400) assert s.n_null == 1 and [p for _, p in s.levels] == [20000.0, 19000.0] def test_classify_cut_rise_minor(): ev = pp.classify([(0.0, 20000.0), (1.0, 19000.0), (2.0, 18950.0), (3.0, 21000.0)]) kinds = [e["kind"] for e in ev] assert kinds == ["entry", "cut", "minor", "rise"] assert abs(ev[1]["change_pct"] + 0.05) < 1e-9 def test_oscillation_flag_threshold(): ts, pr = [], [] t = 0.0 for k in range(8): ts.append(t); pr.append(100.0 if k % 2 == 0 else 110.0); t += 6 * H s = pp.sustain(ts, pr, last_seen=t) assert s.n_transients >= C.OSCILLATION_MAX_TRANSIENTS