Compare commits

..

2 Commits

Author SHA1 Message Date
93e6485ddf moving API route to separate file 2026-01-11 18:29:00 -07:00
7b5a49979e a terrible but somewhat functional state 2026-01-11 17:50:32 -07:00
13 changed files with 9828 additions and 27 deletions

5008
data/2026-01-10/daily.json Normal file

File diff suppressed because it is too large Load Diff

4265
data/2026-01-10/hourly.json Normal file

File diff suppressed because it is too large Load Diff

319
data/2026-01-10/weekly.json Normal file
View File

@@ -0,0 +1,319 @@
{
"@context": [
"https://geojson.org/geojson-ld/geojson-context.jsonld",
{
"@version": "1.1",
"wx": "https://api.weather.gov/ontology#",
"geo": "http://www.opengis.net/ont/geosparql#",
"unit": "http://codes.wmo.int/common/unit/",
"@vocab": "https://api.weather.gov/ontology#"
}
],
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-111.6091,
40.1254
],
[
-111.6126,
40.1473
],
[
-111.6411,
40.1446
],
[
-111.6376,
40.1227
],
[
-111.6091,
40.1254
]
]
]
},
"properties": {
"units": "us",
"forecastGenerator": "BaselineForecastGenerator",
"generatedAt": "2026-01-11T01:15:56+00:00",
"updateTime": "2026-01-10T21:04:44+00:00",
"validTimes": "2026-01-10T15:00:00+00:00/P7DT10H",
"elevation": {
"unitCode": "wmoUnit:m",
"value": 1399.9464
},
"periods": [
{
"number": 1,
"name": "Tonight",
"startTime": "2026-01-10T18:00:00-07:00",
"endTime": "2026-01-11T06:00:00-07:00",
"isDaytime": false,
"temperature": 21,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "SE",
"icon": "https://api.weather.gov/icons/land/night/few?size=medium",
"shortForecast": "Mostly Clear",
"detailedForecast": "Mostly clear, with a low around 21. Southeast wind around 2 mph."
},
{
"number": 2,
"name": "Sunday",
"startTime": "2026-01-11T06:00:00-07:00",
"endTime": "2026-01-11T18:00:00-07:00",
"isDaytime": true,
"temperature": 43,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "S",
"icon": "https://api.weather.gov/icons/land/day/sct?size=medium",
"shortForecast": "Mostly Sunny",
"detailedForecast": "Mostly sunny. High near 43, with temperatures falling to around 39 in the afternoon. South wind around 2 mph."
},
{
"number": 3,
"name": "Sunday Night",
"startTime": "2026-01-11T18:00:00-07:00",
"endTime": "2026-01-12T06:00:00-07:00",
"isDaytime": false,
"temperature": 22,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "E",
"icon": "https://api.weather.gov/icons/land/night/few/haze?size=medium",
"shortForecast": "Mostly Clear then Haze",
"detailedForecast": "Haze after 5am. Mostly clear. Low around 22, with temperatures rising to around 24 overnight. East wind around 2 mph."
},
{
"number": 4,
"name": "Monday",
"startTime": "2026-01-12T06:00:00-07:00",
"endTime": "2026-01-12T18:00:00-07:00",
"isDaytime": true,
"temperature": 45,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "NW",
"icon": "https://api.weather.gov/icons/land/day/haze?size=medium",
"shortForecast": "Haze",
"detailedForecast": "Haze. Sunny, with a high near 45. Northwest wind around 2 mph."
},
{
"number": 5,
"name": "Monday Night",
"startTime": "2026-01-12T18:00:00-07:00",
"endTime": "2026-01-13T06:00:00-07:00",
"isDaytime": false,
"temperature": 24,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "ENE",
"icon": "https://api.weather.gov/icons/land/night/haze?size=medium",
"shortForecast": "Haze",
"detailedForecast": "Haze. Mostly clear, with a low around 24. East northeast wind around 2 mph."
},
{
"number": 6,
"name": "Tuesday",
"startTime": "2026-01-13T06:00:00-07:00",
"endTime": "2026-01-13T18:00:00-07:00",
"isDaytime": true,
"temperature": 40,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "WNW",
"icon": "https://api.weather.gov/icons/land/day/haze?size=medium",
"shortForecast": "Haze",
"detailedForecast": "Haze. Mostly sunny, with a high near 40."
},
{
"number": 7,
"name": "Tuesday Night",
"startTime": "2026-01-13T18:00:00-07:00",
"endTime": "2026-01-14T06:00:00-07:00",
"isDaytime": false,
"temperature": 22,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "E",
"icon": "https://api.weather.gov/icons/land/night/haze?size=medium",
"shortForecast": "Haze",
"detailedForecast": "Haze. Partly cloudy, with a low around 22."
},
{
"number": 8,
"name": "Wednesday",
"startTime": "2026-01-14T06:00:00-07:00",
"endTime": "2026-01-14T18:00:00-07:00",
"isDaytime": true,
"temperature": 42,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "WNW",
"icon": "https://api.weather.gov/icons/land/day/haze?size=medium",
"shortForecast": "Haze",
"detailedForecast": "Haze. Sunny, with a high near 42."
},
{
"number": 9,
"name": "Wednesday Night",
"startTime": "2026-01-14T18:00:00-07:00",
"endTime": "2026-01-15T06:00:00-07:00",
"isDaytime": false,
"temperature": 24,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "ESE",
"icon": "https://api.weather.gov/icons/land/night/haze?size=medium",
"shortForecast": "Haze",
"detailedForecast": "Haze. Clear, with a low around 24."
},
{
"number": 10,
"name": "Thursday",
"startTime": "2026-01-15T06:00:00-07:00",
"endTime": "2026-01-15T18:00:00-07:00",
"isDaytime": true,
"temperature": 40,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "WNW",
"icon": "https://api.weather.gov/icons/land/day/haze?size=medium",
"shortForecast": "Haze",
"detailedForecast": "Haze. Sunny, with a high near 40."
},
{
"number": 11,
"name": "Thursday Night",
"startTime": "2026-01-15T18:00:00-07:00",
"endTime": "2026-01-16T06:00:00-07:00",
"isDaytime": false,
"temperature": 18,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "E",
"icon": "https://api.weather.gov/icons/land/night/haze?size=medium",
"shortForecast": "Haze",
"detailedForecast": "Haze. Clear, with a low around 18."
},
{
"number": 12,
"name": "Friday",
"startTime": "2026-01-16T06:00:00-07:00",
"endTime": "2026-01-16T18:00:00-07:00",
"isDaytime": true,
"temperature": 40,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "N",
"icon": "https://api.weather.gov/icons/land/day/haze?size=medium",
"shortForecast": "Haze",
"detailedForecast": "Haze. Sunny, with a high near 40."
},
{
"number": 13,
"name": "Friday Night",
"startTime": "2026-01-16T18:00:00-07:00",
"endTime": "2026-01-17T06:00:00-07:00",
"isDaytime": false,
"temperature": 22,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "ENE",
"icon": "https://api.weather.gov/icons/land/night/haze/few?size=medium",
"shortForecast": "Haze then Mostly Clear",
"detailedForecast": "Haze before 11pm. Mostly clear, with a low around 22."
},
{
"number": 14,
"name": "Saturday",
"startTime": "2026-01-17T06:00:00-07:00",
"endTime": "2026-01-17T18:00:00-07:00",
"isDaytime": true,
"temperature": 45,
"temperatureUnit": "F",
"temperatureTrend": null,
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 0
},
"windSpeed": "2 mph",
"windDirection": "SSW",
"icon": "https://api.weather.gov/icons/land/day/few?size=medium",
"shortForecast": "Sunny",
"detailedForecast": "Sunny, with a high near 45."
}
]
}
}

View File

@@ -19,10 +19,6 @@ def create_app(test_config=None):
except OSError:
pass
@app.route('/hello')
def hello():
return "'Ello, Wurld!"
from . import db
db.init_app(app)
@@ -30,4 +26,8 @@ def create_app(test_config=None):
app.register_blueprint(weather.bp)
app.add_url_rule('/', endpoint='index')
from . import api
app.register_blueprint(api.bp)
app.add_url_rule('/api', endpoint='api')
return app

22
weather/api.py Normal file
View File

@@ -0,0 +1,22 @@
from flask import (
Blueprint
)
from werkzeug.exceptions import abort
from weather.db import get_db
bp = Blueprint('api', __name__)
@bp.route('/api')
def api():
db = get_db()
latest_period = dict(db.execute(
'SELECT `id` FROM `reports` WHERE `type` = "hourly" ORDER BY `reported_at` DESC'
).fetchone())
current_conditions = dict(db.execute(
f"SELECT * FROM `periods` WHERE `report_id` = {latest_period['id']} LIMIT 1"
).fetchone())
return current_conditions

View File

@@ -5,7 +5,6 @@ CREATE TABLE "periods" (
"id" Integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"report_id" Integer NOT NULL,
"period_number" Integer NOT NULL,
"name" Text,
"start_time" DateTime NOT NULL,
"end_time" DateTime NOT NULL,
"is_daytime" Integer NOT NULL,

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@@ -0,0 +1 @@
Image by <a href="https://pixabay.com/users/couleur-1195798/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=5459972">Couleur</a> from <a href="https://pixabay.com//?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=5459972">Pixabay</a>

View File

@@ -1,20 +1,97 @@
@font-face {
font-family: "Abel";
src: url(Abel-Regular.woff2) format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
#clock {
font-size: 2em;
padding-top: 1em;
text-align: center;
}
.font-abel {
font-family: "Abel", sans-serif;
.frosted {
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
background-color: rgba(78, 86, 106, 0.75);
border: 1px solid rgba(255, 255, 255, 0.125);
}
.antialiased {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
.page-container {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1.4fr 0.6fr;
gap: 2em 0em;
grid-auto-flow: row;
grid-template-areas:
"currentForecast"
"hourlyReport"
"weeklyReport";
max-width: 72em;
margin: 0 auto;
padding: 1.5em 0 2.5em;
}
.min-h-screen {
min-height: 100vh;
.currentForecast {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr;
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas:
"forecast secondaryInfo";
grid-area: currentForecast;
}
.forecast {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: repeat(3, max-content);
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas:
"shortDescription"
"longDescription"
"currentTemp"
"waterConditions";
grid-area: forecast;
}
.shortDescription {
font-size: 3em;
grid-area: shortDescription;
}
.longDescription {
font-size: 1.35em;
grid-area: longDescription;
}
.currentTemp { grid-area: currentTemp; }
.currentTemp > .temperature { font-size: 6em; }
.currentTemp > .unit { font-size: 2em; }
.waterConditions { grid-area: waterConditions; }
.secondaryInfo {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
gap: 2em 0;
grid-auto-flow: row;
grid-template-areas:
"windContainer"
"solarClock";
grid-area: secondaryInfo;
}
.windContainer { grid-area: windContainer; }
.solarClock { grid-area: solarClock; }
.hourlyReport { grid-area: hourlyReport; }
.weeklyReport {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr;
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas:
". . . . . .";
grid-area: weeklyReport;
}

View File

@@ -0,0 +1,36 @@
@font-face {
font-family: "Abel";
src: url(Abel-Regular.woff2) format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
.font-abel { font-family: "Abel", sans-serif; }
.antialiased {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.min-h-screen { min-height: 100vh; }
.grid { display: grid; }
.grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.grid-cols-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
.gap-x-4 { column-gap: 0.5rem; }
.flex { display: flex; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.justify-center { justify-content: center; }
.p-4 { padding: 1rem; }
.my-6 { margin-top: 1.25rem; margin-bottom: 1.25rem; }

View File

@@ -11,18 +11,24 @@
<!-- CSS -->
<link href="{{ url_for('static', filename='reset.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='tailwind.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet" media="screen">
<!-- JS that must be executed before the document is loaded -->
</head>
<body class="font-abel antialiased">
<div id="app" class="min-h-screen">
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
<div id="app" class="min-h-screen" style="background-image: url({{ url_for('static', filename=condition_image) }}); background-size: cover; background-position: center;">
<div id="clock">0:00:00 AM</div>
{% block content %}{% endblock %}
</div>
<script>
let clock = document.getElementById('clock');
setInterval(() => {
clock.innerText = new Date().toLocaleTimeString();
}, 1000)
</script>
</body>
</html>

View File

@@ -1,7 +1,56 @@
{% extends 'base.html' %}
{% block content %}
<div class="page-container">
<div class="currentForecast">
<div class="forecast">
<div class="shortDescription">{{ current_conditions['short_forecast'] }}</div>
<div class="longDescription">{{ current_conditions['detailed_forecast'] }}</div>
<div class="currentTemp">
<span class="temperature">{{ current_conditions['temperature'] }}</span><span class="unit">°{{ current_conditions['temperature_unit'] }}</span>
</div>
<div class="waterConditions">{{ current_conditions['precipitation_probability'] }}% percip | {{ current_conditions['relative_humidity'] }}% humidity (relative)</div>
</div>
<div class="secondaryInfo">
<div class="windContainer frosted">
<div class="flex items-center justify-between">
<div class="flex items-center">Wind Status</div>
<div class="flex items-center">{{ current_conditions['wind_speed'] }}</div>
</div>
<div class="flex items-center justify-center">
</div>
<div class="flex items-center justify-center">
Direction: {{ current_conditions['wind_direction'] }}
</div>
</div>
<div class="solarClock frosted">
<div class="flex items-center justify-between">
<div class="flex items-center">Sunrise</div>
<div class="flex items-center">Sunset</div>
</div>
<div class="flex items-center justify-center">
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">Time AM</div>
<div class="flex items-center">Time PM</div>
</div>
</div>
</div>
</div>
<div class="hourlyForecast frosted my-6">
<h3>Hourly Forecast</h3>
<div class="grid grid-cols-6 gap-x-4">
{{ current_conditions | tojson(2) }}
</div>
</div>
<div class="weeklyReport frosted">
<h3>Weekly Forecast</h3>
<div class="grid grid-cols-7 gap-x-4">
{% for period in periods %}
<p>{{ period['start_time'] }} - {{ period['end_time'] }} | {{ period['temperature'] }}{{ period['temperature_unit'] }}</p>
<div>{{ period['start_time'] }} - {{ period['end_time'] }} | {{ period['temperature'] }}{{ period['temperature_unit'] }}</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,5 +1,5 @@
from flask import (
Blueprint, flash, g, render_template, request, url_for
Blueprint, render_template
)
from werkzeug.exceptions import abort
@@ -10,9 +10,28 @@ bp = Blueprint('weather', __name__)
@bp.route('/')
def index():
db = get_db()
latest_period = dict(db.execute(
'SELECT `id` FROM `reports` WHERE `type` = "hourly" ORDER BY `reported_at` DESC'
).fetchone())
current_conditions = dict(db.execute(
f"SELECT * FROM `periods` WHERE `report_id` = {latest_period['id']} LIMIT 1"
).fetchone())
# TODO: add conditions to check for day/night
condition_image = f"images/{current_conditions['short_forecast'].lower()}.jpg"
periods = db.execute(
'SELECT *'
' FROM `periods`'
' ORDER BY `id` DESC'
' LIMIT 7'
).fetchall()
return render_template('weather/index.html', periods=periods)
return render_template(
'weather/index.html',
current_conditions=current_conditions,
condition_image=condition_image,
periods=periods
)