3.0.2
Fix the Why It Feels calcs
This commit is contained in:
@@ -242,6 +242,7 @@
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
max-width: 270px; /* matches .scope-ring desktop size */
|
||||
padding-bottom: 32px; /* compensates for the extra Air temp row in the Why It Feels panel */
|
||||
}
|
||||
|
||||
/* Unified pill: icon on left, bar fills the right */
|
||||
|
||||
@@ -1607,6 +1607,16 @@
|
||||
color: #3a78a8; /* cool blue - cooling contribution */
|
||||
}
|
||||
|
||||
.delta-base {
|
||||
color: #4a3420; /* neutral — absolute air temperature, not a delta */
|
||||
}
|
||||
|
||||
.insight-row--base {
|
||||
opacity: 0.75;
|
||||
border-bottom: 1px dashed rgba(74,52,32,0.2);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* Values for "Today at a glance" */
|
||||
.insight-value {
|
||||
font-family: JetBrains Mono, monospace;
|
||||
|
||||
+9
-7
@@ -742,6 +742,7 @@ export function UTCIForecast() {
|
||||
</div>
|
||||
${(() => {
|
||||
const _wflItems = [
|
||||
{ label: 'Air temperature', icon: '🌡', value: currentRow.Ta, isBase: true },
|
||||
{ label: 'Sun and sky', icon: '☀', value: whyFeelsLike.sunAndSky },
|
||||
{ label: 'Wind', icon: '🌬', value: whyFeelsLike.wind },
|
||||
{ label: 'Humidity', icon: '💧', value: whyFeelsLike.humidity },
|
||||
@@ -752,19 +753,20 @@ export function UTCIForecast() {
|
||||
{ label: 'Precipitation', icon: '🌧', value: whyFeelsLike.precipitation }
|
||||
] : []),
|
||||
];
|
||||
const _wflMax = Math.max(..._wflItems.map(i => Math.abs(i.value)), 0.1);
|
||||
return _wflItems.map(({ label, icon, value }) => html`
|
||||
<div class="insight-row" key=${label}>
|
||||
const _wflDeltaItems = _wflItems.filter(i => !i.isBase);
|
||||
const _wflMax = Math.max(..._wflDeltaItems.map(i => Math.abs(i.value)), 0.1);
|
||||
return _wflItems.map(({ label, icon, value, isBase }) => html`
|
||||
<div class=${'insight-row' + (isBase ? ' insight-row--base' : '')} key=${label}>
|
||||
<span class="insight-icon">${icon}</span>
|
||||
<span class="insight-label">
|
||||
${label}
|
||||
<span class="insight-bar-track">
|
||||
${!isBase && html`<span class="insight-bar-track">
|
||||
<span class=${'insight-bar ' + (value >= 0 ? 'bar-pos' : 'bar-neg')}
|
||||
style=${{ width: `${Math.round(Math.abs(value) / _wflMax * 100)}%` }}></span>
|
||||
</span>
|
||||
</span>`}
|
||||
</span>
|
||||
<span class=${`insight-delta ${value >= 0 ? 'delta-pos' : 'delta-neg'}`}>
|
||||
${value >= 0 ? '+' : ''}${value.toFixed(1)}°
|
||||
<span class=${`insight-delta ${isBase ? 'delta-base' : value >= 0 ? 'delta-pos' : 'delta-neg'}`}>
|
||||
${isBase ? '' : value >= 0 ? '+' : ''}${value.toFixed(1)}°
|
||||
</span>
|
||||
</div>
|
||||
`);
|
||||
|
||||
+20
-8
@@ -330,15 +330,27 @@ export function computeWhyFeelsLike(row, env) {
|
||||
const { Ta, Tmrt, va, eh, precip, snow } = row;
|
||||
const r1 = (v) => Math.round(v * 10) / 10;
|
||||
|
||||
// Radiation - mean radiant temp above or below air temp.
|
||||
const sunAndSky = r1(Tmrt - Ta);
|
||||
// Environment-adjusted air temp (same as used in buildHourlyRows).
|
||||
const TaEnv = Ta + (env ? env.taOffset : 0);
|
||||
|
||||
// Wind - UTCI with actual wind vs calm, holding Tmrt = Ta and RH neutral.
|
||||
const ehNeutral = vaporPressureHpa(Ta, 50);
|
||||
const wind = r1(utciApprox(Ta, Ta, va, ehNeutral) - utciApprox(Ta, Ta, 0, ehNeutral));
|
||||
// Sequential UTCI isolation — all deltas are in the same UTCI-polynomial
|
||||
// space so they add up: Ta + environment + sunAndSky + wind + humidity
|
||||
// + precipitation ≈ SunSoak (utciAdj), within rounding.
|
||||
//
|
||||
// Tmrt already has env radiation scaling (dirFactor/difFactor/globFactor)
|
||||
// baked in from buildHourlyRows, so each delta automatically reflects
|
||||
// the active Solar Model without any extra work here.
|
||||
const ehNeutral = vaporPressureHpa(TaEnv, 50);
|
||||
const base = utciApprox(TaEnv, TaEnv, 0, ehNeutral); // ≈ TaEnv
|
||||
|
||||
// Humidity - actual vapour pressure vs neutral RH 50%, no wind or sun.
|
||||
const humidity = r1(utciApprox(Ta, Ta, 0, eh) - utciApprox(Ta, Ta, 0, ehNeutral));
|
||||
// Radiation: effect of actual Tmrt vs no-radiation baseline (wind=0, RH=50%).
|
||||
const sunAndSky = r1(utciApprox(TaEnv, Tmrt, 0, ehNeutral) - base);
|
||||
|
||||
// Wind: cooling effect of actual wind with solar present (RH still neutral).
|
||||
const wind = r1(utciApprox(TaEnv, Tmrt, va, ehNeutral) - utciApprox(TaEnv, Tmrt, 0, ehNeutral));
|
||||
|
||||
// Humidity: actual vapour pressure vs neutral RH 50%, with solar and wind.
|
||||
const humidity = r1(utciApprox(TaEnv, Tmrt, va, eh) - utciApprox(TaEnv, Tmrt, va, ehNeutral));
|
||||
|
||||
// Environment modifier - explicit temperature offset from the env config.
|
||||
const environment = env ? r1(env.taOffset) : 0;
|
||||
@@ -346,7 +358,7 @@ export function computeWhyFeelsLike(row, env) {
|
||||
// Precipitation soak penalty - negative or zero.
|
||||
const precipitation = precipPenalty(precip, snow, va);
|
||||
|
||||
return { sunAndSky, wind, humidity, environment, precipitation };
|
||||
return { base: r1(base), sunAndSky, wind, humidity, environment, precipitation };
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user