"""Forecast family fault isolation; no IO, fabricated samples or model switching.""" from collections.abc import Mapping from math import isfinite from numbers import Real def collect_predictions(data, config, models, enabled): """Return complete finite PV/load series independently for each requested model. A model with insufficient history must not prevent other valid families being published. It is omitted, not filled with zeros or replaced by another model. Global raw-telemetry checks still run BEFORE this helper in run_forecast. """ expected = tuple(data['df_fut'].index) if not expected or len(set(expected)) != len(expected): raise ValueError('Nonempty unique future interval index required') expected_keys = set(expected) predictions, errors = {}, {} requested = 0 for variant, model in models.items(): predictions[variant] = {} if not enabled(config, variant): continue requested += 1 try: values = model.predict(data) if not isinstance(values, Mapping) or set(values) != expected_keys: raise ValueError('Missing, extra or non-matching forecast timestamps') cleaned = {} for at in expected: value = values[at] if isinstance(value, bool) or not isinstance(value, Real): raise ValueError('Non-numeric forecast power') value = float(value) if not isfinite(value) or value < 0: raise ValueError('Invalid nonnegative forecast power') cleaned[at] = value predictions[variant] = cleaned except Exception as error: # Expected data failures and unexpected model failures are visible, # but model exception text may contain filesystem paths. Do not leak it. errors[variant] = {'status': 'unavailable', 'errorType': type(error).__name__} if requested and all(not p for p in predictions.values()): raise ValueError('All requested PV/load models failed; no forecasts published') return predictions, errors