Skip to content

fix(research): classify by longest matching pattern, not map order - #138

Open
TimeToBuildBob wants to merge 3 commits into
ActivityWatch:masterfrom
TimeToBuildBob:fix/research-longest-match
Open

fix(research): classify by longest matching pattern, not map order#138
TimeToBuildBob wants to merge 3 commits into
ActivityWatch:masterfrom
TimeToBuildBob:fix/research-longest-match

Conversation

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Problem

classify_title matched study category patterns in insertion order, first
match wins. Study maps routinely list both a generic and a specific form of the
same host:

entry pattern category
64 google.com Search & Navigation
160 docs.google.com Work & Productivity

google.com is a substring of docs.google.com, so every Google Workspace URL
(Docs, Drive, Calendar, Meet) classified as Search & Navigation.

Found during a Research Edition field test — a participant's "Top Applications"
view showed Workspace time filed as Search. The macOS swift strategy captures
URLs via accessibility APIs, so macOS participants hit this on every Workspace
page.

Fix

Select the longest matching pattern instead of the first, in both twins:

  • aw_watcher_window/research_filter.py — new _longest_match() helper, used
    for both the URL and title passes.
  • aw_watcher_window/macos.swiftlongestResearchMatch(), same semantics, so
    the macOS capture path stays consistent with the Python filter.

Behaviour notes:

  • Ties resolve to map order, so maps that contain only one form of each host
    are unaffected.
  • Empty pattern keys are now ignored. Previously "" in haystack was always
    true, so a stray empty key swallowed every window.

Tests

6 regression tests in tests/test_research_filter.py, covering specific-host
precedence on both the URL and title paths, plain google.com/search still
matching the generic entry, tie resolution, and the empty-key guard.

All 6 fail against the previous implementation; the full 50-test suite passes
with the fix. mypy aw_watcher_window/ --ignore-missing-imports clean.

Study category maps list both a generic and a specific form of the same
host: `google.com` -> Search & Navigation appears well before
`docs.google.com` -> Work & Productivity. Because classify_title matched
in insertion order, every Google Workspace URL (Docs, Drive, Calendar,
Meet) was filed under Search & Navigation.

Reported from a Research Edition field test: the participant's Top
Applications view showed Workspace time as Search.

Select the longest matching pattern instead, in both the Python filter
and its Swift twin used by the macOS capture path. Ties resolve to map
order, so existing single-form maps behave exactly as before. Empty
pattern keys are now ignored rather than matching everything.

6 regression tests added; all fail against the previous implementation.
@greptile-apps

greptile-apps Bot commented Aug 26, 2026

Copy link
Copy Markdown

Greptile Summary

The PR changes research classification to select the longest matching URL or title pattern while preserving map order for ties.

  • Applies equivalent longest-match selection in the shared Python filter and native macOS helper.
  • Ignores empty category-map patterns.
  • Adds regression coverage and connects the repository’s pytest suite to make test.
  • Makes configuration-test directory isolation platform-independent.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
aw_watcher_window/research_filter.py Adds longest-pattern classification for URL and title matching while ignoring empty keys.
aw_watcher_window/macos.swift Mirrors longest-pattern classification in the native macOS research path.
tests/test_research_filter.py Covers specific-host precedence, tie resolution, URL/title behavior, and empty patterns.
Makefile Adds the complete pytest suite to the CI-facing test target.
tests/test_config.py Replaces Linux-specific environment isolation with a cross-platform configuration-directory patch.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Captured browser activity] --> B{URL has a configured match?}
  B -->|Yes| C[Select longest URL pattern]
  B -->|No| D[Select longest title pattern]
  C --> E[Resolve equal lengths by map order]
  D --> E
  E --> F[Store configured category]
  E -->|No match| G[Store excluded]
Loading

Reviews (2): Last reviewed commit: "fix(tests): use cross-platform config di..." | Re-trigger Greptile

Comment on lines +78 to +80
pattern_lower = pattern.lower()
if pattern_lower and pattern_lower in haystack and len(pattern_lower) > best_len:
best_category = category

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 Unicode ranking semantics diverge

If a category map contains overlapping non-ASCII patterns, Python ranks lowercased code points while Swift ranks original extended grapheme clusters and uses different case-folding semantics, producing different research categories for the same activity across platforms.

Knowledge Base Used:

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!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4906753. replaces in so Swift matches Python's code-point length semantics. For the study maps in use today (ASCII domain names) there is no behavioural difference, but the fix prevents divergence when non-ASCII patterns are added.

transform(window, self.CATEGORY_MAP, self.APP_CATEGORY_MAP)
self.assertEqual(window, original)

class TestSpecificHostBeatsGenericHost(unittest.TestCase):

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 Regression tests bypass CI

The new longest-match regression tests are not run by make test or the pull-request workflow, so CI remains green when these classification cases fail.

Knowledge Base Used: Quality automation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4906753. Added poetry run python -m pytest tests/ to the Makefile test target — the same target the CI workflow calls with make test. Both the 44 pre-existing tests and the 6 new regression cases now run on every push across ubuntu, macOS, and Windows.

Two findings from review:

- Swift's String.count measures grapheme clusters while Python's len()
  measures code points, so the two longest-match twins could pick
  different winners for non-ASCII patterns of equal visual length.
  Compare unicodeScalars.count instead.

- 'make test' only ran '--help' and mypy, so tests/ has never executed
  in CI — not the new regression cases, and not the 44 that predate
  them. Add pytest to the test target.
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Both review findings addressed in 4906753:

Unicode length mismatch — Swift's String.count measures grapheme clusters, Python's len() measures code points, so the twins could rank non-ASCII patterns of equal visual length differently. Swift now compares pattern.unicodeScalars.count, matching Python.

Tests not run in CI — correct, and the gap is wider than this PR: make test ran only aw-watcher-window --help and mypy, so tests/ has never executed in CI — not the new cases, and not the 44 that predate them. Added poetry run python -m pytest tests/ to the test target, which the build workflow already calls on all three platforms. pytest is already a dev dependency.

Local: 50/50 green, mypy clean.

…_HOME

XDG_CONFIG_HOME is Linux-only; appdirs ignores it on macOS and Windows,
so the two config tests that needed a controlled config dir were failing
on those platforms (StopIteration / False is True assertions).

Replace the env-var approach with a monkeypatch of aw_core.dirs.get_config_dir
that redirects to tmp_path on all platforms. Apply consistently across all
three XDG_CONFIG_HOME test sites.
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

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.

1 participant