Support all time resolutions on all counter views

This commit is contained in:
2026-04-21 12:59:55 +02:00
parent 0cd500e9f2
commit a0bdf9e37e
12 changed files with 442 additions and 263 deletions

View File

@@ -1,7 +1,7 @@
[server] [server]
port = 8501 port = 8501
address = "0.0.0.0" address = "0.0.0.0"
enableStaticServing = true enableStaticServing = false
[browser] [browser]
gatherUsageStats = false gatherUsageStats = false

View File

@@ -26,7 +26,7 @@ VOLUME /app/data
RUN touch .streamlit/secrets.toml \ RUN touch .streamlit/secrets.toml \
&& toml add_section --toml-path='.streamlit/secrets.toml' 'connections.sqlite' \ && toml add_section --toml-path='.streamlit/secrets.toml' 'connections.sqlite' \
&& toml set --toml-path='.streamlit/secrets.toml' 'connections.sqlite.type' 'sql' \ && toml set --toml-path='.streamlit/secrets.toml' 'connections.sqlite.type' 'queries' \
&& toml set --toml-path='.streamlit/secrets.toml' 'connections.sqlite.url' 'sqlite:///data/daily-counter.db' && toml set --toml-path='.streamlit/secrets.toml' 'connections.sqlite.url' 'sqlite:///data/daily-counter.db'
HEALTHCHECK --interval=60s --retries=5 CMD wget -qO- http://127.0.0.1:8501/_stcore/health || exit 1 HEALTHCHECK --interval=60s --retries=5 CMD wget -qO- http://127.0.0.1:8501/_stcore/health || exit 1

View File

@@ -1,11 +1,11 @@
import streamlit as st import streamlit as st
import sql from queries import crud, daily_stats, weekly_stats, monthly_stats, yearly_stats
from enums import CounterType from enums import CounterType
@st.dialog("Add New Counter", icon=":material/add_box:") @st.dialog("Add New Counter", icon=":material/add_box:")
def _add_counter(): def _add_counter():
colors = sql.get_colors(1) colors = crud.get_colors(1)
with st.form(key="add_counter", border=False, clear_on_submit=True): with st.form(key="add_counter", border=False, clear_on_submit=True):
title = st.text_input("Title:") title = st.text_input("Title:")
counter_type_name = st.selectbox("Type", options=[e.name for e in CounterType]) counter_type_name = st.selectbox("Type", options=[e.name for e in CounterType])
@@ -16,7 +16,7 @@ def _add_counter():
format_func=lambda c: f"#{c}") format_func=lambda c: f"#{c}")
with st.container(horizontal=True, width="stretch", horizontal_alignment="center"): with st.container(horizontal=True, width="stretch", horizontal_alignment="center"):
if st.form_submit_button(label="Create", icon=":material/save:"): if st.form_submit_button(label="Create", icon=":material/save:"):
sql.create_counter(title, CounterType[counter_type_name], color) crud.create_counter(title, CounterType[counter_type_name], color)
st.rerun() st.rerun()
@@ -26,10 +26,10 @@ def _remove_counter(counter_id:int):
st.subheader("Are you sure?") st.subheader("Are you sure?")
with st.container(horizontal=True, width="stretch", horizontal_alignment="center"): with st.container(horizontal=True, width="stretch", horizontal_alignment="center"):
if st.form_submit_button("Confirm", icon=":material/delete:"): if st.form_submit_button("Confirm", icon=":material/delete:"):
sql.remove_counter(counter_id) crud.remove_counter(counter_id)
st.rerun() st.rerun()
df = sql.get_counters() df = crud.get_counters()
with st.container(key="counter-table"): with st.container(key="counter-table"):
for counter_id, name, counter_type_str, color in zip(df['id'], df['name'], df['type'], df['color']): for counter_id, name, counter_type_str, color in zip(df['id'], df['name'], df['type'], df['color']):
@@ -38,7 +38,7 @@ with st.container(key="counter-table"):
st.header(f":material/calendar_clock: {name}", width="stretch") st.header(f":material/calendar_clock: {name}", width="stretch")
if st.button("", icon=":material/exposure_plus_1:", key=f"increment_counter_{counter_id}"): if st.button("", icon=":material/exposure_plus_1:", key=f"increment_counter_{counter_id}"):
sql.increment_counter(counter_id) crud.increment_counter(counter_id)
st.rerun() st.rerun()
if st.button("", icon=":material/delete_forever:", key=f"remove_counter_{counter_id}"): if st.button("", icon=":material/delete_forever:", key=f"remove_counter_{counter_id}"):
@@ -51,22 +51,22 @@ with st.container(key="counter-table"):
stats_prev_unit = counter_type.previous_unit_text() stats_prev_unit = counter_type.previous_unit_text()
match counter_type: match counter_type:
case CounterType.DAILY.value | CounterType.SIMPLE.value: case CounterType.DAILY.value | CounterType.SIMPLE.value:
stats = sql.get_daily_analytics(counter_id) stats = daily_stats.get_daily_analytics(counter_id)
stats_current = stats.iloc[0]["count"] stats_current = stats.iloc[0]["count"]
stats_prev = stats.iloc[1]["count"] stats_prev = stats.iloc[1]["count"]
case CounterType.WEEKLY.value: case CounterType.WEEKLY.value:
stats = sql.get_weekly_analytics(counter_id) stats = weekly_stats.get_weekly_analytics(counter_id)
stats_current = stats.iloc[0]["count"] stats_current = stats.iloc[0]["count"]
stats_prev = stats.iloc[1]["count"] stats_prev = stats.iloc[1]["count"]
case CounterType.MONTHLY.value: case CounterType.MONTHLY.value:
stats = sql.get_monthly_analytics(counter_id) stats = monthly_stats.get_monthly_analytics(counter_id)
stats_current = stats.iloc[-1]["count"] stats_current = stats.iloc[-1]["count"]
stats_prev = stats.iloc[-2]["count"] stats_prev = stats.iloc[-2]["count"]
case CounterType.YEARLY.value: case CounterType.YEARLY.value:
stats = sql.get_yearly_analytics(counter_id) stats = yearly_stats.get_yearly_analytics(counter_id)
stats_current = stats.iloc[-1]["count"] stats_current = stats.iloc[-1]["count"]
stats_prev = stats.iloc[-2]["count"] stats_prev = stats.iloc[-2]["count"]

View File

@@ -1,44 +1,89 @@
import enum
import logging import logging
import streamlit as st import streamlit as st
import json import json
import sql
import pandas as pd import pandas as pd
from enums import CounterType from enums import CounterType
from enum import StrEnum
from queries import crud, daily_stats, weekly_stats, monthly_stats, yearly_stats
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
counter_type_names = ([e.name for e in CounterType])
options = counter_type_names
options.remove(CounterType.SIMPLE.name)
if "counter_id" in st.query_params.keys(): if "counter_id" in st.query_params.keys():
'''
Show specific Counter analytics where the counter id is passed as query parameter "counter_id".
'''
counter_id = int(st.query_params["counter_id"]) counter_id = int(st.query_params["counter_id"])
df = sql.get_counter(counter_id) df = crud.get_counter(counter_id)
counter_type_id = df['type'] - 1
counter_type = [e for e in CounterType][counter_type_id]
counter_color ='#' + df['color']
with st.container(horizontal_alignment="right", vertical_alignment="bottom", horizontal=True):
st.header('Counter: ' + df['name']) st.header('Counter: ' + df['name'])
selection = st.segmented_control("Time Range", options, selection_mode="single", required=True, default=counter_type.name, label_visibility="hidden")
match getattr(CounterType, selection):
case CounterType.DAILY:
st.bar_chart(daily_stats.get_daily_analytics(counter_id), x="date", y="count", color=counter_color)
case CounterType.WEEKLY:
st.bar_chart(weekly_stats.get_weekly_analytics(counter_id), x="week", y="count", color=counter_color)
case CounterType.MONTHLY:
st.bar_chart(monthly_stats.get_monthly_analytics(counter_id), x="month", y="count", color=counter_color)
case CounterType.YEARLY:
st.bar_chart(yearly_stats.get_yearly_analytics(counter_id), x="year", y="count", color=counter_color)
case _:
logger.error(f"Unknown selection: {selection}")
color ='#' + df['color']
match df['type']:
case CounterType.DAILY.value | CounterType.SIMPLE.value:
st.bar_chart(sql.get_daily_analytics(int(df['id'])), x="date", y="count", color=color)
case CounterType.WEEKLY.value:
st.bar_chart(sql.get_weekly_analytics(int(df['id'])), x="week", y="count", color=color)
case CounterType.MONTHLY.value:
st.bar_chart(sql.get_monthly_analytics(int(df['id'])), x="month", y="count", color=color)
case CounterType.YEARLY.value:
st.bar_chart(sql.get_yearly_analytics(int(df['id'])), x="year", y="count", color=color)
else: else:
'''
By default, if no counter id is passed then show all counters in a a stacked graph
'''
with st.container(horizontal_alignment="right", vertical_alignment="bottom", horizontal=True):
st.header("Statistics") st.header("Statistics")
selection = st.segmented_control("Time range", options, selection_mode="single", default=f"{CounterType.DAILY.name}", required=True, label_visibility="hidden")
selectedRange = getattr(CounterType, selection)
match getattr(CounterType, selection):
case CounterType.DAILY:
unit = 'date'
unit_label = 'Date'
entries = daily_stats.get_all_daily_analytics()
case CounterType.WEEKLY:
unit = 'week'
unit_label ='Week'
entries = weekly_stats.get_all_weekly_analytics()
case CounterType.MONTHLY:
unit = 'month'
unit_label = 'Month'
entries = monthly_stats.get_all_monthly_analytics()
case CounterType.YEARLY:
unit = 'year'
unit_label = 'Year'
entries = yearly_stats.get_all_yearly_analytics()
case _:
logger.error(f"Unknown selection: {selection}")
entries = sql.get_analytics()
entries_norm = pd.json_normalize(entries.counters.apply(json.loads)).fillna(0) entries_norm = pd.json_normalize(entries.counters.apply(json.loads)).fillna(0)
entries_full = pd.concat([entries, entries_norm], axis=1).drop(['counters'], axis=1) entries_full = pd.concat([entries, entries_norm], axis=1).drop(['counters'], axis=1)
selected_counters = [c for c in entries_full.columns if c != "date"] selected_counters = [c for c in entries_full.columns if c != unit]
all_counters = sql.get_counters() all_counters = crud.get_counters()
colors = all_counters.loc[all_counters['name'].isin(selected_counters), ["name", "color"]] colors = all_counters.loc[all_counters['name'].isin(selected_counters), ["name", "color"]]
colors.name = colors.name.astype("category") colors.name = colors.name.astype("category")
colors.name = colors.name.cat.set_categories(selected_counters) colors.name = colors.name.cat.set_categories(selected_counters)
colors = colors.sort_values(["name"]) colors = colors.sort_values(["name"])
colors = colors.color.apply(lambda c: "#" + c).tolist() colors = colors.color.apply(lambda c: "#" + c).tolist()
st.bar_chart(entries_full, x="date", x_label="Date", y_label="Count", color=colors) st.bar_chart(entries_full, x=unit, x_label=unit_label, y_label="Count", color=colors)

View File

@@ -0,0 +1,9 @@
import streamlit as st
from sqlalchemy.sql import text
from streamlit.connections import BaseConnection
connection: BaseConnection = st.connection("sqlite")
with connection.session as configure_session:
configure_session.execute(text('PRAGMA foreign_keys=ON'))

64
app/queries/crud.py Normal file
View File

@@ -0,0 +1,64 @@
import logging
import streamlit as st
from sqlalchemy.sql import text
from queries.connection import connection
from enums import CounterType
logger = logging.getLogger(__name__)
def create_counter(title:str, counter_type:CounterType, counter_color) -> None:
logger.info("Adding counter %s", counter_type)
with connection.session as session:
try:
query = text('INSERT INTO counters (name, type, color) VALUES (:title, :type, :color)')
session.execute(query, {'title': title, 'type': counter_type, 'color': counter_color})
session.commit()
except Exception as e:
logger.error(e)
session.rollback()
def get_counters():
try:
return connection.query('SELECT id, name, type, color FROM counters', ttl=0)
except Exception as e:
logger.error(e)
return st.dataframe()
def increment_counter(counter_id:int) -> None:
logger.info("Incrementing counter %s", counter_id)
with connection.session as session:
try:
query = text('INSERT INTO entries (counter_id) VALUES (:id)')
session.execute(query, {'id': counter_id})
session.commit()
except Exception as e:
logger.error(e)
session.rollback()
def remove_counter(counter_id:int) -> None:
logger.info("Removing counter %s", counter_id)
with connection.session as session:
try:
query = text('DELETE FROM counters WHERE id = :id')
session.execute(query, {'id': counter_id})
session.commit()
except Exception as e:
logger.error(e)
session.rollback()
def get_counter(counter_id:int):
try:
return connection.query('SELECT * FROM counters WHERE id = :id', params={'id': counter_id}, ttl=0).iloc[0]
except Exception as e:
logger.error(e)
return None
def get_colors(palette_id:int):
try:
return connection.query('''SELECT color1,color2,color3,color4,color5 FROM color_palettes WHERE id = :id''', params={'id': palette_id})
except Exception as e:
logger.error(e)
return None

View File

@@ -0,0 +1,66 @@
import logging
from queries.connection import connection
logger = logging.getLogger(__name__)
def get_all_daily_analytics(end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date))
UNION ALL
SELECT date(d, '-1 day') as d
FROM timeseries
WHERE d > date(:end_date, '-30 days')
),
stats AS (
SELECT
date(timestamp) as d,
counter_id,
sum(increment) as count
FROM entries
group by counter_id, date(timestamp)
)
select
s.d as date,
case
when counter_id is null then json_object()
else json_group_object(name, count)
end as counters
FROM timeseries s
left outer join stats t on s.d = t.d
left join counters c on t.counter_id = c.id
GROUP by s.d
''', params={"end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None
def get_daily_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date))
UNION ALL
SELECT date(d, '-1 day') as d
FROM timeseries
WHERE d > date(:end_date, '-7 days')
),
stats AS (
SELECT
date(timestamp) as d,
sum(increment) as count
FROM entries
where counter_id = :id
group by date(timestamp)
)
SELECT
t.d as "date",
coalesce(s.count, 0) as count
FROM timeseries as t
LEFT JOIN stats as s on s.d = t.d
''', params={'id': counter_id, "end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None

View File

@@ -0,0 +1,79 @@
import logging
from queries.connection import connection
logger = logging.getLogger(__name__)
def get_all_monthly_analytics(end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date,'start of year'))
UNION ALL
SELECT date(d, '+1 month') as d
FROM timeseries
WHERE d < date(:end_date, '-1 month')
),
months AS (
SELECT
strftime('%m',d) as m,
strftime('%Y',d) as y
FROM timeseries
),
stats AS (
SELECT
strftime('%m', timestamp) as m,
strftime('%Y', timestamp) as y,
counter_id,
sum(increment) as count
FROM entries
group by counter_id, strftime('%m', timestamp), strftime('%Y', timestamp)
)
select
concat(m.m,', ',m.y) as "month",
case
when counter_id is null then json_object()
else json_group_object(name, count)
end as counters
FROM months m
left outer join stats t on m.m = t.m and m.y = t.y
left join counters c on t.counter_id = c.id
GROUP by m.m, m.y
''', params={"end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None
def get_monthly_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES( date(:end_date, 'start of year'))
UNION ALL
SELECT date(d, '+1 month')
FROM timeseries
WHERE d < date(:end_date, '-1 month')
),
months AS (
SELECT
strftime('%m',d) as m,
strftime('%Y',d) as y
FROM timeseries
),
stats AS (
SELECT
strftime('%m', timestamp) as m,
strftime('%Y', timestamp) as y,
sum(increment) as count
FROM entries
where counter_id = :id
group by strftime('%m', timestamp), strftime('%Y', timestamp)
)
SELECT
concat(m.m,', ',m.y) as "month",
coalesce(s.count, 0) as count
FROM months as m
LEFT JOIN stats as s on s.m = m.m and s.y = m.y
''', params={'id': counter_id, "end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None

View File

@@ -0,0 +1,73 @@
import logging
from queries.connection import connection
logger = logging.getLogger(__name__)
def get_all_weekly_analytics(end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date, 'weekday 0'))
UNION ALL
SELECT date(d, '-7 day') as d
FROM timeseries
WHERE d > date(:end_date, '-30 days')
),
weeks AS (
SELECT strftime('%W',d) as w
FROM timeseries
),
stats AS (
SELECT
strftime('%W', timestamp) as w,
counter_id,
sum(increment) as count
FROM entries
group by counter_id, strftime('%W', timestamp)
)
select
s.w as week,
case
when counter_id is null then json_object()
else json_group_object(name, count)
end as counters
FROM weeks s
left outer join stats t on s.w = t.w
left join counters c on t.counter_id = c.id
GROUP by s.w
''', params={"end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None
def get_weekly_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date, 'weekday 0'))
UNION ALL
SELECT date(d, '-7 day')
FROM timeseries
WHERE d > date(:end_date, '-30 days')
),
weeks AS (
SELECT strftime('%W',d) as w
FROM timeseries
),
stats AS (
SELECT
strftime('%W', timestamp) as w,
sum(increment) as count
FROM entries
where counter_id = :id
group by strftime('%W', timestamp)
)
SELECT
w.w as "week",
coalesce(s.count, 0) as count
FROM weeks as w
LEFT JOIN stats as s on s.w = w.w
''', params={'id': counter_id, "end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None

View File

@@ -0,0 +1,73 @@
import logging
from queries.connection import connection
logger = logging.getLogger(__name__)
def get_all_yearly_analytics(end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date,'start of year', '-4 years'))
UNION ALL
SELECT date(d, '+1 year') as d
FROM timeseries
WHERE d < date(:end_date, '-1 year')
),
years AS (
SELECT strftime('%Y',d) as y
FROM timeseries
),
stats AS (
SELECT
strftime('%Y', timestamp) as y,
counter_id,
sum(increment) as count
FROM entries
group by counter_id, strftime('%Y', timestamp)
)
select
y.y as "year",
case
when counter_id is null then json_object()
else json_group_object(name, count)
end as counters
FROM years y
left outer join stats t on y.y = t.y
left join counters c on t.counter_id = c.id
GROUP by y.y
''', params={"end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None
def get_yearly_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES( date(:end_date, 'start of year', '-4 years'))
UNION ALL
SELECT date(d, '+1 year')
FROM timeseries
WHERE d < date(:end_date, '-1 year')
),
years AS (
SELECT strftime('%Y',d) as y
FROM timeseries
),
stats AS (
SELECT
strftime('%Y', timestamp) as y,
sum(increment) as count
FROM entries
where counter_id = :id
group by strftime('%Y', timestamp)
)
SELECT
m.y as "year",
coalesce(s.count, 0) as count
FROM years as m
LEFT JOIN stats as s on s.y = m.y
''', params={'id': counter_id, "end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None

View File

@@ -1,230 +0,0 @@
import logging
import streamlit as st
from sqlalchemy.sql import text
from enums import CounterType
logger = logging.getLogger(__name__)
connection = st.connection("sqlite")
with connection.session as configure_session:
configure_session.execute(text('PRAGMA foreign_keys=ON'))
def create_counter(title:str, counter_type:CounterType, counter_color) -> None:
logger.info("Adding counter %s", counter_type)
with connection.session as session:
try:
query = text('INSERT INTO counters (name, type, color) VALUES (:title, :type, :color)')
session.execute(query, {'title': title, 'type': counter_type, 'color': counter_color})
session.commit()
except Exception as e:
logger.error(e)
session.rollback()
def get_counters():
try:
return connection.query('SELECT id, name, type, color FROM counters', ttl=0)
except Exception as e:
logger.error(e)
return st.dataframe()
def increment_counter(counter_id:int) -> None:
logger.info("Incrementing counter %s", counter_id)
with connection.session as session:
try:
query = text('INSERT INTO entries (counter_id) VALUES (:id)')
session.execute(query, {'id': counter_id})
session.commit()
except Exception as e:
logger.error(e)
session.rollback()
def remove_counter(counter_id:int) -> None:
logger.info("Removing counter %s", counter_id)
with connection.session as session:
try:
query = text('DELETE FROM counters WHERE id = :id')
session.execute(query, {'id': counter_id})
session.commit()
except Exception as e:
logger.error(e)
session.rollback()
def get_counter(counter_id:int):
try:
return connection.query('SELECT * FROM counters WHERE id = :id', params={'id': counter_id}, ttl=0).iloc[0]
except Exception as e:
logger.error(e)
return None
def get_analytics(end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date))
UNION ALL
SELECT date(d, '-1 day') as d
FROM timeseries
WHERE d > date(:end_date, '-30 days')
),
stats AS (
SELECT
date(timestamp) as d,
counter_id,
sum(increment) as count
FROM entries
group by counter_id, date(timestamp)
)
select
s.d as date,
case
when counter_id is null then json_object()
else json_group_object(name, count)
end as counters
FROM timeseries s
left outer join stats t on s.d = t.d
left join counters c on t.counter_id = c.id
GROUP by s.d
''', params={"end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None
def get_daily_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date))
UNION ALL
SELECT date(d, '-1 day') as d
FROM timeseries
WHERE d > date(:end_date, '-7 days')
),
stats AS (
SELECT
date(timestamp) as d,
sum(increment) as count
FROM entries
where counter_id = :id
group by date(timestamp)
)
SELECT
t.d as "date",
coalesce(s.count, 0) as count
FROM timeseries as t
LEFT JOIN stats as s on s.d = t.d
''', params={'id': counter_id, "end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None
def get_weekly_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date, 'weekday 0'))
UNION ALL
SELECT date(d, '-7 day')
FROM timeseries
WHERE d > date(:end_date, '-30 days')
),
weeks AS (
SELECT strftime('%W',d) as w
FROM timeseries
),
stats AS (
SELECT
strftime('%W', timestamp) as w,
sum(increment) as count
FROM entries
where counter_id = :id
group by strftime('%W', timestamp)
)
SELECT
w.w as "week",
coalesce(s.count, 0) as count
FROM weeks as w
LEFT JOIN stats as s on s.w = w.w
''', params={'id': counter_id, "end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None
def get_monthly_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES( date(:end_date, 'start of year'))
UNION ALL
SELECT date(d, '+1 month')
FROM timeseries
WHERE d < date(:end_date, '-1 month')
),
months AS (
SELECT
strftime('%m',d) as m,
strftime('%Y',d) as y
FROM timeseries
),
stats AS (
SELECT
strftime('%m', timestamp) as m,
strftime('%Y', timestamp) as y,
sum(increment) as count
FROM entries
where counter_id = :id
group by strftime('%m', timestamp), strftime('%Y', timestamp)
)
SELECT
concat(m.m,', ',m.y) as "month",
coalesce(s.count, 0) as count
FROM months as m
LEFT JOIN stats as s on s.m = m.m and s.y = m.y
''', params={'id': counter_id, "end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None
def get_yearly_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
WITH RECURSIVE timeseries(d) AS (
VALUES( date(:end_date, 'start of year', '-4 years'))
UNION ALL
SELECT date(d, '+1 year')
FROM timeseries
WHERE d < date(:end_date, '-1 year')
),
years AS (
SELECT strftime('%Y',d) as y
FROM timeseries
),
stats AS (
SELECT
strftime('%Y', timestamp) as y,
sum(increment) as count
FROM entries
where counter_id = :id
group by strftime('%Y', timestamp)
)
SELECT
m.y as "year",
coalesce(s.count, 0) as count
FROM years as m
LEFT JOIN stats as s on s.y = m.y
''', params={'id': counter_id, "end_date": end_date}, ttl=0)
except Exception as e:
logger.error(e)
return None
def get_colors(palette_id:int):
try:
return connection.query('''SELECT color1,color2,color3,color4,color5 FROM color_palettes WHERE id = :id''', params={'id': palette_id})
except Exception as e:
logger.error(e)
return None

View File

@@ -1,6 +1,6 @@
import streamlit as st import streamlit as st
import sql import queries
from queries import crud
def _load_css(filepath): def _load_css(filepath):
with open(filepath) as file: with open(filepath) as file:
@@ -8,7 +8,7 @@ def _load_css(filepath):
def _load_color_selector_styles(): def _load_color_selector_styles():
colors = sql.get_colors(1) #FIXME Change to use user profile color palette colors = crud.get_colors(1) #FIXME Change to use user profile color palette
for idx, c in enumerate(colors.keys()): for idx, c in enumerate(colors.keys()):
css_color = '#' + colors[c][0] css_color = '#' + colors[c][0]
st.html(f""" st.html(f"""