Update to the latest AQI ranges
Is That Right
What's Changing
As I noted in the AQI Everywhere post the forum where I had sourced my current AQI calculation had indicated that it was now obsolete, and instead pointed to an updated calculation.
After walking though the update post, it appeared that the only change was in μg/m3 ranges for each AQI level. Since monitor keys of PM2.5 I had to compare the first (obsolete) set of ranges with the second (updated) ranges and update the calculation code accordingly.
Obsolete Table
Current Table
You can see that in the for the "Good" AQI rating, the AQI Number is still between 0 and 50, but the μg/m3 ranges from a low of 0.0 to a hight of 9.0 in the updated calculation. The μg/m3 used to range between 9 and 12.0. To figure out what this means, lets look at the formula being applied to the raw μg/m3 data to calculate the final AQI value & level.
Formula
AQI Calculation: $$ AQI = \frac{(AQI_{Hi}) - (AQI_{Lo})}{(Conc_{Hi}) - (Conc_{Lo})} * ((\bold{Conc_{i}}) - (Conc_{Lo})) + (AQI_{Lo}) $$
For the Good AQI Range with a raw reading of 4.5 μg/m3 Good AQI Calculation: $$ AQI = \frac{(50) - (0)}{(9.0) - (0)} * ((\bold{4.5}) - (0)) + (0) $$ Simplified Good AQI Calculation: $$ AQI = \frac{50}{9.0} * \bold{4.5} $$
The raw reading of 4.5 μg/m3 would generate an AQI score of 25. The formulate is basically mapping the raw range into the AQI range values. Good AQI has raw values from 0-9, where 0 μg/m3 = 0 AQI and 9.0 μg/m3 = 50 AQI. Since 4.5 μg/m3 is half way between 0 and 9. It's AQI is half way between 0 and 50.
What do I need to fix?
In the end, I had to update a pretty straight forward function that figures out which "AQI Range" the raw reading falls into. I just needed to map the values from the updated table into my function and I'd be current. I'd be upgrading some particulate matter ratings to higher risk levels, and then handling the slightly confusing Concentrations above 325.4 ug/m3 are still considered Hazardous and use the Hazardous breakpoints when calculating AQI
line. Some previously Unhealthy Level concentrations were now Hazardous, but now the AQI Numerical values for concentrations above 325.4 ug/m3 would overlap with those between 225.5 and 325.4 ug/m3.
Obsolete Range Calculation
1const pm25aqi = (concentration) => {
2 const _conc = parseFloat(concentration);
3 const c = (Math.floor(10 * _conc)) / 10;
4 switch (true) {
5 case (c >= 0 && c < 12.1):
6 return aqiEquation(50, 0, 12, 0, c);
7 case (c >= 12.1 && c < 35.5):
8 return aqiEquation(100, 51, 35.4, 12.1, c);
9 case (c >= 35.5 && c < 55.5):
10 return aqiEquation(150, 101, 55.4, 35.5, c);
11 case (c >= 55.5 && c < 150.5):
12 return aqiEquation(200, 151, 150.4, 55.5, c);
13 case (c >= 150.5 && c < 250.5):
14 return aqiEquation(300, 201, 250.4, 150.5, c);
15 case (c >= 250.5 && c < 350.5):
16 return aqiEquation(400, 301, 350.4, 250.5, c);
17 case (c >= 350.5 && c < 500.5):
18 return aqiEquation(500, 401, 500.4, 350.5, c);
19 default:
20 // We're in hell
21 return 666;
22 }
23}
First Pass at an update
1const pm25aqi = (concentration) => {
2 const _conc = parseFloat(concentration);
3 const c = (Math.floor(1000 * _conc)) / 1000;
4 switch (true) {
5 case (c >= 0 && c < 9):
6 return aqiEquation(50, 0, 9, 0, c);
7 case (c >= 9.1 && c < 35.4):
8 return aqiEquation(100, 51, 35.4, 9.1, c);
9 case (c >= 35.5 && c < 55.5):
10 return aqiEquation(150, 101, 55.4, 35.5, c);
11 case (c >= 55.5 && c < 125.4):
12 return aqiEquation(200, 151, 125.4, 55.5, c);
13 case (c >= 125.5 && c < 225.4):
14 return aqiEquation(300, 201, 225.4, 125.5, c);
15 case (c >= 225.5 && c < 325.4):
16 return aqiEquation(500, 301, 325.4, 225.5, c);
17 case (c >= 325.5 && c < 500.4):
18 return aqiEquation(500, 301, 325.4, 225.5, c);
19 default:
20 // We're in hell
21 return 666;
22 }
23};
I updated my function and everything was awesome, almost. I happened to glance at the visualization a few days back, and saw the screen above. I looked outside, sniffed, and became somewhat confused. within a minute the rating had updated back to 47 AQI/Green and everything was right with the world.
Except, it happened again. So there had to be a bug, in my code. Unbelievable. Of course a quick look at the lines below and I saw issue. If I had a concentration of 9, I would fall through to the default 666
value.
1case (c >= 0 && c < 9):
2 return aqiEquation(50, 0, 9, 0, c);
3 case (c >= 9.1 && c < 35.4):
There were a few other quirks I had to iron out, but I think I've got it corrected with the version below.
1const pm25aqi = (concentration) => {
2 const _conc = parseFloat(concentration);
3 const c = (Math.floor(1000 * _conc)) / 1000;
4 switch (true) {
5 case (c >= 0 && c <= 9):
6 return aqiEquation(50, 0, 9, 0, c);
7 case (c >= 9.1 && c <= 35.4):
8 return aqiEquation(100, 51, 35.4, 9.1, c);
9 case (c >= 35.5 && c <= 55.4):
10 return aqiEquation(150, 101, 55.4, 35.5, c);
11 case (c >= 55.5 && c <= 125.4):
12 return aqiEquation(200, 151, 125.4, 55.5, c);
13 case (c >= 125.5 && c <= 225.4):
14 return aqiEquation(300, 201, 225.4, 125.5, c);
15 case (c >= 225.5 && c <= 325.4):
16 return aqiEquation(500, 301, 325.4, 225.5, c);
17 case (c >= 325.5 && c <= 500.4):
18 return aqiEquation(500, 301, 500.4, 325.5, c);
19 default:
20 // We're in hell
21 return 666;
22 }
23};
Better
I'll revisit this function the future. I want to walk through it in more detail to see if it work's the way I'm expecting it to. I've also realized I need to clean up my repo. I plan to make specific ones for the each visualization approach, as well as monitor only code. It's way to hard to know what's actually being used, and what was just throw away code.
QOTD
“The dream begins with a teacher who believes in you, who tugs and pushes and leads you to the next plateau, sometimes poking you with a sharp stick called truth.” ― Dan Rather