Author SHA1 Message Date
willemjan c617f4e41b Merge pull request 'Feature/insulin profile' (#33) from feature/insulin-profile into main
CI/CD / test (push) Successful in 17s
CI/CD / sonarqube (push) Successful in 20s
CI/CD / build-and-scan (push) Successful in 42s
CI/CD / deploy (push) Successful in 3s
Reviewed-on: #33
2026-09-05 20:47:43 +00:00
willemjan a10a4c0414 Merge pull request 'Feature/treatment overlays' (#32) from feature/treatment-overlays into feature/insulin-profile
Reviewed-on: #32
2026-09-05 20:47:10 +00:00
willemjan 1376101212 Merge pull request 'Add basal/bolus/carb/target overlays to Live and Dagoverzicht charts' (#30) from feature/treatment-overlays into feature/insulin-profile
Reviewed-on: #30
2026-09-05 20:44:26 +00:00
willemjan 6004f76d76 Merge pull request 'Add pump profile fetching and Insulineprofiel page' (#29) from feature/insulin-profile into main
CI/CD / test (push) Successful in 51s
CI/CD / sonarqube (push) Successful in 24s
CI/CD / build-and-scan (push) Successful in 2m9s
CI/CD / deploy (push) Successful in 22s
willemjan/glucose-dashboard/pipeline/head This commit looks good
Reviewed-on: #29
2026-09-05 19:43:58 +00:00
10 changed files with 14 additions and 125 deletions
-2
View File
@@ -5,6 +5,4 @@ __pycache__/
.git/
.pytest_cache/
.ruff_cache/
.coverage
coverage.xml
tests/
+3 -17
View File
@@ -19,21 +19,11 @@ jobs:
run: pip install -r requirements.txt
- name: Lint (ruff)
run: ruff check .
- name: Unit tests + coverage-gate (pytest)
# fail_under (pyproject.toml, [tool.coverage.report]) laat deze stap
# falen als de dekking op de gemeten modules onder 80% zakt — app.py/
# pages/*.py zijn uitgesloten (zie pyproject.toml se coverage-omit),
# dat zijn dunne Streamlit-UI-scripts die niet zinvol te unit-testen
# zijn zonder een draaiende Streamlit-sessie.
run: pytest tests/ -v --cov=. --cov-report=term-missing --cov-report=xml
- name: Bewaar coverage-rapport
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml
- name: Unit tests (pytest)
run: pytest tests/ -v
# ---------------------------------------------------------------------
# 2. SonarQube — codekwaliteit-gate (incl. coverage uit de vorige stap)
# 2. SonarQube — codekwaliteit-gate
# ---------------------------------------------------------------------
sonarqube:
runs-on: ubuntu-latest
@@ -42,10 +32,6 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # SonarQube wil de volledige git-historie zien
- name: Download coverage-rapport
uses: actions/download-artifact@v4
with:
name: coverage-report
- name: SonarQube scan
uses: sonarsource/sonarqube-scan-action@v6
env:
-2
View File
@@ -4,6 +4,4 @@ __pycache__/
.env
.pytest_cache/
.ruff_cache/
.coverage
coverage.xml
homelab-cicd-werkinstructie.md
-9
View File
@@ -41,12 +41,3 @@ klinische rapportagepagina's (zie `docs/requirements-reporting.md` en
pytest tests/ -v
ruff check .
```
Coverage (zie `pyproject.toml` se `[tool.coverage.*]`): de CI-pipeline faalt
als de dekking op de gemeten modules onder 80% zakt (`app.py`/`pages/*.py`
zijn uitgesloten — dunne Streamlit-UI-scripts, niet zinvol te unit-testen
zonder een draaiende Streamlit-sessie).
```bash
pytest tests/ --cov=. --cov-report=term-missing
```
+1 -6
View File
@@ -17,18 +17,13 @@ import os
import streamlit as st
def require_auth() -> None: # pragma: no cover
def require_auth() -> None:
"""Blokkeer de pagina totdat het juiste wachtwoord is ingevoerd.
Slaat de status op in st.session_state zodat dit maar één keer per
browsersessie hoeft. Moet als eerste statement van elke pagina aangeroepen
worden (app.py en elk bestand in pages/), omdat Streamlit iedere pagina
als los script uitvoert.
Pure Streamlit-widget-orkestratie (st.text_input/st.session_state/
st.rerun) — buiten scope van de unit-testdekking net als pages/*.py,
zie pyproject.toml se coverage-omit-lijst. De veiligheidsrelevante
logica zelf (verify_password()) is wel volledig getest.
"""
if st.session_state.get("authenticated"):
return
-13
View File
@@ -1,15 +1,2 @@
[tool.pytest.ini_options]
pythonpath = ["."]
[tool.coverage.run]
# app.py en pages/*.py zijn dunne Streamlit-UI-scripts (net als de
# functies in ui_common.py/auth.py die zelf al met "# pragma: no cover"
# gemarkeerd zijn) — niet zinvol te unit-testen zonder een draaiende
# Streamlit-sessie, en buiten scope van de coverage-eis. De logica die ze
# aanroepen (reporting.py, nightscout_client.py, data_processing.py,
# pdf_export.py, profile_data.py, ui_common.py) wordt wél volledig gemeten.
omit = ["app.py", "pages/*", "tests/*", ".venv/*"]
[tool.coverage.report]
fail_under = 80
show_missing = true
-1
View File
@@ -6,5 +6,4 @@ python-dotenv>=1.0,<2.0
matplotlib>=3.9,<4.0
fpdf2>=2.7,<3.0
pytest>=8.0,<9.0
pytest-cov>=5.0,<6.0
ruff>=0.6,<1.0
-1
View File
@@ -4,4 +4,3 @@ sonar.sources=.
sonar.exclusions=data/**,tests/**
sonar.tests=tests
sonar.python.version=3.12
sonar.python.coverage.reportPaths=coverage.xml
-47
View File
@@ -1,8 +1,5 @@
from types import SimpleNamespace
import pandas as pd
import plotly.graph_objects as go
import streamlit as st
from ui_common import (
PALETTE,
@@ -13,26 +10,10 @@ from ui_common import (
add_target_line_trace,
add_threshold_lines,
build_tir_stacked_bar_figure,
get_colors,
hex_to_rgba,
)
def test_get_colors_returns_dark_palette_when_theme_is_dark(monkeypatch):
monkeypatch.setattr(st, "context", SimpleNamespace(theme=SimpleNamespace(type="dark")))
assert get_colors() == PALETTE["dark"]
def test_get_colors_returns_light_palette_when_theme_is_light(monkeypatch):
monkeypatch.setattr(st, "context", SimpleNamespace(theme=SimpleNamespace(type="light")))
assert get_colors() == PALETTE["light"]
def test_get_colors_falls_back_to_light_when_theme_context_unavailable(monkeypatch):
monkeypatch.setattr(st, "context", SimpleNamespace()) # geen .theme-attribuut
assert get_colors() == PALETTE["light"]
def test_hex_to_rgba_converts_correctly():
assert hex_to_rgba("#2a78d6", 0.3) == "rgba(42,120,214,0.3)"
@@ -74,34 +55,12 @@ def test_add_basal_area_trace_adds_one_filled_step_trace():
assert fig.data[0].line.shape == "hv"
def test_add_basal_area_trace_supports_secondary_y_on_a_subplot_figure():
from plotly.subplots import make_subplots
basal = pd.DataFrame(
{"timestamp": pd.to_datetime(["2026-01-01 00:00", "2026-01-01 06:00"]), "rate": [0.8, 1.2]}
)
fig = make_subplots(specs=[[{"secondary_y": True}]])
add_basal_area_trace(fig, basal, PALETTE["light"], row=1, secondary_y=True)
assert len(fig.data) == 1
assert fig.data[0].yaxis == "y2"
def test_add_basal_area_trace_skips_empty_input():
fig = go.Figure()
add_basal_area_trace(fig, pd.DataFrame(columns=["timestamp", "rate"]), PALETTE["light"])
assert len(fig.data) == 0
def test_add_carb_bars_trace_supports_secondary_y_on_a_subplot_figure():
from plotly.subplots import make_subplots
carbs = pd.DataFrame({"timestamp": pd.to_datetime(["2026-01-01 08:00"]), "carbs": [40.0]})
fig = make_subplots(specs=[[{"secondary_y": True}]])
add_carb_bars_trace(fig, carbs, PALETTE["light"], row=1, secondary_y=True)
assert len(fig.data) == 1
assert fig.data[0].yaxis == "y2"
def test_add_carb_bars_trace_adds_one_bar_trace():
carbs = pd.DataFrame({"timestamp": pd.to_datetime(["2026-01-01 08:00"]), "carbs": [40.0]})
fig = go.Figure()
@@ -111,12 +70,6 @@ def test_add_carb_bars_trace_adds_one_bar_trace():
assert isinstance(fig.data[0], go.Bar)
def test_add_carb_bars_trace_skips_empty_input():
fig = go.Figure()
add_carb_bars_trace(fig, pd.DataFrame(columns=["timestamp", "carbs"]), PALETTE["light"])
assert len(fig.data) == 0
def test_add_bolus_marker_traces_adds_one_trace_per_category_present():
bolus = pd.DataFrame(
{
+10 -27
View File
@@ -118,12 +118,8 @@ def get_colors() -> dict:
return PALETTE["dark" if theme_type == "dark" else "light"]
def select_period_days(default_label: str) -> tuple[date, date, int]: # pragma: no cover
"""Periodekiezer voor rapportagepagina's; geeft (start, eind, aantal_dagen) terug.
Pure Streamlit-widget-orkestratie (st.radio) — buiten scope van de
unit-testdekking net als pages/*.py, zie pyproject.toml se coverage-omit-lijst.
"""
def select_period_days(default_label: str) -> tuple[date, date, int]:
"""Periodekiezer voor rapportagepagina's; geeft (start, eind, aantal_dagen) terug."""
labels = list(PERIOD_OPTIONS_DAYS.keys())
label = st.radio("Periode", labels, index=labels.index(default_label), horizontal=True)
days = PERIOD_OPTIONS_DAYS[label]
@@ -133,15 +129,11 @@ def select_period_days(default_label: str) -> tuple[date, date, int]: # pragma:
@st.cache_data(ttl=3600, show_spinner="Data ophalen van Nightscout...")
def load_range_data(start: date, end: date): # pragma: no cover
def load_range_data(start: date, end: date):
"""Gecachete Nightscout-fetch voor een vaste periode.
Lange TTL (in tegenstelling tot de 60s van de live-pagina): historische
data van een afgeronde periode verandert niet.
Dunne st.cache_data-wrapper rond al-geteste fetch_entries_range()/
fetch_treatments_range() — buiten scope van de unit-testdekking net als
pages/*.py, zie pyproject.toml se coverage-omit-lijst.
"""
glucose = fetch_entries_range(start, end)
treatments = fetch_treatments_range(start, end)
@@ -149,14 +141,10 @@ def load_range_data(start: date, end: date): # pragma: no cover
@st.cache_data(ttl=21600, show_spinner="Profiel ophalen van Nightscout...")
def load_profile() -> dict: # pragma: no cover
def load_profile() -> dict:
"""Gecachete profiel-fetch. Langere TTL dan load_range_data (6 uur i.p.v.
1 uur): een pompprofiel wijzigt zelden, in tegenstelling tot glucose-/
behandeldata van een lopende periode.
Dunne st.cache_data-wrapper rond al-geteste fetch_profile()/parse_profile()
— buiten scope van de unit-testdekking net als pages/*.py.
"""
behandeldata van een lopende periode."""
raw = fetch_profile()
return parse_profile(raw)
@@ -375,21 +363,19 @@ def add_target_line_trace(
)
def render_completeness_warning(glucose: pd.DataFrame, start: date, end: date) -> None: # pragma: no cover
def render_completeness_warning(glucose: pd.DataFrame, start: date, end: date) -> None:
"""Toon reporting.completeness_warning()'s oordeel als st.warning, indien nodig.
Gebruikt door elke rapportagepagina, direct na de load_range_data-call.
De beoordelingslogica zelf staat in reporting.py (pure functie, wél volledig
getest) zodat het PDF-rapport (pdf_export.py) exact dezelfde disclaimer kan
hergebruiken. Deze functie is alleen de st.warning-weergave — buiten scope
van de unit-testdekking net als pages/*.py.
De beoordelingslogica zelf staat in reporting.py (pure functie) zodat het
PDF-rapport (pdf_export.py) exact dezelfde disclaimer kan hergebruiken.
"""
message = completeness_warning(glucose, start, end)
if message:
st.warning(message)
def render_event_level( # pragma: no cover
def render_event_level(
glucose: pd.DataFrame,
threshold: float,
direction: str,
@@ -400,10 +386,7 @@ def render_event_level( # pragma: no cover
"""Render de metrics/tabel/tijdstip-histogram voor één hypo-/hyperniveau.
Gedeeld tussen de Hypo Events- en Hyper Events-pagina's (FR5/FR6), die
elk twee niveaus (Level 1/Level 2) op dezelfde manier tonen. Pure
Streamlit-widget-weergave rond al-geteste reporting.detect_events()/
summarize_events() — buiten scope van de unit-testdekking net als
pages/*.py.
elk twee niveaus (Level 1/Level 2) op dezelfde manier tonen.
"""
events = detect_events(glucose, threshold, direction)
summary = summarize_events(events, period_days)