diff --git a/gittensor/validator/issue_discovery/scan.py b/gittensor/validator/issue_discovery/scan.py index c30a12e3..f53536be 100644 --- a/gittensor/validator/issue_discovery/scan.py +++ b/gittensor/validator/issue_discovery/scan.py @@ -152,14 +152,16 @@ async def run_issue_discovery( bt.logging.info('No scoring repos — issue discovery skipped') return - client = client or MirrorClient() + mirror_client: MirrorClient = client or MirrorClient() now = datetime.now(timezone.utc) # Each repo is windowed by its own pr_lookback_days; the mirror applies the # per-repo cutoffs server-side for the scoring fetch. since_by_repo = { name: now - timedelta(days=resolve_scoring(rc.scoring).pr_lookback_days) for name, rc in mirror_repos.items() } - enabled_names: Set[str] = set(mirror_repos.keys()) + issue_enabled_names: Set[str] = { + name for name, config in mirror_repos.items() if config.issue_discovery_share > 0.0 + } solving_pr_cache: Dict[Tuple[str, int], CachedSolvingPR] = _build_solving_pr_cache(miner_evaluations) cache_stats = _CacheStats() @@ -187,7 +189,7 @@ async def run_issue_discovery( try: response = await asyncio.to_thread( - client.get_miner_issues, evaluation.github_id, since_by_repo=since_by_repo + mirror_client.get_miner_issues, evaluation.github_id, since_by_repo=since_by_repo ) except MirrorRequestError as e: bt.logging.warning(f'├─ UID {uid}: issue fetch failed ({e}) — skipped this miner') @@ -196,15 +198,15 @@ async def run_issue_discovery( continue try: - current_response = await asyncio.to_thread(client.get_miner_issues, evaluation.github_id) + current_response = await asyncio.to_thread(mirror_client.get_miner_issues, evaluation.github_id) except MirrorRequestError as e: bt.logging.warning(f'├─ UID {uid}: open-issue count fetch failed ({e}) — skipped this miner') _restore_issue_discovery_from_cache(evaluation, evaluation_cache) fetch_errors += 1 continue - open_counts = _count_open_issues(current_response.issues, enabled_names) - filtered = [i for i in response.issues if i.repo_full_name in enabled_names and _should_include_issue(i)] + open_counts = _count_open_issues(current_response.issues, issue_enabled_names) + filtered = [i for i in response.issues if i.repo_full_name in issue_enabled_names and _should_include_issue(i)] if not filtered: _clear_issue_discovery_fields(evaluation) _apply_open_issue_counts(evaluation, open_counts) @@ -222,7 +224,7 @@ async def run_issue_discovery( mirror_repos, solving_pr_cache, cache_stats, - client, + mirror_client, programming_languages, token_config, open_counts=open_counts, diff --git a/tests/validator/issue_discovery/test_scan.py b/tests/validator/issue_discovery/test_scan.py index bc994c67..aa3edfca 100644 --- a/tests/validator/issue_discovery/test_scan.py +++ b/tests/validator/issue_discovery/test_scan.py @@ -1351,6 +1351,73 @@ def _score_with_emission_share(emission_share: float) -> float: assert _score_with_emission_share(0.1) == pytest.approx(_score_with_emission_share(0.9)) + def test_zero_issue_discovery_share_repo_does_not_unlock_eligibility(self): + positive_repo = 'entrius/gittensor-ui' + zero_share_repo = 'entrius/gittensor' + issues = [_issue_dict(issue_number=10, repo=positive_repo, author_github_id='A', solved_by_pr=200)] + issues.extend( + _issue_dict(issue_number=20 + i, repo=zero_share_repo, author_github_id='A', solved_by_pr=300 + i) + for i in range(6) + ) + + client = Mock() + client.get_miner_issues.return_value = _response(issues) + + evaluation = _eval(uid=1, github_id='A') + seed = MinerEvaluation(uid=99, hotkey='hkS', github_id='SEED') + seed.merged_prs = [ + _scored_mirror_pr(positive_repo, 200), + *[_scored_mirror_pr(zero_share_repo, pr_number) for pr_number in range(300, 306)], + ] + + _run( + run_issue_discovery( + {1: evaluation, 99: seed}, + { + positive_repo: RepositoryConfig(emission_share=0.5, issue_discovery_share=0.5), + zero_share_repo: RepositoryConfig(emission_share=0.5, issue_discovery_share=0.0), + }, + _EMPTY_LANGS, + _EMPTY_TOKEN_CONFIG, + client=client, + ) + ) + + assert evaluation.total_solved_issues == 1 + assert evaluation.total_valid_solved_issues == 1 + assert evaluation.is_issue_eligible is False + assert evaluation.issue_discovery_score == 0.0 + assert evaluation.issue_discovery_issues == [] + + def test_positive_issue_discovery_share_repo_still_unlocks_eligibility(self): + repo = 'entrius/gittensor-ui' + issues = [ + _issue_dict(issue_number=10 + i, repo=repo, author_github_id='A', solved_by_pr=200 + i) for i in range(7) + ] + client = Mock() + client.get_miner_issues.return_value = _response(issues) + + evaluation = _eval(uid=1, github_id='A') + seed = MinerEvaluation(uid=99, hotkey='hkS', github_id='SEED') + seed.merged_prs = [_scored_mirror_pr(repo, pr_number) for pr_number in range(200, 207)] + + _run( + run_issue_discovery( + {1: evaluation, 99: seed}, + {repo: RepositoryConfig(emission_share=0.5, issue_discovery_share=0.5)}, + _EMPTY_LANGS, + _EMPTY_TOKEN_CONFIG, + client=client, + ) + ) + + assert evaluation.total_solved_issues == 7 + assert evaluation.total_valid_solved_issues == 7 + assert evaluation.is_issue_eligible is True + assert len(evaluation.issue_discovery_issues) == 7 + assert {issue.repository_full_name for issue in evaluation.issue_discovery_issues} == {repo} + assert evaluation.issue_discovery_score > 0 + def _issues_by_github_id(mapping: dict): """Mock get_miner_issues side effect: each miner's github_id maps to its own