Skip to content

Commit

Permalink
Merge pull request #36 from TimOrme/redesign
Browse files Browse the repository at this point in the history
Redesign
TimOrme authored Apr 26, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents a099ec5 + 49b28f2 commit 5cd0d76
Showing 8 changed files with 366 additions and 130 deletions.
22 changes: 16 additions & 6 deletions aqimon/aqi_common.py
Original file line number Diff line number Diff line change
@@ -15,14 +15,24 @@ class Pollutant(Enum):
PM_10 = 1


class EpaLevels(Enum):
"""Enum of EPA levels."""

GOOD = 0
MODERATE = 1
UNHEALTHY_FOR_SENSITIVE = 2
UNHEALTHY = 3
VERY_UNHEALTHY = 4
HAZARDOUS = 5


AQI: List[Tuple[int, int]] = [
(0, 50),
(51, 100),
(101, 150),
(151, 200),
(201, 300),
(301, 400),
(401, 500),
(301, 500),
]
PM_25: List[Tuple[float, float]] = [
(0.0, 12.0),
@@ -63,11 +73,11 @@ class EpaAqi:
responsible_pollutant: Pollutant


def get_level_from_pm25(pm25: float) -> int:
def get_epa_level(epa_reading: float) -> EpaLevels:
"""Get the EPA level from a PM25 reading."""
for i, pair in enumerate(PM_25):
if pair[0] <= pm25 <= pair[1]:
return i
for i, pair in enumerate(AQI):
if pair[0] <= epa_reading <= pair[1]:
return EpaLevels(i)
raise ValueError("Invalid PM value")


17 changes: 17 additions & 0 deletions aqimon/server.py
Original file line number Diff line number Diff line change
@@ -11,6 +11,8 @@
from .database import (
get_all_reads,
get_all_epa_aqis,
get_latest_read,
get_latest_epa_aqi,
add_read,
add_epa_read,
get_averaged_reads,
@@ -215,6 +217,21 @@ async def all_data(
return all_json


@app.get("/api/latest_data")
async def latest_data(
database: databases.Database = Depends(get_database),
):
"""Retrieve most recent reads."""
latest_reads = await get_latest_read(database)
latest_epa = await get_latest_epa_aqi(database)
return {
"epa": latest_epa.epa_aqi,
"level": aqi_common.get_epa_level(latest_epa.epa_aqi).name,
"pm25": latest_reads.pm25,
"pm10": latest_reads.pm10,
}


@app.get("/api/status")
async def status(reader: ScheduledReader = Depends(get_reader)):
"""Get the system status."""
84 changes: 84 additions & 0 deletions elm/src/CurrentReads.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
module CurrentReads exposing (..)

import Bootstrap.Grid as Grid
import Bootstrap.Grid.Col as Col
import Bootstrap.Grid.Row as Row
import EpaCommon exposing (EpaLevel, getColorForLevel)
import Html exposing (Attribute, Html, a, div, h1, h5, img, text)
import Html.Attributes exposing (alt, class, href, src, style)
import Maybe
import Time exposing (..)


{-| Model for device info widget
-}
type alias CurrentReads =
{ epaLevel : Maybe EpaLevel
, lastEpaRead : Maybe Float
, lastPm10 : Maybe Float
, lastPm25 : Maybe Float
}


{-| HTML widget for displaying the device status.
Includes general status, time to next read, and exception info if applicable.
-}
getCurrentReads : CurrentReads -> Html msg
getCurrentReads currentReads =
Grid.container [ style "padding" "1em" ]
[ Grid.row [ Row.attrs [ class "align-items-center" ] ]
[ Grid.col [ Col.attrs [ style "text-align" "center", style "padding" "2em" ] ]
[ h1
[ style "color" (Maybe.map getColorForLevel currentReads.epaLevel |> Maybe.withDefault "black")
]
[ text (Maybe.map String.fromFloat currentReads.lastEpaRead |> Maybe.withDefault "NA") ]
]
]
, Grid.row [ Row.attrs [ class "align-items-center" ] ]
[ Grid.col [ Col.attrs [ style "text-align" "center", style "padding-bottom" "2em" ] ] [ a [ href "https://www.airnow.gov/aqi/aqi-basics/" ] [ text "EPA AQI Score" ] ] ]
, Grid.row [ Row.attrs [ class "align-items-center" ] ]
[ Grid.col [ Col.attrs [ style "text-align" "right" ] ] [ text "Last PM10:" ]
, Grid.col [ Col.attrs [ style "text-align" "center", style "font-weight" "bold" ] ] [ text (Maybe.map String.fromFloat currentReads.lastPm10 |> Maybe.withDefault "NA") ]
]
, Grid.row [ Row.attrs [ class "align-items-center" ] ]
[ Grid.col [ Col.attrs [ style "text-align" "right" ] ] [ text "Last PM25:" ]
, Grid.col [ Col.attrs [ style "text-align" "center", style "font-weight" "bold" ] ] [ text (Maybe.map String.fromFloat currentReads.lastPm25 |> Maybe.withDefault "NA") ]
]
]


{-| Conditionally display some block of HTML
-}
htmlIf : Html msg -> Bool -> Html msg
htmlIf el cond =
if cond then
el

else
text ""


{-| Format a unix timestamp as a string like MM/DD HH:MM:SS
-}
formatDuration : Posix -> Posix -> String
formatDuration currentTime scheduledTime =
let
durationMillis =
posixToMillis scheduledTime - posixToMillis currentTime

hour =
durationMillis // 1000 // 60 // 60

minute =
modBy 60 (durationMillis // 1000 // 60)

second =
modBy 60 (durationMillis // 1000)
in
if durationMillis > 0 then
String.padLeft 2 '0' (String.fromInt hour) ++ ":" ++ String.padLeft 2 '0' (String.fromInt minute) ++ ":" ++ String.padLeft 2 '0' (String.fromInt second)

else
"00:00:00"
27 changes: 12 additions & 15 deletions elm/src/DeviceStatus.elm
Original file line number Diff line number Diff line change
@@ -36,22 +36,19 @@ Includes general status, time to next read, and exception info if applicable.
getDeviceInfo : DeviceInfo -> Html msg
getDeviceInfo deviceInfo =
Grid.container []
[ Grid.row [ Row.attrs [ class "align-items-center" ] ]
[ Grid.col [ Col.attrs [ style "max-width" "64px", style "margin-right" "1em" ] ] [ img [ src (deviceStatusImage deviceInfo.state) ] [] ]
, Grid.col []
[ Grid.row []
[ Grid.col []
[ h5 [ style "color" (deviceStatusColor deviceInfo.state) ] [ text (deviceStatusToString deviceInfo.state) ] ]
]
, Grid.row []
[ Grid.col [] [ text (deviceInfo.lastException |> Maybe.withDefault "") ] ]
, htmlIf
(Grid.row []
[ Grid.col [] [ text ("Next read in: " ++ (Maybe.map2 formatDuration deviceInfo.currentTime deviceInfo.nextSchedule |> Maybe.withDefault "")) ] ]
)
(shouldShowTimer deviceInfo.state)
[ Grid.row [ Row.attrs [] ]
[ Grid.col [ Col.attrs [ class "text-center", style "padding" "2em" ] ] [ img [ src (deviceStatusImage deviceInfo.state) ] [] ] ]
, Grid.row []
[ Grid.col [ Col.attrs [ style "text-align" "center" ] ] [ h5 [ style "color" (deviceStatusColor deviceInfo.state) ] [ text (deviceStatusToString deviceInfo.state) ] ] ]
, Grid.row []
[ Grid.col [ Col.attrs [ style "text-align" "center" ] ] [ text (deviceInfo.lastException |> Maybe.withDefault "") ] ]
, htmlIf
(Grid.row []
[ Grid.col [ Col.attrs [ style "text-align" "center" ] ]
[ text ("Next read in: " ++ (Maybe.map2 formatDuration deviceInfo.currentTime deviceInfo.nextSchedule |> Maybe.withDefault "")) ]
]
]
)
(shouldShowTimer deviceInfo.state)
]


54 changes: 54 additions & 0 deletions elm/src/EpaCommon.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module EpaCommon exposing (..)


type EpaLevel
= Hazardous
| VeryUnhealthy
| Unhealthy
| UnhealthyForSensitive
| Moderate
| Good


getColorForLevel : EpaLevel -> String
getColorForLevel level =
case level of
Hazardous ->
"maroon"

VeryUnhealthy ->
"purple"

Unhealthy ->
"red"

UnhealthyForSensitive ->
"orange"

Moderate ->
"yellow"

Good ->
"green"


getLabelForLevel : EpaLevel -> String
getLabelForLevel level =
case level of
Hazardous ->
"Hazardous"

VeryUnhealthy ->
"Very Unhealthy"

Unhealthy ->
"Unhealthy"

UnhealthyForSensitive ->
"Unhealthy For Sensitive"

Moderate ->
"Moderate"

Good ->
"Good"
40 changes: 40 additions & 0 deletions elm/src/EpaLevel.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module EpaLevel exposing (..)

import Bootstrap.Grid as Grid
import Bootstrap.Grid.Col as Col
import Bootstrap.Grid.Row as Row
import EpaCommon exposing (..)
import Html exposing (Attribute, Html, a, div, h1, h5, img, text)
import Html.Attributes exposing (alt, class, href, src, style)


{-| HTML widget for displaying the device status.
Includes general status, time to next read, and exception info if applicable.
-}
getEpaLevel : Maybe EpaLevel -> Html msg
getEpaLevel currentLevel =
Grid.container [ class "align-middle", style "padding" "1em" ]
[ getRow Hazardous (currentLevel == Just Hazardous)
, getRow VeryUnhealthy (currentLevel == Just VeryUnhealthy)
, getRow Unhealthy (currentLevel == Just Unhealthy)
, getRow UnhealthyForSensitive (currentLevel == Just UnhealthyForSensitive)
, getRow Moderate (currentLevel == Just Moderate)
, getRow Good (currentLevel == Just Good)
]


getRow : EpaLevel -> Bool -> Html msg
getRow level isSelected =
Grid.row [ Row.attrs (List.append [ style "background-color" (getColorForLevel level) ] (selectedStyles isSelected)) ]
[ Grid.col [ Col.attrs [ style "text-align" "center", style "padding" ".25em", style "color" "white" ] ] [ text (getLabelForLevel level) ] ]


selectedStyles : Bool -> List (Attribute msg)
selectedStyles isSelected =
if isSelected then
[ style "border-radius" "1em" ]

else
[ style "margin" "0 0.25em 0 0.25em" ]
18 changes: 10 additions & 8 deletions elm/src/Graph.elm
Original file line number Diff line number Diff line change
@@ -50,13 +50,14 @@ Accepts a model of graph data, and an event that occurs on graph hover.
getReadChart : GraphReadModel -> (List (CI.One GraphReadData CI.Dot) -> msg) -> Html msg
getReadChart graphModel onHover =
C.chart
[ CA.height 300
, CA.width 1000
[ CA.height 200
, CA.width 800
, CA.margin { left = 40, top = 0, right = 20, bottom = 0 }
, CE.onMouseMove onHover (CE.getNearest CI.dots)
, CE.onMouseLeave (onHover [])
]
[ C.xLabels [ CA.moveDown 25, CA.withGrid, CA.rotate 60, CA.format formatTime ]
, C.yLabels [ CA.withGrid ]
[ C.xLabels [ CA.fontSize 10, CA.withGrid, CA.format formatTime ]
, C.yLabels [ CA.fontSize 10, CA.withGrid ]
, C.series .time
[ C.interpolated .pm25 [ CA.monotone, CA.color CA.yellow ] [ CA.circle, CA.size 3 ] |> C.named "PM2.5"
, C.interpolated .pm10 [ CA.monotone, CA.color CA.red ] [ CA.circle, CA.size 3 ] |> C.named "PM10"
@@ -88,13 +89,14 @@ Accepts a model of graph data, and an event that occurs on graph hover.
getEpaChart : GraphEpaModel -> (List (CI.One GraphEpaData CI.Dot) -> msg) -> Html msg
getEpaChart graphModel onHover =
C.chart
[ CA.height 300
, CA.width 1000
[ CA.height 200
, CA.width 800
, CA.margin { left = 40, top = 0, right = 20, bottom = 0 }
, CE.onMouseMove onHover (CE.getNearest CI.dots)
, CE.onMouseLeave (onHover [])
]
[ C.xLabels [ CA.moveDown 25, CA.withGrid, CA.rotate 60, CA.format formatTime ]
, C.yLabels [ CA.withGrid ]
[ C.xLabels [ CA.fontSize 10, CA.withGrid, CA.format formatTime ]
, C.yLabels [ CA.fontSize 10, CA.withGrid ]
, C.series .time
[ C.interpolated .epa [ CA.monotone, CA.color CA.blue ] [ CA.circle, CA.size 3 ] |> C.named "EPA"
]
234 changes: 133 additions & 101 deletions elm/src/Main.elm
Original file line number Diff line number Diff line change
@@ -2,16 +2,20 @@ module Main exposing (..)

import Bootstrap.Button as Button
import Bootstrap.ButtonGroup as ButtonGroup
import Bootstrap.Form.Select as Select
import Bootstrap.Grid as Grid
import Bootstrap.Grid.Col as Col
import Bootstrap.Grid.Row as Row
import Bootstrap.Text as Text
import Browser
import Chart.Item as CI
import CurrentReads as CR exposing (..)
import DeviceStatus as DS exposing (..)
import EpaCommon as EC exposing (..)
import EpaLevel as EL exposing (..)
import Graph as G exposing (..)
import Html exposing (Attribute, Html, div, h1, h2, h5, text)
import Html.Attributes exposing (class, style)
import Html.Attributes exposing (class, style, value)
import Http
import Json.Decode exposing (Decoder, andThen, fail, field, float, int, list, map2, map3, map4, maybe, string, succeed)
import Task
@@ -42,14 +46,18 @@ type WindowDuration
| Week


type VisibleGraph
= Epa
| ParticleMatter


{-| Lastest data point
-}
type alias LatestData =
{ readTime : Float
, pm25 : Float
, pm10 : Float
, epaTime : Float
, epa : Float
{ pm25 : Maybe Float
, pm10 : Maybe Float
, epa : Maybe Float
, epaLevel : Maybe EpaLevel
}


@@ -101,6 +109,7 @@ type alias Model =
, hoveringReads : List (CI.One ReadData CI.Dot)
, hoveringEpas : List (CI.One EpaData CI.Dot)
, errorData : ErrorData
, currentGraph : VisibleGraph
}


@@ -111,16 +120,17 @@ init _ =
( { currentTime = Nothing
, lastStatusPoll = Nothing
, readerState = { state = Idle, lastException = Nothing, currentTime = Nothing, nextSchedule = Nothing }
, lastReads = { readTime = 0, pm25 = 0.0, pm10 = 0.0, epaTime = 0, epa = 0.0 }
, lastReads = { pm25 = Nothing, pm10 = Nothing, epa = Nothing, epaLevel = Nothing }
, allReads = []
, allEpas = []
, windowDuration = Hour
, dataLoading = True
, hoveringReads = []
, hoveringEpas = []
, errorData = { hasError = False, errorTitle = "", errorMessage = "" }
, currentGraph = Epa
}
, Task.perform FetchData Time.now
, Cmd.batch [ Task.perform FetchData Time.now, Task.perform FetchLatest Time.now ]
)


@@ -149,6 +159,16 @@ getData windowDuration =
}


{-| Get latest read data.
-}
getLatest : Cmd Msg
getLatest =
Http.get
{ url = "/api/latest_data"
, expect = Http.expectJson GotLatest latestDataDecoder
}


getStatus : Cmd Msg
getStatus =
Http.get
@@ -165,13 +185,16 @@ getStatus =
-}
type Msg
= FetchData Posix
| FetchLatest Posix
| FetchStatus Posix
| GotData (Result Http.Error AllData)
| GotLatest (Result Http.Error LatestData)
| GotStatus (Result Http.Error DeviceInfoResponse)
| ChangeWindow WindowDuration
| OnReadHover (List (CI.One ReadData CI.Dot))
| OnEpaHover (List (CI.One EpaData CI.Dot))
| Tick Posix
| ChangeGraphView String


{-| Core update handler.
@@ -183,7 +206,7 @@ update msg model =
-- On Data received
case result of
Ok data ->
( { model | lastReads = getLastListItem data, allReads = data.reads, allEpas = data.epas, errorData = { hasError = False, errorTitle = "", errorMessage = "" } }, Cmd.none )
( { model | allReads = data.reads, allEpas = data.epas, errorData = { hasError = False, errorTitle = "", errorMessage = "" } }, Cmd.none )

Err e ->
( { model | errorData = { hasError = True, errorTitle = "Failed to retrieve read data", errorMessage = errorToString e } }, Cmd.none )
@@ -192,6 +215,17 @@ update msg model =
-- Data requested
( { model | currentTime = Just newTime }, getData model.windowDuration )

GotLatest result ->
case result of
Ok data ->
( { model | lastReads = data, errorData = { hasError = False, errorTitle = "", errorMessage = "" } }, Cmd.none )

Err e ->
( { model | errorData = { hasError = True, errorTitle = "Failed to retrieve latest data", errorMessage = errorToString e } }, Cmd.none )

FetchLatest newTime ->
( { model | currentTime = Just newTime }, getLatest )

GotStatus result ->
case result of
Ok data ->
@@ -241,6 +275,17 @@ update msg model =
in
( { model | currentTime = Just newTime, readerState = updatedReaderState }, cmd )

ChangeGraphView newView ->
let
newGraph =
if newView == "pm" then
ParticleMatter

else
Epa
in
( { model | currentGraph = newGraph }, Cmd.none )



-- SUBSCRIPTIONS
@@ -250,7 +295,7 @@ update msg model =
-}
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch [ Time.every 5000 FetchData, Time.every 500 Tick ]
Sub.batch [ Time.every 5000 FetchData, Time.every 5000 FetchLatest, Time.every 500 Tick ]



@@ -264,57 +309,69 @@ view : Model -> Html Msg
view model =
div []
[ Grid.container [ style "margin-bottom" ".5em" ]
[ Grid.row [ Row.attrs [ class "bg-info", style "padding" "1em" ] ]
[ Grid.row [ Row.attrs [ style "padding" "1em" ] ]
[ Grid.col []
[ h1
[ class "text-center"
]
[]
[ text "AQI Monitor" ]
]
, Grid.col [] [ DS.getDeviceInfo model.readerState ]
]
]
, htmlIf
(Grid.container []
[ Grid.row []
, Grid.row [ Row.attrs [ style "padding" "1em" ] ]
[ Grid.col [ Col.attrs [ style "background-color" "#D9D9D9", style "margin" "1em" ] ]
[ CR.getCurrentReads { epaLevel = model.lastReads.epaLevel, lastEpaRead = model.lastReads.epa, lastPm25 = model.lastReads.pm25, lastPm10 = model.lastReads.pm10 } ]
, Grid.col [ Col.attrs [ class "align-items-center", class "d-flex", style "background-color" "#D9D9D9", style "margin" "1em" ] ]
[ EL.getEpaLevel model.lastReads.epaLevel ]
, Grid.col [ Col.attrs [ style "background-color" "#D9D9D9", style "margin" "1em" ] ]
[ DS.getDeviceInfo model.readerState ]
]
, htmlIf
(Grid.row []
[ Grid.col [ Col.attrs [ class "alert", class "alert-danger" ] ]
[ h5 [] [ text model.errorData.errorTitle ]
, text model.errorData.errorMessage
]
]
]
)
model.errorData.hasError
, Grid.container []
[ Grid.row [ Row.centerMd ]
[ Grid.col [ Col.lg3 ] [ viewBigNumber model.lastReads.epa "EPA" ]
, Grid.col [ Col.lg3 ] [ viewBigNumber model.lastReads.pm25 "PM2.5" ]
, Grid.col [ Col.lg3 ] [ viewBigNumber model.lastReads.pm10 "PM10" ]
]
, Grid.row [ Row.attrs [ style "padding-top" "1em", class "justify-content-end" ] ]
[ Grid.col [ Col.lg3 ]
[ ButtonGroup.radioButtonGroup []
)
model.errorData.hasError
, Grid.row [ Row.attrs [ style "padding" "1em" ] ]
[ Grid.col []
[ h2
[]
[ text "History" ]
]
, Grid.col []
[ Select.select [ Select.onChange ChangeGraphView ]
[ Select.item [ value "epa" ] [ text "EPA AQI" ]
, Select.item [ value "pm" ] [ text "Particulate Matter" ]
]
]
, Grid.col [ Col.lg3 ]
[ ButtonGroup.radioButtonGroup [ ButtonGroup.attrs [] ]
[ getSelector All "All" model.windowDuration
, getSelector Hour "Hour" model.windowDuration
, getSelector Day "Day" model.windowDuration
, getSelector Week "Week" model.windowDuration
]
]
]
, Grid.row [] [ Grid.col [] [ h2 [] [ text "EPA AQI" ] ] ]
, Grid.row [ Row.attrs [ style "padding-top" "1em", style "padding-bottom" "3em" ], Row.centerMd ]
[ Grid.col [ Col.lg ]
[ div [ style "height" "400px" ]
[ G.getEpaChart { graphData = model.allEpas, currentHover = model.hoveringEpas } OnEpaHover ]
, htmlIf
(Grid.row [ Row.attrs [ style "padding-top" "1em", style "padding-bottom" "3em" ], Row.centerMd ]
[ Grid.col [ Col.lg ]
[ div [ style "height" "400px" ]
[ G.getEpaChart { graphData = model.allEpas, currentHover = model.hoveringEpas } OnEpaHover ]
]
]
]
, Grid.row [] [ Grid.col [] [ h2 [] [ text "PM2.5/PM10 Reads" ] ] ]
, Grid.row [ Row.attrs [ style "padding-top" "1em", style "padding-bottom" "3em" ], Row.centerMd ]
[ Grid.col [ Col.lg ]
[ div [ style "height" "400px" ]
[ G.getReadChart { graphData = model.allReads, currentHover = model.hoveringReads } OnReadHover ]
)
(model.currentGraph == Epa)
, htmlIf
(Grid.row [ Row.attrs [ style "padding-top" "1em", style "padding-bottom" "3em" ], Row.centerMd ]
[ Grid.col [ Col.lg ]
[ div [ style "height" "400px" ]
[ G.getReadChart { graphData = model.allReads, currentHover = model.hoveringReads } OnReadHover ]
]
]
]
)
(model.currentGraph == ParticleMatter)
]
]

@@ -325,42 +382,10 @@ getSelector : WindowDuration -> String -> WindowDuration -> ButtonGroup.RadioBut
getSelector windowDuration textDuration currentDuration =
ButtonGroup.radioButton
(windowDuration == currentDuration)
[ Button.outlinePrimary, Button.onClick <| ChangeWindow windowDuration ]
[ Button.outlineDark, Button.onClick <| ChangeWindow windowDuration ]
[ text textDuration ]


{-| Get a "big number" view for the headline.
-}
viewBigNumber : Float -> String -> Html Msg
viewBigNumber value numberType =
Grid.container [ style "background-clip" "border-box", style "border" "1px solid darkgray", style "padding" "0", style "border-radius" ".25rem" ]
[ Grid.row []
[ Grid.col
[ Col.textAlign Text.alignMdCenter ]
[ h1
[ style "padding" ".5em"
, style "margin" "0"
, style "color" "white"
, style "background-color" "lightblue"
]
[ text (String.fromFloat value) ]
]
]
, Grid.row []
[ Grid.col
[ Col.textAlign Text.alignMdCenter ]
[ h5
[ style "padding" ".25em"
, style "margin" "0"
, style "color" "darkblue"
, style "background-color" "lightgray"
]
[ text numberType ]
]
]
]


{-| Decoder function for JSON read data
-}
readDataDecoder : Decoder (List ReadData)
@@ -391,6 +416,15 @@ allDataDecoder =
(field "epas" epaDataDecoder)


latestDataDecoder : Decoder LatestData
latestDataDecoder =
map4 LatestData
(maybe (field "pm25" float))
(maybe (field "pm10" float))
(maybe (field "epa" float))
(maybe (field "level" epaLevelDecoder))


type alias DeviceInfoResponse =
{ readerStatus : DS.DeviceState
, readerException : Maybe String
@@ -433,37 +467,35 @@ stateDecoder =
)


{-| Given a list of read data, retrieve the last item from that list.
Useful for grabbing the most recent read from the device.
If the list is empty, a read with all 0 values is returned.
{-| JSON decoder to convert a device state to its type.
-}
epaLevelDecoder : Decoder EpaLevel
epaLevelDecoder =
string
|> andThen
(\str ->
case str of
"HAZARDOUS" ->
succeed Hazardous

"VERY_UNHEALTHY" ->
succeed VeryUnhealthy

getLastListItem [
{time = 1, epa = 1, pm25 = 1, pm 10 = 1},
{time = 2, epa = 2, pm25 = 2, pm 10 = 2},
{time = 3, epa = 3, pm25 = 3, pm 10 = 3},
] = [{time = 3, epa = 3, pm25 = 3, pm 10 = 3}]
"UNHEALTHY" ->
succeed Unhealthy

-}
getLastListItem : AllData -> LatestData
getLastListItem allData =
let
lastReads =
case List.head (List.reverse allData.reads) of
Just a ->
a
"UNHEALTHY_FOR_SENSITIVE" ->
succeed UnhealthyForSensitive

Nothing ->
{ time = 0, pm25 = 0, pm10 = 0 }
"MODERATE" ->
succeed Moderate

lastEpas =
case List.head (List.reverse allData.epas) of
Just a ->
a
"GOOD" ->
succeed Good

Nothing ->
{ time = 0, epa = 0 }
in
{ readTime = lastReads.time, pm25 = lastReads.pm25, pm10 = lastReads.pm10, epaTime = lastEpas.time, epa = lastEpas.epa }
_ ->
fail "Invalid Epa Level"
)


{-| Convert HTTP error to a string.

0 comments on commit 5cd0d76

Please sign in to comment.