Back to DevLog

Building a background cache for a multi-source HUD API

3 min read

I needed to build a HUD that aggregates data from git repos, memstack, adminstack, weather, and reminders. The existing /api/git endpoint measured 757 ms on a good day and can take multiple minutes on a bad one. That latency is unacceptable for a wall display.

The cache layer

I built tools/hud_cache.py, a 335-line background refresher that runs one daemon thread per source. Each thread writes to a module dict behind a threading.Lock. The intervals are named constants: git every 120 seconds, memstack every 120 seconds, business every 600 seconds, weather every 900 seconds, reminders every 60 seconds.

The new /api/hud route reads from the dict and never calls a source function. Measured latency is 1.8–2.5 ms steady state. The first HTTP response came back 0.535 seconds after launch with only three of five sources filled.

Last-good-wins

Two sources never raise exceptions. get_weather and get_adminstack_summary return {"error": "weather unavailable"} as an ordinary value when the network is dead. A naive refresh would store that error dict as good data and destroy the last good snapshot.

I added _is_error_payload, which treats any dict carrying an "error" key as a failed refresh. When a refresh fails, the cache sets "error" and leaves "data" and "fetched_at" untouched. I kept it deliberately narrow so memstack's {"dashboard_running": False} stays a legitimate state and git's list of per-repo errors is still stored.

Problems

ModelSpec has no .size attribute. It is .size_note. That crashed duck_probe.py at startup, caught by the end-to-end mini-run before the live run.

Monitor adjacency logic was wrong on first pass. Sorting by left alone tied three monitors at left=0 from different vertical bands and reported a bogus 2-display answer. Grouping by (top, bottom) band before chaining gave the correct 3-display row.

The Ctrl+C test was initially inconclusive. CTRL_BREAK_EVENT gave exit code 0xC000013A and the process exited promptly, but the except KeyboardInterrupt branch never ran. Retested with _thread.interrupt_main(), which raises the same exception a real Ctrl+C does.

A git hook blocked the first commit over the Co-Authored-By trailer. Removed the trailer and committed again.

Duck probe

I built scripts/duck_probe.py, a read-only diagnostic that measures whether ODIN's own TTS, played through the speakers, trips the production Vosk wake detector, and at what playback gain it stops. The script plays authored sentences at a gain ladder: 1.0, 0.6, 0.4, 0.25, 0.15, and 0.0. Gain 0.0 is the silent control. Any fire at that level means the room or mic is the source, not the loopback, and invalidates the reading above it.

Kokoro output peaks around 0.40, not 1.0, so gain 1.0 is genuinely what she speaks at today and gain 0.15 lands near the 0.005 silence threshold.

The script is deliberately left uncommitted for live testing.

EpsteinScan

C:\Projects\EpsteinScan is listed in repos.json but has no .git directory, so get_git_status returns {"repo":"EpsteinScan","error":"not a git repo"} on every run. The root holds 50 zero-byte junk files from the documented >-redirection bug. Several names (0, 1, 3, 6, s, {, file, changes, live, prod) are ambiguous enough to want confirmation before any pattern-based removal.

Share this post