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
- Operation State Flag -
OperationStateFlaggingwritesOperationStateAnalytics: 0=IDLING, 1=RUNNING, 2=STOPPING, -1=discarded/bad quality - Stationarity Flag -
StationarityFlaggingwritesStationaryFlag: True/1=stationary, False/0=non-stationary, -1=discarded/bad quality - Control State Flag -
ControlStateFlaggingwritesControlStateAnalytics: 3=PITCH_CONTROL, 4=TORQUE_CONTROL, 1=still RUNNING when no control condition is assigned, -1=discarded/bad quality - Abnormal Operation Score -
AbnormalOperationFlaggingwritesAbnormalOperationScore: 0=normal, higher=count of anomalous ARR pairs, -1=discarded/bad quality - Inter-Turbine Wind Speed Anomaly -
InterTurbineWindspeedAnomalyFlaggingwritesInterTurbineWindSpeedAnomalitywith point nameAnemometerFlag: 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:
- 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 levelwind_sch.WTUR_ACTIVE_POWER_MIN(ActivePower_min) - Minimum power in intervalwind_sch.WNAC_WIND_SPEED(WindSpeed) - Ambient wind speed
Turbine Parameters
cut_in- FromTurbine.get_cut_in_wind_speed()cut_out- FromTurbine.get_cut_out_wind_speed()power_threshold- FromTurbine.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.

Figure: Detection of stationary operating windows

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_PitchAngleorActivePowerreadings - 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.

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_thresholdof running and stationary rows haveControlStateCons.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.

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

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

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 fromINTER_TURBINE_WS_ZSCORE_Kconsecutive_hours_threshold: rolling-window length in hours, default fromINTER_TURBINE_WS_CONSECUTIVE_HOURSmin_population_percent: default fromINTER_TURBINE_WS_MIN_POPULATION_PCTmin_absolute_count: default fromINTER_TURBINE_WS_MIN_ABSOLUTE_COUNTcomplex_terrain: defaultFalse; whenTrue, compare each turbine only against itsreferenceTurbinespool
Filtering
- Data quality - Remove bad
WindSpeedreadings
Compute Logic
- Compute population z-scores with
PopulationZscoreAnomalyDetector - Set per-row flags to 1 where
abs(zscore) > k, 0 where normal, and -1 where z-score is NaN - For each turbine, aggregate with a time-based rolling window of
consecutive_hours_thresholdhours - The aggregated flag is 1 only when all valid points in the rolling window are abnormal and the window has at least
consecutive_hours_thresholdpoints
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