Fix the 3am notification avalanche and the failing morning digest

Three bugs from the first day of real use.

1. Quiet hours were evaluated in UTC. The router is handed a UTC `now` and
   compared its hour against "22:00-07:00" directly, so notifications were
   silenced from 18:00 local -- prime reading time -- and the whole day's budget
   was released at 03:00 local. The reported avalanche landed at 03:39 and 03:55.
   Quiet hours are now converted to the reader's wall clock first.

2. The timezone never resolved anyway. TZ was "America/Louisville", which exists
   on this Arch host but NOT in a Debian container, which ships only the
   canonical "America/Kentucky/Louisville". The container fell back to UTC and
   `date` printed "America" as the zone abbreviation. The zone is now read from
   config rather than the environment, so a bad TZ cannot silently change
   behaviour, and the compose entry uses the canonical name. NOTE: ~20 other
   services in compose.yml carry the same bad string; they were left alone since
   it only affects their log timestamps.

3. Budgets are not a rate. Six notifications fired in a single minute because
   the daily allowance could all be spent in one cycle. Each lane now has a
   minimum gap (interest 75min, importance 30min) and sends at most one per
   cycle, which turns the budget into a rate spread across the day.

Separately, the morning digest had silently fallen back to the templated list.
The real 28-story payload takes 70.3s to generate against a 60s timeout. Raised
to 180s -- and write_prose now LOGS why it failed instead of returning None in
silence, which is what made a one-line timeout cost a debugging session.
This commit is contained in:
connor committed 2026-09-09 09:55:49 -04:00
1 parent 161c518aa7
commit 98bba62976
8 files changed
+151 -4

No files matched your search

Generated
+12
View File
@@ -565,6 +565,7 @@ dependencies = [
{ name = "python-multipart" },
{ name = "starlette" },
{ name = "tomlkit" },
{ name = "tzdata" },
{ name = "uvicorn" },
]
@@ -584,7 +585,9 @@ requires-dist = [
{ name = "numpy", specifier = ">=1.26" },
{ name = "python-multipart", specifier = ">=0.0.9" },
{ name = "starlette", specifier = ">=0.37" },
{ name = "tomlkit", specifier = ">=0.13" },
{ name = "tomlkit", specifier = ">=0.15.1" },
{ name = "tzdata", specifier = ">=2024.1" },
{ name = "uvicorn", specifier = ">=0.30" },
]
@@ -1032,6 +1035,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
]
[[package]]
name = "tzdata"
version = "2026.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" },
]
[[package]]
name = "urllib3"
version = "2.7.0"