~pvsr/weather

weather/weather.py -rw-r--r-- 3.1 KiB View raw
db8a58a0Peter Rice Reduce temperature graph from 48 to 36 hours 8 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
from datetime import date, datetime, timedelta
from typing import Optional

from flask import Flask, render_template

from location import read_locations

app = Flask(__name__)


def forecasts(data):
    forecast_data = data["properties"]["periods"]
    for period in forecast_data:
        if period["temperatureUnit"] == "F":
            period["fahrenheit"] = period["temperature"]
            period["celsius"] = round((period["temperature"] - 32) * (5 / 9))
        elif period["temperatureUnit"] == "C":
            period["celsius"] = period["temperature"]
            period["fahrenheit"] = round(period["temperature"] * (9 / 5) + 32)

        del period["temperatureUnit"]
        del period["temperature"]
        period["kelvin"] = period["celsius"] + 273.15

    return forecast_data


@app.route("/")
def default_weather():
    return weather(None)


@app.route("/<key>")
def weather(key: Optional[str]):
    locations = read_locations()
    assert len(locations) > 0

    location = (key and locations.get(key)) or list(locations.values())[0]

    alerts = map(alert_properties, location.alerts()["features"] or [])

    return render_template(
        "weather.html",
        current_location=location,
        available_locations=locations.keys(),
        forecast=forecasts(location.forecast()),
        hourly=forecasts(location.hourly())[0:36],
        alerts=alerts,
    )


@app.template_filter('quote')
def quote(s: str):
    return f'"{s}"'


def alert_properties(feature):
    # definitely useful
    # onset: datetime
    # expires: datetime
    # event: Flash Flood Watch | ...
    # severity: Severe | ???
    # maybe useful
    # affectedZones: [zoneId]
    # areaDesc: readable affectedZones
    # sent: datetime
    # effective: datetime
    # status: Actual | ???
    # messageType: Update | ???
    # category: Met (ie meterological?) | ???
    # certainty: Possible | ???
    # urgency: Future | ???
    # response: Monitor | ???
    # headline: Watch issued at time until time by office
    # description
    # instruction
    properties = feature["properties"]
    # TODO more detail. event can be "Special Weather Statement", which isn't
    # very useful on its own
    str_props = ["event", "severity", "description"]
    date_props = ["onset", "expires"]
    return {
        **{k: properties[k] for k in str_props},
        **{k: pretty_date(properties[k]) for k in date_props},
    }


def pretty_date(d_str: str) -> str:
    try:
        d = datetime.fromisoformat(d_str)
    except ValueError as e:
        return d_str

    if d.date() == date.today():
        day = "today"
    elif d.date() == date.today() + timedelta(days=1):
        day = "tomorrow"
    elif d.date() > date.today() and d.date() - date.today() < timedelta(days=7):
        day = "%A"
    else:
        day = f"%b {ordinal(d.day)}"

    return d.strftime(f"%R {day}")


def ordinal(n: int) -> str:
    if n % 10 == 1 and n != 11:
        suff = "st"
    elif n % 10 == 2 and n != 12:
        suff = "nd"
    elif n % 10 == 3 and n != 13:
        suff = "rd"
    else:
        suff = "th"
    return f"{n}{suff}"