One possibility is to use a recording rule for the expensive repeated query.
If I rewrite avg_over_time(ambient_pm25_env{instance=~"$room.*"}[$aqi_interval]) to X, I get:
((50 - 0) / (12 - 0) * ((X <= 12) - 0) + 0) or
((100 - 51) / (35.4 - 12.1) * ((X > 12 and X <= 35.4) - 12.1) + 51) or
((150 - 101) / (55.4 - 35.5) * ((X > 35.4 and X <= 55.4) - 35.5) + 101) or
((200 - 151) / (150.4 - 55.5) * ((X > 55.4 and X <= 150.4) - 55.5) + 151) or
((300 - 201) / (250.4 - 150.5) * ((X > 150.4 and X <= 250.4) - 150.5) + 201) or
((400 - 301) / (350.4 - 250.5) * ((X > 250.4 and X <= 350.4) - 250.5) + 301) or
((500 - 401) / (500.4 - 350.5) * ((X > 350.4 and X <= 500.4) - 350.5) + 401) or
clamp_max(X, 600)
I guess you're trying to apply different scaling for different ranges of X:
- if X is between 0 and 12 (or negative) then rescale to 0 to 50
- if X is between 12 and 35.4 then rescale to 50(?) to 100
- if X is between 35.4 and 55.4 then rescale to 100(?) to 150
etc (except there seem to be some small discontinuities at the boundaries, e.g. 12 versus 12.1, 50 versus 51)
"A or B" will suppress elements in the B vector where the A vector has a value (i.e. with a matching label set). That means it's unnecessary to test the lower bounds, and I think your expression could simplify to something like this:
(X <= 12) * k1 + o1 or
(X <= 35.4) * k2 + o2 or
(X <= 55.4) * k3 + o3 or
(X <= 150.4) * k4 + o4 or
(X <= 250.4) * k5 + o5 or
(X <= 350.4) * k6 + o6 or
(X <= 500.4) * k7 + o7 or
clamp_max(X, 600)
That would roughly halve the number of the subexpressions X.