Instead of GET+PUT on every Append call, cache the day's entries in memory
and flush to WebDAV every 4 hours. On a day rollover, buffered entries are
flushed to the outgoing day's file before the new day is loaded, ensuring
no entries are written to the wrong day's URL.
Remove the in-memory cache from geocode/nominatim.go (cacheTTL, cacheMu,
cachedLoc, cacheExpiry) so CityCenter always makes a live request when called.
Call frequency is now controlled by the caller rather than a time-based TTL.
In the poll goroutine, log the raw GPS coordinates on every cycle. Track the
last geocoded position and only call CityCenter on the first fix or when the
device has moved more than 5 miles (haversine). WebDAV snapshots are written
every poll cycle using the cached city centroid with fresh weather data.
The simple atmosphericCeiling*sin(elevation) model overestimates clear-sky
radiation at low sun angles because it ignores the longer atmospheric path
light travels near the horizon. At 5° elevation the path is ~11x vertical,
making the real clear-sky ceiling ~26 W/m² rather than the 83 W/m² the
naive model predicts — causing sunny low-angle readings to be misclassified
as mostly cloudy.
Replace with the Beer-Lambert correction:
expectedClearSky = 950 * exp(-opticalDepth / sin(elevation)) * sin(elevation)
opticalDepth=0.1 (clear atmosphere) produces accurate values across all
angles: ~26 W/m² at 5°, ~93 W/m² at 10°, ~715 W/m² at solar noon.
Replace the sinusoidal day model with solarPosition(), which computes the
actual solar elevation and hour angle from latitude, longitude, and UTC time.
Expected clear-sky radiation at any moment is simply atmosphericCeiling *
sin(elevation), eliminating all hardcoded or approximated sunrise/sunset
windows.
Dawn/dusk is now detected when the sun is geometrically below the horizon
but the sensor still reads positive (scattered twilight), and morning vs.
afternoon is determined from the sign of the hour angle rather than a
clock-time comparison.
Replace the hardcoded peakSolarRadiation constant with solarNoonPeak(), which
scales the atmospheric ceiling (950 W/m²) by sin(solar elevation angle). Elevation
is computed from the current latitude and solar declination (function of day-of-year),
so the normalization is accurate across seasons and locations rather than assuming
equatorial/summer-solstice conditions.
Also switch the sun-angle time calculation from d.LastUpdated to time.Now() so
that a stale cache entry (e.g. after a Starlink connectivity gap) doesn't cause the
wrong time-of-day to be used when inferring conditions.
Detect twilight by checking when the solar radiation sensor reads low but
positive values (< 30 W/m²) while the sun is geometrically near the horizon
(sinusoidal fraction < 0.2 or outside model daylight hours). Morning/afternoon
local time distinguishes dawn from dusk without hard-coding sunrise/sunset times.