feat: add wind speed and a same-day hive-inspection verdict to weather
Temperature and rain were already tracked, but wind matters just as much for whether it's safe/sensible to open a hive - bees get more defensive and frames are harder to handle in wind. Adds wind_speed_10m_max to the Open-Meteo request and a derived inspectionSuitable flag per day (>=15C, <=1mm rain, <=25km/h wind; null when a value is missing rather than guessing). The apiary weather card now shows a clear "good/not good for a Durchsicht today" banner plus wind alongside each day's forecast. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
1f67cd5548
commit
c4f970f071
4 files changed
+150
-29
No files matched your search
@@ -15,11 +15,23 @@ const GOOD_PRECIPITATION_THRESHOLD_MM = 15;
|
||||
const POOR_TEMP_THRESHOLD_C = 12;
|
||||
const POOR_PRECIPITATION_THRESHOLD_MM = 30;
|
||||
|
||||
// Rule-of-thumb minimums for opening a hive without unduly stressing the
|
||||
// colony or making the inspection unpleasant/risky for the beekeeper:
|
||||
// below ~14-15°C bees cluster and brood chills when the hive is opened,
|
||||
// noticeable rain and strong wind both make bees more defensive and the
|
||||
// frames harder to handle safely.
|
||||
const INSPECTION_MIN_TEMP_C = 15;
|
||||
const INSPECTION_MAX_PRECIPITATION_MM = 1;
|
||||
const INSPECTION_MAX_WIND_KMH = 25;
|
||||
|
||||
export interface DailyWeather {
|
||||
date: string;
|
||||
tempMaxC: number | null;
|
||||
tempMinC: number | null;
|
||||
precipitationMm: number | null;
|
||||
windSpeedMaxKmh: number | null;
|
||||
// null when a required value is missing (rather than a misleading guess).
|
||||
inspectionSuitable: boolean | null;
|
||||
}
|
||||
|
||||
export interface ApiaryWeather {
|
||||
@@ -41,6 +53,7 @@ interface OpenMeteoDaily {
|
||||
temperature_2m_max: (number | null)[];
|
||||
temperature_2m_min: (number | null)[];
|
||||
precipitation_sum: (number | null)[];
|
||||
wind_speed_10m_max: (number | null)[];
|
||||
}
|
||||
|
||||
interface OpenMeteoResponse {
|
||||
@@ -95,8 +108,9 @@ export class WeatherService {
|
||||
url.searchParams.set('longitude', String(longitude));
|
||||
url.searchParams.set(
|
||||
'daily',
|
||||
'temperature_2m_max,temperature_2m_min,precipitation_sum',
|
||||
'temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max',
|
||||
);
|
||||
url.searchParams.set('wind_speed_unit', 'kmh');
|
||||
url.searchParams.set('past_days', String(PAST_DAYS));
|
||||
url.searchParams.set('forecast_days', String(FORECAST_DAYS));
|
||||
url.searchParams.set('timezone', 'auto');
|
||||
@@ -116,14 +130,33 @@ export class WeatherService {
|
||||
if (!body.daily) {
|
||||
return [];
|
||||
}
|
||||
const { time, temperature_2m_max, temperature_2m_min, precipitation_sum } =
|
||||
body.daily;
|
||||
return time.map((date, index) => ({
|
||||
date,
|
||||
tempMaxC: temperature_2m_max[index] ?? null,
|
||||
tempMinC: temperature_2m_min[index] ?? null,
|
||||
precipitationMm: precipitation_sum[index] ?? null,
|
||||
}));
|
||||
const {
|
||||
time,
|
||||
temperature_2m_max,
|
||||
temperature_2m_min,
|
||||
precipitation_sum,
|
||||
wind_speed_10m_max,
|
||||
} = body.daily;
|
||||
return time.map((date, index) => {
|
||||
const tempMaxC = temperature_2m_max[index] ?? null;
|
||||
const precipitationMm = precipitation_sum[index] ?? null;
|
||||
const windSpeedMaxKmh = wind_speed_10m_max[index] ?? null;
|
||||
return {
|
||||
date,
|
||||
tempMaxC,
|
||||
tempMinC: temperature_2m_min[index] ?? null,
|
||||
precipitationMm,
|
||||
windSpeedMaxKmh,
|
||||
inspectionSuitable:
|
||||
tempMaxC === null ||
|
||||
precipitationMm === null ||
|
||||
windSpeedMaxKmh === null
|
||||
? null
|
||||
: tempMaxC >= INSPECTION_MIN_TEMP_C &&
|
||||
precipitationMm <= INSPECTION_MAX_PRECIPITATION_MM &&
|
||||
windSpeedMaxKmh <= INSPECTION_MAX_WIND_KMH,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getSeason(month: number): { season: string; inBloom: boolean } {
|
||||
|
||||
Reference in new issue
Block a user