Waste management — smart collection
The problem. Fixed collection schedules get it wrong in both directions: bins in busy spots overflow between visits — a health and litter problem — while trucks burn fuel and hours emptying half-full bins on quiet streets. The operator needs collection driven by how full bins actually are, so crews go where the waste is and skip where it isn't.
What we solve here is collecting on demand instead of on a calendar — fewer overflows, fewer wasted trips.
Set up demo data
New workspace? Run this once (Python) to create a bin's fill-level series with a near-full
reading — so the collection_due rule below fires. Safe to re-run.
import datahub_sdk, pandas as pd
client = datahub_sdk.DataHubClient.from_env()
client.timeseries.create([datahub_sdk.TimeSeries(external_id="bin_grunerlokka_114_fill_pct", name="Bin 114 fill", unit="pct", value_type="float")])
client.timeseries.insert_from_lists(timestamps=[pd.Timestamp.now(tz="UTC")], values=[87.0], ts="bin_grunerlokka_114_fill_pct")
1. Act on fill level, not the calendar
Each bin reports its fill level. When a bin crosses its collection threshold, raise a
collection_due event that drops it onto the next route. See
Turn readings into events.
- Java
- Python
- Rust
var series = client.timeseries().retrieve(lastReadingOf("bin_grunerlokka_114_fill_pct"))
.getItems().get(0);
double fill = Double.parseDouble(series.getDatapoints().get(0).getValue());
if (fill >= 80.0) {
EventModel due = new EventModel();
due.setExternalId("collection_due_114_" + System.currentTimeMillis());
due.setType("collection_due");
due.setStatus("open");
due.setMetadata(Map.of("bin", "bin_grunerlokka_114", "fill_pct", String.valueOf(fill)));
due.setEventTime(Long.parseLong(series.getDatapoints().get(0).getTimestamp()));
client.events().create(List.of(due));
}
points = client.timeseries.retrieve_datapoints(last_reading("bin_grunerlokka_114_fill_pct"))[0]
fill = float(points.get_datapoints()[0].value)
if fill >= 80.0:
client.events.create([datahub_sdk.Event(
external_id=f"collection_due_114_{int(pd.Timestamp.now().timestamp())}",
type="collection_due", status="open",
event_time=points.get_datapoints()[0].timestamp,
metadata={"bin": "bin_grunerlokka_114", "fill_pct": str(fill)})])
use chrono::Utc;
use dataplatform_rust_sdk::events::Event;
let points = api.time_series.retrieve_datapoints(&last_reading("bin_grunerlokka_114_fill_pct")).await?
.get_items().remove(0);
let fill: f64 = points.datapoints[0].value.as_deref().unwrap_or("0").parse().unwrap_or(0.0);
if fill >= 80.0 {
let mut due = Event::new(format!("collection_due_114_{}", Utc::now().timestamp()));
due.r#type = Some("collection_due".into());
due.status = Some("open".into());
due.add_metadata("bin".into(), "bin_grunerlokka_114".into());
due.add_metadata("fill_pct".into(), fill.to_string());
due.set_event_time(points.datapoints[0].timestamp);
api.events.create(&vec![due]).await?;
}
2. Plan routes from real demand
Fill levels across a district, rolled up over the week, show the fill-rate pattern — which areas fill fast and need frequent visits, which can be left longer. That pattern sizes routes and frequencies far better than a flat schedule. See Query & aggregate.
3. Flag contamination
A bin reading the wrong weight or material for its stream becomes a contamination
event for the education and enforcement team.
See the result
The near-full bin in the demo trips the rule:
collection_due bin_grunerlokka_114 (fill 87%)
See also
- Turn readings into events — the collection-due rule.
- Query & aggregate — fill-rate patterns for routing.
- High-throughput ingestion — city-wide sensor volume.