"Tutorial: Cold Chain Monitoring for Pharmaceuticals"

June 2, 2026 | 25 min read

Introduction

Pharmaceutical cold chains are among the most heavily regulated edge-computing domains. A single temperature excursion can render a batch of vaccines unusable, while a leaked patient identifier can trigger a GDPR fine. This tutorial builds a complete GDP-compliant cold-chain monitor using Pyvorin Edge: temperature sensors, privacy redaction, tamper-evident audit logging, signed bundles, and offline-capable cloud sync.

Regulatory Context

Two frameworks dominate pharmaceutical transport:

  • EU GDP (Good Distribution Practice): Requires continuous temperature monitoring, alarm records, and documented corrective actions.
  • WHO PQS (Performance, Quality & Safety): Defines temperature ranges for vaccine cold boxes: +2 °C to +8 °C for standard refrigerators, −25 °C to −15 °C for freezer transport.

Our pipeline will enforce the +2 °C to +8 °C window and log every excursion with a cryptographically signed audit record.

Sensor Setup

We use two temperature sensors: one inside the refrigerator compartment and one ambient sensor to detect door-open events caused by high ambient influx.


from pyvorin_edge.pipeline import Pipeline, WindowConfig, RuleConfig
from pyvorin_edge.sensors import Sensor, SensorType, SensorReading

pipeline = Pipeline(name="cold_chain_pharma")

# Refrigerator core temperature
pipeline.add_sensor(
    Sensor(
        name="fridge_temp",
        sensor_type=SensorType.TEMPERATURE,
        unit="C",
        normal_range=(2.0, 8.0),
        location="Refrigerator_Compartment",
    )
)

# Ambient temperature for contextual analysis
pipeline.add_sensor(
    Sensor(
        name="ambient_temp",
        sensor_type=SensorType.TEMPERATURE,
        unit="C",
        normal_range=(15.0, 25.0),
        location="Vehicle_Cabin",
    )
)

# 60-second rolling window for trend analysis
pipeline.add_window(
    WindowConfig(
        duration_seconds=60.0,
        sensor_name="fridge_temp",
        window_type="rolling",
    )
)

# GDP-critical excursion rules (no cooldown — every second matters)
pipeline.add_rule(
    RuleConfig(
        name="temp_excursion_low",
        condition_expr="ctx.value < 2.0",
        severity="critical",
        cooldown_seconds=0.0,
    )
)
pipeline.add_rule(
    RuleConfig(
        name="temp_excursion_high",
        condition_expr="ctx.value > 8.0",
        severity="critical",
        cooldown_seconds=0.0,
    )
)
  

Privacy Redaction

Transport manifests often contain patient identifiers, batch numbers, and trial codes. The PrivacyPolicy class in edge_sdk/pyvorin_edge/policies.py lets you declare which fields are redacted before any data leaves the device.


from pyvorin_edge.policies import PrivacyPolicy

privacy = PrivacyPolicy(
    redact_fields=["patient_id", "shipment_id", "trial_code"],
    retain_hours=72.0,
    pseudonymize_ids=True,
    local_only_sensors=["gps_location"],
)
pipeline.add_policy(privacy)
  

Signed Audit Reports

Every privacy action and temperature excursion must be auditable. The PrivacyAudit class in edge_runtime/pyv_edge_agent/privacy_firewall/audit.py maintains a SHA-256 hash chain in SQLite, making tampering evident.


from pyv_edge_agent.privacy_firewall.audit import PrivacyAudit
import time

audit = PrivacyAudit(db_path="edge_store.db")

# Log policy activation
audit.log_action(
    action="PRIVACY_POLICY_LOADED",
    details={
        "redact_fields": privacy.redact_fields,
        "pseudonymize_ids": privacy.pseudonymize_ids,
        "purpose": "GDP compliance",
        "data_subjects": ["patients"],
        "data_categories": ["temperature", "shipment_manifest"],
    },
)

# Simulate an excursion and log it
excursion_reading = SensorReading(
    sensor_name="fridge_temp",
    timestamp=time.time(),
    value=10.5,
    unit="C",
    metadata={"batch": "B12345", "patient_id": "REDACTED"},
)
audit.log_action(
    action="TEMPERATURE_EXCURSION",
    details={
        "sensor": excursion_reading.sensor_name,
        "value": excursion_reading.value,
        "threshold_breached": "upper",
        "severity": "critical",
    },
)

# Verify chain integrity
assert audit.verify_chain(), "Audit chain has been tampered with!"
print(f"Audit records: {len(audit.get_audit_chain())}")
  

To prove bundle integrity to a third-party auditor, sign the deployment directory with BundleSigner from edge_sdk/pyvorin_edge/packaging/signer.py.


from pyvorin_edge.packaging.signer import BundleSigner
from pathlib import Path

# Generate an Ed25519 keypair (do this once and protect the private key)
private_key, public_key = BundleSigner.generate_keypair()

# Sign the entire deployment bundle
bundle_dir = Path("./cold_chain_bundle")
BundleSigner.sign_bundle(
    bundle_dir=bundle_dir,
    private_key=private_key,
    output_manifest=bundle_dir / "manifest.json",
)

# Verify on the auditor's side
is_valid = BundleSigner.verify_bundle(
    bundle_dir=bundle_dir,
    manifest_path=bundle_dir / "manifest.json",
    public_key=public_key,
)
print(f"Bundle integrity: {is_valid}")
  

Offline Operation Mode

Refrigerated trucks lose cellular coverage in rural areas and underground loading bays. The CloudSyncQueue in edge_runtime/pyv_edge_agent/cloud_sync/queue.py persists every item to SQLite and retries with exponential backoff when the link returns.


from pyv_edge_agent.cloud_sync.queue import CloudSyncQueue, Priority
import json

queue = CloudSyncQueue(db_path="sync_queue.db")

# Enqueue a critical excursion payload
payload = {
    "pipeline": "cold_chain_pharma",
    "timestamp": time.time(),
    "event": "TEMPERATURE_EXCURSION",
    "value": 10.5,
    "unit": "C",
}
item_id = queue.enqueue(payload, priority=Priority.CRITICAL, ttl_seconds=172800)
print(f"Enqueued item {item_id}, pending: {queue.pending_count()}")

# On reconnect, flush the queue
# uploader = HTTPCloudUploader(endpoint="...", api_key="...")
# flushed = queue.maybe_flush(uploader=uploader)
# print(f"Flushed {flushed} items")
  

Complete Working Script

Save the following as cold_chain.py. It creates the pipeline, loads privacy policies, logs audit events, signs the bundle, and demonstrates offline queue behaviour.


#!/usr/bin/env python3
"""Cold Chain Monitoring — complete working example."""

import time
from pathlib import Path
from pyvorin_edge.pipeline import Pipeline, WindowConfig, RuleConfig
from pyvorin_edge.sensors import Sensor, SensorType, SensorReading
from pyvorin_edge.policies import PrivacyPolicy, CloudPolicy
from pyv_edge_agent.privacy_firewall.audit import PrivacyAudit
from pyvorin_edge.packaging.signer import BundleSigner
from pyv_edge_agent.cloud_sync.queue import CloudSyncQueue, Priority


def main():
    pipeline = Pipeline(name="cold_chain_pharma")

    # Sensors
    pipeline.add_sensor(
        Sensor(name="fridge_temp", sensor_type=SensorType.TEMPERATURE, unit="C",
               normal_range=(2.0, 8.0), location="Refrigerator_Compartment")
    )
    pipeline.add_sensor(
        Sensor(name="ambient_temp", sensor_type=SensorType.TEMPERATURE, unit="C",
               normal_range=(15.0, 25.0), location="Vehicle_Cabin")
    )

    # Windows
    pipeline.add_window(
        WindowConfig(duration_seconds=60.0, sensor_name="fridge_temp", window_type="rolling")
    )

    # Rules
    pipeline.add_rule(
        RuleConfig(name="temp_excursion_low", condition_expr="ctx.value < 2.0",
                   severity="critical", cooldown_seconds=0.0)
    )
    pipeline.add_rule(
        RuleConfig(name="temp_excursion_high", condition_expr="ctx.value > 8.0",
                   severity="critical", cooldown_seconds=0.0)
    )

    # Privacy policy
    privacy = PrivacyPolicy(
        redact_fields=["patient_id", "shipment_id", "trial_code"],
        retain_hours=72.0,
        pseudonymize_ids=True,
        local_only_sensors=["gps_location"],
    )
    pipeline.add_policy(privacy)

    # Cloud policy
    pipeline.add_policy(
        CloudPolicy(
            endpoint_url="https://api.pyvorin.com/v1/ingest",
            protocol="mqtt",
            max_messages_per_minute=10,
            summary_interval_seconds=60.0,
        )
    )

    # Audit
    audit = PrivacyAudit(db_path="edge_store.db")
    audit.log_action(
        action="PRIVACY_POLICY_LOADED",
        details={
            "redact_fields": privacy.redact_fields,
            "purpose": "GDP compliance",
            "data_subjects": ["patients"],
        },
    )

    # Simulate readings and run
    now = time.time()
    readings = [
        SensorReading(sensor_name="fridge_temp", timestamp=now, value=1.5, unit="C",
                      metadata={"batch": "B12345", "patient_id": "REDACTED"}),
        SensorReading(sensor_name="fridge_temp", timestamp=now + 5.0, value=10.5, unit="C",
                      metadata={"batch": "B12345", "patient_id": "REDACTED"}),
    ]
    result = pipeline.run(readings)
    print(f"Processed {result.readings_processed} readings, {len(result.events)} events")
    for ev in result.events:
        print(f"  [{ev.severity}] {ev.rule_name}")
        audit.log_action(
            action="TEMPERATURE_EXCURSION",
            details={"sensor": ev.rule_name, "value": ev.sensor_readings[0].value},
        )

    assert audit.verify_chain(), "Audit chain tampered!"

    # Bundle signing
    bundle_dir = Path("./cold_chain_bundle")
    bundle_dir.mkdir(exist_ok=True)
    private_key, public_key = BundleSigner.generate_keypair()
    BundleSigner.sign_bundle(
        bundle_dir=bundle_dir,
        private_key=private_key,
        output_manifest=bundle_dir / "manifest.json",
    )
    print("Bundle signed.")

    # Offline queue
    queue = CloudSyncQueue(db_path="sync_queue.db")
    payload = {
        "pipeline": "cold_chain_pharma",
        "timestamp": time.time(),
        "event": "TEMPERATURE_EXCURSION",
        "value": 10.5,
    }
    queue.enqueue(payload, priority=Priority.CRITICAL, ttl_seconds=172800)
    print(f"Queue depth: {queue.pending_count()}")


if __name__ == "__main__":
    main()
  

Summary

You now have a cold-chain pipeline that enforces GDP temperature limits, redacts sensitive identifiers, maintains a tamper-evident audit chain, signs deployment bundles with Ed25519, and buffers critical events during network outages. In production, pair this with a calibrated RTD or thermocouple probe and validate against your regulatory auditor's checklist.