import base64
import json
import os
from concurrent.futures import ThreadPoolExecutor
from datetime import date, datetime, timedelta
from urllib.parse import parse_qs
import pandas as pd
from jupyterhealth_client import JupyterHealthClient
from zoneinfo import ZoneInfo
# Presenter switch: leave the default line in place for the baseline-only dashboard.
SHOW_FOLLOW_UP = False
# SHOW_FOLLOW_UP = True
try:
from provider_app import (
jhe_auth,
launch_context,
)
ctx = launch_context.current()
except Exception as e:
# print("No SMART context", e)
client = JupyterHealthClient()
PATIENT_ID = 40032
patient = client.get_patient(PATIENT_ID)
else:
client = jhe_auth.client_for_launch(ctx)
patient = client.lookup_patient(
external_id=ctx.patient_mrn,
system=os.getenv("MRN_IDENTIFIER_SYSTEM"),
)
PATIENT_ID = patient["id"]
view_name = None
# allow selecting patient/practitioner view
if "VOILA_REQUEST_URL" in os.environ:
_url, _, query_s = os.environ["VOILA_REQUEST_URL"].partition("?")
query = parse_qs(query_s)
if "view" in query:
view_name = query["view"][0]
else:
view_name = "practitioner"
practitioner = client.get_user()# heading for patient/practitioner views
from IPython.display import HTML, Markdown, display
if view_name == "patient":
display(
Markdown(f"""
# Patient dashboard for {patient['id']} {patient['nameGiven']} {patient['nameFamily']}
"""
)
)
elif view_name == "practitioner":
display(HTML(f"""
<div id="practitioner-heading">
EHR Practitioner View {practitioner['id']}: {practitioner['firstName']} {practitioner['lastName']}
</div>
<style type="text/css">
div#practitioner-heading {{
border-bottom: 6px solid rgb(20, 130, 140);
width: 100%;
padding: 4px;
position: fixed;
top: 0;
left: 0;
}}
div#rendered_cells {{
border: 6px solid rgb(20, 130, 140);
}}
</style>
"""))
elif not view_name:
display(Markdown(f"Practitioner {practitioner['id']}: {practitioner['firstName']} {practitioner['lastName']}"))# Alex K.'s records in the Laude Demo study.
STUDY_ID = 30014
FHIR_SOURCE_ID = 90009
STUDY_TIMEZONE = ZoneInfo("America/Los_Angeles")
EXCHANGE_URL = os.environ["JHE_URL"]
study = client.get_study(STUDY_ID)
ALL_SESSION_WINDOWS = (
{
"name": "baseline",
"start": date(2026, 8, 23),
"end": date(2026, 9, 5),
"label": "Aug 23–Sep 5, 2026",
},
{
"name": "follow_up",
"start": date(2026, 9, 13),
"end": date(2026, 9, 26),
"label": "Sep 13–26, 2026",
},
)
# Every downstream filter, metric, and view uses this visible mode-specific selection.
SESSION_WINDOWS = ALL_SESSION_WINDOWS if SHOW_FOLLOW_UP else ALL_SESSION_WINDOWS[:1]
# The eight mapped-Observation families this dashboard reads, as Open mHealth / IEEE codes.
OMH = "https://w3id.org/openmhealth"
IEEE = "https://w3id.org/ieee1752"
CODE_BLOOD_GLUCOSE = "omh:blood-glucose:4.0"
CODE_STEP_COUNT = "omh:step-count:3.0"
CODE_HEART_RATE = "omh:heart-rate:2.0"
CODE_FOOD_ENTRY = "ieee:food-entry:0.1"
CODE_SLEEP_EPISODE = "ieee:sleep-episode:1.0"
CODE_BODY_WEIGHT = "omh:body-weight:3.0"
CODE_BODY_HEIGHT = "omh:body-height:2.0"
CODE_BLOOD_PRESSURE = "omh:blood-pressure:4.0"
FAMILY_SYSTEMS = {
CODE_BLOOD_GLUCOSE: OMH,
CODE_STEP_COUNT: OMH,
CODE_HEART_RATE: OMH,
CODE_FOOD_ENTRY: IEEE,
CODE_SLEEP_EPISODE: IEEE,
CODE_BODY_WEIGHT: OMH,
CODE_BODY_HEIGHT: OMH,
CODE_BLOOD_PRESSURE: OMH,
}
# Sparse clinical context is intentionally retained outside CGM session dates.
STATIC_FAMILY_CODES = {
CODE_BODY_WEIGHT,
CODE_BODY_HEIGHT,
CODE_BLOOD_PRESSURE,
}
MAX_QUERY_WORKERS = 4
OBSERVATION_URL = f"{EXCHANGE_URL}/FHIR/R5/Observation"
def fhir_observations(code, system, start=None, end=None, source=None):
"""Fetch one exact-code Observation search, safely following all pages."""
params = [
("patient", str(PATIENT_ID)),
("patient._has:Group:member:_id", str(STUDY_ID)),
("code", f"{system}|{code}"),
("_count", "1000"),
]
if source is not None:
params.append(("_source", source))
if start is not None and end is not None:
params.extend([
("date", f"ge{start.isoformat()}"),
("date", f"lt{(end + timedelta(days=1)).isoformat()}"),
])
resources = []
page = 1
while True:
response = client.session.get(
OBSERVATION_URL, params=[*params, ("_page", str(page))], timeout=60
)
response.raise_for_status()
bundle = response.json()
resources.extend(
entry["resource"] for entry in bundle.get("entry", []) if "resource" in entry
)
if not any(link.get("relation") == "next" for link in bundle.get("link", [])):
return resources
page += 1
def date_chunks(code):
if code in STATIC_FAMILY_CODES:
yield None, None
return
for window in SESSION_WINDOWS:
start = window["start"]
if code == CODE_STEP_COUNT:
# Minute-level steps are day-chunked to keep each FHIR search small.
while start <= window["end"]:
yield start, start
start += timedelta(days=1)
else:
yield start, window["end"]
def unique_resources(resources):
by_id = {resource.get("id"): resource for resource in resources}
return [by_id[resource_id] for resource_id in sorted(by_id, key=str)]
family_jobs = [
(code, system, start, end)
for code, system in FAMILY_SYSTEMS.items()
for start, end in date_chunks(code)
]
with ThreadPoolExecutor(max_workers=MAX_QUERY_WORKERS) as executor:
family_pages = list(
executor.map(lambda job: fhir_observations(job[0], job[1], job[2], job[3]), family_jobs)
)
families = {code: [] for code in FAMILY_SYSTEMS}
for (code, _, _, _), resources in zip(family_jobs, family_pages):
families[code].extend(resources)
families = {code: unique_resources(resources) for code, resources in families.items()}
# The auxiliary clinical context is small, so fetch each exact synthetic code directly.
SYNTHETIC_SYSTEM = "urn:jupyterhealth:synthetic:alex-k:2026"
AUXILIARY_CODES = (
"source-demographics",
"hba1c",
"fasting_glucose",
"heart-rate-variability-rmssd",
"body-mass-index",
)
fhir_source = f"https://jupyterhealth.org/fhir/fhir-source/{FHIR_SOURCE_ID}"
with ThreadPoolExecutor(max_workers=MAX_QUERY_WORKERS) as executor:
auxiliary_pages = list(
executor.map(
lambda code: fhir_observations(code, SYNTHETIC_SYSTEM, source=fhir_source),
AUXILIARY_CODES,
)
)
auxiliary_observations = unique_resources(
resource for resources in auxiliary_pages for resource in resources
)## Decode raw records into data frames
def decode_attachments(resources):
decoded = []
for resource in resources:
payload = json.loads(base64.b64decode(resource['valueAttachment']['data']))
decoded.append(
{'id': resource['id'], 'header': payload['header'], 'body': payload['body']}
)
return decoded
def local_time(text):
value = datetime.fromisoformat(str(text))
return value if value.tzinfo else value.replace(tzinfo=STUDY_TIMEZONE)
def source_name(item):
provenance = item['header'].get('acquisition_provenance') or {}
return str(provenance.get('source_name') or 'Exchange')
def frame(rows, time_column='recorded_at'):
data = pd.DataFrame(rows)
if not data.empty:
data[time_column] = pd.to_datetime(data[time_column])
data = data.sort_values(time_column).reset_index(drop=True)
return data
def cgm_frame():
rows = []
for item in decode_attachments(families[CODE_BLOOD_GLUCOSE]):
body = item['body']
rows.append({
'recorded_at': local_time(body['effective_time_frame']['date_time']),
'mg_dl': float(body['blood_glucose']['value']),
'source': source_name(item),
})
return frame(rows)
def steps_frame():
rows = []
for item in decode_attachments(families[CODE_STEP_COUNT]):
body = item['body']
interval = body['effective_time_frame']['time_interval']
rows.append({
'recorded_at': local_time(interval['start_date_time']),
'steps': int(body['step_count']['value']),
'source': source_name(item),
})
return frame(rows)
def meals_frame():
rows = []
for item in decode_attachments(families[CODE_FOOD_ENTRY]):
body = item['body']
rows.append({
'meal_id': item['id'],
'occurred_at': local_time(body['effective_time_frame']['date_time']),
'label': str(body.get('food_name') or 'Meal'),
'meal_type': str(body.get('meal') or 'meal'),
'carbohydrate_g': float((body.get('carbohydrate') or {}).get('value') or 0),
'protein_g': float((body.get('protein') or {}).get('value') or 0),
'fat_g': float((body.get('fat') or {}).get('value') or 0),
'fiber_g': float((body.get('fiber') or {}).get('value') or 0),
'calories_kcal': float((body.get('calories') or {}).get('value') or 0),
'source': source_name(item),
})
data = frame(rows, 'occurred_at')
data['recorded_at'] = data['occurred_at']
return data
def sleep_frame():
rows = []
for item in decode_attachments(families[CODE_SLEEP_EPISODE]):
body = item['body']
interval = body['effective_time_frame']['time_interval']
rows.append({
'night_id': item['id'],
'start_at': local_time(interval['start_date_time']),
'end_at': local_time(interval['end_date_time']),
'recorded_at': local_time(interval['end_date_time']),
'total_sleep_minutes': float((body.get('total_sleep_time') or {}).get('value') or 0),
'efficiency_percent': float(
(body.get('sleep_efficiency_percentage') or {}).get('value') or 0
),
'deep_minutes': float((body.get('deep_sleep_duration') or {}).get('value') or 0),
'light_minutes': float((body.get('light_sleep_duration') or {}).get('value') or 0),
'rem_minutes': float((body.get('rem_sleep_duration') or {}).get('value') or 0),
'awakenings': int(body.get('number_of_awakenings') or 0),
'source': source_name(item),
})
return frame(rows)
def heart_rate_frame():
rows = []
for item in decode_attachments(families[CODE_HEART_RATE]):
body = item['body']
rows.append({
'recorded_at': local_time(body['effective_time_frame']['date_time']),
'bpm': float(body['heart_rate']['value']),
'source': source_name(item),
})
return frame(rows)
def body_frame(code, key):
rows = []
for item in decode_attachments(families[code]):
body = item['body']
rows.append({
'recorded_at': local_time(body['effective_time_frame']['date_time']),
'value': float(body[key]['value']),
'unit': str(body[key].get('unit') or ''),
'source': source_name(item),
})
return frame(rows)
def blood_pressure_frame():
rows = []
for item in decode_attachments(families[CODE_BLOOD_PRESSURE]):
body = item['body']
rows.append({
'recorded_at': local_time(body['effective_time_frame']['date_time']),
'systolic_mm_hg': float(body['systolic_blood_pressure']['value']),
'diastolic_mm_hg': float(body['diastolic_blood_pressure']['value']),
'source': source_name(item),
})
return frame(rows)
def auxiliary_frame():
rows = []
for resource in auxiliary_observations:
coding = (resource.get('code') or {}).get('coding') or [{}]
quantity = resource.get('valueQuantity') or {}
when = resource.get('effectiveDateTime')
if quantity.get('value') is None or when is None:
continue
rows.append({
'code': coding[0].get('code'),
'recorded_at': local_time(when),
'value': float(quantity['value']),
'unit': str(quantity.get('unit') or ''),
})
return frame(rows)
def demographic_values():
values = {}
for resource in auxiliary_observations:
coding = (resource.get('code') or {}).get('coding') or [{}]
if coding[0].get('code') != 'source-demographics':
continue
for component in resource.get('component') or []:
component_coding = (component.get('code') or {}).get('coding') or [{}]
code = component_coding[0].get('code')
if code == 'age-years':
values['age_years'] = int(component['valueQuantity']['value'])
elif code == 'administrative-sex':
gender_coding = (
(component.get('valueCodeableConcept') or {}).get('coding') or [{}]
)
values['gender'] = str(gender_coding[0].get('code') or '').lower()
return values
cgm = cgm_frame()
steps = steps_frame()
meals = meals_frame()
sleep = sleep_frame()
heart_rate = heart_rate_frame()
weights = body_frame(CODE_BODY_WEIGHT, 'body_weight')
heights = body_frame(CODE_BODY_HEIGHT, 'body_height')
blood_pressure = blood_pressure_frame()
auxiliary = auxiliary_frame()
demographics = demographic_values()## Prepare the baseline and follow-up sessions
def session_of(moment):
day = moment.date()
for window in SESSION_WINDOWS:
if window["start"] <= day <= window["end"]:
return window["name"]
return None
for active in (cgm, steps, meals, sleep, heart_rate):
active["session"] = active["recorded_at"].map(session_of)## Calculate the report metrics
BAND_KEYS = ('very_low', 'low', 'target', 'high', 'very_high')
VERY_LOW_BOUND = 54.0
LOW_BOUND = 70.0
TARGET_HIGH = 180.0
HIGH_BOUND = 250.0
GMI_INTERCEPT = 3.31
GMI_SLOPE = 0.02392
def mean_glucose(values):
return round(float(values.mean()), 1) if len(values) else None
def gmi_percent(values):
return round(GMI_INTERCEPT + GMI_SLOPE * float(values.mean()), 1) if len(values) else None
def coefficient_of_variation(values):
if len(values) < 2:
return None
return round(float(values.std(ddof=1) / values.mean() * 100), 1)
def time_active(recording):
stamps = sorted(set(recording['recorded_at']))
if not stamps:
return None
if len(stamps) == 1:
return 100.0
expected = int((stamps[-1] - stamps[0]).total_seconds() // 300) + 1
return round(min(100.0, len(stamps) / expected * 100), 1)
def time_in_ranges(values):
counts = {key: 0 for key in BAND_KEYS}
total = len(values)
for value in values:
if value < VERY_LOW_BOUND:
counts['very_low'] += 1
elif value < LOW_BOUND:
counts['low'] += 1
elif value <= TARGET_HIGH:
counts['target'] += 1
elif value <= HIGH_BOUND:
counts['high'] += 1
else:
counts['very_high'] += 1
return {key: (round(counts[key] / total * 100, 1) if total else 0.0) for key in BAND_KEYS}
def percentile_profile(recording, step_minutes=15):
buckets = {}
for moment, value in zip(recording['recorded_at'], recording['mg_dl']):
bucket = (moment.hour * 60 + moment.minute) // step_minutes * step_minutes
buckets.setdefault(bucket, []).append(float(value))
profile = []
for minute in sorted(buckets):
series = pd.Series(sorted(buckets[minute]))
profile.append({
'minute': minute,
'p05': round(float(series.quantile(0.05)), 1),
'p25': round(float(series.quantile(0.25)), 1),
'p50': round(float(series.quantile(0.50)), 1),
'p75': round(float(series.quantile(0.75)), 1),
'p95': round(float(series.quantile(0.95)), 1),
})
return profile
agp_by_session = {}
for window in SESSION_WINDOWS:
recording = cgm[cgm['session'] == window['name']]
values = recording['mg_dl']
agp_by_session[window['name']] = {
'metrics': {
'mean_mg_dl': mean_glucose(values),
'gmi_percent': gmi_percent(values),
'cv_percent': coefficient_of_variation(values),
},
'time_active_percent': time_active(recording),
'bands': time_in_ranges(values),
'percentiles': percentile_profile(recording),
}## Compare the previous and current measurements
def selected_measurements(points):
ordered = sorted(points, key=lambda item: item[0])
return ordered if SHOW_FOLLOW_UP else ordered[:1]
def aux_points(code):
rows = auxiliary[auxiliary["code"] == code]
return selected_measurements(list(zip(rows["recorded_at"], rows["value"])))
def weight_points():
return selected_measurements(list(zip(weights["recorded_at"], weights["value"])))
def height_points():
return selected_measurements(list(zip(heights["recorded_at"], heights["value"])))
def bmi_points():
return aux_points("body-mass-index")
def when(moment):
return moment.strftime("%b %-d, %Y") if moment is not None else "date not recorded"
def number(value, decimals):
return "n/a" if value is None else f"{value:.{decimals}f}"
def signed(delta, decimals):
return f"{delta:+.{decimals}f}".replace("-", "−")
def direction_of(delta, decimals):
if abs(delta) < 10 ** -(decimals + 1):
return "flat", "→"
return ("down", "↓") if delta < 0 else ("up", "↑")
def comparison_card(label, points, unit, decimals=1):
ordered = selected_measurements(points)
if not ordered:
return None
current_moment, current_value = ordered[-1]
card = {
"label": label,
"current": f"{number(current_value, decimals)} {unit}".strip(),
"current_date": when(current_moment),
"comparison": False,
"detail": f"One measurement · {when(current_moment)}",
}
if len(ordered) < 2:
return card
previous_moment, previous_value = ordered[-2]
delta = current_value - previous_value
direction, arrow = direction_of(delta, decimals)
card.update({
"comparison": True,
"previous": f"{number(previous_value, decimals)} {unit}".strip(),
"previous_date": when(previous_moment),
"delta": f"{signed(delta, decimals)} {unit}".strip(),
"direction": direction,
"arrow": arrow,
})
return card
def blood_pressure_card():
ordered = selected_measurements(
list(
zip(
blood_pressure["recorded_at"],
blood_pressure["systolic_mm_hg"],
blood_pressure["diastolic_mm_hg"],
)
)
)
if not ordered:
return None
moment, systolic, diastolic = ordered[-1]
card = {
"label": "Blood pressure",
"current": f"{systolic:.0f}/{diastolic:.0f} mmHg",
"current_date": when(moment),
"comparison": False,
"detail": f"One measurement · {when(moment)}",
}
if len(ordered) < 2:
return card
previous_moment = ordered[-2][0]
delta_systolic = systolic - ordered[-2][1]
delta_diastolic = diastolic - ordered[-2][2]
direction, arrow = direction_of(delta_systolic, 0)
card.update({
"comparison": True,
"previous": f"{ordered[-2][1]:.0f}/{ordered[-2][2]:.0f} mmHg",
"previous_date": when(previous_moment),
"delta": f"{signed(delta_systolic, 0)}/{signed(delta_diastolic, 0)} mmHg",
"direction": direction,
"arrow": arrow,
})
return card
context_cards = [
comparison_card("HbA1c", aux_points("hba1c"), "%", 1),
comparison_card("Fasting glucose", aux_points("fasting_glucose"), "mg/dL", 0),
comparison_card("HRV (RMSSD)", aux_points("heart-rate-variability-rmssd"), "ms", 0),
comparison_card("BMI", bmi_points(), "kg/m²", 1),
blood_pressure_card(),
comparison_card("Weight", weight_points(), "kg", 1),
comparison_card("Height", height_points(), "cm", 1),
]## Prepare the explorer and the dashboard view
ACTIVITY_BUCKET_MINUTES = 30
def activity_buckets(session):
rows = steps[steps['session'] == session]
if rows.empty:
return pd.DataFrame(columns=['bucket_at', 'steps'])
return (
rows.assign(bucket_at=rows['recorded_at'].dt.floor(f'{ACTIVITY_BUCKET_MINUTES}min'))
.groupby('bucket_at', as_index=False)['steps'].sum()
.sort_values('bucket_at')
.reset_index(drop=True)
)
def explorer_for(window):
name = window['name']
return {
'name': name,
'label': window['label'],
'glucose': cgm.loc[
cgm['session'] == name, ['recorded_at', 'mg_dl', 'session']
].reset_index(drop=True),
'meals': meals.loc[
meals['session'] == name,
['occurred_at', 'label', 'meal_type', 'carbohydrate_g', 'calories_kcal', 'session'],
].reset_index(drop=True),
'activity': activity_buckets(name).assign(session=name),
'sleep': sleep.loc[
sleep['session'] == name,
['start_at', 'end_at', 'deep_minutes', 'light_minutes', 'rem_minutes',
'efficiency_percent', 'total_sleep_minutes', 'session'],
].reset_index(drop=True),
}
# The native selector receives only the session(s) enabled by the presenter switch.
explorers = [explorer_for(window) for window in SESSION_WINDOWS]
def initials(name):
return ''.join(part[0] for part in name.split() if part)[:2].upper()
# Keep the live demographic fields, while protecting the demo's known male patient contract.
birth_date_text = str(patient.get("birthDate") or "1989-02-14")
birth_date = datetime.fromisoformat(birth_date_text).date()
report_date = date(2026, 10, 8)
calculated_age = report_date.year - birth_date.year - (
(report_date.month, report_date.day) < (birth_date.month, birth_date.day)
)
age_years = int(demographics.get("age_years") or calculated_age)
display_gender = str(
demographics.get("gender") or patient.get("gender") or "male"
).strip().lower()
date_span = f"{SESSION_WINDOWS[0]['start']:%b %-d} – {SESSION_WINDOWS[-1]['end']:%b %-d, %Y}"
dashboard_view = {
'patient': {
'name': 'Alex K.',
'initials': initials('Alex K.'),
'facts': [
f'{age_years} years · {display_gender}',
f"MRN {patient['id']} · {date_span}",
],
},
'context_cards': [card for card in context_cards if card],
'sessions': [
{
'name': window['name'],
'label': window['label'],
'period': f"14 days · {window['start']:%b %-d} – {window['end']:%b %-d, %Y}",
'time_active_percent': agp_by_session[window['name']]['time_active_percent'],
'metrics': agp_by_session[window['name']]['metrics'],
'bands': [
{'key': key, 'percent': percent}
for key, percent in agp_by_session[window['name']]['bands'].items()
],
'percentiles': agp_by_session[window['name']]['percentiles'],
}
for window in SESSION_WINDOWS
],
'explorers': explorers,
'provenance': {
'study': f"{study['name']} ({study['id']})",
'fhir_source_id': FHIR_SOURCE_ID,
},
}## Compare postprandial responses
# Postprandial response comparison for the prescribed meals (lunch and dinner).
# Fixed definitions from the project decision: the pre-meal baseline is the mean CGM over
# -30 to 0 minutes, the response window is 0 to +180 minutes, and iAUC is the trapezoidal
# integral of the positive glucose increment above that baseline.
PPGR_STEP_MINUTES = 5
PPGR_BASELINE_OFFSETS = tuple(range(-30, 1, PPGR_STEP_MINUTES))
PPGR_RESPONSE_OFFSETS = tuple(range(0, 181, PPGR_STEP_MINUTES))
PPGR_OFFSETS = tuple(range(-30, 181, PPGR_STEP_MINUTES))
PPGR_MEAL_TYPES = ('lunch', 'dinner')
PPGR_WALK_WINDOW_MINUTES = 10
PPGR_MIN_BIN_STEPS = 60
PPGR_MIN_WALK_STEPS = 700
def glucose_by_time(session):
rows = cgm[cgm['session'] == session]
return dict(zip(rows['recorded_at'], rows['mg_dl'].astype(float)))
def steps_by_time(session):
rows = steps[steps['session'] == session]
return dict(zip(rows['recorded_at'], rows['steps'].astype(float)))
def meal_ppgr(start, glucose):
moments = {minute: start + timedelta(minutes=minute) for minute in PPGR_OFFSETS}
if any(moment not in glucose for moment in moments.values()):
return {'eligible': False, 'exclusion_reason': 'incomplete -30 to +180 min CGM coverage'}
baseline = sum(glucose[moments[m]] for m in PPGR_BASELINE_OFFSETS) / len(PPGR_BASELINE_OFFSETS)
increments = [glucose[moments[m]] - baseline for m in PPGR_OFFSETS]
positive = [max(0.0, glucose[moments[m]] - baseline) for m in PPGR_RESPONSE_OFFSETS]
iauc = sum(
(positive[index] + positive[index + 1]) * PPGR_STEP_MINUTES / 2
for index in range(len(positive) - 1)
)
return {
'eligible': True,
'exclusion_reason': None,
'baseline_mg_dl': baseline,
'increments': increments,
'iauc': iauc,
}
def aligned_steps(start, activity):
return [activity.get(start + timedelta(minutes=minute), 0.0) for minute in PPGR_OFFSETS]
def walk_detected(start, activity):
# Demo rule: a walk starts within 15 minutes after the meal, and the next 10 minutes hold
# at least 8 one-minute bins at 60+ steps and at least 700 steps in total.
for delay in range(16):
window = [
activity.get(start + timedelta(minutes=delay + offset), 0.0)
for offset in range(PPGR_WALK_WINDOW_MINUTES)
]
enough_bins = sum(value >= PPGR_MIN_BIN_STEPS for value in window) >= 8
if enough_bins and sum(window) >= PPGR_MIN_WALK_STEPS:
return True
return False
def column_stats(columns):
frame = pd.DataFrame(columns)
return {
'mean': [round(float(value), 1) for value in frame.mean(axis=0)],
'p25': [round(float(value), 1) for value in frame.quantile(0.25, axis=0)],
'p75': [round(float(value), 1) for value in frame.quantile(0.75, axis=0)],
}if SHOW_FOLLOW_UP:
ppgr_sessions = []
for window in SESSION_WINDOWS:
name = window['name']
glucose = glucose_by_time(name)
activity = steps_by_time(name)
prescribed = meals[(meals['session'] == name) & meals['meal_type'].isin(PPGR_MEAL_TYPES)]
curves, activity_curves, iauc_values = [], [], []
excluded = 0
for row in prescribed.itertuples():
metric = meal_ppgr(row.occurred_at, glucose)
if not metric['eligible']:
excluded += 1
continue
curves.append(metric['increments'])
iauc_values.append(metric['iauc'])
activity_curves.append(aligned_steps(row.occurred_at, activity))
glucose_stats = column_stats(curves)
activity_stats = column_stats(activity_curves)
ppgr_sessions.append({
'name': name,
'label': window['label'],
'glucose_mean': glucose_stats['mean'],
'glucose_low': glucose_stats['p25'],
'glucose_high': glucose_stats['p75'],
'activity_mean': activity_stats['mean'],
'iauc_values': [round(value, 1) for value in iauc_values],
'mean_iauc': round(sum(iauc_values) / len(iauc_values), 1),
'median_iauc': round(float(pd.Series(iauc_values).median()), 1),
'total_meals': len(prescribed),
'eligible_meals': len(iauc_values),
'excluded_meals': excluded,
})
post_meal_minutes = [offset for offset in PPGR_OFFSETS if 0 <= offset <= 30]
def mean_post_meal_steps(session):
values = [session['activity_mean'][PPGR_OFFSETS.index(minute)] for minute in post_meal_minutes]
return round(sum(values) / len(values), 1)
for session in ppgr_sessions:
session['mean_activity_first_30'] = mean_post_meal_steps(session)
baseline, follow_up = ppgr_sessions
iauc_delta = round(follow_up['mean_iauc'] - baseline['mean_iauc'], 1)
iauc_percent = round(iauc_delta / baseline['mean_iauc'] * 100, 1) if baseline['mean_iauc'] else None
baseline_median = baseline['median_iauc']
non_improving = [value for value in follow_up['iauc_values'] if value >= baseline_median]
adherence = {}
for window in SESSION_WINDOWS:
name = window['name']
activity = steps_by_time(name)
prescribed = meals[(meals['session'] == name) & meals['meal_type'].isin(PPGR_MEAL_TYPES)]
walks = [walk_detected(row.occurred_at, activity) for row in prescribed.itertuples()]
adherence[name] = {'walks': sum(walks), 'meals': len(walks)}
dashboard_view['ppgr_comparison'] = {
'x_minutes': list(PPGR_OFFSETS),
'sessions': ppgr_sessions,
'comparison': {
'mean_iauc_delta': iauc_delta,
'mean_iauc_percent': iauc_percent,
'non_improving_meals': len(non_improving),
'compared_meals': follow_up['eligible_meals'],
'comparator_value': baseline_median,
},
'adherence': {
'baseline': adherence['baseline'],
'follow_up': adherence['follow_up'],
},
}## Inspect the results
if "VOILA_REQUEST_URL" in os.environ:
# don't show in voila
def show(*args):
pass
else:
show = display
show(pd.DataFrame([
{
'session': session['label'],
'mean_mg_dl': session['metrics']['mean_mg_dl'],
'gmi_percent': session['metrics']['gmi_percent'],
'cv_percent': session['metrics']['cv_percent'],
'time_active_percent': session['time_active_percent'],
}
for session in dashboard_view['sessions']
]))
show(meals.loc[
meals['session'].notna(),
['session', 'occurred_at', 'meal_type', 'carbohydrate_g', 'calories_kcal'],
])
show(auxiliary if SHOW_FOLLOW_UP else auxiliary.groupby('code', sort=False).head(1))## Build the dashboard
from IPython.display import display
from jupyterhealth_dashboard.ui import build_dashboard
dashboard = build_dashboard(dashboard_view)
display(dashboard)