Skip to content

Instantly share code, notes, and snippets.

@bbartling
Created April 1, 2026 16:58
Show Gist options
  • Select an option

  • Save bbartling/6532c9605dd24a9b034c1102de8cf7fb to your computer and use it in GitHub Desktop.

Select an option

Save bbartling/6532c9605dd24a9b034c1102de8cf7fb to your computer and use it in GitHub Desktop.
Open FDD BRICK To Googles Digital Building DBO

Open-FDD Expression Rule Cookbook — DBO-Oriented Translation

This document recasts the Open-FDD expression rule cookbook into a Digital Buildings Ontology (DBO) style.

It is not a claim that DBO itself stores or executes fault rules. In DBO, the ontology primarily models entities, fields, translations, and relationships, while the fault logic still lives in your analytics engine (for example, Open-FDD, pandas, SQL, or another rules engine).

So the conversion here means:

  1. Replace Brick-class-centric rule inputs with DBO field names.
  2. Preserve the original fault logic as close as possible.
  3. Show how those fields would typically appear in a DBO building config.
  4. Keep the rule engine external to DBO.

Key mindset shift: Brick-driven vs DBO-driven

Original Open-FDD cookbook style

The original cookbook is Brick-model-driven. A rule references inputs like:

  • Supply_Air_Temperature_Sensor
  • Outside_Air_Temperature_Sensor
  • Valve_Command

Open-FDD resolves those from Brick classes and external timeseries references.

DBO style

In DBO, the model is usually centered on:

  • a DBO entity type such as HVAC/AHU_*, HVAC/VAV_*, HVAC/CHWS_*, etc.
  • a translation block that maps canonical DBO field names to raw telemetry paths
  • validation that the modeled entity really contains the required fields

So the equivalent pattern becomes:

  • supply_air_temperature_sensor
  • outside_air_temperature_sensor
  • chilled_water_valve_percentage_command
  • supply_fan_speed_percentage_command
  • supply_air_static_pressure_sensor

In other words, Brick classes become DBO canonical field names.


Important caveat about DBO

DBO generally models:

  • measured telemetry
  • setpoints
  • control states / commands
  • entity typing
  • relationships and connections

It generally does not try to model inferred alarms or faults as first-class ontology points unless there is a specific reason to do so. That makes DBO a good fit as the semantic layer feeding an FDD engine, rather than replacing the FDD engine.


Suggested rule file shape for a DBO-backed Open-FDD workflow

You could keep your Open-FDD YAML format and simply use DBO field names as the resolved inputs.

name: high_supply_air_temp
platform: dbo
entity_types: [HVAC/AHU]

inputs:
  supply_air_temperature_sensor:
    dbo_field: supply_air_temperature_sensor

params:
  max_temp: 90.0

expression: |
  supply_air_temperature_sensor > max_temp

You could also allow an optional entity type constraint, for example:

entity_type_patterns:
  - HVAC/AHU
  - HVAC/AHU_*

DBO field naming guidance for cookbook conversion

Because DBO uses highly specific canonical field names, avoid generic placeholders like Valve_Command whenever possible.

Recommended mapping pattern

Open-FDD / Brick-style input DBO-style field to prefer
Supply_Air_Temperature_Sensor supply_air_temperature_sensor
Mixed_Air_Temperature_Sensor mixed_air_temperature_sensor
Return_Air_Temperature_Sensor return_air_temperature_sensor
Outside_Air_Temperature_Sensor outside_air_temperature_sensor
Supply_Air_Temperature_Setpoint supply_air_temperature_setpoint
Supply_Air_Static_Pressure_Sensor supply_air_static_pressure_sensor
Supply_Air_Static_Pressure_Setpoint supply_air_static_pressure_setpoint
Supply_Fan_Speed_Command supply_fan_speed_percentage_command
Supply_Fan_Status supply_fan_run_status
Damper_Position_Command outside_air_damper_percentage_command or the more specific damper field available on the entity
Valve_Command (heating) heating_water_valve_percentage_command
Valve_Command (cooling) chilled_water_valve_percentage_command
Reheat_Valve_Command heating_water_valve_percentage_command
Differential_Pressure_Sensor differential_pressure_sensor
Differential_Pressure_Setpoint differential_pressure_setpoint
Pump_Speed_Command pump_speed_percentage_command
Water_Flow_Sensor water_flowrate_sensor
Chilled_Water_Supply_Temperature_Sensor chilled_water_supply_temperature_sensor
Chilled_Water_Supply_Temperature_Setpoint chilled_water_supply_temperature_setpoint
Zone_Temperature_Sensor zone_air_temperature_sensor
Chiller_Status run_status or the specific chiller run status field present on the entity
Wind_Speed_Sensor wind_speed_sensor
Wind_Gust_Speed_Sensor wind_gust_speed_sensor

If your DBO entity uses a slightly different canonical field name than shown here, use the ontology explorer / existing entity type as source of truth.


Example DBO building-config shape

AHU-1:
  type: HVAC/AHU_SFSS_SFVSC_CHWSC_OADC_MIXM
  translation:
    supply_air_temperature_sensor:
      present_value: points.sa_temp.present_value
    supply_air_temperature_setpoint:
      present_value: points.sa_temp_sp.present_value
    outside_air_temperature_sensor:
      present_value: points.oa_temp.present_value
    return_air_temperature_sensor:
      present_value: points.ra_temp.present_value
    mixed_air_temperature_sensor:
      present_value: points.ma_temp.present_value
    supply_air_static_pressure_sensor:
      present_value: points.sa_static.present_value
    supply_air_static_pressure_setpoint:
      present_value: points.sa_static_sp.present_value
    supply_fan_speed_percentage_command:
      present_value: points.sf_vfd_cmd.present_value
    chilled_water_valve_percentage_command:
      present_value: points.chw_valve_cmd.present_value
    heating_water_valve_percentage_command:
      present_value: points.hhw_valve_cmd.present_value
    outside_air_damper_percentage_command:
      present_value: points.oa_dmpr_cmd.present_value

Once those translations exist, the rules below can run on top of the translated DBO field names.


AHU rules translated to DBO-style fields

Rule A — Duct static below setpoint at full fan speed

name: duct_static_low_at_full_speed
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  supply_air_static_pressure_sensor:
    dbo_field: supply_air_static_pressure_sensor
  supply_air_static_pressure_setpoint:
    dbo_field: supply_air_static_pressure_setpoint
  supply_fan_speed_percentage_command:
    dbo_field: supply_fan_speed_percentage_command

params:
  sp_margin: 0.12
  drv_hi_frac: 0.93
  drv_near_hi: 0.06

expression: |
  (supply_air_static_pressure_sensor < supply_air_static_pressure_setpoint - sp_margin) &
  (supply_fan_speed_percentage_command >= drv_hi_frac - drv_near_hi)

Rule B — Mixed air temperature below expected band

name: mixed_air_temp_below_band
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  mixed_air_temperature_sensor:
    dbo_field: mixed_air_temperature_sensor
  return_air_temperature_sensor:
    dbo_field: return_air_temperature_sensor
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor
  supply_fan_speed_percentage_command:
    dbo_field: supply_fan_speed_percentage_command

params:
  mat_tol: 1.15
  rat_tol: 1.15
  oat_tol: 1.15

expression: |
  (mixed_air_temperature_sensor - mat_tol <
   np.minimum(return_air_temperature_sensor - rat_tol,
              outside_air_temperature_sensor - oat_tol)) &
  (supply_fan_speed_percentage_command > 0.01)

Rule C — Mixed air temperature above expected band

name: mixed_air_temp_above_band
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  mixed_air_temperature_sensor:
    dbo_field: mixed_air_temperature_sensor
  return_air_temperature_sensor:
    dbo_field: return_air_temperature_sensor
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor
  supply_fan_speed_percentage_command:
    dbo_field: supply_fan_speed_percentage_command

params:
  mat_tol: 1.15
  rat_tol: 1.15
  oat_tol: 1.15

expression: |
  (mixed_air_temperature_sensor - mat_tol >
   np.maximum(return_air_temperature_sensor + rat_tol,
              outside_air_temperature_sensor + oat_tol)) &
  (supply_fan_speed_percentage_command > 0.01)

Rule D — Supply air cold when heating commanded

name: supply_air_cold_when_heating
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  mixed_air_temperature_sensor:
    dbo_field: mixed_air_temperature_sensor
  supply_air_temperature_sensor:
    dbo_field: supply_air_temperature_sensor
  heating_water_valve_percentage_command:
    dbo_field: heating_water_valve_percentage_command
  supply_fan_speed_percentage_command:
    dbo_field: supply_fan_speed_percentage_command

params:
  mat_tol: 1.15
  sat_tol: 1.15
  fan_delta_t: 0.55

expression: |
  (supply_air_temperature_sensor + sat_tol <=
   mixed_air_temperature_sensor - mat_tol + fan_delta_t) &
  (heating_water_valve_percentage_command > 0.01) &
  (supply_fan_speed_percentage_command > 0.01)

Rule E — Supply air temperature too low with full heating

name: sat_too_low_full_heating
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  supply_air_temperature_sensor:
    dbo_field: supply_air_temperature_sensor
  supply_air_temperature_setpoint:
    dbo_field: supply_air_temperature_setpoint
  heating_water_valve_percentage_command:
    dbo_field: heating_water_valve_percentage_command
  supply_fan_speed_percentage_command:
    dbo_field: supply_fan_speed_percentage_command

params:
  supply_err_thresh: 1.0

expression: |
  (supply_air_temperature_sensor < supply_air_temperature_setpoint - supply_err_thresh) &
  (heating_water_valve_percentage_command > 0.9) &
  (supply_fan_speed_percentage_command > 0)

Rule F — SAT vs MAT mismatch in economizer mode

name: supply_air_mixed_air_mismatch_econ
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  mixed_air_temperature_sensor:
    dbo_field: mixed_air_temperature_sensor
  supply_air_temperature_sensor:
    dbo_field: supply_air_temperature_sensor
  outside_air_damper_percentage_command:
    dbo_field: outside_air_damper_percentage_command
  chilled_water_valve_percentage_command:
    dbo_field: chilled_water_valve_percentage_command

params:
  fan_delta_t: 0.55
  mat_tol: 1.15
  sat_tol: 1.15
  econ_min_open: 0.12

expression: |
  (np.abs(supply_air_temperature_sensor - fan_delta_t - mixed_air_temperature_sensor) >
   np.sqrt(sat_tol**2 + mat_tol**2)) &
  (outside_air_damper_percentage_command > econ_min_open) &
  (chilled_water_valve_percentage_command < 0.1)

Rule G — Outdoor air too warm for free cooling

name: ambient_warm_free_cool
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor
  supply_air_temperature_setpoint:
    dbo_field: supply_air_temperature_setpoint
  outside_air_damper_percentage_command:
    dbo_field: outside_air_damper_percentage_command
  chilled_water_valve_percentage_command:
    dbo_field: chilled_water_valve_percentage_command

params:
  oat_tol: 1.15
  fan_delta_t: 0.55
  sat_tol: 1.15
  econ_min_open: 0.12

expression: |
  (outside_air_temperature_sensor - oat_tol >
   supply_air_temperature_setpoint - fan_delta_t + sat_tol) &
  (outside_air_damper_percentage_command > econ_min_open) &
  (chilled_water_valve_percentage_command < 0.1)

Rule H — Outdoor vs mixed air mismatch with economizer + mechanical cooling

name: outdoor_mixed_air_mismatch_econ_mech
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor
  mixed_air_temperature_sensor:
    dbo_field: mixed_air_temperature_sensor
  chilled_water_valve_percentage_command:
    dbo_field: chilled_water_valve_percentage_command
  outside_air_damper_percentage_command:
    dbo_field: outside_air_damper_percentage_command

params:
  oat_tol: 1.15
  mat_tol: 1.15

expression: |
  (np.abs(mixed_air_temperature_sensor - outside_air_temperature_sensor) >
   np.sqrt(mat_tol**2 + oat_tol**2)) &
  (chilled_water_valve_percentage_command > 0.01) &
  (outside_air_damper_percentage_command > 0.9)

Rule I — Outdoor vs mixed air mismatch in economizer-only mode

name: outdoor_mixed_air_mismatch_econ_only
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor
  mixed_air_temperature_sensor:
    dbo_field: mixed_air_temperature_sensor
  outside_air_damper_percentage_command:
    dbo_field: outside_air_damper_percentage_command

params:
  oat_tol: 1.15
  mat_tol: 1.15

expression: |
  (np.abs(mixed_air_temperature_sensor - outside_air_temperature_sensor) >
   np.sqrt(mat_tol**2 + oat_tol**2)) &
  (outside_air_damper_percentage_command > 0.9)

Rule J — Supply air above mixed air in cooling

name: supply_air_above_mixed_air_cooling
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  supply_air_temperature_sensor:
    dbo_field: supply_air_temperature_sensor
  mixed_air_temperature_sensor:
    dbo_field: mixed_air_temperature_sensor
  chilled_water_valve_percentage_command:
    dbo_field: chilled_water_valve_percentage_command
  outside_air_damper_percentage_command:
    dbo_field: outside_air_damper_percentage_command

params:
  fan_delta_t: 0.55
  mat_tol: 1.15
  sat_tol: 1.15
  econ_min_open: 0.12

expression: |
  (supply_air_temperature_sensor >
   mixed_air_temperature_sensor + np.sqrt(sat_tol**2 + mat_tol**2) + fan_delta_t) &
  (((outside_air_damper_percentage_command > 0.9) & (chilled_water_valve_percentage_command > 0)) |
   ((outside_air_damper_percentage_command <= econ_min_open) & (chilled_water_valve_percentage_command > 0.9)))

Rule K — Supply air above setpoint in full cooling

name: supply_air_above_setpoint_full_cool
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  supply_air_temperature_sensor:
    dbo_field: supply_air_temperature_sensor
  supply_air_temperature_setpoint:
    dbo_field: supply_air_temperature_setpoint
  chilled_water_valve_percentage_command:
    dbo_field: chilled_water_valve_percentage_command
  outside_air_damper_percentage_command:
    dbo_field: outside_air_damper_percentage_command

params:
  sat_tol: 1.15
  econ_min_open: 0.12

expression: |
  (supply_air_temperature_sensor > supply_air_temperature_setpoint + sat_tol) &
  (((outside_air_damper_percentage_command > 0.9) & (chilled_water_valve_percentage_command > 0.9)) |
   ((outside_air_damper_percentage_command <= econ_min_open) & (chilled_water_valve_percentage_command > 0.9)))

Rule L — Cooling coil delta-T when inactive

name: cooling_coil_delta_t_when_inactive
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  cooling_coil_entering_air_temperature_sensor:
    dbo_field: cooling_coil_entering_air_temperature_sensor
  cooling_coil_leaving_air_temperature_sensor:
    dbo_field: cooling_coil_leaving_air_temperature_sensor
  heating_water_valve_percentage_command:
    dbo_field: heating_water_valve_percentage_command
  chilled_water_valve_percentage_command:
    dbo_field: chilled_water_valve_percentage_command
  outside_air_damper_percentage_command:
    dbo_field: outside_air_damper_percentage_command

params:
  enter_tol: 1.15
  leave_tol: 1.15
  econ_min_open: 0.12

expression: |
  ((cooling_coil_entering_air_temperature_sensor - cooling_coil_leaving_air_temperature_sensor) >
   np.sqrt(enter_tol**2 + leave_tol**2)) &
  (((heating_water_valve_percentage_command > 0) &
    (chilled_water_valve_percentage_command == 0) &
    (outside_air_damper_percentage_command <= econ_min_open)) |
   ((heating_water_valve_percentage_command == 0) &
    (chilled_water_valve_percentage_command == 0) &
    (outside_air_damper_percentage_command > econ_min_open)))

Rule M — Heating coil delta-T when inactive

name: heating_coil_delta_t_when_inactive
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  heating_coil_entering_air_temperature_sensor:
    dbo_field: heating_coil_entering_air_temperature_sensor
  heating_coil_leaving_air_temperature_sensor:
    dbo_field: heating_coil_leaving_air_temperature_sensor
  heating_water_valve_percentage_command:
    dbo_field: heating_water_valve_percentage_command
  chilled_water_valve_percentage_command:
    dbo_field: chilled_water_valve_percentage_command
  outside_air_damper_percentage_command:
    dbo_field: outside_air_damper_percentage_command

params:
  enter_tol: 1.15
  leave_tol: 1.15
  fan_delta_t: 0.55
  econ_min_open: 0.12

expression: |
  ((heating_coil_leaving_air_temperature_sensor - heating_coil_entering_air_temperature_sensor) >
   np.sqrt(enter_tol**2 + leave_tol**2) + fan_delta_t) &
  (((heating_water_valve_percentage_command == 0) &
    (chilled_water_valve_percentage_command == 0) &
    (outside_air_damper_percentage_command > econ_min_open)) |
   ((heating_water_valve_percentage_command == 0) &
    (chilled_water_valve_percentage_command > 0) &
    (outside_air_damper_percentage_command > 0.9)) |
   ((heating_water_valve_percentage_command == 0) &
    (chilled_water_valve_percentage_command > 0) &
    (outside_air_damper_percentage_command <= econ_min_open)))

Central plant rules translated to DBO-style fields

Differential pressure below setpoint at max pump speed

name: dp_below_setpoint_pump_max
platform: dbo
entity_type_patterns: [HVAC/CHWS, HVAC/HWS, HVAC/*PUMP*]

inputs:
  differential_pressure_sensor:
    dbo_field: differential_pressure_sensor
  differential_pressure_setpoint:
    dbo_field: differential_pressure_setpoint
  pump_speed_percentage_command:
    dbo_field: pump_speed_percentage_command

params:
  dp_margin: 2.2
  pmp_hi_frac: 0.93
  pmp_near_hi: 0.06

expression: |
  (differential_pressure_sensor < differential_pressure_setpoint - dp_margin) &
  (pump_speed_percentage_command >= pmp_hi_frac - pmp_near_hi)

Plant flow high at max pump speed

name: flow_high_pump_max
platform: dbo
entity_type_patterns: [HVAC/CHWS, HVAC/HWS, HVAC/*PUMP*]

inputs:
  water_flowrate_sensor:
    dbo_field: water_flowrate_sensor
  pump_speed_percentage_command:
    dbo_field: pump_speed_percentage_command

params:
  flow_hi_limit: 1100.0
  pmp_hi_frac: 0.93
  pmp_near_hi: 0.06

expression: |
  (water_flowrate_sensor > flow_hi_limit) &
  (pump_speed_percentage_command >= pmp_hi_frac - pmp_near_hi)

Chilled water supply temperature outside deadband

name: chw_supply_temp_deadband
platform: dbo
entity_type_patterns: [HVAC/CHWS, HVAC/CHILLER, HVAC/*]

inputs:
  chilled_water_supply_temperature_sensor:
    dbo_field: chilled_water_supply_temperature_sensor
  chilled_water_supply_temperature_setpoint:
    dbo_field: chilled_water_supply_temperature_setpoint
  pump_speed_percentage_command:
    dbo_field: pump_speed_percentage_command

params:
  sp_band: 2.2

expression: |
  (pump_speed_percentage_command > 0.01) &
  ((chilled_water_supply_temperature_sensor < chilled_water_supply_temperature_setpoint - sp_band) |
   (chilled_water_supply_temperature_sensor > chilled_water_supply_temperature_setpoint + sp_band))

Chiller runtime over daily limit

name: chiller_excessive_runtime
platform: dbo
entity_type_patterns: [HVAC/CHILLER, HVAC/CH_*]

inputs:
  run_status:
    dbo_field: run_status

params:
  rolling_samples: 276
  max_runtime_samples: 264

expression: |
  run_status.rolling(window=rolling_samples).sum() > max_runtime_samples

Heat pump rule translated to DBO-style fields

Discharge cold when heating

name: hp_discharge_cold_when_heating
platform: dbo
entity_type_patterns: [HVAC/HP, HVAC/HEAT_PUMP, HVAC/*HP*]

inputs:
  supply_air_temperature_sensor:
    dbo_field: supply_air_temperature_sensor
  zone_air_temperature_sensor:
    dbo_field: zone_air_temperature_sensor
  supply_fan_run_status:
    dbo_field: supply_fan_run_status

params:
  min_discharge_temp: 85
  zone_cold_threshold: 69.0
  fan_on_threshold: 0.01

expression: |
  (supply_fan_run_status > fan_on_threshold) &
  (zone_air_temperature_sensor < zone_cold_threshold) &
  (supply_air_temperature_sensor < min_discharge_temp)

VAV rules translated to DBO-style fields

Excessive heating during warm weather

name: zone_reheat_warm_ambient
platform: dbo
entity_type_patterns: [HVAC/VAV, HVAC/VAV_*]

inputs:
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor
  heating_water_valve_percentage_command:
    dbo_field: heating_water_valve_percentage_command

params:
  t_amb_cutoff: 78.0
  reheat_open_min: 0.52

expression: |
  (outside_air_temperature_sensor > t_amb_cutoff) &
  (heating_water_valve_percentage_command > reheat_open_min)

Damper at full open for extended period

name: zone_damper_full_open
platform: dbo
entity_type_patterns: [HVAC/VAV, HVAC/VAV_*]

inputs:
  supply_air_damper_percentage_command:
    dbo_field: supply_air_damper_percentage_command

params:
  full_open_pct: 97.5
  roll_samples: 105

expression: |
  (supply_air_damper_percentage_command > full_open_pct) &
  (supply_air_damper_percentage_command.rolling(roll_samples).min() > full_open_pct)

Opportunistic ventilation and economizer rules

Economizing when outdoor conditions are unfavorable

name: econ_active_warm_ambient
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor
  outside_air_damper_percentage_command:
    dbo_field: outside_air_damper_percentage_command

params:
  t_amb_econ_cutoff: 63.0
  dpr_econ_min: 0.42

expression: |
  (outside_air_temperature_sensor > t_amb_econ_cutoff) &
  (outside_air_damper_percentage_command > dpr_econ_min)

Mechanical cooling when economizer could suffice

name: mech_cool_when_econ_available
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor
  outside_air_damper_percentage_command:
    dbo_field: outside_air_damper_percentage_command
  chilled_water_valve_percentage_command:
    dbo_field: chilled_water_valve_percentage_command

params:
  t_amb_econ_cutoff: 63.0
  dpr_not_econ_max: 0.32

expression: |
  (outside_air_temperature_sensor < t_amb_econ_cutoff) &
  (outside_air_damper_percentage_command < dpr_not_econ_max) &
  (chilled_water_valve_percentage_command > 0.01)

Low ventilation from estimated OA fraction

name: low_oa_fraction_estimated
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  mixed_air_temperature_sensor:
    dbo_field: mixed_air_temperature_sensor
  return_air_temperature_sensor:
    dbo_field: return_air_temperature_sensor
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor
  supply_fan_speed_percentage_command:
    dbo_field: supply_fan_speed_percentage_command

params:
  oa_min_pct: 21.0
  t_rat_oat_min_gap: 2.2

expression: |
  (supply_fan_speed_percentage_command > 0.01) &
  (np.abs(return_air_temperature_sensor - outside_air_temperature_sensor) > t_rat_oat_min_gap) &
  (((mixed_air_temperature_sensor - return_air_temperature_sensor) /
    (outside_air_temperature_sensor - return_air_temperature_sensor) * 100) < oa_min_pct)

Preheat over-conditioning

name: preheat_excess_temp
platform: dbo
entity_type_patterns: [HVAC/AHU, HVAC/AHU_*]

inputs:
  preheat_coil_leaving_air_temperature_sensor:
    dbo_field: preheat_coil_leaving_air_temperature_sensor
  supply_air_temperature_setpoint:
    dbo_field: supply_air_temperature_setpoint
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor
  heating_water_valve_percentage_command:
    dbo_field: heating_water_valve_percentage_command

params:
  excess_tol: 2.2

expression: |
  (heating_water_valve_percentage_command > 0.01) &
  (((outside_air_temperature_sensor > supply_air_temperature_setpoint) &
    (preheat_coil_leaving_air_temperature_sensor - outside_air_temperature_sensor > excess_tol)) |
   ((outside_air_temperature_sensor < supply_air_temperature_setpoint) &
    (preheat_coil_leaving_air_temperature_sensor - supply_air_temperature_setpoint > excess_tol)))

Weather station rules translated to DBO-style fields

Unrealistic temperature spike

name: weather_temp_spike
platform: dbo
entity_type_patterns: [WEATHER/*, FACILITIES/*, HVAC/*]

inputs:
  outside_air_temperature_sensor:
    dbo_field: outside_air_temperature_sensor

params:
  spike_limit: 16.0

expression: |
  outside_air_temperature_sensor.diff().abs() > spike_limit

Wind gust lower than sustained wind

name: weather_gust_lt_wind
platform: dbo
entity_type_patterns: [WEATHER/*, FACILITIES/*, HVAC/*]

inputs:
  wind_gust_speed_sensor:
    dbo_field: wind_gust_speed_sensor
  wind_speed_sensor:
    dbo_field: wind_speed_sensor

expression: |
  wind_gust_speed_sensor.notna() & wind_speed_sensor.notna() &
  (wind_gust_speed_sensor < wind_speed_sensor)

What to change in Open-FDD if you want true DBO-native rule authoring

Option 1 — light-touch approach

Keep Open-FDD exactly as-is conceptually, but allow a dbo_field key in inputs.

Example:

inputs:
  supply_air_temperature_sensor:
    dbo_field: supply_air_temperature_sensor

Then the runtime would:

  1. identify the DBO entity instance
  2. read the entity's translation
  3. map dbo_field to the raw timeseries path / column
  4. build the DataFrame
  5. evaluate the expression

Option 2 — compile DBO building config into the existing Open-FDD input registry

This is probably the easiest migration path.

  • keep the current Open-FDD rule engine
  • write a DBO adapter that reads the building config
  • emit a normalized internal registry such as:
{
  "AHU-1": {
    "supply_air_temperature_sensor": "points.sa_temp.present_value",
    "mixed_air_temperature_sensor": "points.ma_temp.present_value",
    "outside_air_temperature_sensor": "points.oa_temp.present_value"
  }
}

Then the rest of Open-FDD can stay mostly unchanged.

Option 3 — hybrid Brick + DBO support

This is likely the most practical real-world approach.

  • Brick remains very strong for graph semantics and cross-equipment querying.
  • DBO remains very strong for canonical telemetry naming and configuration validation.
  • Open-FDD could support either:
    • brick: inputs
    • dbo_field: inputs

That would let users choose the semantic source they already have.


Recommended naming conventions for your project

If you want this to feel clean in Open-FDD, I would recommend these conventions:

Rule authoring

  • use lower snake case for DBO field references
  • prefer specific command fields over generic placeholders
  • keep expressions identical wherever possible

Example

Prefer:

chilled_water_valve_percentage_command

over:

Valve_Command

And prefer:

outside_air_damper_percentage_command

over:

Damper_Position_Command

That makes the rules much more portable and much more "DBO-like".


Practical limitations and honest notes

  1. This document converts the cookbook at the rule authoring layer, not by changing DBO itself.
  2. Some exact canonical DBO field names depend on the specific entity type you choose.
  3. A few generic cookbook inputs, especially Valve_Command and Damper_Position_Command, had to be specialized into likely DBO fields.
  4. For some equipment, the correct DBO entity type may expose a slightly different canonical field name than the one shown here.
  5. Faults are still better treated as analytics outputs rather than ontology-native telemetry fields in DBO.

Best short summary

A clean way to think about this is:

  • Brick version: rules reference semantic classes.
  • DBO version: rules reference canonical DBO fields on validated entity types.
  • Open-FDD stays the fault engine in both cases.

References

"""Brick -> Digital Buildings Ontology (DBO) helper mappings.
This module is intentionally pragmatic rather than authoritative.
Why this file exists
--------------------
Brick and DBO solve related but different problems:
- Brick focuses on RDF classes and relationships for buildings/equipment/points.
- DBO focuses on canonical entity types and canonical field names, then uses
translations to map site-native telemetry into those fields.
That means many mappings here are approximate or context-dependent. Treat this
file as a starter dictionary for LLM-assisted onboarding, rule translation, or
manual review.
Primary references:
- DBO ontology overview: https://github.com/google/digitalbuildings/blob/master/ontology/docs/ontology.md
- DBO building config / translations: https://github.com/google/digitalbuildings/blob/master/ontology/docs/building_config.md
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Iterable
# ---------------------------------------------------------------------------
# Entity / equipment mappings
# ---------------------------------------------------------------------------
#
# Values are the most likely DBO general type or canonical-type family hints.
# In many cases, the exact DBO type depends on the full device feature set.
# Example: an AHU could map to several canonical DBO AHU types depending on
# whether it has economizer, cooling coil, heating coil, supply fan control,
# static pressure control, etc.
# ---------------------------------------------------------------------------
BRICK_ENTITY_TO_DBO_TYPE: dict[str, str] = {
# airside equipment
"Air_Handler_Unit": "HVAC/AHU",
"Dedicated_Outdoor_Air_System": "HVAC/DOAS",
"Rooftop_Unit": "HVAC/RTU",
"Variable_Air_Volume_Box": "HVAC/VAV",
"Fan_Coil_Unit": "HVAC/FCU",
"Unit_Heater": "HVAC/UNIT_HEATER",
"Heat_Pump": "HVAC/HP",
"Exhaust_Fan": "HVAC/FAN",
"Supply_Fan": "HVAC/FAN",
"Return_Fan": "HVAC/FAN",
# waterside / plant
"Boiler": "HVAC/BLR",
"Chiller": "HVAC/CH",
"Cooling_Tower": "HVAC/CT",
"Pump": "HVAC/PMP",
"Hot_Water_System": "HVAC/HWS",
"Chilled_Water_System": "HVAC/CHWS",
"Condenser_Water_System": "HVAC/CDWS",
# spaces / logical areas
"HVAC_Zone": "HVAC/ZONE",
"Room": "FACILITIES/ROOM",
"Floor": "FACILITIES/FLOOR",
"Building": "FACILITIES/BUILDING",
}
# ---------------------------------------------------------------------------
# Point / field mappings
# ---------------------------------------------------------------------------
#
# These map common Brick point classes to likely DBO standard fields.
# Some Brick classes are intentionally repeated into the same DBO field because
# DBO field naming is canonical and may abstract away implementation details.
# ---------------------------------------------------------------------------
BRICK_POINT_TO_DBO_FIELD: dict[str, str] = {
# temperatures
"Outside_Air_Temperature_Sensor": "outside_air_temperature_sensor",
"Outdoor_Air_Temperature_Sensor": "outside_air_temperature_sensor",
"Return_Air_Temperature_Sensor": "return_air_temperature_sensor",
"Mixed_Air_Temperature_Sensor": "mixed_air_temperature_sensor",
"Supply_Air_Temperature_Sensor": "supply_air_temperature_sensor",
"Discharge_Air_Temperature_Sensor": "discharge_air_temperature_sensor",
"Zone_Air_Temperature_Sensor": "zone_air_temperature_sensor",
"Space_Air_Temperature_Sensor": "zone_air_temperature_sensor",
"Entering_Chilled_Water_Temperature_Sensor": "entering_chilled_water_temperature_sensor",
"Leaving_Chilled_Water_Temperature_Sensor": "leaving_chilled_water_temperature_sensor",
"Entering_Hot_Water_Temperature_Sensor": "entering_heating_water_temperature_sensor",
"Leaving_Hot_Water_Temperature_Sensor": "leaving_heating_water_temperature_sensor",
"Condenser_Water_Supply_Temperature_Sensor": "condenser_water_supply_temperature_sensor",
"Condenser_Water_Return_Temperature_Sensor": "condenser_water_return_temperature_sensor",
# setpoints
"Supply_Air_Temperature_Setpoint": "supply_air_temperature_setpoint",
"Discharge_Air_Temperature_Setpoint": "discharge_air_temperature_setpoint",
"Zone_Air_Temperature_Setpoint": "zone_air_temperature_setpoint",
"Space_Air_Temperature_Setpoint": "zone_air_temperature_setpoint",
"Heating_Air_Temperature_Setpoint": "zone_air_heating_temperature_setpoint",
"Cooling_Air_Temperature_Setpoint": "zone_air_cooling_temperature_setpoint",
"Static_Pressure_Setpoint": "discharge_air_static_pressure_setpoint",
"Supply_Air_Static_Pressure_Setpoint": "discharge_air_static_pressure_setpoint",
# pressure
"Static_Pressure_Sensor": "discharge_air_static_pressure_sensor",
"Supply_Air_Static_Pressure_Sensor": "discharge_air_static_pressure_sensor",
"Differential_Pressure_Sensor": "differential_pressure_sensor",
# humidity / IAQ
"Relative_Humidity_Sensor": "relative_humidity_sensor",
"Return_Air_Humidity_Sensor": "return_air_relative_humidity_sensor",
"Zone_Air_Humidity_Sensor": "zone_air_relative_humidity_sensor",
"Space_Air_Humidity_Sensor": "zone_air_relative_humidity_sensor",
"CO2_Sensor": "carbon_dioxide_concentration_sensor",
"Zone_Air_CO2_Sensor": "zone_air_carbon_dioxide_concentration_sensor",
"Return_Air_CO2_Sensor": "return_air_carbon_dioxide_concentration_sensor",
# airflow / flow
"Air_Flow_Sensor": "air_flowrate_sensor",
"Supply_Air_Flow_Sensor": "supply_air_flowrate_sensor",
"Discharge_Air_Flow_Sensor": "discharge_air_flowrate_sensor",
"Water_Flow_Sensor": "water_flowrate_sensor",
"Chilled_Water_Flow_Sensor": "chilled_water_flowrate_sensor",
"Hot_Water_Flow_Sensor": "heating_water_flowrate_sensor",
# dampers / valves / commands
"Damper_Position_Command": "damper_percentage_command",
"Outside_Air_Damper_Command": "outside_air_damper_percentage_command",
"Outdoor_Air_Damper_Command": "outside_air_damper_percentage_command",
"Return_Air_Damper_Command": "return_air_damper_percentage_command",
"Exhaust_Air_Damper_Command": "exhaust_air_damper_percentage_command",
"Mixed_Air_Damper_Command": "mixed_air_damper_percentage_command",
"Valve_Command": "valve_percentage_command",
"Cooling_Valve_Command": "chilled_water_valve_percentage_command",
"Chilled_Water_Valve_Command": "chilled_water_valve_percentage_command",
"Heating_Valve_Command": "heating_water_valve_percentage_command",
"Hot_Water_Valve_Command": "heating_water_valve_percentage_command",
# fan / speed / status
"Fan_Command": "fan_run_command",
"Fan_Status": "fan_run_status",
"Supply_Fan_Command": "supply_fan_run_command",
"Supply_Fan_Status": "supply_fan_run_status",
"Return_Fan_Command": "return_fan_run_command",
"Return_Fan_Status": "return_fan_run_status",
"Exhaust_Fan_Command": "exhaust_fan_run_command",
"Exhaust_Fan_Status": "exhaust_fan_run_status",
"Speed_Command": "speed_percentage_command",
"Fan_Speed_Command": "fan_speed_percentage_command",
"Supply_Fan_Speed_Command": "supply_fan_speed_percentage_command",
"Return_Fan_Speed_Command": "return_fan_speed_percentage_command",
"Fan_Speed_Sensor": "fan_speed_percentage_sensor",
# occupancy / mode / enable
"Occupancy_Sensor": "occupancy_sensor",
"Occupancy_Command": "occupancy_command",
"Occupancy_Mode_Status": "occupancy_mode",
"Run_Command": "run_command",
"Run_Status": "run_status",
"Enable_Command": "run_enable_command",
"Enable_Status": "run_enable_status",
"Mode_Command": "mode_command",
"Mode_Status": "mode_status",
# electrical / power / energy
"Power_Sensor": "power_sensor",
"Electric_Power_Sensor": "power_sensor",
"Energy_Sensor": "energy_accumulator",
"Electric_Energy_Sensor": "energy_accumulator",
"Current_Sensor": "current_sensor",
"Voltage_Sensor": "voltage_sensor",
"Frequency_Sensor": "frequency_sensor",
}
# ---------------------------------------------------------------------------
# Relationship mappings
# ---------------------------------------------------------------------------
#
# DBO uses named directed connections in its model. These are not always a
# direct one-to-one replacement for Brick predicates, but these are useful
# translation hints.
# ---------------------------------------------------------------------------
BRICK_RELATIONSHIP_TO_DBO_CONNECTION: dict[str, str] = {
"feeds": "FEEDS",
"isFedBy": "FEEDS (reverse)",
"hasPart": "contains / composition-like modeling; review context",
"isPartOf": "part-of / contained-by; review context",
"hasPoint": "translation or links, not usually a DBO connection",
"isPointOf": "translation or links, not usually a DBO connection",
"hasLocation": "CONTAINS / located-in; review context",
}
# ---------------------------------------------------------------------------
# Rule input aliases useful for Open-FDD <-> DBO workflows
# ---------------------------------------------------------------------------
#
# These are convenient for converting common Open-FDD rule inputs to DBO fields.
# ---------------------------------------------------------------------------
OPENFDD_RULE_INPUT_TO_DBO_FIELD: dict[str, str] = {
"oat": "outside_air_temperature_sensor",
"rat": "return_air_temperature_sensor",
"mat": "mixed_air_temperature_sensor",
"sat": "supply_air_temperature_sensor",
"dat": "discharge_air_temperature_sensor",
"sat_sp": "supply_air_temperature_setpoint",
"dat_sp": "discharge_air_temperature_setpoint",
"zone_temp": "zone_air_temperature_sensor",
"zone_temp_sp": "zone_air_temperature_setpoint",
"zone_htg_sp": "zone_air_heating_temperature_setpoint",
"zone_clg_sp": "zone_air_cooling_temperature_setpoint",
"sa_static": "discharge_air_static_pressure_sensor",
"sa_static_sp": "discharge_air_static_pressure_setpoint",
"oa_dmpr_cmd": "outside_air_damper_percentage_command",
"ra_dmpr_cmd": "return_air_damper_percentage_command",
"ea_dmpr_cmd": "exhaust_air_damper_percentage_command",
"ma_dmpr_cmd": "mixed_air_damper_percentage_command",
"chw_vlv_cmd": "chilled_water_valve_percentage_command",
"hw_vlv_cmd": "heating_water_valve_percentage_command",
"sf_cmd": "supply_fan_run_command",
"sf_status": "supply_fan_run_status",
"sf_speed_cmd": "supply_fan_speed_percentage_command",
"rf_cmd": "return_fan_run_command",
"rf_status": "return_fan_run_status",
"occ": "occupancy_mode",
"co2": "carbon_dioxide_concentration_sensor",
"zone_co2": "zone_air_carbon_dioxide_concentration_sensor",
"rh": "relative_humidity_sensor",
"zone_rh": "zone_air_relative_humidity_sensor",
"power": "power_sensor",
"energy": "energy_accumulator",
}
# ---------------------------------------------------------------------------
# Optional metadata-rich wrapper for future use
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class MappingResult:
source: str
target: str | None
confidence: str
note: str | None = None
def map_brick_entity(brick_class: str) -> MappingResult:
"""Map a Brick equipment/entity class to a likely DBO type family."""
target = BRICK_ENTITY_TO_DBO_TYPE.get(brick_class)
if target:
return MappingResult(brick_class, target, "medium")
return MappingResult(
brick_class,
None,
"low",
"No direct starter mapping found; review DBO canonical type manually.",
)
def map_brick_point(brick_class: str) -> MappingResult:
"""Map a Brick point class to a likely DBO canonical field."""
target = BRICK_POINT_TO_DBO_FIELD.get(brick_class)
if target:
return MappingResult(brick_class, target, "medium")
return MappingResult(
brick_class,
None,
"low",
"No direct starter mapping found; review DBO field composition manually.",
)
def map_openfdd_rule_input(rule_input: str) -> MappingResult:
"""Map an Open-FDD rule input alias to a DBO field."""
target = OPENFDD_RULE_INPUT_TO_DBO_FIELD.get(rule_input)
if target:
return MappingResult(rule_input, target, "high")
return MappingResult(
rule_input,
None,
"low",
"No direct starter mapping found for this rule input alias.",
)
def invert_mapping(mapping: dict[str, str]) -> dict[str, list[str]]:
"""Invert a one-to-many-ish dict into target -> [sources]."""
result: dict[str, list[str]] = {}
for source, target in mapping.items():
result.setdefault(target, []).append(source)
return dict(sorted(result.items()))
def lookup_many(classes: Iterable[str], *, kind: str = "point") -> list[MappingResult]:
"""Batch lookup convenience helper.
kind:
- "point"
- "entity"
- "rule_input"
"""
mapper = {
"point": map_brick_point,
"entity": map_brick_entity,
"rule_input": map_openfdd_rule_input,
}[kind]
return [mapper(item) for item in classes]
if __name__ == "__main__":
examples = [
"Supply_Air_Temperature_Sensor",
"Return_Air_Temperature_Sensor",
"Cooling_Valve_Command",
"Variable_Air_Volume_Box",
]
print("Point mappings:")
for item in examples[:3]:
print(map_brick_point(item))
print("\nEntity mapping:")
print(map_brick_entity(examples[3]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment