Skip to content

fix(query): expose pulsetime parameter in flood() query function#139

Open
behdadmansouri wants to merge 1 commit into
ActivityWatch:masterfrom
behdadmansouri:fix/flood-pulsetime-configurable
Open

fix(query): expose pulsetime parameter in flood() query function#139
behdadmansouri wants to merge 1 commit into
ActivityWatch:masterfrom
behdadmansouri:fix/flood-pulsetime-configurable

Conversation

@behdadmansouri

Copy link
Copy Markdown

Summary

Fixes ActivityWatch/activitywatch#1177

The flood() query function previously accepted no arguments, always using the hardcoded default pulsetime of 5 seconds from aw_transform.flood(). Users who raised their watcher polling interval above 5s (e.g. to 30s) would see unexplained gaps — the inter-poll gap exceeds the pulsetime so flood never fills it. A user working 8h could see only 7.5h reported.

Changes:

  • aw_query/functions.py: Add optional pulsetime: float = 5.0 to q2_flood so queries can call flood(events, 30) for a 30s polling interval. Default of 5.0 is fully backward-compatible.
  • tests/test_flood.py: Add two regression tests — one confirming large gaps are not filled at default pulsetime, one confirming they are filled when pulsetime is raised.

Test plan

  • Run pytest tests/test_flood.py — all tests pass including the two new ones
  • Existing query calls flood(events) continue to work unchanged (default pulsetime=5)
  • A query with flood(events, 31) correctly fills 30s gaps

Note: This pairs with a web UI change in ActivityWatch/aw-webui that exposes a "Flood pulsetime" setting in Developer Settings and threads it into all query flood() calls.

🤖 Implemented with Claude Code — AI assistance disclosed per contribution guidelines.

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 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR exposes the pulsetime parameter in the flood() query function, allowing users with high polling intervals (e.g. 30 s) to pass a matching pulsetime so that inter-poll gaps are filled correctly. The default of 5.0 is preserved, making the change fully backward-compatible.

  • aw_query/functions.py: q2_flood gains an optional pulsetime: float = 5.0 parameter that is forwarded directly to aw_transform.flood().
  • tests/test_flood.py: Two regression tests are added — one confirming large gaps are not filled at the default pulsetime, and one confirming they are filled when pulsetime is raised — but both test aw_transform.flood directly, not the q2_flood wrapper.

Confidence Score: 4/5

Safe to merge; the change is a small, backward-compatible addition with no modifications to core logic.

The wiring is straightforward and the default preserves existing behaviour. The two new tests exercise the underlying transform function rather than the query wrapper, so the query-layer path is untested. There is also no type guard for the new optional parameter, which would surface as a cryptic runtime error on bad input rather than a clean query exception.

tests/test_flood.py — the new tests don't cover q2_flood itself.

Important Files Changed

Filename Overview
aw_query/functions.py Adds optional pulsetime: float = 5.0 to q2_flood, passing it through to aw_transform.flood. Change is backward-compatible; optional parameters are not validated by q2_typecheck, leaving an unguarded type path.
tests/test_flood.py Adds two regression tests for large-gap flood behaviour; both call aw_transform.flood directly rather than the changed q2_flood wrapper, so the query-layer wiring is not covered.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User as Query caller
    participant q2_flood as q2_flood (functions.py)
    participant flood as aw_transform.flood (flood.py)

    User->>q2_flood: "flood(events, pulsetime=30)"
    Note over q2_flood: pulsetime: float = 5.0 (default)
    q2_flood->>flood: "flood(events, pulsetime=30)"
    flood-->>q2_flood: List[Event] (gaps ≤ 30 s filled)
    q2_flood-->>User: List[Event]

    Note over User,flood: Before this PR: flood(events) always called flood(events) with hardcoded default 5 s
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User as Query caller
    participant q2_flood as q2_flood (functions.py)
    participant flood as aw_transform.flood (flood.py)

    User->>q2_flood: "flood(events, pulsetime=30)"
    Note over q2_flood: pulsetime: float = 5.0 (default)
    q2_flood->>flood: "flood(events, pulsetime=30)"
    flood-->>q2_flood: List[Event] (gaps ≤ 30 s filled)
    q2_flood-->>User: List[Event]

    Note over User,flood: Before this PR: flood(events) always called flood(events) with hardcoded default 5 s
Loading

Reviews (1): Last reviewed commit: "fix(query): expose pulsetime parameter i..." | Re-trigger Greptile

Comment thread tests/test_flood.py
Comment on lines +83 to +110
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 New tests exercise aw_transform.flood directly, not q2_flood

Both new tests import and call flood from aw_transform, bypassing the q2_flood wrapper in aw_query/functions.py that was actually changed. A bug in how pulsetime is threaded through q2_flood (e.g. a copy-paste that keeps the old return flood(events)) would leave these tests green while the query API remains broken. Adding a test (or even a smoke-test) that calls q2_flood or exercises it through the query engine would close this gap.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread aw_query/functions.py
Comment on lines +283 to +292
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 pulsetime type is not validated by q2_typecheck

The q2_typecheck decorator only enforces type annotations on required parameters (param.default == param.empty). Because pulsetime has a default value of 5.0, it is silently skipped. If a user calls flood(events, "30") in a query, Python will pass a string into aw_transform.flood, which then calls timedelta(seconds="30"), producing a cryptic TypeError instead of a QueryFunctionException. A manual guard (if not isinstance(pulsetime, (int, float)): raise QueryFunctionException(...)) before the return would give users a clear error message.

behdadmansouri added a commit to behdadmansouri/aw-webui that referenced this pull request Jun 18, 2026
…h high poll intervals

Users who raise the watcher polling interval above 5s lose trackable time because the
default 5s pulsetime in flood() no longer fills the inter-poll gaps. A user working 8h
could see only 7.5h in the dashboard.

Changes:
- queries.ts: add optional floodPulsetime to BaseQueryParams (default 5);
  pass it as the explicit second argument to all flood() calls in
  canonicalEvents() and browserEvents()
- settings.ts: add floodPulsetime: number (default 5, persisted like
  other settings)
- stores/activity.ts: thread useSettingsStore().floodPulsetime into both
  fullDesktopQuery and multideviceQuery param objects
- DeveloperSettings.vue: add a number input for floodPulsetime with
  guidance text explaining the poll_interval + 1 rule

The default of 5 is fully backward-compatible. Users who increased their
polling interval can now set this to polling_interval + 1 in Developer
Settings and immediately recover the missing time.

Pairs with aw-core fix: ActivityWatch/aw-core#139
Fixes ActivityWatch/activitywatch#1177

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fundamental flaw in duration calculation

1 participant