From 7254719794b1792f3efdb9103f2279d7e92072ea Mon Sep 17 00:00:00 2001 From: James Pattinson Date: Thu, 18 Jun 2026 18:39:26 +0100 Subject: [PATCH] fix zero handling --- README.md | 14 +++++++ app/collector.py | 8 ++++ scripts/remove_zero_readings.sh | 68 +++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100755 scripts/remove_zero_readings.sh diff --git a/README.md b/README.md index c8cad19..54d11a9 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,20 @@ Compose builds the internal database URL from `MYSQL_USER`, `MYSQL_PASSWORD`, and `MYSQL_DATABASE`. Set `DATABASE_URL` only if you want to point the app at a different database. +## Maintenance + +Remove bad readings where temperature, humidity, and battery are all zero: + +```sh +scripts/remove_zero_readings.sh +``` + +Preview the count without deleting: + +```sh +scripts/remove_zero_readings.sh --dry-run +``` + ## Reports Open `/reports` in the web app to generate a date-range summary. Use the diff --git a/app/collector.py b/app/collector.py index 531de54..773981a 100644 --- a/app/collector.py +++ b/app/collector.py @@ -24,6 +24,10 @@ logger = logging.getLogger(__name__) SENSOR_DEVICE_TYPES = {"WoIOSensor"} +def is_all_zero_reading(status: dict[str, Any]) -> bool: + return status.get("temperature") == 0 and status.get("humidity") == 0 and status.get("battery") == 0 + + def upsert_device(device_data: dict[str, Any]) -> None: device_id = device_data["deviceId"] with session_scope() as session: @@ -55,6 +59,10 @@ def save_reading(device: Device, status: dict[str, Any]) -> None: logger.info("Skipping %s (%s): status has no temperature/humidity", device.name, device.id) return + if is_all_zero_reading(status): + logger.info("Skipping %s (%s): status is all zeros", device.name, device.id) + return + reading = Reading( device_id=device.id, recorded_at=datetime.now(timezone.utc).replace(tzinfo=None), diff --git a/scripts/remove_zero_readings.sh b/scripts/remove_zero_readings.sh new file mode 100755 index 0000000..4b4d939 --- /dev/null +++ b/scripts/remove_zero_readings.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Usage: scripts/remove_zero_readings.sh [--dry-run] [--yes] + +Removes readings where temperature, humidity, and battery are all zero. + +Options: + --dry-run Count matching readings without deleting them. + --yes Delete without prompting. + --help Show this help. +USAGE +} + +dry_run=0 +assume_yes=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) + dry_run=1 + ;; + --yes|-y) + assume_yes=1 + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + esac + shift +done + +where_clause="temperature = 0 AND humidity = 0 AND battery = 0" + +run_mysql() { + docker compose exec -T db sh -c 'MYSQL_PWD="$MYSQL_PASSWORD" exec mysql -u"$MYSQL_USER" "$MYSQL_DATABASE" "$@"' sh "$@" +} + +matching_count=$( + printf 'SELECT COUNT(*) FROM readings WHERE %s;\n' "$where_clause" | run_mysql --batch --skip-column-names +) + +echo "Found ${matching_count} all-zero reading(s)." + +if [[ "$dry_run" -eq 1 || "$matching_count" -eq 0 ]]; then + exit 0 +fi + +if [[ "$assume_yes" -ne 1 ]]; then + read -r -p "Delete these readings? Type 'yes' to continue: " answer + if [[ "$answer" != "yes" ]]; then + echo "No records deleted." + exit 0 + fi +fi + +printf 'DELETE FROM readings WHERE %s;\nSELECT ROW_COUNT();\n' "$where_clause" | + run_mysql --batch --skip-column-names | + tail -n 1 | + xargs printf 'Deleted %s all-zero reading(s).\n'