A privacy-first, on-device noise monitor that timestamps barking events and sound-level spikes and turns them into a clean report — so the next time a downstairs neighbor complains, you have objective data instead of a he-said-she-said. It measures sound levels and event metadata only. It never records, stores, or transmits audio. By design, there is no recording to leak, subpoena, or wiretap.
Status: Beta · Track: Personal (on-device monitor + report generator) · License: MIT · Data: on-device/local
Supported versions: pre-1.0 — only the latest 0.y release receives fixes; no LTS branch (REL-24).
You've been on the receiving end of vague noise complaints about Olive with nothing concrete to point to. A small device that runs in your apartment and logs when sound crossed a threshold and for how long gives you an honest, time-stamped record — useful for property management or just for understanding the real pattern — without the legal and ethical problems of recording your home (or your neighbors).
- Listens for levels, not content: computes sound level (dBFS, with a documented calibration offset) frame-by-frame in memory and discards the audio immediately.
- Detects events: threshold + minimum-duration + debounce → a "bark/noise event" with start, duration, and peak/average level.
- Logs to SQLite: events only — timestamps and levels, no audio.
- Generates reports: daily/hourly distributions, quiet-hours summaries, and event counts as an accessible HTML report with charts and a methodology + limitations section. An optional, opt-in tagged PDF/A-3a export (
--pdf, needs thepdfextra) is also available — see Standards Conformance for exactly what its accessibility claim does and does not cover. - Runs on-device: a Raspberry Pi service (primary) or a browser PWA (zero-hardware alternative), no network required.
- Build entrypoint:
docs/ROADMAP.md→ Implementation Plan. - Hard guardrails: never write audio bytes to disk and never transmit audio anywhere — raw frames are processed in memory and discarded; only derived levels + event metadata are persisted (this is the central design gate and has a merge-blocking test); the report must state its methodology and limitations honestly (uncalibrated dBFS is relative, not absolute SPL unless calibrated; the device cannot prove a sound's source); the tool runs local-only (no cloud, no telemetry); data is presented to inform, never fabricated or cherry-picked to manufacture a case.
- Commands:
make dev·make verify·make a11y·make report·make pwa-test.
make dev # create .venv and install (dev extras)
make verify # lint, type, coverage, security, a11y, PWA tests, i18n gate
make report # render report.html from a demo session (no hardware)
# Live capture on a Pi/laptop (optional audio dependency):
uv sync --locked --group dev --extra live
.venv/bin/olive-tune --config config.sample.json # live meter; suggests a threshold
.venv/bin/olive-calibrate --config config.sample.json --reference-db 70 # store SPL offset
.venv/bin/olive-monitor --config config.sample.json # logs events; Ctrl-C to stop
.venv/bin/olive-report --config config.sample.json --out report.html --csv events.csvThe core (level math, detector, store, report) has zero runtime dependencies and runs
on any Python 3.9+ with no installs; only live microphone capture needs the live extra.
Calibration is a single source of truth. Events are stored as raw dBFS and the calibration offset is applied at report time from an append-only history owned by
olive-calibrate. Thecalibration_offset/calibration_notefields inconfig.sample.jsonare bootstrap-only (deprecated for steady-state use): they seed a database that has never been calibrated and are ignored onceolive-calibratehas run. The monitor never writes calibration, soolive-calibratefollowed byolive-monitorwith a default config no longer reverts the device to uncalibrated.threshold_dbfsis defined against the same raw stored scale, so recalibrating never changes detection sensitivity. Render-time calibration is applied identically to the HTML report and to the--csv/--violations-csv/--violations-htmlexports; each CSV row records the offset included in its levels (calibration_offset_db).
| Command | What it does |
|---|---|
olive-monitor |
Run the monitor: capture → level → detect → SQLite. Creates a capture session (lineage), writes a heartbeat file, reconnects on device failure, prunes per retention_days. |
olive-tune |
Show the live level so you can pick a threshold by ear; prints a suggestion. |
olive-calibrate |
Measure mean level against a reference SPL reading and append a calibration offset (with optional --reference-instrument provenance). This is the only writer of calibration; it is an append-only history applied at report time, so recalibrating never rewrites earlier events. |
olive-report |
Render the accessible HTML report (distributions + day×hour calendar heatmap + quiet-hours summary). Optional --csv event export, --violations-csv / --violations-html for an honest quiet-hours report suitable for a neighbor/landlord/HOA submission, and --pdf / --violations-pdf for a tagged PDF/A-3a of either (needs the pdf extra; see Standards Conformance). |
When health_path is configured, the monitor writes a static status.html next to
the heartbeat file on every check-in. You can instead enable only the page by setting
status_path explicitly. No server or network is involved. Open it straight from disk
(double-click, or open status.html) for an at-a-glance
ops view: heartbeat freshness (with a stale-heartbeat warning if the monitor has gone
quiet), the most recent level, frame coverage, recorded monitoring gaps, and a recent
summary (event count, minutes with events, busiest hour, quiet-hours totals). The page
is atomically rewritten, so you never catch it half-written, and it auto-refreshes every
60s if left open in a browser. It inherits the report's accessibility (keyboard-complete,
scoped table headers, reduced-motion) and the same no-audio guarantee.
For home-automation confounder context — e.g. correlating a doorbell, robot vacuum, or
smart speaker with a logged spike — the monitor can emit its heartbeat and each event to a
local AF_UNIX datagram socket.
It is off by default, one-way, and emit-only: nothing is ever read back and no
network socket is opened, so the no-egress guarantee is unchanged (there is a merge-blocking
test that permits socket only in monitor/ipc.py, and only for AF_UNIX). Enable it with
--ipc-socket /run/olive/ipc.sock (or "ipc_socket" in the JSON config; "" = disabled).
Sending is nonblocking and best-effort: if the listener is missing, stalled, or unable to
accept a datagram, that update is dropped instead of delaying sound capture.
A Home Assistant listener (same host) can pick up the JSON datagrams via a shell/command_line
sensor that reads the socket, e.g. with socat:
# configuration.yaml — reads one JSON line per datagram from the local socket.
command_line:
- sensor:
name: Olive Bark Event
command: "socat -u UNIX-RECV:/run/olive/ipc.sock,fork - "
value_template: "{{ value_json.peak_level | default('idle') }}"
json_attributes:
- type
- start
- duration
- peak_level
- session_idPayloads are {"type": "event", "session_id", "start", "duration", "peak_level"} per event
and the heartbeat health dict on each beat. Levels and metadata only — never audio.
-
Raspberry Pi service:
scripts/setup-pi.shinstalls PortAudio + a venv and thedeploy/olive-monitor.servicesystemd unit (auto-restart, network-isolated, sandboxed). -
Browser PWA (zero hardware):
pwa/— Web Audio levels, IndexedDB events, same no-audio guarantee, works offline. Seepwa/README.md. -
Container:
Dockerfilebuilds the report/analysis side for reproducible CI. -
Definition of done: the monitor runs unattended, logs noise events (levels + timestamps, zero audio) to local SQLite, and produces an honest, accessible report with charts and a stated methodology — all applicable
/STANDARDSgates green (see Standards Conformance below) and the no-audio test passing. Full checklist:DEFINITION_OF_DONE.md.
Tier C — OTel tracing out-of-scope (no network surface). Opt-in --log-format json is
not implemented yet (tracked: GAP-OBS-1);
today's surface is operator-facing print() lines plus a heartbeat JSON file
(monitor/service.py) with no secret/PII fields by design.
Inherits /STANDARDS (this table is the individual declaration DOC-11
requires; a bare "inherits" statement with no table is the exact silent-omission defect
the standard forbids — a prior version of this README made that mistake). Applies — gap tracked in GAP-NN rows resolve to a real, dated, append-only entry in
docs/GAP-LEDGER.md (a GitHub issue was the original plan, but
this repo's tooling correctly refuses unsolicited issue creation as an external
write-effect, so gaps live here instead — see that file's header for why).
| Standard | State |
|---|---|
| Quality & Metrics | Applies — gap tracked in GAP-QM-1 (DORA ledger; release-gate checklist exists in DEFINITION_OF_DONE.md but has never been run, since no release has happened) |
| Code Quality | Applies — gap tracked in GAP-CQ-1 (Python-floor divergence recorded in ADR-0002; pre-commit enforcement, hatchling, and src/ layout still open) |
| Security & Supply-Chain | Applies — hardened posture (ASVS L2); gap tracked in GAP-SEC-1 |
| CI/CD | Applies — gap tracked in GAP-CICD-1 (ruleset committed at .github/rulesets/main.json, not yet applied — that's a live GitHub action for the maintainer, see the file's header) |
| Release & Versioning | Applies — release-producing deployed app; gap tracked in GAP-REL-1 (tag-triggered release.yml now exists, REL-14 — no tag cut yet, and PyPI/GHCR/cosign are still open; CITATION.cff intentionally carries no date-released until a tag exists) |
| Accessibility | Applies — gap tracked in GAP-A11Y-1 (PWA surface unscanned; walkthrough stale since 8a9f1eb) and GAP-A11Y-2 (the optional tagged PDF/A-3a export's structure is tested; its PDF/UA/"fully accessible" conformance is not verified — no human AT walkthrough has been done) |
| Observability | Applies — Tier C: OTel out-of-scope (no network surface); --log-format json opt-in planned, gap tracked in GAP-OBS-1 |
| Internationalization | N/A — single-user tool, operator-only English output (docs/I18N.md) |
| AI Evaluation | N/A — no model/prompt/retrieval surface; nothing in this codebase calls an LLM SDK |
| Documentation | Applies — gap tracked in GAP-DOC-1 (/STANDARDS vendoring blocked on a portfolio-level tag prerequisite; ADR migration in progress) |
| Responsible-Tech Framework | Applies — this repo's strongest standard: no-audio, no-egress, and honest-report-content are merge-blocking tests (tests/test_no_audio.py, tests/test_no_egress.py, tests/test_report_content.py); full treatment in docs/RESPONSIBLE-TECH-AUDITS.md; gap tracked in GAP-RTF-1 (per-section sign-off dates) |
Last full audit: 2026-07-05 (audit-2026-07-05/olive-bark-logger-AUDIT.md,
33/138 controls PASS before that day's remediation pass; this table reflects the
post-remediation state and will drift from a fresh audit run — treat the audit file as
the point-in-time evidence trail, this table as the current claim).