"""Offline regressions against the exact reviewed orchestration source. Only selected function definitions are compiled; main is NOT imported, and there is no database, network, model-file, prediction-publication or device access. """ import ast import contextlib import datetime import io from pathlib import Path import unittest from unittest.mock import Mock, patch import numpy as np import pandas as pd from telemetry_quality import (TelemetryUnavailable, require_recent_telemetry, sanitize_measured_frame, repeat_daily_profile, profile_source_value) ROOT = Path(__file__).resolve().parents[1] AT = pd.Timestamp('2026-10-01T20:00:00') def functions(*names, **extra): tree = ast.parse((ROOT/'main.py').read_text()) nodes = [n for n in tree.body if isinstance(n,ast.FunctionDef) and n.name in names] if {n.name for n in nodes} != set(names): raise AssertionError('Reviewed function missing') ns = {'pd':pd,'np':np,'datetime':datetime, 'sanitize_measured_frame':sanitize_measured_frame, 'require_recent_telemetry':require_recent_telemetry} ns.update(extra) exec(compile(ast.Module(body=nodes,type_ignores=[]),str(ROOT/'main.py'),'exec'),ns) return ns def raw_frame(start=None, periods=288): index=pd.date_range(start if start is not None else AT-pd.Timedelta(days=1),periods=periods,freq='5min') return pd.DataFrame({'PV':200.,'Hausverbrauch':3200.,'SOC':25.,'Netzleistung':3000.},index=index) class MeasuredTelemetryTest(unittest.TestCase): def test_empty_history_is_not_zero_consumption(self): ns=functions('_fill_defaults','_add_time_features') frame=ns['_fill_defaults'](pd.DataFrame(index=pd.date_range(AT,periods=3,freq='5min')),True) self.assertTrue(frame[['PV','Hausverbrauch','SOC','Netzleistung']].isna().all().all()) self.assertEqual(frame['temp_c'].tolist(),[15.]*3) def test_recorded_zero_is_preserved(self): frame=raw_frame(periods=3);frame[:]=0. ns=functions('_fill_defaults','_add_time_features') out=ns['_fill_defaults'](frame,True) self.assertEqual(out['Hausverbrauch'].tolist(),[0.]*3) self.assertEqual(out['PV'].tolist(),[0.]*3) def test_internal_and_trailing_measurement_gaps_remain_missing(self): frame=raw_frame(periods=7) frame.loc[frame.index[[0,2,3,6]],['PV','Hausverbrauch','SOC','Netzleistung']]=np.nan ns=functions('_fill_defaults','_add_time_features') out=ns['_fill_defaults'](frame,True) self.assertEqual(int(out['Hausverbrauch'].isna().sum()),4) self.assertEqual(int(out['SOC'].isna().sum()),4) self.assertEqual(int(out['Netzleistung'].isna().sum()),4) def test_bad_values_not_real_measurements(self): frame=pd.DataFrame({'Hausverbrauch':[np.inf,-1.,True,0.,250.], 'Netzleistung':[-100.,np.nan,0.,1.,2.], 'SOC':[101.,-1.,np.inf,0.,100.]}) out=sanitize_measured_frame(frame) self.assertTrue(out['Hausverbrauch'].iloc[:3].isna().all()) self.assertEqual(out['Hausverbrauch'].iloc[3],0.) self.assertEqual(out['Netzleistung'].iloc[0],-100.) self.assertTrue(out['SOC'].iloc[:3].isna().all()) def test_recent_recorded_zero_passes(self): frame=raw_frame();frame[['PV','Hausverbrauch']]=0. report=require_recent_telemetry({'now':AT,'df_recent_raw':frame}) self.assertEqual(report['Hausverbrauch']['ageMinutes'],5.) def test_missing_raw_cannot_be_hidden_by_filled_feature_grid(self): with self.assertRaises(TelemetryUnavailable): require_recent_telemetry({'now':AT,'df_recent_raw':pd.DataFrame(),'df_hist':raw_frame()}) def test_stale_values_fail(self): with self.assertRaises(TelemetryUnavailable): require_recent_telemetry({'now':AT,'df_recent_raw':raw_frame(AT-pd.Timedelta(days=2))}) def test_freshness_is_checked_per_field(self): frame=raw_frame();frame.loc[frame.index[-12:],'Hausverbrauch']=np.nan with self.assertRaisesRegex(TelemetryUnavailable,'Hausverbrauch'): require_recent_telemetry({'now':AT,'df_recent_raw':frame}) def test_future_measurements_do_not_rescue_freshness(self): frame=raw_frame(AT,periods=3) with self.assertRaises(TelemetryUnavailable): require_recent_telemetry({'now':AT,'df_recent_raw':frame}) def test_aware_timestamps_normalized_to_utc(self): frame=raw_frame();frame.index=frame.index.tz_localize('UTC').tz_convert('Europe/Zurich') report=require_recent_telemetry({'now':AT.tz_localize('UTC'),'df_recent_raw':frame}) self.assertEqual(report['Hausverbrauch']['ageMinutes'],5.) def test_soc_staleness_cannot_be_hidden(self): frame=raw_frame();frame.loc[frame.index[-24:],'SOC']=np.nan with self.assertRaisesRegex(TelemetryUnavailable,'SOC'): require_recent_telemetry({'now':AT,'df_recent_raw':frame},['SOC']) def test_only_exogenous_future_defaults_are_filled(self): ns=functions('_fill_defaults','_add_time_features') out=ns['_fill_defaults'](pd.DataFrame(index=pd.date_range(AT,periods=6,freq='5min')),False) self.assertTrue(out[['PV','Hausverbrauch','SOC','Netzleistung']].isna().all().all()) def test_input_unchanged(self): frame=raw_frame();before=frame.copy(deep=True) sanitize_measured_frame(frame);require_recent_telemetry({'now':AT,'df_recent_raw':frame}) pd.testing.assert_frame_equal(frame,before) class HistoryOrchestrationTest(unittest.TestCase): def data(self,telemetry): class FrozenDateTime(datetime.datetime): @classmethod def utcnow(cls):return AT.to_pydatetime() import types dates=types.SimpleNamespace(datetime=FrozenDateTime,timedelta=datetime.timedelta) weather=pd.DataFrame({'temp_c':15.,'cloud':20.},index=pd.date_range(AT-pd.Timedelta(days=14),AT+pd.Timedelta(days=2),freq='5min')) fetch=Mock(return_value=(telemetry,weather,pd.DataFrame())) ns=functions('build_data_object','_fill_defaults','_add_time_features','_longest_consistent_segment',datetime=dates,fetch_influx_frames=fetch,FORECAST_HORIZON_HOURS=48) return ns['build_data_object']({'anlagen_id':'offline'},False) def test_weather_tail_does_not_erase_historical_load(self): telemetry=raw_frame(AT-pd.Timedelta(days=2),periods=288) data=self.data(telemetry) self.assertEqual(data['df_hist']['Hausverbrauch'].count(),len(telemetry)) self.assertEqual(data['df_recent_raw']['Hausverbrauch'].count(),len(telemetry)) self.assertEqual(data['df_recent_raw'].index.max(),telemetry.index.max()) self.assertTrue(data['df_hist']['Hausverbrauch'].iloc[-24:].isna().all()) with self.assertRaises(TelemetryUnavailable):require_recent_telemetry(data) def test_no_measurements_preserves_all_missing(self): data=self.data(pd.DataFrame()) self.assertTrue(data['df_hist']['Hausverbrauch'].isna().all()) with self.assertRaises(TelemetryUnavailable):require_recent_telemetry(data) def test_reconnected_short_tail_does_not_delete_past_profile(self): old=raw_frame(AT-pd.Timedelta(days=2),periods=288) new=raw_frame(AT-pd.Timedelta(minutes=10),periods=2) data=self.data(pd.concat([old,new])) self.assertEqual(data['df_hist']['Hausverbrauch'].count(),290) self.assertEqual(require_recent_telemetry(data)['Hausverbrauch']['ageMinutes'],5.) def test_stale_forecast_never_predicts_or_publishes(self): import types models={n:types.SimpleNamespace(predict=Mock()) for n in (1,2,3,10,11,13,21,22,23)} client=Mock();write=Mock();client.write_api.return_value=write publish=Mock();trace=Mock() ns=functions('run_forecast',get_configs=lambda:[{'anlagen_id':'offline','batt_capacity_kwh':10}], InfluxDBClient=Mock(return_value=client),SYNCHRONOUS=object(), INFLUX_URL='offline',INFLUX_TOKEN='synthetic',INFLUX_ORG='offline',INFLUX_BUCKET='offline',INFLUX_TIMEOUT_MS=1, build_data_object=lambda *a,**k:{'now':AT,'df_recent_raw':pd.DataFrame()},active=lambda *args:True, _v4_publish_forecasts=publish,traceback=trace,**{'v'+str(k):v for k,v in models.items()}) with contextlib.redirect_stdout(io.StringIO()),self.assertRaises(RuntimeError):ns['run_forecast']() for model in models.values():model.predict.assert_not_called() publish.assert_not_called();write.write.assert_not_called();client.close.assert_called_once() def test_stale_training_does_not_overwrite_model(self): import types train=Mock();mod=types.SimpleNamespace(train=train) ns=functions('run_training',get_configs=lambda:[{'anlagen_id':'offline'}], MODEL_MODULES={2:mod},QUALITY_TARGETS={2:'Hausverbrauch'},active=lambda *a:True, build_data_object=lambda *a,**k:{'now':AT,'df_recent_raw':pd.DataFrame()},traceback=Mock()) with contextlib.redirect_stdout(io.StringIO()):ns['run_training']() train.assert_not_called() def test_queries_exclude_forecasts_and_align_interval_starts(self): source=(ROOT/'main.py').read_text();ns=functions('fetch_influx_frames', HISTORY_START='1970-01-01T00:00:00Z',FORECAST_HORIZON_HOURS=48,INFLUX_BUCKET='offline', _query_df=Mock(return_value=pd.DataFrame()),_pivot_frame=lambda df,fields:df, _tariff_frame=lambda df,cfg:df,_time_literal=lambda t:t.isoformat()) ns['fetch_influx_frames']({'anlagen_id':'offline'},False) query=ns['_query_df'].call_args_list[0].args[0] self.assertIn('timeSrc: "_start"',query) self.assertIn('!= "forecast_snapshot"',query) self.assertIn('!= "forecast"',query) class RepeatProfileIntegrityTest(unittest.TestCase): def test_pv_repeats_on_second_day(self): history=raw_frame();history['PV']=np.maximum(0.,np.sin(np.arange(288)*2*np.pi/288))*12000. idx=pd.date_range(AT,periods=576,freq='5min') actual=np.array(list(repeat_daily_profile(history,idx,'PV').values())) np.testing.assert_allclose(actual[:288],history['PV']) np.testing.assert_allclose(actual[288:],history['PV']) def test_missing_yesterday_uses_older_finite_day(self): t=AT history=pd.DataFrame({'Hausverbrauch':[3500.,np.nan]},index=[t-pd.Timedelta(days=7),t-pd.Timedelta(days=1)]) self.assertEqual(profile_source_value(history,t,'Hausverbrauch'),3500.) def test_missing_profile_is_not_zero(self): history=pd.DataFrame({'Hausverbrauch':[np.nan]},index=[AT-pd.Timedelta(days=1)]) with self.assertRaises(TelemetryUnavailable):repeat_daily_profile(history,[AT],'Hausverbrauch') def test_recorded_profile_zero_is_valid(self): history=pd.DataFrame({'PV':[0.]},index=[AT-pd.Timedelta(days=1)]) self.assertEqual(profile_source_value(history,AT,'PV'),0.) def test_negative_profile_cannot_be_silently_clamped(self): history=pd.DataFrame({'Hausverbrauch':[-100.]},index=[AT-pd.Timedelta(days=1)]) with self.assertRaises(TelemetryUnavailable):profile_source_value(history,AT,'Hausverbrauch') def test_future_value_is_never_used_as_history(self): history=pd.DataFrame({'Hausverbrauch':[3300.]},index=[AT+pd.Timedelta(days=1)]) with self.assertRaises(TelemetryUnavailable):profile_source_value(history,AT,'Hausverbrauch') if __name__=='__main__':unittest.main()