Module: Ocpp::Rails::MeterAnomalyDetector
- Defined in:
- app/services/ocpp/rails/meter_anomaly_detector.rb
Overview
Checks Energy.Active.Import.Register readings against the session's series: the register must never decrease (rollover, meter swap) and must not jump implausibly between samples. Anomalous readings are flagged for review instead of silently entering energy accounting.
Constant Summary collapse
- ENERGY_MEASURAND =
"Energy.Active.Import.Register".freeze
- REGISTER_DECREASE =
"register_decrease".freeze
- IMPLAUSIBLE_JUMP =
"implausible_jump".freeze
Class Method Summary collapse
-
.baseline_wh(session) ⇒ Object
Highest trustworthy register reading seen so far: meterStart plus all previously accepted (unflagged) energy samples, normalised to Wh.
-
.check(session:, measurand:, value:, unit:) ⇒ Object
Returns a flag reason string, or nil when the reading is plausible.
- .normalize_to_wh(value, unit) ⇒ Object
Class Method Details
.baseline_wh(session) ⇒ Object
Highest trustworthy register reading seen so far: meterStart plus all previously accepted (unflagged) energy samples, normalised to Wh.
42 43 44 45 46 47 |
# File 'app/services/ocpp/rails/meter_anomaly_detector.rb', line 42 def self.baseline_wh(session) previous = session.meter_values.energy.where(flagged: false).maximum( Arel.sql("CASE WHEN unit = 'kWh' THEN value * 1000 ELSE value END") ) [ previous, session.start_meter_value ].compact.max end |
.check(session:, measurand:, value:, unit:) ⇒ Object
Returns a flag reason string, or nil when the reading is plausible.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'app/services/ocpp/rails/meter_anomaly_detector.rb', line 14 def self.check(session:, measurand:, value:, unit:) return nil unless session && measurand == ENERGY_MEASURAND candidate = normalize_to_wh(value, unit) return nil unless candidate baseline = baseline_wh(session) return nil unless baseline max_jump = Ocpp::Rails.configuration.implausible_energy_jump_wh if candidate < baseline REGISTER_DECREASE elsif max_jump && candidate - baseline > max_jump IMPLAUSIBLE_JUMP end end |
.normalize_to_wh(value, unit) ⇒ Object
31 32 33 34 35 36 37 38 |
# File 'app/services/ocpp/rails/meter_anomaly_detector.rb', line 31 def self.normalize_to_wh(value, unit) return nil if value.nil? wh = BigDecimal(value.to_s) unit == "kWh" ? wh * 1000 : wh rescue ArgumentError nil end |