Spaxiom Logo
Spaxiom Use Case - Appendix A.3

Indoor Air Quality, Occupancy & Health Risk

Using Spaxiom for Intelligent Ventilation and Health Risk Monitoring

Joe Scanlin

November 2025

About Spaxiom & INTENT

Spaxiom is a sensor abstraction layer and runtime that translates billions of heterogeneous sensor streams into structured, semantic events. It provides a spatial-temporal DSL for defining zones, entities, and conditions, making it easy to build context-aware applications across industries.

INTENT (Intelligent Network for Temporal & Embodied Neuro-symbolic Tasks) is Spaxiom's high-level event vocabulary. Instead of overwhelming AI agents with raw sensor data, Spaxiom emits compact, meaningful events that agents can immediately understand and act upon.

TL;DR

Poor indoor air quality in offices, schools, and conference rooms affects health, cognitive performance, and disease transmission. Most buildings have CO₂ sensors and HVAC systems, but they're only used for basic comfort control and don't account for how many people are actually in each space. The challenge is that building management systems, occupancy sensors, and HVAC controllers are siloed in separate systems with incompatible data formats that AI agents can't easily access or reason about.

Spaxiom acts as a "ventilation and health cortex" that combines CO₂ sensors, occupancy tracking, temperature, humidity, and HVAC data into one intelligent system. Instead of just monitoring CO₂ levels, it calculates actual outdoor air per person and tracks "ventilation debt" when spaces are under-ventilated. The system sends simple alerts like "Conference Room A has stale air" or "High-risk gathering detected in Classroom 204" so facility managers can adjust ventilation, schedule meetings differently, or open windows before air quality becomes a health concern.

A.3 Indoor Air Quality, Occupancy & Health Risk

Indoor air quality (IAQ) and ventilation are increasingly recognized as critical to health, cognitive performance, and resilience to airborne disease. Most commercial buildings already have a wealth of signals (CO2 sensors, temperature, humidity, HVAC control points), but they are typically used only for crude comfort bands. There is rarely a unified, spatially aware representation of:

Spaxiom can act as a ventilation and health cortex for buildings, fusing IAQ sensors, occupancy estimates, and HVAC state into intelligible INTENT events like StaleAirEpisode, VentilationDebt, and HighRiskGathering.

Sensors and deployment

For a zone z (e.g., conference room, open office bay, classroom), typical signals include:

We assume each Zone in Spaxiom is wired to one or more of these signals and that zones can be grouped into higher-level areas for policy and analytics.

INTENT events and risk indices

We define several IAQ- and health-related INTENT events:

Let us formalize some of these.

Ventilation per person. For zone z, at time t, the outdoor airflow per person can be approximated as:

qz(t) = (FOAz(t) · V̇supz(t)) / max(Nz(t), 1)   [m³/s/person]

where FOAz(t) is the fraction of supply air that is outdoor air, and supz(t) is the total supply airflow.

Given a recommended per-person outdoor airflow qrec (e.g., from a standard), we define a ventilation deficit rate:

dz(t) = max(0, qrec - qz(t))

Over a monitoring window [t0, t1] of length Δt, the ventilation debt is:

Dz = ∫t0t1 dz(t) dt

CO2 excursion and stale air. Let Cstale be a CO2 threshold (e.g., 1000 ppm). Define an indicator:

Istalez(t) = { 1 if Cz(t) > Cstale, 0 otherwise }

and the duration of stale air in the window:

Sz = ∫t0t1 Istalez(t) dt

Composite health risk score. A simple IAQ/health risk score for zone z over [t0, t1] can be defined as:

RIAQz = αDz + βSz + γEPMz + δERHz

where:

Normalization (e.g., dividing by window length or baseline values) can map RIAQz into a dimensionless score in [0,1].

Spaxiom-style implementation sketch

In the Spaxiom DSL, a zone-level IAQ tracker can encapsulate sensor histories and risk computation:

from spaxiom import Zone, Condition
from spaxiom.temporal import within
from spaxiom.logic import on

class IaqZone:
    def __init__(self, name, co2, temp, rh, oa_frac, sup_flow, occupancy):
        self.zone = Zone.named(name)
        self.co2 = co2              # ppm sensor
        self.temp = temp            # degC
        self.rh = rh                # %RH
        self.oa_frac = oa_frac      # 0..1
        self.sup_flow = sup_flow    # m^3/s
        self.occupancy = occupancy  # persons

        # Configurable thresholds / recommendations
        self.q_rec = 10.0 / 3600.0  # 10 m^3/h/person -> m^3/s/person
        self.co2_stale = 1000.0     # ppm
        self.pm_threshold = 25.0    # ug/m^3 (example)
        self.rh_low = 30.0          # %
        self.rh_high = 60.0         # %

    def history(self, sensor, window_s: float):
        return sensor.history(window_s=window_s)  # [(dt, value), ...]

    def ventilation_debt(self, window_s: float) -> float:
        series_oa = self.history(self.oa_frac, window_s)
        series_flow = self.history(self.sup_flow, window_s)
        series_occ = self.history(self.occupancy, window_s)

        # Assume aligned histories or interpolate in real implementation
        total_debt = 0.0
        for ((dt, oa), (_, flow), (_, occ)) in zip(series_oa, series_flow, series_occ):
            q = oa * flow / max(occ, 1.0)
            d = max(0.0, self.q_rec - q)
            total_debt += d * dt
        return total_debt

    def stale_air_duration(self, window_s: float) -> float:
        series = self.history(self.co2, window_s)
        total = 0.0
        for dt, c in series:
            if c > self.co2_stale:
                total += dt
        return total

    def rh_excursion(self, window_s: float) -> float:
        series = self.history(self.rh, window_s)
        total = 0.0
        for dt, r in series:
            if r < self.rh_low or r > self.rh_high:
                total += dt
        return total

    def risk_score(self, window_s: float) -> float:
        D = self.ventilation_debt(window_s)
        S = self.stale_air_duration(window_s)
        RH = self.rh_excursion(window_s)

        alpha, beta, gamma, delta = 1.0, 0.5, 0.0, 0.2  # PM omitted here
        score = alpha * D + beta * S + delta * RH
        # Example squashing to [0,1]
        return 1.0 - (1.0 / (1.0 + score * 1e-4))

# Wire a specific conference room
conf_A = IaqZone(
    name="Conf_Room_A",
    co2=co2_conf_A,
    temp=temp_conf_A,
    rh=rh_conf_A,
    oa_frac=oa_conf_A,
    sup_flow=flow_conf_A,
    occupancy=occ_conf_A,
)

# Condition: high risk over the last 2 hours
high_risk_iaq = Condition(lambda: conf_A.risk_score(window_s=2 * 3600) > 0.7)

@on(within(300, high_risk_iaq))  # check every 5 minutes
def iaq_agent():
    snapshot = {
        "zone": conf_A.zone.name,
        "risk": conf_A.risk_score(window_s=2 * 3600),
        "vent_debt": conf_A.ventilation_debt(2 * 3600),
        "stale_air_s": conf_A.stale_air_duration(2 * 3600),
        "rh_excursion_s": conf_A.rh_excursion(2 * 3600),
    }
    # An LLM or rules engine can:
    # - suggest schedule changes,
    # - recommend window opening where applicable,
    # - adjust ventilation setpoints if allowed.
    propose_iaq_actions(snapshot)

This example shows how Spaxiom:

IAQ and Ventilation Risk for Conference Room A (Workday)
CO2 Concentration (ppm)
Outdoor Air per Person (m³/s)
IAQ Risk Score (RIAQz)
CO2 Concentration 1400 1200 1000 400 Stale (1000) Outdoor Air per Person 0.008 0.006 0.004 0 Rec Composite IAQ Risk Score RIAQz 1.0 0.75 0.5 0.25 0 8am 9am 10am 12pm 2pm 4pm 6pm High Risk (0.7) 🚨 High Risk 🚨 High Risk Meeting 1 Meeting 2

Figure A.3: IAQ and ventilation risk for Conference Room A over a workday. Panel 1 shows CO2 concentration (red) exceeding the stale threshold (1000 ppm) during two meetings. Panel 2 shows outdoor air per person (blue) dropping below recommended levels during those same periods. Panel 3 shows the composite IAQ risk score RIAQz (purple) crossing into high-risk territory during meetings with poor ventilation. Shaded regions indicate StaleAirEpisode and HighRiskGathering INTENT events.