From a76c99d6ab8db875c0d404d4bf63cb6e6c773391 Mon Sep 17 00:00:00 2001 From: stellarshenson Date: Sun, 25 Jan 2026 11:50:19 +0100 Subject: [PATCH] feat: increase activity monitor half-life to 72 hours (3 days) Changed JUPYTERHUB_ACTIVITYMON_HALF_LIFE default from 48h to 72h for more stable activity scores. Activity from 3 days ago now has 50% weight, better suited for users with irregular schedules. Updated: Dockerfile, custom_handlers.py, activity_sampler.py, settings_dictionary.yml, README.md, docs/activity-tracking-methodology.md --- .claude/JOURNAL.md | 3 +++ README.md | 2 +- docs/activity-tracking-methodology.md | 11 +++++------ 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 +- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.claude/JOURNAL.md b/.claude/JOURNAL.md index 3365e21..131e329 100644 --- a/.claude/JOURNAL.md +++ b/.claude/JOURNAL.md @@ -249,3 +249,6 @@ This journal tracks substantive work on documents, diagrams, and documentation c 82. **Task - Simplify Activity column header**: Removed retention period from column title
**Result**: Changed Activity column header from "Activity (7 days)" to just "Activity" in activity.html + +83. **Task - Activity monitor half-life to 72h (3 days)**: Extended decay half-life for smoother scoring
+ **Result**: Updated JUPYTERHUB_ACTIVITYMON_HALF_LIFE default from 48h to 72h across Dockerfile, custom_handlers.py, activity_sampler.py, settings_dictionary.yml, README.md, and docs/activity-tracking-methodology.md. With 72h half-life, activity from 3 days ago has 50% weight, providing more stable activity scores for users with irregular schedules diff --git a/README.md b/README.md index c0c483b..5bee13e 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=48 # 48 hours (default) - decay half-life for scoring + - JUPYTERHUB_ACTIVITYMON_HALF_LIFE=72 # 72 hours / 3 days (default) - decay half-life for scoring - JUPYTERHUB_ACTIVITYMON_INACTIVE_AFTER=60 # 60 minutes (default) - threshold for inactive status ``` diff --git a/docs/activity-tracking-methodology.md b/docs/activity-tracking-methodology.md index a02c447..3205ed0 100644 --- a/docs/activity-tracking-methodology.md +++ b/docs/activity-tracking-methodology.md @@ -7,7 +7,7 @@ Our current approach uses **exponential decay scoring**: - Each sample marked active/inactive based on `last_activity` within threshold - Score calculated as weighted ratio: `weighted_active / weighted_total` - Weight formula: `weight = exp(-λ × age_hours)` where `λ = ln(2) / half_life` -- Default half-life: 24 hours (activity from yesterday worth 50%) +- Default half-life: 72 hours / 3 days (activity from 3 days ago worth 50%) ## Industry Approaches @@ -157,14 +157,13 @@ Our current implementation is actually well-designed for the use case: |--------|------------------------| | Sampling | Every 10 min (configurable) | | Active threshold | 60 min since last_activity | -| Decay | 24-hour half-life | +| Decay | 72-hour (3-day) half-life | | Score range | 0-100% | | Visualization | 5-segment bar with color coding | **Suggested improvements:** 1. Add tooltip showing actual score percentage -2. Consider longer half-life (48-72h) for less frequent users -3. Document what the score represents +2. Document what the score represents ### Option B: Hybrid Daily + Decay @@ -205,8 +204,8 @@ Weekly score = sum of daily points / 7 - Option: Relative to user's own historical average 2. **How fast should old activity decay?** - - Current: 24-hour half-life (aggressive decay) - - Alternative: 72-hour half-life (more stable) + - Current: 72-hour / 3-day half-life (balanced decay) + - Alternative: 24-hour half-life (aggressive decay) - Alternative: 7-day half-life (weekly trend) 3. **Should weekends count differently?** diff --git a/services/jupyterhub/Dockerfile.jupyterhub b/services/jupyterhub/Dockerfile.jupyterhub index 54feaf9..bb602cd 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=48 +ENV JUPYTERHUB_ACTIVITYMON_HALF_LIFE=72 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 6ff0e8d..f0e1d72 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: 48) + JUPYTERHUB_ACTIVITYMON_HALF_LIFE: Score decay half-life in hours (default: 72) 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', 48)) + self.half_life_hours = int(os.environ.get('JUPYTERHUB_ACTIVITYMON_HALF_LIFE', 72)) 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 bdf0d8c..dc9908a 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 = 48 # 48 hours + DEFAULT_HALF_LIFE = 72 # 72 hours (3 days) 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 83a9c57..917a692 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: "48" + default: "72" - name: JUPYTERHUB_ACTIVITYMON_INACTIVE_AFTER description: Minutes until user considered inactive (1-1440)