DEV Community

Cover image for The Cold and Hard Log
Pentine Pejay
Pentine Pejay Subscriber

Posted on AI-assisted

The Cold and Hard Log

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

Dede Mensah sells cheese. One display fridge out front, one walk-in in the back,
and a clipboard screwed to the wall next to each. Twice a day she writes down a
temperature. The UK Food Standards Agency scheme she trades under (Safer Food
Better Business, the retail HACCP flavour) says she has to: monitor the cold
points, record them, act when something goes out of range. The clipboard does
none of that between the two times she looks at it. If the display fridge drifts
warm at 2pm and she checks at 9am and 5pm, the paper says everything was fine.

Dede is a composite. I did not have a real cheesemonger to hand during the build
window, so I built for a specific made-up one with a real, legally-driven
problem. The HACCP obligation is not invented. The clipboard gap is not
invented. That is the part worth solving.

What I Built

Baridi turns a fridge sensor stream into two things Dede's clipboard cannot give
her: a breach timeline and an alert that does not lie.

The name is Swahili. "Baridi" means cold, and it is the everyday word for the
weather being chilly or a drink being cold, so it names exactly what the tool
watches: is the cold still cold? That fits the Side Hustle theme too. This is
built for the person running the whole shop alone, the cheesemonger who is also
the buyer, the cleaner, and the compliance officer. Baridi is the extra pair of
hands that watches the fridge overnight so the side hustle does not fail its
inspection over a thing nobody was awake to see.

Replay a day of readings and you get the timeline. Every stretch where the
temperature stayed over 5C for at least ten minutes shows up as an excursion
with its start, end, duration, and peak. A headline sums it: "2 excursions over
5C, 43 min total." The alert side writes one record per distinct excursion, and
it keeps writing correctly even if the process running it is killed halfway
through.

Who pays for this? A one-person food business already pays, in time and in risk.
The clipboard is unpaid labour that still leaves a hole an inspector can see.
The commercial fix is a subscription HACCP-logging service, priced monthly,
holding your readings on someone else's server. Baridi is the local version: the
readings stay in the shop, there is no per-reading fee, and the breach math is
inspectable. For a margin-thin cheese counter, local and $0-per-reading is the
difference between doing it and not.

Demo

Live: baridi.tech. The breach timeline runs there against
a real TimescaleDB, served over HTTPS. The dashboard is at
baridi.tech/dashboard.

Here is the timeline as Dede sees it. A FAIL status for the day, the two
excursions each drawn against the 5C line, with the peak temperature and minutes
over called out. You can switch sensors and days, and the footer reports the live
dataset size and the measured query time:

Baridi breach timeline:

And the day she wants. Every sensor held at or below 5C, a PASS / all-clear state
instead of a wall of green-wash:

Baridi all-clear state: PASS status,

The same data as raw API output (curl https://baridi.tech/api/timeline):

{
  "day": "2026-10-12",
  "threshold_c": 5.0,
  "sustain_minutes": 10,
  "headline": "2 excursions over 5C, 43 min total",
  "excursions": [
    {"sensor_id": "fridge-01", "start": "2026-10-12T10:04:00+00:00",
     "end": "2026-10-12T10:35:00+00:00", "minutes_over": 32, "max_temp": 9.404},
    {"sensor_id": "fridge-01", "start": "2026-10-12T17:04:00+00:00",
     "end": "2026-10-12T17:14:00+00:00", "minutes_over": 11, "max_temp": 7.11}
  ],
  "disclaimer": "A logging and alert aid, not a certified compliance record."
}
Enter fullscreen mode Exit fullscreen mode

The first excursion is a door left open: a 32-minute climb peaking at 9.4C mid
morning. The second is a defrost cycle: 11 minutes peaking at 7.1C. A third
event, a single-minute spike to 9.87C at 05:00, is in the data on purpose and
does not appear here, because ten minutes of sustain is the rule and one minute
is not a breach.

The kill-and-resume proof. This is the one that matters for a logging tool,
because a logger you cannot trust after a crash is worse than paper. The alert
activity was given a 25-second delay so I could kill its worker after the breach
was detected but before the alert was written, then restart a fresh worker.

time (UTC) event
21:01:20.590 worker A started (25s alert delay)
21:01:24.595 workflow submitted
21:01:25.301 worker A: alert started for the 10:04 excursion, not yet written
21:01:25.614 worker A killed with kill -9 mid-alert; nothing is running
21:01:25.617 alerts.log line count while down: 0 (the alert was not written before the crash)
21:01:30.623 worker B restarted, no delay
21:01:56.316 Temporal redelivers the activity; the alert fires, about 31s late
21:01:56.544 final alert count: 2, one per excursion, none lost, none doubled

The alert arrived late, not lost, and it did not fire twice. Final log:

{"key": "fridge-01:2026-10-12:2026-10-12T10:04:00+00:00", "minutes_over": 32, "max_temp": 9.404, ...}
{"key": "fridge-01:2026-10-12:2026-10-12T17:04:00+00:00", "minutes_over": 11, "max_temp": 7.11, ...}
Enter fullscreen mode Exit fullscreen mode

This proof is on the site too, at baridi.tech/durability,
shown as a recorded test rather than a live action:

Baridi durability page: the recorded kill-and-resume sequence, worker killed mid-alert, Temporal redelivering the activity about 31 seconds later, final alert count 2 with none lost and none doubled.

Naive row-scan vs continuous aggregate. The breach math has a dumb version
(pull every raw row, stitch in Python) and the TimescaleDB version (read a
continuous aggregate, group in SQL). On one clean day they agree exactly and the
naive one is a touch faster, because 1440 rows is nothing:

naive row-scan:            best of 5 runs 4.08 ms
continuous-aggregate:      best of 5 runs 4.52 ms
paths agree on breaches:   True
Enter fullscreen mode Exit fullscreen mode

That is the honest toy-scale result. A compliance log is not one day, though. It
is months across several fridges. Loaded to 1,038,240 readings, the same
single-day query tells a different story in the plan:

NAIVE single-day:  Buffers: shared hit=5782   (hands 5760 raw rows to Python)
HEAVY single-day:  Buffers: shared hit=59      (returns 4 finished rows)
Enter fullscreen mode Exit fullscreen mode

About 98% fewer page reads, and the breach detection happens in the engine
instead of in a Python loop. Wall-clock is 14.96 ms vs 19.63 ms, a 1.31x
single-day speedup at that volume. Adding one new day to the million-row history
refreshes only that day's buckets in 1.27s, not a full rebuild.

The time_bucket SQL that produces the timeline:

CREATE MATERIALIZED VIEW readings_1min
WITH (timescaledb.continuous) AS
SELECT sensor_id, time_bucket('1 minute', ts) AS bucket,
       max(temp_c) AS max_temp, avg(temp_c) AS avg_temp
FROM readings
GROUP BY sensor_id, time_bucket('1 minute', ts);
Enter fullscreen mode Exit fullscreen mode

Excursions come from a gaps-and-islands query over that view: flag each minute
over or under threshold, open a new island on each change of state, keep islands
that last at least the sustain window. Full query and plans are in NOTES.md.

Code

Baridi

A cold-chain temperature stream becomes a breach timeline and a durable alert log. Replay a day of fridge readings, see every excursion over threshold with its start, end, duration, and peak temperature, and get an alert that survives a worker crash without firing twice or losing a reading.

Baridi is a logging and alert aid, not a certified compliance record.

What it does

  • Ingests a time-series of temperature readings into TimescaleDB as a hypertable.
  • Computes sustained excursions with a continuous aggregate (time_bucket) and an engine-side gaps-and-islands query, not a raw row scan.
  • Runs a durable Temporal workflow that ingests, checks, and alerts, with an idempotent alert keyed by {sensor_id}:{day}:{excursion-start}.
  • Shows the day's breach timeline on a one-page UI.

Prerequisites

  • Docker (for TimescaleDB)
  • Python 3.12 with venv
  • Temporal CLI (temporal server start-dev) for the durable workflow. Not needed for the offline durability proof.

Setup

python3
…
Enter fullscreen mode Exit fullscreen mode

How I Built It

  data/day.jsonl  (1440 readings, injected excursions)
        |
        v
  +----------------+        +---------------------------+
  |  Temporal      |        |  TimescaleDB              |
  |  workflow      | -----> |  readings (hypertable)    |
  |  (deterministic|ingest  |  readings_1min (cont. agg)|
  |   body)        |        +---------------------------+
  |     |          |                 |
  |     | check    | <---------------+  excursions(day) in SQL
  |     v          |
  |  activities    |        +---------------------------+
  |  (side effects)| -----> |  data/alerts.log          |
  |  idempotent    | alert  |  (one line per excursion) |
  |  alert         |        +---------------------------+
  +----------------+
        |
        v
  FastAPI /api/timeline  -->  breach timeline, served at baridi.tech
Enter fullscreen mode Exit fullscreen mode

The split is deliberate. The Temporal workflow body is deterministic: it reads
no clock except workflow.now(), touches no disk, and makes no network call
itself. Everything with a side effect (writing readings, running the excursion
query, firing an alert) is an activity. That is what lets Temporal replay the
workflow history after a crash and land in the same place.

The alert activity is the careful part. Its key is
{sensor_id}:{day}:{excursion-start}, it checks a persisted seen-set before
firing, and it records the key with an atomic os.replace before appending the
human-readable line. A crash between the two still leaves the key marked seen,
so a resume cannot double-fire.

There is no AI model in the critical path, and that is on purpose. The breach
decision is arithmetic: temperature over threshold, sustained past a window. A
model surfaces nothing the code cannot verify, so the code owns the facts. I
left a hook for an optional blip-vs-breach model and then did not wire it,
because the sustain window already rejects the single-minute spike that a model
would have been asked to catch. Shipping the model anyway would have been
decoration.

The live site runs on an open stack end to end: the FastAPI app behind Caddy for
automatic HTTPS, TimescaleDB in a container, and the breach query in SQL. It is
hosted on a .tech domain, baridi.tech, with the
TimescaleDB bound to loopback and the public API rate-limited.

Why Does Open Innovation Matter?

Dede's readings are her own compliance liability. They are the record an
inspector asks for and the record that protects her if a customer complains.
That record should not live behind someone's subscription, on someone's server,
billed per reading.

Baridi runs on open parts end to end: TimescaleDB in a container, Temporal's open
dev server, a Python stack. The readings never leave the machine. There is no
per-reading cloud fee. The breach math is a SQL query she or her inspector can
read, not a vendor's black box.

Honest limits, stated plainly. Baridi is a logging and alert aid, not a certified
compliance record, and the UI says so. The 1.31x database win is real but modest
at one day; the stronger case is buffer reads and incremental refresh at real
volume, and I would not claim otherwise. The data is synthetic, generated by a
seeded script, and the write-up says where. The blip-vs-breach model is deferred,
not hidden: the sustain window did the job, so the model stays out. A closed API
could have done the arithmetic too, but it could not have kept the readings in
the shop for free, which is the whole point.

Prize Categories

  • Best Use of Temporal: the breach alert is one idempotent activity per excursion, and a worker killed mid-alert resumes and fires it about 31 seconds late without losing it or firing it twice, proven by a timestamped table and an offline idempotency test.
  • Best Use of Tiger Data: the excursion timeline is computed by a time_bucket continuous aggregate and an in-engine gaps-and-islands query that, at a million readings, reads about 98% fewer buffers than a raw scan and returns finished excursions instead of raw rows.
  • Best Use of a .tech Domain: the project is live at baridi.tech, the breach timeline served over HTTPS from the real TimescaleDB-backed app. The domain fits the product by meaning, not just by availability. "Baridi" is Swahili for cold, the plain word for a chilly day or a cold drink, so baridi.tech reads as "cold, built in tech": a cold-chain tool that watches whether the cold is still cold. The name carries the one question the product answers, and the .tech domain puts it in the address bar.

Top comments (0)