Skip to content

Commit 62fcb77

Browse files
authored
Merge pull request #27 from oyve/copilot/implement-pet-thermal-comfort-index
Implement PET thermal comfort index
2 parents 12c486e + 678a70e commit 62fcb77

File tree

3 files changed

+500
-0
lines changed

3 files changed

+500
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ console.log(`Dew Point: ${dewPoint} K`);
135135
|-----------------------------|---------------------------------------------------------------------------|
136136
| **Heat Index** | Measure the perceived temperature based on air temperature and humidity. [🔗](https://en.wikipedia.org/wiki/Heat_index) |
137137
| **Humidex** | Calculate the humidex, a Canadian measure of perceived temperature. [🔗](https://en.wikipedia.org/wiki/Humidex) |
138+
| **PET (Physiological Equivalent Temperature)** | Calculate the perceived temperature based on air temperature, humidity, wind speed, and mean radiant temperature. Includes thermal comfort categories. [🔗](https://en.wikipedia.org/wiki/Thermal_comfort) |
138139
| **UTCI Assessment Scale** | Universal Thermal Climate Index for assessing thermal stress. [🔗](https://en.wikipedia.org/wiki/UTCI) |
139140

140141
### Scales

src/indices/PET.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { kelvinToCelcius, celciusToKelvin } from '../formulas/temperature';
2+
import { saturationVaporPressure, actualVaporPressure } from '../formulas/humidity';
3+
4+
/**
5+
* PET (Physiological Equivalent Temperature) Thermal Comfort Index
6+
*
7+
* PET is defined as the air temperature at which, in a typical indoor setting,
8+
* the heat balance of the human body is maintained with core and skin temperatures
9+
* equal to those under the conditions being assessed.
10+
*
11+
* This implementation uses a simplified approximation based on:
12+
* - Air temperature
13+
* - Relative humidity
14+
* - Wind speed
15+
* - Mean radiant temperature
16+
*
17+
* The full PET calculation involves the Munich Energy-balance Model for Individuals (MEMI),
18+
* which is computationally intensive. This implementation provides a practical approximation
19+
* suitable for most applications.
20+
*
21+
* @param temperature - Air temperature in Kelvin
22+
* @param humidity - Relative humidity (0-100%)
23+
* @param windSpeed - Wind speed in m/s
24+
* @param meanRadiantTemperature - Mean radiant temperature in Kelvin (optional, defaults to air temperature)
25+
* @returns PET in Kelvin
26+
*
27+
* @example
28+
* const pet = calculatePET(298.15, 60, 2.5, 303.15);
29+
* console.log(pet); // Returns PET in Kelvin
30+
*
31+
* @see https://en.wikipedia.org/wiki/Thermal_comfort
32+
* @see https://doi.org/10.1007/s00484-011-0453-2
33+
*/
34+
export function calculatePET(
35+
temperature: number,
36+
humidity: number,
37+
windSpeed: number,
38+
meanRadiantTemperature?: number
39+
): number {
40+
if (humidity < 0 || humidity > 100) {
41+
throw new Error("Humidity must be between 0 and 100");
42+
}
43+
if (windSpeed < 0) {
44+
throw new Error("Wind speed must be non-negative");
45+
}
46+
47+
const Ta = kelvinToCelcius(temperature); // Air temperature in °C
48+
const Tmrt = meanRadiantTemperature
49+
? kelvinToCelcius(meanRadiantTemperature)
50+
: Ta; // Mean radiant temperature in °C
51+
const RH = humidity; // Relative humidity %
52+
const v = windSpeed; // Wind speed m/s
53+
54+
// Calculate vapor pressure using existing library functions
55+
const es = saturationVaporPressure(temperature); // Pa
56+
const ea = actualVaporPressure(es, RH); // Actual vapor pressure in Pa
57+
const vp = ea / 100; // Convert to hPa for calculation
58+
59+
// Simplified PET approximation based on empirical relationships
60+
// This uses a regression-based approach derived from MEMI model outputs
61+
62+
// Base temperature effect - weighted average of air temp and MRT
63+
// MRT has stronger influence on perceived temperature
64+
let PET = 0.5 * Ta + 0.5 * Tmrt;
65+
66+
// Humidity effect - reduces comfort at higher humidity
67+
const humidityEffect = -0.006 * RH * (1 + 0.008 * Math.max(0, Ta - 20));
68+
PET += humidityEffect;
69+
70+
// Wind effect - increases heat loss
71+
// Wind effect is more pronounced at higher temperatures
72+
const windEffect = -0.4 * Math.sqrt(v) * (1 + 0.015 * Math.max(0, Ta - 15));
73+
PET += windEffect;
74+
75+
// Additional vapor pressure effect
76+
const vpEffect = -0.015 * vp * Math.max(0, Ta - 15);
77+
PET += vpEffect;
78+
79+
return celciusToKelvin(PET);
80+
}
81+
82+
/**
83+
* PET Assessment Categories
84+
*
85+
* Provides thermal perception and physiological stress level based on PET value.
86+
* These categories are based on standard PET assessment scales for central Europeans.
87+
*
88+
* @param petTemperature - PET value in Kelvin
89+
* @returns Assessment object with lower limit (°C), thermal perception, and grade of physiological stress, or null if outside range
90+
*
91+
* @example
92+
* const pet = calculatePET(298.15, 60, 2.5);
93+
* const assessment = petCategory(pet);
94+
* console.log(assessment); // { lowerLimit: 23, perception: "Slightly warm", stress: "Slight heat stress" }
95+
*
96+
* @see https://doi.org/10.1007/s004840050118
97+
*/
98+
export function petCategory(
99+
petTemperature: number
100+
): null | { lowerLimit: number; perception: string; stress: string } {
101+
const petCelsius = kelvinToCelcius(petTemperature);
102+
103+
const thresholds = [
104+
{ lowerLimit: 41, perception: "Very hot", stress: "Extreme heat stress" },
105+
{ lowerLimit: 35, perception: "Hot", stress: "Strong heat stress" },
106+
{ lowerLimit: 29, perception: "Warm", stress: "Moderate heat stress" },
107+
{ lowerLimit: 23, perception: "Slightly warm", stress: "Slight heat stress" },
108+
{ lowerLimit: 18, perception: "Comfortable", stress: "No thermal stress" },
109+
{ lowerLimit: 13, perception: "Slightly cool", stress: "Slight cold stress" },
110+
{ lowerLimit: 8, perception: "Cool", stress: "Moderate cold stress" },
111+
{ lowerLimit: 4, perception: "Cold", stress: "Strong cold stress" },
112+
{ lowerLimit: -Infinity, perception: "Very cold", stress: "Extreme cold stress" },
113+
];
114+
115+
const result = thresholds.find((t) => petCelsius >= t.lowerLimit);
116+
117+
return result === undefined ? null : result;
118+
}
119+
120+
/**
121+
* Simplified PET calculation for cases where mean radiant temperature is unknown
122+
*
123+
* This version estimates mean radiant temperature from air temperature and assumes
124+
* standard conditions. Suitable for quick assessments when detailed radiation data
125+
* is not available.
126+
*
127+
* @param temperature - Air temperature in Kelvin
128+
* @param humidity - Relative humidity (0-100%)
129+
* @param windSpeed - Wind speed in m/s
130+
* @returns PET in Kelvin
131+
*
132+
* @example
133+
* const pet = simplePET(298.15, 60, 2.5);
134+
* console.log(pet); // Returns PET in Kelvin
135+
*/
136+
export function simplePET(
137+
temperature: number,
138+
humidity: number,
139+
windSpeed: number
140+
): number {
141+
return calculatePET(temperature, humidity, windSpeed);
142+
}

0 commit comments

Comments
 (0)