I now have all the available rainfall/snowfall data in the database. And, I have an utility module for adding new data as it becomes available. Though that module may prove temporary if I decide to add that functionality to the dashboard. I will leave parsing out daily temperature data for the future.

At this point, I think it is time to try to get some Streamlit code working to start generating the dashboard. Though that process will be slow and a small step at a time.

Basic Setup

What I would like to do to start is to get the basic code in place for a preliminary dashboard. I will add some sections with labels identifying what I expect to eventually put in that section of the dashboard. Sort of an intitial design attempt. There will definitely be a side bar. It will, at a minimum, provide a way to specify what the dashboard should display. E.G. current month, year-to-date or…

# dashboard.py: module to generate and display the weather dashboard
# ver 0.1.0: rek, 2025.08.30, init setup, draft dashboard

from pathlib import Path
from utils.weather_db import Weather_db

import pandas as pd
import numpy as np
import streamlit as st
import altair as alt
import plotly.express as px

st.set_page_config(
    page_title="Surrey Weather Dashboard",
    page_icon="🌧️",
    layout="wide",
    initial_sidebar_state="expanded")

alt.theme.enable("dark")

with st.sidebar:
  st.title('🌧️ Surrey Weather')

  w_items = ["Month-to-date rainfall", "Year-to-date rainfall", "Current month's temperatures"]
  w_item = st.selectbox("Select item to display:", w_items, index=0)
  st.write(f"{w_item} selected")

row1 = st.columns(2)
row2 = st.container(height=400)
grid1 = [col.container(height=300) for col in row1]

if w_item == "Month-to-date rainfall":
  # dev: assume want August 2025 rainfall data
  grid1[0].header("Current Month's Rainfall")
  grid1[1].header("Month's Rainfall Data")
  row2.header("Current Month's Rainfall Last 10 Years")
elif w_item == "Year-to-date rainfall":
  grid1[0].header("Current Year's Rainfall")
  grid1[1].header("2025 Monthly Rainfall")
  row2.header("Annual Rainfall Last 10 Years")
elif w_item == "Current month's temperatures":
  grid1[0].header("Current Month's Average Temperature")
  grid1[1].header("Current Month's Daily Temperatures")
  row2.header("Average Temperature Last 10 Years")

And, here’s what showed up in a browser tab.

Draft Concept of Dashboard I
dashboard concept generated using Streamlit when 'Month-to-date rainfall' selected

And when I selected “Year-to-date rainfall” from the selection box in the sidebar, this is what was displayed.

Draft Concept of Dashboard II
dashboard concept generated using Streamlit when 'Year-to-date rainfall' selected

Quite amazing how easily we get interactivity—courtesy of Streamlit.

Display Table of Rainfall Data for Current Month

Okay, before sorting out the generation and display of charts, let’s see if we can get a table of the monthly rainfall for August 2025 displayed in the second column of the first row.

New Method in Weather_db Class

I don’t currently have a method in the database class that I can use to get this data. So I am going to add one.

  def get_mon_rf(self, yr_mn):
    """Get the rainfall records for the specified month.
       Full or partial month.

      Param:
        yr_mn: specifies the month, 'yyyy.mm'
    
      Returns:
        pandas dataframe of rainfall data
    """
    d_qry = f"""SELECT datetime, daily, monthly FROM {self.tnms["rf_tnm"]} WHERE datetime LIKE '{yr_mn}%';"""
    d_rslt = self.qry_pd(d_qry, i_col="datetime")
    return d_rslt

Short and sweet. But this way I don’t need to write and execute queries in the dashboard module.

Display Dataframe

I am returning a Pandas dataframe from that new query. Streamlit has a method specifically for displaying a dataframe on screen. But a few other bits of code as well. E.G. to get current year and month.

When instantiating the Weather_db class, I am caching the database object using the st.cache_resource decorator on the function I am using to instantiate and return the object. Don’t want it getting instantiated on every Streamlit rerun.

... ...
import time
... ...
# instantiate and cache Weather_db class
@st.cache_resource
def init_wdb():
  cwd = Path(__file__).cwd()
  fl_pth = cwd/"data"
  fl_nm = "weather.db"
  db_pth = fl_pth/fl_nm
  rfall = Weather_db(db_pth)
  return rfall
rfall = init_wdb()
... ...
c_dtm = time.localtime()
# year and month as "yyyy.mm"
c_ym = time.strftime("%Y.%m", c_dtm)
# month name as string
s_mon = time.strftime("%B", c_dtm)
... ...
if w_item == "Month-to-date rainfall":
  grid1[0].header("Current Month's Rainfall")
  m_rf = rfall.get_mon_rf(c_ym)
  grid1[1].header(f"{s_mon} {c_ym[:4]} Rainfall")
  with grid1[1]:
    st.dataframe(m_rf, width=350)
  row2.header(f"{s_mon} Rainfall for Last 10 Years")

And in the browser the following was displayed (on start-up or when “Month-to-date rainfall” selected in the sidebar).

Dashboard with August 2025 Rainfall Table
dashboard with August 2025 rainfall displayed in table form

And here’s a close up of that block with the rainfall data table.

Dashboard Block with Rainfall Data Table
close up of dashboard block with August 2025 rainfall data table

Display Table of Monthly Rainfall Totals for Current Year

Well, that was pretty easy. Let’s do the same for the yearly rainfall selection.

Another New Database Method

Okay, as with the monthly data, I don’t currently have a method I can use to get the monthly total rainfall for a given year. So I added one. Once again short and sweet. As with the monthly one I am returning a Pandas dataframe.

  def get_year_rf(self, yr):
    """Get the montaly total rainfall for the specified yr.
       Full or partial months.

      Param:
        yr: specifies the year, 'yyyy'
    
      Returns:
        pandas dataframe of rainfall data
    """
    q_yrly = f"""SELECT SUBSTR(dttm, 6, 2) as Month, ROUND(m_tot, 2) as 'Rainfall to-date'
      FROM
      (SELECT MAX(datetime) as dttm, monthly as m_tot
      FROM {self.tnms["rf_tnm"]}
      WHERE SUBSTR(datetime, 1, 4)='{yr}'
      GROUP BY SUBSTR(datetime, 1, 7));"""
    d_rslt = self.qry_pd(q_yrly, i_col="Month")
    return d_rslt

Display the Table

This code is definitely going to look pretty familiar. Wondering if should write a function that does this for both cases. But…

elif w_item == "Year-to-date rainfall":
  grid1[0].header("Current Year's Rainfall")
  # grid1[1].header("2025 Monthly Rainfall")
  m_rf = rfall.get_year_rf(c_ym[:4])
  grid1[1].header(f"{c_ym[:4]} Monthly Rainfall")
  with grid1[1]:
    st.dataframe(m_rf, width=250)
  row2.header("Annual Rainfall for Last 10 Years")

And in the dashboard, I got the following when making the appropriate selection in the side bar. I won’t bother with a close-up of the table.

Dashboard with 2025 Monthly Rainfall Table
dashboard with 2025 monthly rainfall displayed in table form

That’s It

I was going to move on to adding the charts/graphs to the dashboard, but have decided to leave that for the next post. As this is a hobby, I felt that there was sufficient content in this post. And, I have some errands to run.

Until next time, do enjoy your hobbies.

Resources