@@ -810,7 +810,7 @@ def __init__(self) -> None:
810810 super ().__init__ ()
811811 self .models : dict [pt .PdChannel , Callable ] = {}
812812 self .verifiers : dict [pt .PdChannel , Callable ] = {}
813- self .has_logged_warning = False
813+ self ._last_bound_warning_at : float | None = None
814814
815815 def hydate_models (self , calibration_data : structs .ODCalibration | None ) -> None :
816816 if calibration_data is None :
@@ -889,31 +889,35 @@ def clamp_to_recorded_extrema() -> pt.OD:
889889 try :
890890 return calibration_data .y_to_x (observed_voltage , enforce_bounds = True )
891891 except exc .NoSolutionsFoundError :
892- if not self .has_logged_warning :
892+ if self ._should_warn_about_bounds () :
893893 self .logger .warning (
894894 f"No solution found for calibrated signal. Trimming signal. Calibrated for OD=[{ min_OD :0.3g} , { max_OD :0.3g} ], V=[{ min_voltage :0.3g} , { max_voltage :0.3g} ]. Observed { observed_voltage :0.3f} V, which would map outside the allowed values."
895895 )
896- self .has_logged_warning = True
897896 return clamp_to_recorded_extrema ()
898897 except exc .SolutionBelowDomainError :
899- if not self .has_logged_warning :
898+ if self ._should_warn_about_bounds () :
900899 below_or_above = "below" if observed_voltage <= min_voltage else "outside of"
901900 self .logger .warning (
902901 f"Signal { below_or_above } suggested calibration range. Trimming signal. Calibrated for OD=[{ min_OD :0.3g} , { max_OD :0.3g} ], V=[{ min_voltage :0.3g} , { max_voltage :0.3g} ]. Observed { observed_voltage :0.3f} V, which would map outside the allowed values."
903902 )
904- self .has_logged_warning = True
905903 return clamp_to_recorded_extrema ()
906904 except exc .SolutionAboveDomainError :
907- if not self .has_logged_warning :
905+ if self ._should_warn_about_bounds () :
908906 above_or_outside = "above" if observed_voltage >= max_voltage else "outside of"
909907 self .logger .warning (
910908 f"Signal { above_or_outside } suggested calibration range. Trimming signal. Calibrated for OD=[{ min_OD :0.3g} , { max_OD :0.3g} ], V=[{ min_voltage :0.3g} , { max_voltage :0.3g} ]. Observed { observed_voltage :0.3f} V, which would map outside the allowed values."
911909 )
912- self .has_logged_warning = True
913910 return clamp_to_recorded_extrema ()
914911
915912 return _calibrate_signal , _verify
916913
914+ def _should_warn_about_bounds (self ) -> bool :
915+ now = time ()
916+ if self ._last_bound_warning_at is None or (now - self ._last_bound_warning_at ) > 300 :
917+ self ._last_bound_warning_at = now
918+ return True
919+ return False
920+
917921 def __call__ (self , od_readings : structs .ODReadings ) -> structs .ODReadings :
918922 calibrated_od_readings = structs .ODReadings (ods = {}, timestamp = od_readings .timestamp )
919923 for channel in od_readings .ods :
@@ -957,6 +961,7 @@ class CachedEstimatorTransformer(LoggerMixin, EstimatorTransformerProtocol):
957961 def __init__ (self ) -> None :
958962 super ().__init__ ()
959963 self .estimator : structs .ODFusionEstimator | None = None
964+ self ._last_bound_warning_at : float | None = None
960965
961966 def hydrate_estimator (self , estimator : structs .ODFusionEstimator | None ) -> None :
962967 if estimator is None :
@@ -973,12 +978,34 @@ def __call__(self, raw_od_readings: structs.ODReadings) -> structs.ODFused | Non
973978 }
974979 try :
975980 od_fused_value = compute_fused_od (self .estimator , fused_inputs )
981+ if self ._should_warn_about_bounds (od_fused_value ):
982+ self .logger .warning (
983+ "Fused OD estimate hit estimator bounds: estimator=%s min_logc=%s max_logc=%s od_fused=%s ref_normalization=%s" ,
984+ self .estimator .estimator_name ,
985+ self .estimator .min_logc ,
986+ self .estimator .max_logc ,
987+ od_fused_value ,
988+ config .get ("od_reading.config" , "ref_normalization" , fallback = "classic" ),
989+ )
976990 except Exception as e :
977991 self .logger .debug (f"Failed to compute fused OD: { e } " , exc_info = True )
978992 return None
979993
980994 return structs .ODFused (od_fused = od_fused_value , timestamp = raw_od_readings .timestamp )
981995
996+ def _should_warn_about_bounds (self , od_fused_value : float ) -> bool :
997+ if self .estimator is None :
998+ return False
999+
1000+ lower_bound = 10 ** self .estimator .min_logc
1001+ upper_bound = 10 ** self .estimator .max_logc
1002+ if (od_fused_value <= lower_bound * 1.001 ) or (od_fused_value >= upper_bound * 0.999 ):
1003+ now = time ()
1004+ if self ._last_bound_warning_at is None or (now - self ._last_bound_warning_at ) > 300 :
1005+ self ._last_bound_warning_at = now
1006+ return True
1007+ return False
1008+
9821009
9831010class ODReader (BackgroundJob ):
9841011 """
0 commit comments