improve charts and logs

This commit is contained in:
2026-01-28 17:00:24 +11:00
parent aa71b51524
commit 536ec7ad70
4 changed files with 25 additions and 29 deletions

View File

@@ -429,7 +429,6 @@ function renderDashboard(data) {
const fcWinds = forecast.map((p) => p.wind_m_s); const fcWinds = forecast.map((p) => p.wind_m_s);
const fcGusts = forecast.map((p) => p.wind_gust_m_s); const fcGusts = forecast.map((p) => p.wind_gust_m_s);
const fcRH = forecast.map((p) => p.rh); const fcRH = forecast.map((p) => p.rh);
const fcPrecip = forecast.map((p) => p.precip_mm);
const obsTempSummary = minMax(obsTemps); const obsTempSummary = minMax(obsTemps);
const obsWindSummary = minMax(obsWinds); const obsWindSummary = minMax(obsWinds);
@@ -580,16 +579,6 @@ function renderDashboard(data) {
}; };
upsertChart("chart-rain", rainChart); upsertChart("chart-rain", rainChart);
const precipChart = {
type: "bar",
data: {
datasets: [
{ label: "forecast precip mm", data: series(forecast, "precip_mm"), backgroundColor: colors.precip },
],
},
options: baseOptions(range),
};
upsertChart("chart-precip", precipChart);
updateSingleChartMode(); updateSingleChartMode();
} }

View File

@@ -155,15 +155,6 @@
<canvas id="chart-rain"></canvas> <canvas id="chart-rain"></canvas>
</div> </div>
</div> </div>
<div class="chart-card wide" data-chart="chart-precip">
<div class="chart-header">
<div class="chart-title">Precipitation (forecast)</div>
<button class="chart-link" data-chart="chart-precip" title="Copy chart link">Share</button>
</div>
<div class="chart-canvas">
<canvas id="chart-precip"></canvas>
</div>
</div>
</div> </div>
</section> </section>
</main> </main>

View File

@@ -34,6 +34,7 @@ type ForecastPoint struct {
WindGustMS *float64 `json:"wind_gust_m_s,omitempty"` WindGustMS *float64 `json:"wind_gust_m_s,omitempty"`
WindDirDeg *float64 `json:"wind_dir_deg,omitempty"` WindDirDeg *float64 `json:"wind_dir_deg,omitempty"`
PrecipMM *float64 `json:"precip_mm,omitempty"` PrecipMM *float64 `json:"precip_mm,omitempty"`
PrecipProb *float64 `json:"precip_prob,omitempty"`
CloudCover *float64 `json:"cloud_cover,omitempty"` CloudCover *float64 `json:"cloud_cover,omitempty"`
} }
@@ -195,6 +196,7 @@ func (d *DB) ForecastSeriesLatest(ctx context.Context, site, model string) (Fore
wind_gust_m_s, wind_gust_m_s,
wind_dir_deg, wind_dir_deg,
precip_mm, precip_mm,
precip_prob,
cloud_cover cloud_cover
FROM forecast_openmeteo_hourly FROM forecast_openmeteo_hourly
WHERE site = $1 AND model = $2 AND retrieved_at = $3 WHERE site = $1 AND model = $2 AND retrieved_at = $3
@@ -210,9 +212,9 @@ func (d *DB) ForecastSeriesLatest(ctx context.Context, site, model string) (Fore
var ( var (
ts time.Time ts time.Time
temp, rh, msl, wind, gust sql.NullFloat64 temp, rh, msl, wind, gust sql.NullFloat64
dir, precip, cloud sql.NullFloat64 dir, precip, prob, cloud sql.NullFloat64
) )
if err := rows.Scan(&ts, &temp, &rh, &msl, &wind, &gust, &dir, &precip, &cloud); err != nil { if err := rows.Scan(&ts, &temp, &rh, &msl, &wind, &gust, &dir, &precip, &prob, &cloud); err != nil {
return ForecastSeries{}, err return ForecastSeries{}, err
} }
points = append(points, ForecastPoint{ points = append(points, ForecastPoint{
@@ -224,6 +226,7 @@ func (d *DB) ForecastSeriesLatest(ctx context.Context, site, model string) (Fore
WindGustMS: nullFloatPtr(gust), WindGustMS: nullFloatPtr(gust),
WindDirDeg: nullFloatPtr(dir), WindDirDeg: nullFloatPtr(dir),
PrecipMM: nullFloatPtr(precip), PrecipMM: nullFloatPtr(precip),
PrecipProb: nullFloatPtr(prob),
CloudCover: nullFloatPtr(cloud), CloudCover: nullFloatPtr(cloud),
}) })
} }
@@ -249,6 +252,7 @@ func (d *DB) ForecastSeriesRange(ctx context.Context, site, model string, start,
wind_gust_m_s, wind_gust_m_s,
wind_dir_deg, wind_dir_deg,
precip_mm, precip_mm,
precip_prob,
cloud_cover cloud_cover
FROM forecast_openmeteo_hourly FROM forecast_openmeteo_hourly
WHERE site = $1 AND model = $2 AND ts >= $3 AND ts <= $4 WHERE site = $1 AND model = $2 AND ts >= $3 AND ts <= $4
@@ -266,9 +270,9 @@ func (d *DB) ForecastSeriesRange(ctx context.Context, site, model string, start,
ts time.Time ts time.Time
retrieved time.Time retrieved time.Time
temp, rh, msl, wind, gust sql.NullFloat64 temp, rh, msl, wind, gust sql.NullFloat64
dir, precip, cloud sql.NullFloat64 dir, precip, prob, cloud sql.NullFloat64
) )
if err := rows.Scan(&ts, &retrieved, &temp, &rh, &msl, &wind, &gust, &dir, &precip, &cloud); err != nil { if err := rows.Scan(&ts, &retrieved, &temp, &rh, &msl, &wind, &gust, &dir, &precip, &prob, &cloud); err != nil {
return ForecastSeries{}, err return ForecastSeries{}, err
} }
if retrieved.After(maxRetrieved) { if retrieved.After(maxRetrieved) {
@@ -283,6 +287,7 @@ func (d *DB) ForecastSeriesRange(ctx context.Context, site, model string, start,
WindGustMS: nullFloatPtr(gust), WindGustMS: nullFloatPtr(gust),
WindDirDeg: nullFloatPtr(dir), WindDirDeg: nullFloatPtr(dir),
PrecipMM: nullFloatPtr(precip), PrecipMM: nullFloatPtr(precip),
PrecipProb: nullFloatPtr(prob),
CloudCover: nullFloatPtr(cloud), CloudCover: nullFloatPtr(cloud),
}) })
} }

View File

@@ -77,23 +77,34 @@ func (o *OpenMeteo) Fetch(ctxDone <-chan struct{}, site Site, model string) (*Fo
} }
defer resp.Body.Close() defer resp.Body.Close()
const maxLogBody = int64(65536)
bodyBuf, _ := io.ReadAll(io.LimitReader(resp.Body, maxLogBody+1))
truncated := int64(len(bodyBuf)) > maxLogBody
if truncated {
bodyBuf = bodyBuf[:maxLogBody]
}
if truncated {
log.Printf("open-meteo response status=%d bytes=%d truncated=true body=%s", resp.StatusCode, len(bodyBuf), string(bodyBuf))
} else {
log.Printf("open-meteo response status=%d bytes=%d body=%s", resp.StatusCode, len(bodyBuf), string(bodyBuf))
}
if resp.StatusCode/100 != 2 { if resp.StatusCode/100 != 2 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
var apiErr struct { var apiErr struct {
Error bool `json:"error"` Error bool `json:"error"`
Reason string `json:"reason"` Reason string `json:"reason"`
} }
if err := json.Unmarshal(body, &apiErr); err == nil && apiErr.Reason != "" { if err := json.Unmarshal(bodyBuf, &apiErr); err == nil && apiErr.Reason != "" {
return nil, fmt.Errorf("open-meteo HTTP %d: %s", resp.StatusCode, apiErr.Reason) return nil, fmt.Errorf("open-meteo HTTP %d: %s", resp.StatusCode, apiErr.Reason)
} }
if len(body) > 0 { if len(bodyBuf) > 0 {
return nil, fmt.Errorf("open-meteo HTTP %d: %s", resp.StatusCode, string(body)) return nil, fmt.Errorf("open-meteo HTTP %d: %s", resp.StatusCode, string(bodyBuf))
} }
return nil, fmt.Errorf("open-meteo HTTP %d", resp.StatusCode) return nil, fmt.Errorf("open-meteo HTTP %d", resp.StatusCode)
} }
var raw map[string]any var raw map[string]any
if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil { if err := json.Unmarshal(bodyBuf, &raw); err != nil {
return nil, err return nil, err
} }