3.2.1.0
Tweaked and updated the day tab background shades depending on cloud & rain. Give comfortable special treatment to avoid the green as it just looks weird.
This commit is contained in:
+101
-2
@@ -16,7 +16,7 @@ import {
|
||||
calcIndoorTempPass, calcManagedIndoorTempPass, calcShadeAirTemp,
|
||||
} from './physics.js';
|
||||
import { windCompass8, uvSplit, cloudCategory, precipPenalty, sunburnMinutes, burnLabel } from './utils.js';
|
||||
import { UTCI_ENVIRONMENTS } from './config.js';
|
||||
import { UTCI_ENVIRONMENTS, CROP_CALENDAR } from './config.js';
|
||||
|
||||
export function buildHourlyRows({ forecast, airQuality, location, vehicleType, vehicleVent, vehicleSpeed, buildingType, utciEnv }) {
|
||||
const env = UTCI_ENVIRONMENTS[utciEnv] ?? UTCI_ENVIRONMENTS.open;
|
||||
@@ -380,7 +380,7 @@ export function computeWhyFeelsLike(row, env) {
|
||||
// variant - active sub-variant key e.g. "running", "beach" (or null)
|
||||
// skinType - Fitzpatrick skin type key for UV burn time
|
||||
// ------------------------------------------------------------------------
|
||||
export function computeGlanceSummary(todayRows, profile, variant, skinType, cols = null, vehicleSpeed = 'static') {
|
||||
export function computeGlanceSummary(todayRows, profile, variant, skinType, cols = null, vehicleSpeed = 'static', weekDays = null, lat = null) {
|
||||
if (!todayRows || todayRows.length === 0) return [];
|
||||
const show = (key) => !cols || !!cols[key];
|
||||
|
||||
@@ -546,6 +546,7 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType, cols
|
||||
value: moistureLabel(soilM) ?? '-',
|
||||
alert: soilM != null && (soilM < 0.10 || soilM > 0.50),
|
||||
}] : []),
|
||||
...(weekDays ? computeCropAdvice(weekDays, lat) : []),
|
||||
...(show('pollen') && maxPollen >= 10 ? [{
|
||||
icon: '🌿',
|
||||
label: 'Grass pollen',
|
||||
@@ -722,4 +723,102 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType, cols
|
||||
alert: maxPollen >= 50,
|
||||
}] : []),
|
||||
];
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// computeCropAdvice(weekDays, lat) - Seasonal "Good to sow / harvest"
|
||||
// advice for the Farming "At a glance" panel.
|
||||
//
|
||||
// Blends the current calendar month (which crops are in their sow/harvest
|
||||
// window) with the upcoming week's weather (is the seedbed warm and
|
||||
// workable, is there a dry spell to bring grain in?). Returns 0-2
|
||||
// glance-style { icon, label, value, alert } items, appended after the
|
||||
// standard farming insights.
|
||||
//
|
||||
// Parameters:
|
||||
// weekDays - the `days` array: [{ key: 'YYYY-MM-DD', rows: [...] }, ...]
|
||||
// lat - forecast latitude; < 0 flips the UK calendar by +6 months
|
||||
// ------------------------------------------------------------------------
|
||||
export function computeCropAdvice(weekDays, lat) {
|
||||
if (!weekDays || weekDays.length === 0) return [];
|
||||
const days = weekDays.slice(0, 7).filter(d => d && d.rows && d.rows.length);
|
||||
if (days.length === 0) return [];
|
||||
|
||||
// "Now" - the month of the first available day (1-12).
|
||||
const month = parseInt((days[0].key || '').slice(5, 7), 10);
|
||||
if (!month) return [];
|
||||
|
||||
// Southern hemisphere: shift the stored UK months by +6 before testing.
|
||||
const south = lat != null && lat < 0;
|
||||
const inSeason = (months) => {
|
||||
const shifted = south ? months.map(m => ((m + 5) % 12) + 1) : months;
|
||||
return shifted.includes(month);
|
||||
};
|
||||
|
||||
// Warmth the seedbed actually reaches this week: the highest of each day's
|
||||
// peak surface soil temperature.
|
||||
let weekSoilT = null;
|
||||
for (const d of days) {
|
||||
const peak = Math.max(-Infinity, ...d.rows.map(r => r.soilT0 ?? -Infinity));
|
||||
if (peak > -Infinity && (weekSoilT == null || peak > weekSoilT)) weekSoilT = peak;
|
||||
}
|
||||
|
||||
// Latest known soil moisture - saturated ground (>=45%) is unworkable.
|
||||
let soilM = null;
|
||||
for (const d of days) {
|
||||
for (const r of d.rows) if (r.soilM != null) soilM = r.soilM;
|
||||
}
|
||||
const groundWet = soilM != null && soilM >= 0.45;
|
||||
|
||||
// Dry days this week: under 2 mm total rain and rain chance staying < 40%.
|
||||
const dryNames = [];
|
||||
for (const d of days) {
|
||||
const totalRain = d.rows.reduce((s, r) => s + (r.precip ?? 0), 0);
|
||||
const maxProb = Math.max(0, ...d.rows.map(r => r.precipProb ?? 0));
|
||||
if (totalRain < 2 && maxProb < 40) {
|
||||
const dt = new Date((d.key || '') + 'T00:00Z');
|
||||
dryNames.push(dt.toLocaleDateString('en-GB', { weekday: 'short', timeZone: 'UTC' }));
|
||||
}
|
||||
}
|
||||
const hasDrySpell = dryNames.length > 0;
|
||||
|
||||
// Full crop-name list for the glance row.
|
||||
const listCrops = (crops) => crops.map(c => c.label).join(', ');
|
||||
const dryRange = () => dryNames.length === 1
|
||||
? `dry ${dryNames[0]}`
|
||||
: `dry ${dryNames[0]}–${dryNames[dryNames.length - 1]}`;
|
||||
|
||||
const out = [];
|
||||
|
||||
// ── Good to sow ──────────────────────────────────────────────────────────
|
||||
const sowSeason = CROP_CALENDAR.filter(c => inSeason(c.sow.months));
|
||||
if (sowSeason.length) {
|
||||
const ready = sowSeason.filter(c =>
|
||||
weekSoilT != null && weekSoilT >= c.sow.minSoilT && !groundWet);
|
||||
out.push(ready.length ? {
|
||||
icon: '🌱', label: 'Good to sow', value: listCrops(ready), alert: false,
|
||||
} : {
|
||||
icon: '🌱', label: 'Good to sow',
|
||||
value: groundWet ? 'Hold off — ground too wet' : 'Hold off — soil still cold',
|
||||
alert: true,
|
||||
});
|
||||
}
|
||||
|
||||
// ── Good to harvest ──────────────────────────────────────────────────────
|
||||
const harvestSeason = CROP_CALENDAR.filter(c => inSeason(c.harvest.months));
|
||||
if (harvestSeason.length) {
|
||||
// Grain/rape/onions need a dry spell; everything else can be lifted in window.
|
||||
const ready = harvestSeason.filter(c => !c.harvest.dry || hasDrySpell);
|
||||
const dryDriven = hasDrySpell && ready.some(c => c.harvest.dry);
|
||||
out.push(ready.length ? {
|
||||
icon: '🌾', label: 'Good to harvest',
|
||||
value: dryDriven ? `${listCrops(ready)} (${dryRange()})` : listCrops(ready),
|
||||
alert: false,
|
||||
} : {
|
||||
icon: '🌾', label: 'Good to harvest',
|
||||
value: 'Hold — too wet to harvest grain', alert: true,
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user