From fdae9b249d01fe225f8bdf28178dcf658f7afbd9 Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 29 Sep 2026 13:06:09 +0000 Subject: [PATCH 1/3] fix(android-ratings): drop 0 'no data' rating that plotted as a spike --- analyze_stats.py | 1 + android_ratings.py | 11 ++++++++++- data/android-ratings.csv | 1 - test_android_ratings.py | 9 +++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 test_android_ratings.py diff --git a/analyze_stats.py b/analyze_stats.py index 19a01ac1..8292fba9 100644 --- a/analyze_stats.py +++ b/analyze_stats.py @@ -46,6 +46,7 @@ def _load_android_rating(): df = pd.read_csv("data/android-ratings.csv", index_col="date", parse_dates=True) df = df.tz_localize(tz=timezone.utc) df.columns = ["Android Play Store rating"] + df = df[df.iloc[:, 0] > 0] # 0 = "no data" placeholder, not a rating return df diff --git a/android_ratings.py b/android_ratings.py index 99331625..4fb66e79 100644 --- a/android_ratings.py +++ b/android_ratings.py @@ -42,6 +42,13 @@ def list_rating_files(bucket: str, package: str, token: str) -> list[str]: return sorted(n for n in names if n.endswith("_overview.csv")) +def _is_valid_rating(value: str) -> bool: + try: + return 1.0 <= float(value) <= 5.0 + except ValueError: + return False + + def fetch_rating_series(bucket: str, package: str, credentials) -> dict[str, str]: token = _token(credentials) series: dict[str, str] = {} @@ -50,7 +57,9 @@ def fetch_rating_series(bucket: str, package: str, credentials) -> dict[str, str for row in csv.DictReader(io.StringIO(text)): date = (row.get("Date") or "").strip() value = (row.get(METRIC) or "").strip() - if date and value: + # Play Console emits 0 on days it has no rating data (not a real + # rating; valid ratings are 1-5). Skip so it isn't plotted as a spike. + if date and value and _is_valid_rating(value): series[date] = value return series diff --git a/data/android-ratings.csv b/data/android-ratings.csv index 75f3b523..fa73739b 100644 --- a/data/android-ratings.csv +++ b/data/android-ratings.csv @@ -2751,6 +2751,5 @@ date,rating 2026-09-21,3.125 2026-09-22,3.3137 2026-09-23,3.3999 -2026-09-24,0.0 2026-09-25,3.2884 2026-09-26,3.3333 diff --git a/test_android_ratings.py b/test_android_ratings.py new file mode 100644 index 00000000..631de507 --- /dev/null +++ b/test_android_ratings.py @@ -0,0 +1,9 @@ +from android_ratings import _is_valid_rating + + +def test_zero_and_garbage_ratings_rejected(): + assert not _is_valid_rating("0.0") + assert not _is_valid_rating("0") + assert not _is_valid_rating("abc") + assert _is_valid_rating("3.2884") + assert _is_valid_rating("5.00") From e82c27516302e351cb0932efa2142277e3c7640a Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 29 Sep 2026 13:20:18 +0000 Subject: [PATCH 2/3] fix(test): use result.stdout to exclude stderr warnings from CSV assertion CliRunner.output mixes stdout and stderr by default. The stale-feed WARNING is correctly printed to stderr (err=True) but was contaminating the CSV parser because the test read result.output. Use result.stdout instead. Git-Session-Id: 5e2c --- test_vitals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_vitals.py b/test_vitals.py index 2a6076af..9df6fc8c 100644 --- a/test_vitals.py +++ b/test_vitals.py @@ -152,7 +152,7 @@ def test_by_version_csv_escapes_comma_in_label(monkeypatch, tmp_path): ["by-version", "--as-csv", "--package", "net.activitywatch.android"], ) assert result.exit_code == 0, result.output - rows = list(csv.reader(_io.StringIO(result.output))) + rows = list(csv.reader(_io.StringIO(result.stdout))) # Header + one data row; the label's commas must not create extra columns assert rows[0] == ["version", "days", "mean"] assert len(rows[1]) == 3 From fbe88ea9de2a271c0286947d7f36bf0c319c986d Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 29 Sep 2026 13:25:40 +0000 Subject: [PATCH 3/3] test(android-ratings): cover fetch path so placeholder zeros stay out Greptile 4/5 on ActivityWatch/stats#29: the validator unit test would still pass if fetch_rating_series stopped applying the filter. Mock the report download and assert 0.0/garbage never enter the series. Git-Session-Id: 7b774fe3-8be7-5667-8bd9-ea33121fd65e --- test_android_ratings.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/test_android_ratings.py b/test_android_ratings.py index 631de507..94832176 100644 --- a/test_android_ratings.py +++ b/test_android_ratings.py @@ -1,4 +1,5 @@ -from android_ratings import _is_valid_rating +import android_ratings as ar +from android_ratings import _is_valid_rating, fetch_rating_series def test_zero_and_garbage_ratings_rejected(): @@ -7,3 +8,28 @@ def test_zero_and_garbage_ratings_rejected(): assert not _is_valid_rating("abc") assert _is_valid_rating("3.2884") assert _is_valid_rating("5.00") + + +def test_fetch_rating_series_skips_placeholder_zero(monkeypatch): + """Collection path, not just the validator: 0.0 must not enter the series.""" + report = ( + "Date,Package Name,Total Average Rating\n" + "2026-09-23,net.activitywatch.android,3.3999\n" + "2026-09-24,net.activitywatch.android,0.0\n" + "2026-09-25,net.activitywatch.android,3.2884\n" + "2026-09-26,net.activitywatch.android,abc\n" + "2026-09-27,net.activitywatch.android,5.00\n" + ).encode("utf-16") + + monkeypatch.setattr(ar, "_token", lambda creds: "tok") + monkeypatch.setattr( + ar, "list_rating_files", lambda bucket, package, token: ["overview.csv"] + ) + monkeypatch.setattr(ar, "_download", lambda bucket, name, token: report) + + series = fetch_rating_series("bucket", "net.activitywatch.android", credentials=None) + assert series == { + "2026-09-23": "3.3999", + "2026-09-25": "3.2884", + "2026-09-27": "5.00", + }