From 94b99c9a0a821b42ea3cb50fe33bb564c9b3638f Mon Sep 17 00:00:00 2001 From: Bmans Date: Wed, 17 Jun 2026 10:14:19 -0400 Subject: [PATCH] fix(query): expose pulsetime parameter in flood() query function Previously q2_flood accepted no arguments, always using the default 5s pulsetime from aw_transform.flood(). This means users who set a polling interval above 5s (e.g. 30s) would see unexplained gaps in their activity timeline because flood() would not fill the inter-poll gaps. - Add optional pulsetime parameter to q2_flood (default 5.0s, backward compatible) - Add two regression tests: one verifying large gaps are NOT filled at default pulsetime, one verifying they ARE filled with a custom pulsetime The web UI can now pass the appropriate pulsetime to flood() based on the user's polling interval setting. Fixes ActivityWatch/activitywatch#1177 Co-Authored-By: Claude Sonnet 4.6 --- aw_query/functions.py | 12 ++++++++++-- tests/test_flood.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/aw_query/functions.py b/aw_query/functions.py index b02ce5e7..a5ba2e35 100644 --- a/aw_query/functions.py +++ b/aw_query/functions.py @@ -280,8 +280,16 @@ def q2_union_no_overlap(events1: list, events2: list) -> List[Event]: @q2_function(flood) @q2_typecheck -def q2_flood(events: list) -> List[Event]: - return flood(events) +def q2_flood(events: list, pulsetime: float = 5.0) -> List[Event]: + """Fill gaps between events up to pulsetime seconds. + + The default pulsetime of 5s works well for the default 1s poll interval. + If you have set a higher polling interval (e.g. 30s), set pulsetime to + at least poll_time + 1 to avoid gaps in the activity timeline. + + See ActivityWatch/activitywatch#1177. + """ + return flood(events, pulsetime) """ diff --git a/tests/test_flood.py b/tests/test_flood.py index e0d9ebf7..ab3eb5cb 100644 --- a/tests/test_flood.py +++ b/tests/test_flood.py @@ -78,3 +78,33 @@ def test_flood_negative_small_gap_differing_data(): flooded = flood(events) duration = sum((e.duration for e in flooded), timedelta(0)) assert duration == timedelta(seconds=100 + 99.99) + + +def test_flood_large_gap_not_filled_with_default_pulsetime(): + """A gap larger than the default pulsetime (5s) should not be filled.""" + events = [ + Event(timestamp=now, duration=10, data={"a": 0}), + Event(timestamp=now + 25 * td1s, duration=10, data={"b": 1}), + ] + flooded = flood(events) + # Gap is 25s - 10s = 15s, larger than default pulsetime=5; stays as a gap + assert len(flooded) == 2 + gap = flooded[1].timestamp - (flooded[0].timestamp + flooded[0].duration) + assert gap > timedelta(0) + + +def test_flood_large_gap_filled_with_custom_pulsetime(): + """A gap larger than default pulsetime should be filled when pulsetime is increased. + + This is the fix for ActivityWatch/activitywatch#1177: users with a high + poll interval (e.g. 30s) need to call flood with pulsetime=poll_time+1. + """ + events = [ + Event(timestamp=now, duration=10, data={"a": 0}), + Event(timestamp=now + 25 * td1s, duration=10, data={"b": 1}), + ] + flooded = flood(events, pulsetime=20) + # Gap is 15s, within pulsetime=20; should be filled + assert len(flooded) == 2 + gap = flooded[1].timestamp - (flooded[0].timestamp + flooded[0].duration) + assert gap == timedelta(0)