Quick reference for concrete flagging algorithm implementations. The operational-state stack is sequential; abnormal-operation and inter-turbine windspeed anomaly flagging are separate flagging jobs that consume their own inputs.

Overview: Individual Flags and Composite Flags

The mapped code contains 5 concrete flagging algorithms plus the OperationalStateClassifier orchestrator:

Individual Flags

  1. Operation State Flag - OperationStateFlagging writes OperationStateAnalytics: 0=IDLING, 1=RUNNING, 2=STOPPING, -1=discarded/bad quality
  2. Stationarity Flag - StationarityFlagging writes StationaryFlag: True/1=stationary, False/0=non-stationary, -1=discarded/bad quality
  3. Control State Flag - ControlStateFlagging writes ControlStateAnalytics: 3=PITCH_CONTROL, 4=TORQUE_CONTROL, 1=still RUNNING when no control condition is assigned, -1=discarded/bad quality
  4. Abnormal Operation Score - AbnormalOperationFlagging writes AbnormalOperationScore: 0=normal, higher=count of anomalous ARR pairs, -1=discarded/bad quality
  5. Inter-Turbine Wind Speed Anomaly - InterTurbineWindspeedAnomalyFlagging writes InterTurbineWindSpeedAnomality with point name AnemometerFlag: 0=normal, 1=abnormal, -1=bad quality or insufficient population

Composite Flag Generated by OperationalStateClassifier

OperationalStateClassifier runs OperationStateFlagging, StationarityFlagging, and ControlStateFlagging, then writes one composite operation-state signal, OperationStateCompositeAnalytics:

  1. Composite Operation State - Combines base operation state with control mode: - 0 (IDLING): Not generating power - 1 (RUNNING): Generating power and no pitch/torque override - 2 (STOPPING): Transitioning between states - 3 (PITCH_CONTROL): Running in pitch control mode - 4 (TORQUE_CONTROL): Running in torque control mode

OperationCategory and OperationCategoryWorse are not produced by the flagging algorithms in this reference. They are allocation outputs from wind_allocation_from_tags.py / wind_allocations_rasterizer.py and are consolidated elsewhere.


1. Operation State Flagging

Purpose: Classify turbine operational mode as IDLING, RUNNING, or STOPPING.

Class: OperationStateFlagging

Output: wind_sch.WTUR_OPERATION_STATE (OperationStateAnalytics)

Required Signals

  • wind_sch.WTUR_ACTIVE_POWER (ActivePower) - Power production level
  • wind_sch.WTUR_ACTIVE_POWER_MIN (ActivePower_min) - Minimum power in interval
  • wind_sch.WNAC_WIND_SPEED (WindSpeed) - Ambient wind speed

Turbine Parameters

  • cut_in - From Turbine.get_cut_in_wind_speed()
  • cut_out - From Turbine.get_cut_out_wind_speed()
  • power_threshold - From Turbine.get_power_threshold_kw()

Filtering

  • Data quality only - No custom filters

Compute Logic

When cut-in, cut-out, and wind speed are available: - RUNNING (1): ActivePower_min > power_threshold and WindSpeed > cut_in and WindSpeed < cut_out - STOPPING (2): ActivePower_min <= power_threshold and ActivePower > 0 - IDLING (0): All other conditions

When cut-in, cut-out, or wind speed is unavailable: - RUNNING (1): ActivePower_min > power_threshold - STOPPING (2): ActivePower_min <= power_threshold and ActivePower > 0 - IDLING (0): All other conditions

Bad-quality rows are restored as -1 by default.


2. Stationarity Flagging

Purpose: Detect stationary vs. non-stationary running periods from nacelle-direction variability.

Class: StationarityFlagging

Output: wind_sch.WTUR_STATIONNARY_FLAG (StationaryFlag)

Required Signals

Sensors: - wind_sch.WNAC_DIRECTION_ANGLE_MIN (NacelleDirection_min) - Minimum nacelle direction in interval - wind_sch.WNAC_DIRECTION_ANGLE_MAX (NacelleDirection_max) - Maximum nacelle direction in interval

Derived: - wind_sch.WTUR_OPERATION_STATE (OperationStateAnalytics) - From previous step

Turbine Parameters

None

Filtering

  • Data quality - Remove bad sensor readings
  • Non-running periods - Only evaluate where OperationStateAnalytics == 1

Compute Logic

Uses median-based z-score anomaly detection on a cosine-transformed nacelle-direction variability signal: - Variability signal: cos(NacelleDirection_max * pi / 180) - cos(NacelleDirection_min * pi / 180) - Detector: Zscore(use_median=True, k=3.0) - Stationary (True/1): no z-score anomaly is detected - Non-stationary (False/0): z-score anomaly is detected

Bad-quality and non-running rows are restored as -1 by default.

Output: Boolean-like flag (True = stationary, False = non-stationary) with -1 for discarded rows.


Stationary vs Non-stationary Periods

Figure: Detection of stationary operating windows



Stationary vs Non-stationary on power curve

Figure: Distribution of Stationnarity Non stationnarity on a power curve


3. Control State Flagging

Purpose: Identify pitch-control and torque-control samples during running, stationary operation.

Class: ControlStateFlagging

Output: wind_sch.WTUR_CONTROL_STATE (ControlStateAnalytics)

Required Signals

Sensors requiring data quality validation: - wind_sch.WROT_PITCH_BLD_ANGLE (Blade_PitchAngle) - Blade pitch position - wind_sch.WTUR_ACTIVE_POWER (ActivePower) - Power output and fallback pitch-control detection

Derived: - wind_sch.WTUR_OPERATION_STATE (OperationStateAnalytics) - From operation-state flagging - wind_sch.WTUR_STATIONNARY_FLAG (StationaryFlag) - From stationarity flagging

Turbine Parameters

Static: - rated_power - From Turbine.get_rated_power() - inflection_power - From Turbine.get_inflection_power()

Estimated from filtered data: - pitch_locked_pos and pitch_locked_unc - From Turbine.estimate_pitch_locked_values(df_filtered, inflection_power) - rated_gen_torque and rated_gen_torque_uncertainty - From Turbine.estimate_generator_rated_torque(data=df_filtered) when the needed data is available

Filtering

  • Data quality - Remove bad Blade_PitchAngle or ActivePower readings
  • Non-running periods - Only evaluate where OperationStateAnalytics == 1
  • Non-stationary periods - Only evaluate where StationaryFlag == 1

Compute Logic

CONTROL_THRESHOLD = 2.0. The output starts as a copy of OperationStateAnalytics for the filtered rows.

  • TORQUE_CONTROL (4): Blade_PitchAngle < pitch_locked_pos + 2.0 * pitch_locked_unc
  • PITCH_CONTROL (3) primary branch: GeneratorTorque > rated_gen_torque - 2.0 * rated_gen_torque_uncertainty, when generator torque exists in the working DataFrame
  • PITCH_CONTROL (3) fallback branch: ActivePower > inflection_power, when the primary branch is unavailable
  • Pitch control is assigned after torque control and therefore takes precedence when both conditions are met
  • Filtered rows that do not meet either control condition remain at their copied operation-state value, usually RUNNING (1)

Bad-quality, non-running, and non-stationary rows are restored as -1 by default.


Pitch vs Torque Control Regions

Figure: Control state regions in pitch-torque space


4. Abnormal Operation Flagging

Purpose: Detect sensor-to-sensor relationship anomalies with Analytical Redundancy Relations (ARR).

Class: AbnormalOperationFlagging

Output: wind_sch.WTUR_ABNORMAL_OPERATION_SCORE (AbnormalOperationScore)

Required Signals

Sensors: - wind_sch.GENERATOR_SPEED_CONS (GeneratorSpeedCons.10m) - wind_sch.GENERATOR_TORQUE_CONS (GeneratorTorqueCons.10m) - wind_sch.PITCH_ANGLE_CONS (PitchAngleCons.10m) - wind_sch.ACTIVE_POWER_CONS (ActivePowerCons.10m)

Derived: - wind_sch.OPERATION_STATE_CONS (OperationStateCons.10m) - wind_sch.STATIONARY_FLAG_CONS (StationaryFlagCons.10m) - wind_sch.CONTROL_STATE_CONS (ControlStateCons.10m)

Model Configuration

Factory Pattern: Uses configurable factories to create regression models and anomaly detectors: - model_factory: Creates LGBMRegressionModel with n_estimators=100, max_depth=4, and num_leaves=5 - detector_factory: Creates BinwiseZscore(k=2.5, use_median=True) - control_state_threshold: Default 0.1; valid range 0.0 to 1.0

Why factories: Encapsulates all hyperparameters within zero-argument factory functions, making the algorithm interface clean while allowing customization without changing the algorithm code.

Filtering

  • Data quality - Remove bad sensor readings for the four consolidated sensor inputs
  • Non-running periods - Only evaluate where OperationStateCons.10m == 1
  • Non-stationary periods - Only evaluate where StationaryFlagCons.10m == 1
  • Control-state periods - If at least control_state_threshold of running and stationary rows have ControlStateCons.10m == 4, filter to torque-control rows only; otherwise skip control-state filtering

Compute Logic

Uses Analytical Redundancy Relations (ARR): 1. Build regression models for all ordered source -> target sensor pairs among the four sensor columns, excluding self-pairs 2. Fit models on clean, filtered data 3. Detect anomalies in residuals using the configured anomaly detector; BinwiseZscore receives a bin_by callable when supported 4. Aggregate with method="count": count how many ARR pairs flag anomalies at each timestamp

Output: Anomaly score from 0 (normal) to N, where N is the number of ARR pairs. With four sensors, N can be 12. Discarded rows are restored as -1 by default. When persisted scoring has no model for a turbine, or required inputs are unavailable, the scoring path writes a default zero score.

Why extensive filtering: ARR models learn normal sensor relationships. Training on non-running, non-stationary, or non-selected control-state samples would corrupt these relationships, causing false positives during normal operation.

Note on Data Requirements: The model-builder pipeline step enforces ABNORMAL_MIN_ROWS_PER_TURBINE = 100 as a coarse minimum for the monthly baseline query. It fits one ARR detector per turbine for the month before end_date and persists models in dim_wind_abnormal_operation_models.


ARR Anomaly Detection

Figure: Multi-sensor anomaly detection using ARR models with major control change

---

ARR Anomaly Detection on normal functionning

Figure: Multi-sensor anomaly detection using ARR models with minor abnormal falg

---

ARR Anomaly Detection on normal functionning

Figure: Abnormal falg on power curve


5. Inter-Turbine Wind Speed Anomaly Flagging

Purpose: Detect wind-speed anomalies by comparing a turbine with the population at each timestamp.

Class: InterTurbineWindspeedAnomalyFlagging

Output: signal category wind_sch.WTUR_INTER_TURBINE_WINDSPEED_ANOMALY (InterTurbineWindSpeedAnomality) with point name wind_sch.POINT_NAME_ANEMOMETER_ANOMALY (AnemometerFlag)

Required Signals

Sensors: - wind_sch.WNAC_WIND_SPEED (WindSpeed)

Derived: None

Model Configuration

  • k: population z-score threshold, default from INTER_TURBINE_WS_ZSCORE_K
  • consecutive_hours_threshold: rolling-window length in hours, default from INTER_TURBINE_WS_CONSECUTIVE_HOURS
  • min_population_percent: default from INTER_TURBINE_WS_MIN_POPULATION_PCT
  • min_absolute_count: default from INTER_TURBINE_WS_MIN_ABSOLUTE_COUNT
  • complex_terrain: default False; when True, compare each turbine only against its referenceTurbines pool

Filtering

  • Data quality - Remove bad WindSpeed readings

Compute Logic

  1. Compute population z-scores with PopulationZscoreAnomalyDetector
  2. Set per-row flags to 1 where abs(zscore) > k, 0 where normal, and -1 where z-score is NaN
  3. For each turbine, aggregate with a time-based rolling window of consecutive_hours_threshold hours
  4. The aggregated flag is 1 only when all valid points in the rolling window are abnormal and the window has at least consecutive_hours_threshold points

Algorithm Pipeline Sequence

Step 4_0: OperationStateFlagging -> StationarityFlagging -> ControlStateFlagging -> OperationalStateClassifier composite
Step 4_1: InterTurbineWindspeedAnomalyFlagging
Step 8_5: AbnormalOperationFlagging.fit_models for monthly ARR model persistence
Step 8_6: AbnormalOperationFlagging.score for recent-window abnormal-operation scoring

Step 4_0 reads fact_wtg_dq_10m (ts.WIND_WTG_DQ_TABLE) and writes fact_wtg_flagging_10m (ts.WIND_WTG_FLAGGING_TABLE) with the operation-state sub-flags plus OperationStateCompositeAnalytics. Step 8_6 writes fact_wtg_abnormal_operation_10m (ts.WIND_WTG_ABNORMAL_OPERATION_TABLE). Step 4_1 writes fact_wtg_anemometer_anomaly_10m (ts.WIND_WTG_ANEMOMETER_ANOMALY_TABLE).

Composite Flag Logic Details

Composite Operation State Computation: - Starts with base OperationStateAnalytics (IDLING=0, RUNNING=1, STOPPING=2) - If the base state is RUNNING and ControlStateAnalytics is available: - Override to PITCH_CONTROL (3) if ControlStateAnalytics == 3 - Override to TORQUE_CONTROL (4) if ControlStateAnalytics == 4 - Otherwise: keep the base operation state

Operation Categories: - No operation-category composite is computed by OperationalStateClassifier - OperationCategory and OperationCategoryWorse come from allocation/category logic outside the mapped flagging code