Schedule icon
Write icon
Queries icon
If icon
Log icon
SlackIncomingWebhook icon

Find Cold-Chain Temperature Excursions in Fridge and Freezer Sensor Logs

Review fridge and freezer sensor logs daily with DuckDB. Find temperature excursions with duration and peak, sensor gaps and bad readings, with Slack alerts.

Categories
BusinessData

Vaccines, insulin, biologics and chilled food are only safe if they stayed in their temperature range the whole time. Data loggers record a reading every few minutes, but a CSV with thousands of rows does not tell anyone that a fridge sat at 11 C for two hours last night, or that a freezer logger went quiet. This blueprint reviews the sensor log on a schedule, turns it into a short list of excursions with their duration and peak temperature, and tells the quality team which stock to quarantine. Every check runs locally in DuckDB: no IoT platform, no paid API, and no data leaving your Kestra instance.

How it works

  1. sample_readings (io.kestra.plugin.core.storage.Write) writes a 12-hour sensor log for four units with a Pebble loop, one reading every 15 minutes. It contains one example of every problem and is used only when the readings_file input is empty, so the flow runs out of the box.

  2. find_excursions (io.kestra.plugin.jdbc.duckdb.Queries) loads the CSV through inputFiles and:

    • matches each reading to the allowed range of its unit type: FRIDGE 2 to 8 C, FREEZER -25 to -15 C, AMBIENT 15 to 25 C;
    • groups consecutive out-of-range readings into one excursion with window functions (the gaps-and-islands pattern), and measures it from the first bad reading to the next good one;
    • marks an excursion CRITICAL when it lasts at least min_excursion_minutes, and BRIEF when it is shorter (typically a door opening);
    • reports a SENSOR_GAP when a unit sends no reading for longer than max_gap_minutes, including a unit that stopped reporting before the end of the log;
    • reports DATA_ERROR for rows with an unknown unit type, a timestamp that cannot be read, or a temperature that is not a number.

    The event report is written to excursion_report.csv with outputFiles, and the final SELECT returns the summary row through fetchType: FETCH_ONE, available as outputs.find_excursions.outputs[0].row.

  3. route_findings (io.kestra.plugin.core.flow.If) logs every critical excursion, sensor gap, data error and brief spike when anything needs action, and a nested If posts the findings to Slack when notify_slack is true. A clean review is logged as well.

  4. The errors block logs a failed review loudly, because a review that silently does not run looks the same as a cold chain with no excursions.

  5. The daily_cold_chain_review Schedule trigger (every day at 07:00 IST, shipped disabled) reviews the previous day before stock is picked or dispatched.

What you get

  • outputs.excursion_report: CSV with site, unit_id, unit_type, event, status, start_time, end_time, duration_minutes, readings, peak_c, allowed_range and action, sorted with the most serious events first.
  • outputs.excursion_summary: JSON with total_readings, units_checked, units_ok, critical_count, brief_count, gap_count, data_error_count, longest_excursion_minutes and the details for each group.
  • Plain-language actions such as "Above 8.0 C for 120 min, peak 11.2 C: quarantine the stock and check stability data before use" or "No reading for 165 min: check the logger and treat the stock as unverified for this period".
  • With the sample data: 2 critical excursions (a fridge above 8 C for 120 minutes and a fridge below 2 C for 45 minutes), 1 sensor gap of 165 minutes on the freezer, 1 brief 15-minute spike, and 2 data errors (an ERR reading and an unknown unit type CHILLER). The ambient store is clean.

Who it's for

  • Pharmacies, hospitals, vaccine stores and clinical trial depots that keep temperature-sensitive medicine in fridges and freezers.
  • Food and grocery warehouses, dark stores and cold storage operators.
  • Quality and data teams that receive logger exports and want a repeatable excursion review with an audit trail.

Why orchestrate this with Kestra

Most data loggers can raise an alarm, but nobody reviews the full history every day, sensor gaps go unnoticed, and the review is not recorded anywhere. Kestra runs the same review on a schedule, keeps every report in the execution history as evidence that the cold chain was checked, branches on the result, and alerts the people who act on it. Because the input is just a file, the same flow can be triggered when a new logger export lands in S3, SFTP or Google Drive, or run once per site with a ForEach.

Prerequisites

  • Nothing for the demo: the sample sensor log is built by the flow.
  • For your own data: a CSV export from your data loggers or monitoring system with the columns site, unit_id, unit_type, reading_time and temperature_c.

Inputs

  • readings_file (FILE, optional): your sensor log CSV. When empty, the built-in sample is used.
  • min_excursion_minutes (INT, default 30): excursions shorter than this are reported as BRIEF.
  • max_gap_minutes (INT, default 60): a longer silence between two readings is a SENSOR_GAP.
  • notify_slack (BOOL, default false): post the findings to Slack when anything needs action.

Secrets

  • SLACK_WEBHOOK_URL: Slack incoming webhook URL. Only needed when notify_slack is true.

Quick start

  1. Save the flow and run it with the default inputs.
  2. Open the logs of log_findings and download excursion_report from the Outputs tab to see the result for the sample sensor log.
  3. Run it again and upload your own logger export as readings_file.
  4. Adjust the ranges in the ranges table of find_excursions if your products need different limits, and set max_gap_minutes to a few times your logger interval.
  5. Optional: add the SLACK_WEBHOOK_URL secret and set notify_slack to true.
  6. Replace the input with your real source and set disabled: false on daily_cold_chain_review.

Common pitfalls

  • The allowed ranges are typical defaults, not product rules. Always follow the storage conditions on the product label and your stability data before releasing stock after an excursion.
  • Use one time zone for all readings. Mixing local time and UTC creates fake gaps and overlaps.
  • An excursion is measured until the next reading in range. With a long logging interval, durations are rounded up to that interval.
  • Readings that cannot be parsed are skipped and reported as DATA_ERROR, so a logger that writes ERR or an empty value never hides inside an average.

How to extend

  • Read the readings straight from your monitoring database or time-series store instead of a file, and write report back to a table.
  • Trigger the review when a new logger export lands, with the S3, SFTP or Google Drive triggers.
  • Add a per-product range table and join it on unit_id to check each unit against what it actually stores.
  • Open a deviation ticket in Jira or ServiceNow for every critical excursion.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.