DEV Community

Cover image for Does CO2 actually predict room occupancy more than temperature? I checked against ground truth instead of assuming.
VAAS-X
VAAS-X

Posted on

Does CO2 actually predict room occupancy more than temperature? I checked against ground truth instead of assuming.

Most "smart" claims in IoT are unfalsifiable in practice — a vendor says their system "learned what matters," and you have no real way to check it against anything. I wanted to see what happens when you can.

I'm building vaas-x, a memory layer that profiles a data stream and classifies which channels carry real signal, with no schema and no domain knowledge supplied up front. So I ran it against UCI's Occupancy Detection dataset, which has something most sensor data doesn't: a real, human-verified ground-truth label on every single row.

The dataset

Five environmental channels — Temperature, Humidity, Light, CO2, HumidityRatio — sampled about once a minute in a single office, with a column called Occupancy (0 or 1) recording whether someone was actually in the room. 8,143 rows.

import pandas as pd

df = pd.read_csv("datatraining.txt", index_col=0, parse_dates=["date"])
channels = ["Temperature", "Humidity", "Light", "CO2", "HumidityRatio"]
Enter fullscreen mode Exit fullscreen mode

Occupancy doesn't go into the ingest payload. It's held back — used afterward to tag outcomes and to check the classifier's answer.

Ingest, then tell it what actually happened

from vaasx import Bootstrap

brain = Bootstrap(api_key="YOUR_API_KEY", device_id="office_occupancy")

records = df[channels].to_dict(orient="records")
timestamps = df.index.astype(str).tolist()

ids = []
for i in range(0, len(records), 500):
    batch = [{"payload": r, "timestamp": t} for r, t in zip(records[i:i+500], timestamps[i:i+500])]
    resp = brain.ingest(batch)
    ids.extend(resp["episode_ids"])

occupied = df["Occupancy"].tolist()
for episode_id, is_occupied in zip(ids, occupied):
    if episode_id is None:
        continue
    brain.outcome(episode_id, success=bool(is_occupied))
Enter fullscreen mode Exit fullscreen mode

The outcome() call is the part that's easy to skip and shouldn't be: it's what lets retrieval later distinguish "looks similar" from "looks similar and actually was an occupied room."

Query it

hits = brain.query("occupied room, motion detected, elevated CO2 and light", k=5, prefer_success=True)
for h in hits:
    print(h["timestamp"], h["outcome"]["success"], h["score"])
Enter fullscreen mode Exit fullscreen mode

Since outcomes were tagged straight from the real Occupancy column, every hit here should read success: True when prefer_success=True is set. If one doesn't, that's a real, checkable discrepancy — go look up that timestamp in the dataframe directly.

The actual question: does the classifier agree with reality?

from vaasx.bootstrap import StatisticalProfiler, SchemaClassifier

profiler = StatisticalProfiler(device_id="office_occupancy")
for r in records:
    profiler.observe(r)

result = SchemaClassifier().classify(profiler.snapshot())
print("significant channels:", sorted(result["significant_channels"]))
Enter fullscreen mode Exit fullscreen mode

Now check that against the actual correlation with the real label — no SDK involved in this step at all, just pandas:

correlations = df[channels + ["Occupancy"]].corr()["Occupancy"].drop("Occupancy").abs().sort_values(ascending=False)
print(correlations)
Enter fullscreen mode Exit fullscreen mode

Light and CO2 correlate with Occupancy far more strongly than Temperature, Humidity, or HumidityRatio — which matches intuition (a room fills with exhaled CO2 and someone usually turns the lights on). The question isn't whether that's surprising, it's whether the classifier's significant_channels list matches that ranking, computed independently, on the same public data anyone can download.

Two labeled test sets you can run this against yourself

datatest.txt and datatest2.txt cover different date ranges under the same schema and are also labeled — ingest either as a second device_id and repeat the correlation check against its own ground truth rather than trusting a single run.

Full steps in the reproduction guide. Free-tier API key at vaasx.com/pricing, no card required. If you've got sensor data with any kind of real label attached — a failure flag, a conversion event, anything — that's the actual test worth running: not "does this look smart," but "does it agree with something you can independently check."

Top comments (0)