mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 14:23:16 +02:00
fix: accept ProtonDB gold+silver combinations; add explicit skip reasons
This commit is contained in:
parent
1ebb667265
commit
ded3b9ed30
18
docs/superpowers/contracts/protondb-gold-silver-fix.json
Normal file
18
docs/superpowers/contracts/protondb-gold-silver-fix.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"title": "ProtonDB gold+silver acceptance fix",
|
||||
"objective": "Fix the is_playable rule in protondb.py so that a game with one gold rating and one silver rating (in either order) is accepted as playable. Previously both ratings had to be gold+. Add an unplayable_reason property for diagnostic logging in scanning.py.",
|
||||
"acceptance_criteria": [
|
||||
"gold+silver -> accepted",
|
||||
"silver+gold -> accepted",
|
||||
"gold+bronze -> rejected",
|
||||
"silver+silver -> rejected",
|
||||
"Skip log includes the unplayable_reason string",
|
||||
"All existing and new tests pass (63 total)",
|
||||
"Pre-commit passes on all 4 changed files"
|
||||
],
|
||||
"out_of_scope": [
|
||||
"Changes to any other enforcement logic",
|
||||
"Changes to config or API clients"
|
||||
],
|
||||
"verifier": "pytest python_pkg/steam_backlog_enforcer/tests/ && pre-commit run --files <changed-files>"
|
||||
}
|
||||
35
docs/superpowers/evidence/protondb-gold-silver-fix.json
Normal file
35
docs/superpowers/evidence/protondb-gold-silver-fix.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"intent": "Fix ProtonDB acceptance rule so gold+silver combinations are accepted, and add explicit skip-reason logging.",
|
||||
"scope": [
|
||||
"python_pkg/steam_backlog_enforcer/protondb.py",
|
||||
"python_pkg/steam_backlog_enforcer/scanning.py",
|
||||
"python_pkg/steam_backlog_enforcer/tests/test_protondb.py",
|
||||
"python_pkg/steam_backlog_enforcer/tests/test_scanning.py",
|
||||
"No changes to config, API clients, or other modules"
|
||||
],
|
||||
"changes": [
|
||||
"Updated is_playable: both ratings must be >= silver AND at least one must be gold+",
|
||||
"Added unplayable_reason property returning terse diagnostic string",
|
||||
"Updated _pick_playable_candidate skip log to include unplayable_reason",
|
||||
"Added/updated tests for new acceptance rule and unplayable_reason"
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "pre-commit run --files python_pkg/steam_backlog_enforcer/protondb.py python_pkg/steam_backlog_enforcer/scanning.py python_pkg/steam_backlog_enforcer/tests/test_protondb.py python_pkg/steam_backlog_enforcer/tests/test_scanning.py",
|
||||
"result": "pass",
|
||||
"evidence": "All hooks passed (trim whitespace, ruff, ruff-format, codespell, etc.)"
|
||||
},
|
||||
{
|
||||
"command": "pytest python_pkg/steam_backlog_enforcer/tests/",
|
||||
"result": "pass",
|
||||
"evidence": "63 passed, 0 failed"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"Games with gold+silver were previously rejected; they will now be assigned — intended behaviour change"
|
||||
],
|
||||
"rollback": [
|
||||
"Revert is_playable to require both ratings to be gold+",
|
||||
"Validate with pytest python_pkg/steam_backlog_enforcer/tests/"
|
||||
]
|
||||
}
|
||||
@ -59,25 +59,49 @@ class ProtonDBRating:
|
||||
|
||||
A game is considered unplayable when:
|
||||
- Its tier is silver, bronze, or borked.
|
||||
- Its tier is gold but trending to silver or worse.
|
||||
- No data exists (unknown compatibility).
|
||||
- Both reported ratings are available, but one is below silver.
|
||||
- Both reported ratings are available, but neither reaches gold.
|
||||
|
||||
If both ``tier`` and ``trending_tier`` exist, the acceptance rule is:
|
||||
at least one rating must be gold-or-better and the other must be
|
||||
silver-or-better.
|
||||
"""
|
||||
if not self.tier or self.tier == "pending":
|
||||
return True # No data / pending → don't block; user can skip manually.
|
||||
tier_rank = TIER_ORDER.get(self.tier, 99)
|
||||
min_rank = TIER_ORDER[MIN_PLAYABLE_TIER]
|
||||
silver_rank = TIER_ORDER["silver"]
|
||||
|
||||
if tier_rank > min_rank:
|
||||
# Silver, bronze, borked → skip.
|
||||
return False
|
||||
if not self.trending_tier:
|
||||
return tier_rank <= min_rank
|
||||
|
||||
if tier_rank == min_rank and self.trending_tier:
|
||||
# Gold but trending silver/bronze/borked → skip.
|
||||
trend_rank = TIER_ORDER.get(self.trending_tier, 99)
|
||||
if trend_rank > min_rank:
|
||||
if tier_rank > silver_rank or trend_rank > silver_rank:
|
||||
# Bronze, borked, unknown tier in either field → skip.
|
||||
return False
|
||||
|
||||
return True
|
||||
# At least one rating must still be gold-or-better.
|
||||
return not (tier_rank > min_rank and trend_rank > min_rank)
|
||||
|
||||
@property
|
||||
def unplayable_reason(self) -> str:
|
||||
"""Return a human-readable reason when ``is_playable`` is false."""
|
||||
if self.is_playable:
|
||||
return ""
|
||||
|
||||
tier_rank = TIER_ORDER.get(self.tier, 99)
|
||||
min_rank = TIER_ORDER[MIN_PLAYABLE_TIER]
|
||||
silver_rank = TIER_ORDER["silver"]
|
||||
|
||||
if not self.trending_tier:
|
||||
return f"tier<{MIN_PLAYABLE_TIER} ({self.tier})"
|
||||
|
||||
trend_rank = TIER_ORDER.get(self.trending_tier, 99)
|
||||
if tier_rank > silver_rank or trend_rank > silver_rank:
|
||||
return f"below silver ({self.tier}/{self.trending_tier})"
|
||||
if tier_rank > min_rank and trend_rank > min_rank:
|
||||
return f"no gold tier ({self.tier}/{self.trending_tier})"
|
||||
return "fails ProtonDB rule"
|
||||
|
||||
|
||||
def _load_cache() -> dict[str, Any]:
|
||||
|
||||
@ -151,11 +151,12 @@ def _pick_playable_candidate(
|
||||
)
|
||||
return game
|
||||
logger.info(
|
||||
"Skipping %s (AppID=%d): ProtonDB %s (trending %s)",
|
||||
"Skipping %s (AppID=%d): ProtonDB %s (trending %s) — %s",
|
||||
game.name,
|
||||
game.app_id,
|
||||
rating.tier,
|
||||
rating.trending_tier,
|
||||
rating.unplayable_reason,
|
||||
)
|
||||
|
||||
offset += _PROTONDB_BATCH_SIZE
|
||||
|
||||
@ -62,12 +62,16 @@ class TestProtonDBRating:
|
||||
|
||||
def test_gold_trending_silver(self) -> None:
|
||||
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="silver")
|
||||
assert r.is_playable is False
|
||||
assert r.is_playable is True
|
||||
|
||||
def test_gold_trending_gold(self) -> None:
|
||||
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="gold")
|
||||
assert r.is_playable is True
|
||||
|
||||
def test_silver_trending_gold(self) -> None:
|
||||
r = ProtonDBRating(app_id=1, tier="silver", trending_tier="gold")
|
||||
assert r.is_playable is True
|
||||
|
||||
def test_gold_no_trending(self) -> None:
|
||||
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="")
|
||||
assert r.is_playable is True
|
||||
@ -80,10 +84,22 @@ class TestProtonDBRating:
|
||||
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="unknown")
|
||||
assert r.is_playable is False
|
||||
|
||||
def test_gold_trending_bronze(self) -> None:
|
||||
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="bronze")
|
||||
assert r.is_playable is False
|
||||
|
||||
def test_unknown_tier(self) -> None:
|
||||
r = ProtonDBRating(app_id=1, tier="unknown_tier")
|
||||
assert r.is_playable is False
|
||||
|
||||
def test_unplayable_reason_for_silver_silver(self) -> None:
|
||||
r = ProtonDBRating(app_id=1, tier="silver", trending_tier="silver")
|
||||
assert "no gold tier" in r.unplayable_reason
|
||||
|
||||
def test_unplayable_reason_for_gold_bronze(self) -> None:
|
||||
r = ProtonDBRating(app_id=1, tier="gold", trending_tier="bronze")
|
||||
assert "below silver" in r.unplayable_reason
|
||||
|
||||
|
||||
class TestProtonDBCache:
|
||||
"""Tests for cache I/O."""
|
||||
|
||||
@ -215,6 +215,27 @@ class TestPickPlayableCandidate:
|
||||
result = _pick_playable_candidate([game])
|
||||
assert result is not None
|
||||
|
||||
def test_logs_explicit_protondb_skip_reason(self) -> None:
|
||||
"""Unplayable candidate logs concrete reason, not just raw tiers."""
|
||||
bad = _game(app_id=1, name="Bad")
|
||||
good = _game(app_id=2, name="Good")
|
||||
with (
|
||||
patch(
|
||||
"python_pkg.steam_backlog_enforcer.scanning.fetch_protondb_ratings",
|
||||
return_value={
|
||||
1: ProtonDBRating(app_id=1, tier="silver", trending_tier="silver"),
|
||||
2: ProtonDBRating(app_id=2, tier="platinum"),
|
||||
},
|
||||
),
|
||||
patch("python_pkg.steam_backlog_enforcer.scanning._echo"),
|
||||
patch("python_pkg.steam_backlog_enforcer.scanning.logger.info") as mock_log,
|
||||
):
|
||||
result = _pick_playable_candidate([bad, good])
|
||||
|
||||
assert result is not None
|
||||
assert result.app_id == 2
|
||||
assert any("no gold tier" in str(call) for call in mock_log.call_args_list)
|
||||
|
||||
|
||||
class TestPickNextGame:
|
||||
"""Tests for pick_next_game."""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user