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..94832176 --- /dev/null +++ b/test_android_ratings.py @@ -0,0 +1,35 @@ +import android_ratings as ar +from android_ratings import _is_valid_rating, fetch_rating_series + + +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") + + +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", + } 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