From 4d9a97eb82e95682e6293b8a47adb087fc94c968 Mon Sep 17 00:00:00 2001 From: stellarshenson Date: Thu, 22 Jan 2026 01:52:01 +0100 Subject: [PATCH] feat: increase activity monitor half-life to 48 hours Changed JUPYTERHUB_ACTIVITYMON_HALF_LIFE default from 24h to 48h for smoother decay in activity scoring. A sample from 48 hours ago now has 50% weight (was 50% at 24h). --- .claude/JOURNAL.md | 3 +++ README.md | 2 +- services/jupyterhub/Dockerfile.jupyterhub | 2 +- services/jupyterhub/conf/bin/activity_sampler.py | 4 ++-- services/jupyterhub/conf/bin/custom_handlers.py | 2 +- services/jupyterhub/conf/settings_dictionary.yml | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.claude/JOURNAL.md b/.claude/JOURNAL.md index 1dd9976..0fd3a22 100644 --- a/.claude/JOURNAL.md +++ b/.claude/JOURNAL.md @@ -240,3 +240,6 @@ This journal tracks substantive work on documents, diagrams, and documentation c 79. **Task - Activity tracking methodology research**: Documented industry approaches for activity scoring
**Result**: Created `docs/activity-tracking-methodology.md` covering: (1) Exponential Moving Average with decay - our current approach using half-life parameterization, (2) Time-Window Percentage (Hubstaff) - active seconds per 10-min window with 60-80% typical for development, (3) Daily Target (8h=100%) - maps to work expectations, (4) GitHub Contribution Graph - threshold-based intensity levels. Research confirms our EMA approach is industry-standard. Key insight from Hubstaff: 100% activity is unrealistic, typical ranges 30-80% depending on role + +80. **Task - Activity monitor half-life to 48h**: Changed decay half-life default from 24h to 48h
+ **Result**: Updated JUPYTERHUB_ACTIVITYMON_HALF_LIFE default across Dockerfile, custom_handlers.py (DEFAULT_HALF_LIFE constant), activity_sampler.py (code and docstring), settings_dictionary.yml, and README.md. With 48h half-life, a sample from 48 hours ago has 50% weight, providing smoother decay for activity scoring diff --git a/README.md b/README.md index 0d87bf3..c0c483b 100644 --- a/README.md +++ b/README.md @@ -392,7 +392,7 @@ services: environment: - JUPYTERHUB_ACTIVITYMON_SAMPLE_INTERVAL=600 # 10 minutes (default) - how often to record samples - JUPYTERHUB_ACTIVITYMON_RETENTION_DAYS=7 # 7 days (default) - how long to keep samples - - JUPYTERHUB_ACTIVITYMON_HALF_LIFE=24 # 24 hours (default) - decay half-life for scoring + - JUPYTERHUB_ACTIVITYMON_HALF_LIFE=48 # 48 hours (default) - decay half-life for scoring - JUPYTERHUB_ACTIVITYMON_INACTIVE_AFTER=60 # 60 minutes (default) - threshold for inactive status ``` diff --git a/services/jupyterhub/Dockerfile.jupyterhub b/services/jupyterhub/Dockerfile.jupyterhub index e415a64..54feaf9 100644 --- a/services/jupyterhub/Dockerfile.jupyterhub +++ b/services/jupyterhub/Dockerfile.jupyterhub @@ -90,7 +90,7 @@ ENV JUPYTERHUB_IDLE_CULLER_MAX_EXTENSION=24 # Activity monitor ENV JUPYTERHUB_ACTIVITYMON_SAMPLE_INTERVAL=600 ENV JUPYTERHUB_ACTIVITYMON_RETENTION_DAYS=7 -ENV JUPYTERHUB_ACTIVITYMON_HALF_LIFE=24 +ENV JUPYTERHUB_ACTIVITYMON_HALF_LIFE=48 ENV JUPYTERHUB_ACTIVITYMON_INACTIVE_AFTER=60 ENV JUPYTERHUB_ACTIVITYMON_RESOURCES_UPDATE_INTERVAL=10 ENV JUPYTERHUB_ACTIVITYMON_VOLUMES_UPDATE_INTERVAL=3600 diff --git a/services/jupyterhub/conf/bin/activity_sampler.py b/services/jupyterhub/conf/bin/activity_sampler.py index 706b31b..6ff0e8d 100644 --- a/services/jupyterhub/conf/bin/activity_sampler.py +++ b/services/jupyterhub/conf/bin/activity_sampler.py @@ -14,7 +14,7 @@ Environment Variables: JUPYTERHUB_API_URL: Hub API URL (provided by JupyterHub) JUPYTERHUB_ACTIVITYMON_SAMPLE_INTERVAL: Sampling interval in seconds (default: 600) JUPYTERHUB_ACTIVITYMON_RETENTION_DAYS: Days to retain samples (default: 7) - JUPYTERHUB_ACTIVITYMON_HALF_LIFE: Score decay half-life in hours (default: 24) + JUPYTERHUB_ACTIVITYMON_HALF_LIFE: Score decay half-life in hours (default: 48) JUPYTERHUB_ACTIVITYMON_INACTIVE_AFTER: Minutes before marking inactive (default: 60) """ @@ -71,7 +71,7 @@ class ActivitySamplerService: # Sampling configuration self.sample_interval = int(os.environ.get('JUPYTERHUB_ACTIVITYMON_SAMPLE_INTERVAL', 600)) self.retention_days = int(os.environ.get('JUPYTERHUB_ACTIVITYMON_RETENTION_DAYS', 7)) - self.half_life_hours = int(os.environ.get('JUPYTERHUB_ACTIVITYMON_HALF_LIFE', 24)) + self.half_life_hours = int(os.environ.get('JUPYTERHUB_ACTIVITYMON_HALF_LIFE', 48)) self.inactive_after_minutes = int(os.environ.get('JUPYTERHUB_ACTIVITYMON_INACTIVE_AFTER', 60)) # Database diff --git a/services/jupyterhub/conf/bin/custom_handlers.py b/services/jupyterhub/conf/bin/custom_handlers.py index b66ee79..1cda88c 100755 --- a/services/jupyterhub/conf/bin/custom_handlers.py +++ b/services/jupyterhub/conf/bin/custom_handlers.py @@ -94,7 +94,7 @@ class ActivityMonitor: # Default configuration DEFAULT_RETENTION_DAYS = 7 # 7 days - DEFAULT_HALF_LIFE = 24 # 24 hours + DEFAULT_HALF_LIFE = 48 # 48 hours DEFAULT_INACTIVE_AFTER = 60 # 60 minutes DEFAULT_ACTIVITY_UPDATE_INTERVAL = 600 # 10 minutes diff --git a/services/jupyterhub/conf/settings_dictionary.yml b/services/jupyterhub/conf/settings_dictionary.yml index b590e14..83a9c57 100644 --- a/services/jupyterhub/conf/settings_dictionary.yml +++ b/services/jupyterhub/conf/settings_dictionary.yml @@ -87,7 +87,7 @@ Activity Monitor: - name: JUPYTERHUB_ACTIVITYMON_HALF_LIFE description: Decay half-life in hours (1-168) - default: "24" + default: "48" - name: JUPYTERHUB_ACTIVITYMON_INACTIVE_AFTER description: Minutes until user considered inactive (1-1440)