Skip to content

Commit

Permalink
Merge pull request #7 from cander67/feat-new-docs
Browse files Browse the repository at this point in the history
Update HTML output
  • Loading branch information
cander67 committed Feb 28, 2024
2 parents e513c51 + 8dc41fb commit 763f785
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 52 deletions.
18 changes: 8 additions & 10 deletions function_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,14 @@ def cron(skiForecastTimer: func.TimerRequest) -> None:
html += "</table>"
html += f"<h3>Updated: {local_time.strftime('%Y-%m-%d %H:%M')} (PT)</h3>\n"
html += '<section id="notes">\n<h3>NOTES</h3>\n'
html += "<dl>\n<dt>- Data compiled from <a href='https://www.noaa.gov/'>NOAA</a> and scored according to subjective criteria for what makes a great ski day</dt>\n"
html += '<dt>- Hover over table cells for more data</dt>\n'
html += "<dt>- STATUS: <span class='color-1'>RED</span> = Don't Bother | <span class='color-2'>YELLOW</span> = Maybe | <span class='color-3'>GREEN</span> = Shred on!</dt>\n"
html += '<dt>- MIX: Rain/Snow mixture; forecast snowfall amount reported</dt>\n'
html += '<dt>- Trace = < 0.1in forecast precip</dt>\n'
html += '<dt>- SLVL: Snow level; min & max for 24 hrs (6am to 6am) starting on the forecast date</dt>\n'
html += '<dt>- AM|PM|ON: avg temp, morning = 6am - 12pm | afternoon = 12pm - 6pm | overnight = 6pm - 6am</dt>\n'
html += '<dt>- MIN|MAX: min & max temp for 24 hrs (6am to 6am) starting on the forecast date</dt>\n'
html += '<dt>- Questions? Comments? Suggestions? See email link below.</dt>\n'
html += '</dl>\n'
html += '<p>\nHover over table cells for more data.\n</p>\n'
html += '<p>\nKey:\n</p>\n'
html += '<ul>\n<li><span class="color-3"><b>GREEN</b></span> = Shred on!</li>\n<li><span class="color-2"><b>YELLOW</b></span> = Meh</li>\n<li><span class="color-1"><b>RED</b></span> = Don&#39;t Bother!</li>\n</ul>\n'
html += '<p>\nAbbreviations:\n</p>\n'
html += '<ul>\n<li><b>MIX:</b> Rain/Snow mixture; forecast snowfall amount reported</li>\n<li><b>Trace</b> &#8804; 0.1 in forecast precipitation</li>\n<li><b>SLVL:</b> Snow level; min &amp; max for 24 hours (6am&#8211;6am) starting on the forecast date</li>\n<li><b>AM|PM|ON:</b> avg temp, morning = 6am&#8211;12pm | afternoon = 12pm&#8211;6pm | overnight = 6pm&#8211;6am</li>\n<li><b>MIN|MAX:</b> min &amp; max temp for 24 hours (6am&#8211;6am) starting on the forecast date</li>\n</ul>\n'
html += '<p>\nRead the <a href="https://skiforecast.z5.web.core.windows.net/pages/doc.html">docs.</a>\n</p>\n'
html += '<p>\nData compiled from <a href="https://www.noaa.gov/">NOAA.</a>\n</p>\n'
html += '<p>\nQuestions? Comments? Suggestions? Send an <a href="mailto:info@digitalglissade.com">email.</a>\n</p>\n'
html += '<footer>\n'
html += '<div class="footer-content">\n'
html += '<p><a href="mailto:info@digitalglissade.com">info@digitalglissade.com</a></p>\n'
Expand Down
21 changes: 21 additions & 0 deletions license.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Cyrus Anderson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
53 changes: 53 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Ski Weather Outlook
Lift tickets are expensive. Ski area parking lots fill up. Day pass limits... There are plenty of reasons to choose wisely when planning a ski weekend. This app helps you plan by providing a dashboard view of NOAA weather forecasts for popular ski areas located in the Cascade Mountains of Washington State. The app updates once per day and summarizes precipitation, snow levels, temperature and wind data for each day of the upcoming week.

Access the [Ski Weather Outlook](https://skiforecast.z5.web.core.windows.net/) app.

### Version 1.0.0 (Released: February 24, 2024)

## Getting Started
Clone the repository from the terminal using the following command: `git clone https://github.com/cander67/skiforecast.git`

### Prerequisites
Local development can be accomplished by creating a virtual environment using Python 3.11 and installing the necessary dependencies as described in the next section.

The following prerequisites must be met for cloud deployment:
- Azure Developer CLI
- Azure Function App
- Azure Function App Registration and Service Principal
- Azure Storage Account, standard general-purpose v2 account
- Microsoft Entra security group
- Azure RBAC assignments
- Properly configured environment variables
- `AZURE_CLIENT_ID`
- `AZURE_TENANT_ID`
- `AZURE_CLIENT_SECRET`
- User-Agent Header for API requests

### Installing
- Install [Azure Developer CLI](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/overview)
- Install dependencies from the terminal using the following command: `pip install -r requirements.txt`

## Deployment
This app is setup for [continous deployment](https://learn.microsoft.com/en-us/azure/azure-functions/functions-continuous-deployment) to [Azure Functions](https://azure.microsoft.com/en-us/products/functions) using [GitHub Actions](https://docs.github.com/en/actions).

## Built With
- [Azure Functions](https://azure.microsoft.com/en-us/products/functions)
- [Azure Static Website Hosing](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website)

## Contributing
Contributions and suggestions are welcome and may be submitted by forking this project and submitting a pull request through GitHub.

## Changelog

### Version 1.0.0 (Released: February 24, 2024)
- Initial release

## Authors
Written by Cyrus Anderson.

## License
This project is licensed under the MIT License - see the LICENSE file for details.

## Acknowledements
Thanks to varunr89 for brainstorming and motivation.
40 changes: 17 additions & 23 deletions test/test_forecast_proc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import os
import json
from datetime import datetime
from datetime import datetime, time
import pytz
import bs4 as BeautifulSoup
import utils as utils

#now = datetime.now(pytz.UTC)
now = datetime(2024, 2, 23, 13, 8, 0, 0, pytz.UTC)
local_time = now.astimezone(pytz.timezone('US/Pacific'))

# Set datetime and timezone
now = datetime.now().date()
process_time = time(13, 8, 0, 0)
tz = pytz.timezone('UTC')
simulated_time = datetime.combine(now, process_time)
simulated_time = tz.localize(simulated_time)
local_time = simulated_time.astimezone(pytz.timezone('US/Pacific'))

# Define parameters
locations = {
Expand Down Expand Up @@ -68,7 +71,7 @@
f.close()

# Instantiate TableData object
setup = utils.TableData(now, file, time_periods, properties)
setup = utils.TableData(simulated_time, file, time_periods, properties)
print(f'TABLE DATA: {setup}\n')

# Parse forecast data
Expand Down Expand Up @@ -100,24 +103,17 @@
# Create table row
try:
row = setup.create_row(table_data)
# Append row to table
#table.append_row(row)
#print(f'ROW:\n{json.dumps(row, sort_keys=False, indent=4)}\n')
except Exception as e:
print(f'ERROR CREATING TABLE ROW, {file}: {e}')

table.append_row(row)

#print(f'TABLE: {table.get_table()}\n')
t = table.get_table()

#Assign columns and rows
columns = t['columns']
rows = t['rows']

#print(f'COLUMNS:\n{columns}\n')
#print(f'ROWS:\n{rows}\n')

# Get dates
day0 = datetime.strptime(columns[1][1], '%Y-%m-%d')
day6 = datetime.strptime(columns[7][1], '%Y-%m-%d')
Expand Down Expand Up @@ -171,16 +167,14 @@
html += "</table>"
html += f"<h3>Updated: {local_time.strftime('%Y-%m-%d %H:%M')} (PT)</h3>\n"
html += '<section id="notes">\n<h3>NOTES</h3>\n'
html += "<dl>\n<dt>- Data compiled from <a href='https://www.noaa.gov/'>NOAA</a> and scored according to subjective criteria for what makes a great ski day</dt>\n"
html += '<dt>- Hover over table cells for more data</dt>\n'
html += "<dt>- STATUS: <span class='color-1'>RED</span> = Don't Bother | <span class='color-2'>YELLOW</span> = Maybe | <span class='color-3'>GREEN</span> = Shred on!</dt>\n"
html += '<dt>- MIX: Rain/Snow mixture; forecast snowfall amount reported</dt>\n'
html += '<dt>- Trace = < 0.1in forecast precip</dt>\n'
html += '<dt>- SLVL: Snow level; min & max for 24 hrs (6am to 6am) starting on the forecast date</dt>\n'
html += '<dt>- AM|PM|ON: avg temp, morning = 6am - 12pm | afternoon = 12pm - 6pm | overnight = 6pm - 6am</dt>\n'
html += '<dt>- MIN|MAX: min & max temp for 24 hrs (6am to 6am) starting on the forecast date</dt>\n'
html += '<dt>- Questions? Comments? Suggestions? See email link below.</dt>\n'
html += '</dl>\n'
html += '<p>\nHover over table cells for more data.\n</p>\n'
html += '<p>\nKey:\n</p>\n'
html += '<ul>\n<li><span class="color-3"><b>GREEN</b></span> = Shred on!</li>\n<li><span class="color-2"><b>YELLOW</b></span> = Meh</li>\n<li><span class="color-1"><b>RED</b></span> = Don&#39;t Bother!</li>\n</ul>\n'
html += '<p>\nAbbreviations:\n</p>\n'
html += '<ul>\n<li><b>MIX:</b> Rain/Snow mixture; forecast snowfall amount reported</li>\n<li><b>Trace</b> &#8804; 0.1 in forecast precipitation</li>\n<li><b>SLVL:</b> Snow level; min &amp; max for 24 hours (6am&#8211;6am) starting on the forecast date</li>\n<li><b>AM|PM|ON:</b> avg temp, morning = 6am&#8211;12pm | afternoon = 12pm&#8211;6pm | overnight = 6pm&#8211;6am</li>\n<li><b>MIN|MAX:</b> min &amp; max temp for 24 hours (6am&#8211;6am) starting on the forecast date</li>\n</ul>\n'
html += '<p>\nRead the <a href="https://skiforecast.z5.web.core.windows.net/pages/doc.html">docs.</a>\n</p>\n'
html += '<p>\nData compiled from <a href="https://www.noaa.gov/">NOAA.</a>\n</p>\n'
html += '<p>\nQuestions? Comments? Suggestions? Send an <a href="mailto:info@digitalglissade.com">email.</a>\n</p>\n'
html += '<footer>\n'
html += '<div class="footer-content">\n'
html += '<p><a href="mailto:info@digitalglissade.com">info@digitalglissade.com</a></p>\n'
Expand Down
94 changes: 75 additions & 19 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,8 @@ def create_row(self, table_data):
row (list) : []
'''

#print(f'TABLE DATA: {table_data}')

now = self._time
location = self._location
data = table_data[location]
Expand Down Expand Up @@ -1175,39 +1177,86 @@ def create_row(self, table_data):
precip_range = []
sky_cover = None
wind_descr = None
weather = None
prob_precip = None
hi = None
lo = None
try:
# Precipitation
try:
weather = list(day_data['weather']['data'])
prob_precip = day_data['probabilityOfPrecipitation']['data']['avg']
lo = day_data['quantitativePrecipitation']['data']['sum']
hi = day_data['snowfallAmount']['data']['sum']
except:
prob_precip = day_data['probabilityOfPrecipitation']['data']['avg']
lo = day_data['quantitativePrecipitation']['data']['sum']
hi = day_data['snowfallAmount']['data']['sum']
if hi > 0 and lo > 0:
except Exception as e:
print(f'EXCEPT: {day}, {e}')
dt_str = dt.strftime('%Y-%m-%dT06:00:00')
try:
prob_precip = day_data['probabilityOfPrecipitation']['data']['avg']
except:
prob_precip = 0
try:
lo = day_data['quantitativePrecipitation']['data']['sum']
except:
lo = 0
data['predictions'][day]['time_period']['24h']['status']['quantitativePrecipitation'] = 2
data['predictions'][day]['time_period']['24h']['data']['quantitativePrecipitation'] = {"units": "in", "data": {'sum': 0}}
try:
hi = day_data['snowfallAmount']['data']['sum']
except:
hi = 0
data['predictions'][day]['time_period']['24h']['status']['snowfallAmount'] = 2
data['predictions'][day]['time_period']['24h']['data']['snowfallAmount'] = {"units": "in", "data": {'sum': 0}}

if hi == 0 and lo == 0:
weather = [(dt_str, [[None, None, None]])]
data['predictions'][day]['time_period']['24h']['status']['weather'] = 2
snow = False
rain = False
if hi == 0 and lo > 0:
if data['predictions'][day]['time_period']['24h']['data']['temperature']['data']['max'][1] > 32:
weather = [(dt, [['snow'], ['rain']])]
data['predictions'][day]['time_period']['24h']['status']['weather'] = 1
weather = [(dt_str, [['rain']])]
rain = True
if data['predictions'][day]['time_period']['24h']['data']['temperature']['data']['max'][1] <= 32:
weather = [(dt, [['snow']])]
if hi == 0 and lo > 0:
weather = [(dt, [['rain']])]
data['predictions'][day]['time_period']['24h']['status']['weather'] = 3
weather = [(dt_str, [['snow']])]
snow = True
if hi > 0 and lo == 0:
weather = [(dt, [['snow']])]
if data['predictions'][day]['time_period']['24h']['data']['temperature']['data']['max'][1] > 32:
data['predictions'][day]['time_period']['24h']['status']['weather'] = 2
weather = [(dt_str, [['snow'], ['rain']])]
snow = True
rain = True
if data['predictions'][day]['time_period']['24h']['data']['temperature']['data']['max'][1] <= 32:
data['predictions'][day]['time_period']['24h']['status']['weather'] = 3
data['predictions'][day]['time_period']['24h']['status']['snowfallAmount'] = 3
weather = [(dt_str, [['snow']])]
snow = True

if (len(weather) == 1 and weather[0][1] == [[None, None, None]]) or ((prob_precip == None) or (lo == None) or (hi == None)):
precip_string = 'NONE'
if (len(weather) == 1 and weather[0][1] == [[None, None, None]]) and (prob_precip < 10):
if (len(weather) == 1 and weather[0][1] == [[None, None, None]]) and (prob_precip <= 10):
precip_string = 'NONE'
if (len(weather) == 1 and weather[0][1] == [[None, None, None]]) and (prob_precip > 10):
max_temp = data['predictions'][day]['time_period']['24h']['data']['temperature']['data']['max']
if max_temp[1] <= 32:
precip_amt = day_data['snowfallAmount']['data']['sum']
precip_string = f'SNOW: {precip_amt:.1f}in'
if max_temp[1] > 32:
precip_amt = day_data['quantitativePrecipitation']['data']['sum']
precip_string = f'RAIN: {precip_amt:.1f}in'
try:
if (len(weather) == 1 and weather[0][1] == [[None, None, None]]) and (prob_precip > 10):
max_temp = data['predictions'][day]['time_period']['24h']['data']['temperature']['data']['max']
if max_temp[1] <= 32:
precip_amt = day_data['snowfallAmount']['data']['sum']
if precip_amt >= 0.1:
precip_string = f'SNOW: {precip_amt:.1f}in'
if precip_amt < 0.1:
precip_string = 'SNOW: trace'
if max_temp[1] > 32:
precip_amt = day_data['quantitativePrecipitation']['data']['sum']
if precip_amt >= 0.1:
precip_string = f'RAIN: {precip_amt:.1f}in'
if precip_amt < 0.1:
precip_string = 'RAIN: trace'
data['predictions'][day]['time_period']['24h']['status']['weather'] = 1
except Exception as e:
print(f'INNER EXCEPT: {day}, {e}')
print(f"output: {data['predictions'][day]['time_period']['24h']['data']}")

if (len(weather) >= 1 and weather[0][1] != [[None, None, None]]) and prob_precip != None:
for i in range(len(weather)):
Expand Down Expand Up @@ -1269,6 +1318,12 @@ def create_row(self, table_data):
if snow == False and rain == False:
precip_string = f'NONE'

reference_status = data['predictions'][day]['time_period']['24h']['status']['overall']
for property in data['predictions'][day]['time_period']['24h']['status'].keys():
if data["predictions"][day]["time_period"]["24h"]["status"][property] < reference_status:
reference_status = data["predictions"][day]["time_period"]["24h"]["status"][property]
data['predictions'][day]['time_period']['24h']['status']['overall'] = reference_status

precipitation = f'{precip_string}, {prob_precip:.0f}%'

# Snow Level
Expand Down Expand Up @@ -1409,6 +1464,7 @@ def create_row(self, table_data):

except Exception as e:
logging.info(f'\n\nEXCEPT: {location}, {day}, {e}\n\n')
print(f'\n\nEXCEPT: {location}, {day}, {e}\n\n')
pass

self._row = row
Expand Down

0 comments on commit 763f785

Please sign in to comment.