Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions gittensor/validator/issue_discovery/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

import bittensor as bt

from gittensor.classes import Issue, MinerEvaluation, MinerEvaluationCache
from gittensor.classes import Issue, MinerEvaluation, MinerEvaluationCache, RepoEvaluation
from gittensor.constants import (
MAINTAINER_ASSOCIATIONS,
)
Expand Down Expand Up @@ -252,6 +252,22 @@ async def run_issue_discovery(
)


def _reset_repo_issue_fields(repo_eval: RepoEvaluation) -> None:
"""Zero one repo_evaluation's issue-discovery fields. Shared by the no-issues
reset path and the scoring path (for repos not seen this round), so a repo
issue-scored in a prior round can't leave stale per-repo values that
_roll_up_issue_totals would sum back into the round-level totals. See #1610.
"""
repo_eval.is_issue_eligible = False
repo_eval.issue_credibility = 0.0
repo_eval.issue_discovery_score = 0.0
repo_eval.issue_token_score = 0.0
repo_eval.total_solved_issues = 0
repo_eval.total_valid_solved_issues = 0
repo_eval.total_closed_issues = 0
repo_eval.total_open_issues = 0


def _clear_issue_discovery_fields(evaluation: MinerEvaluation) -> None:
"""Reset issue-discovery aggregates after a successful fetch with no mirror issues."""
evaluation.issue_discovery_score = 0.0
Expand All @@ -264,14 +280,7 @@ def _clear_issue_discovery_fields(evaluation: MinerEvaluation) -> None:
evaluation.total_open_issues = 0
evaluation.issue_discovery_issues = []
for repo_eval in evaluation.repo_evaluations.values():
repo_eval.is_issue_eligible = False
repo_eval.issue_credibility = 0.0
repo_eval.issue_discovery_score = 0.0
repo_eval.issue_token_score = 0.0
repo_eval.total_solved_issues = 0
repo_eval.total_valid_solved_issues = 0
repo_eval.total_closed_issues = 0
repo_eval.total_open_issues = 0
_reset_repo_issue_fields(repo_eval)


def _apply_open_issue_counts(evaluation: MinerEvaluation, open_counts: Dict[str, int]) -> None:
Expand Down Expand Up @@ -546,7 +555,17 @@ def _finalize_repo_issue_scores(
"""Gate + score issue discovery per repository, then roll up the totals."""
evaluation.issue_discovery_issues = []

for repo_name in sorted(set(repo_acc) | set(open_counts)):
seen_repos = set(repo_acc) | set(open_counts)
# A repo issue-scored in a prior round but absent this round (e.g. an
# evaluation restored from the cache after an OSS fetch failure) is never
# rewritten by the loop below, so its stale per-repo issue values would be
# summed back into the round totals by _roll_up_issue_totals. Zero those
# here — matching the full reset in _clear_issue_discovery_fields. See #1610.
for stale_name, stale_eval in evaluation.repo_evaluations.items():
if stale_name not in seen_repos:
_reset_repo_issue_fields(stale_eval)

for repo_name in sorted(seen_repos):
repo_config = mirror_repos.get(repo_name)
if repo_config is None:
continue
Expand Down
40 changes: 40 additions & 0 deletions tests/validator/issue_discovery/test_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -1919,3 +1919,43 @@ def _run_discovery(issues):
assert ev_unlabeled.issue_discovery_score > 0.0
assert ev_labeled.issue_discovery_score < ev_unlabeled.issue_discovery_score
assert all(i.discovery_label_multiplier == pytest.approx(0.25) for i in ev_labeled.issue_discovery_issues)


class TestFinalizeStaleRepoReset:
"""A repo issue-scored in a prior round but absent this round must be zeroed by
the scoring path, not summed into the round totals — parity with the full reset
in _clear_issue_discovery_fields. See #1610."""

def test_repo_absent_this_round_is_zeroed_not_summed(self):
ev = _eval(uid=1, github_id='999')
# stale per-repo issue values carried over from a prior round (e.g. a
# cache-restored evaluation after an OSS fetch failure).
ev.repo_evaluations['owner/repo-a'] = RepoEvaluation(
repository_full_name='owner/repo-a',
total_solved_issues=7,
total_valid_solved_issues=7,
total_closed_issues=1,
total_open_issues=4,
issue_discovery_score=8.12,
issue_token_score=700.0,
is_issue_eligible=True,
issue_credibility=1.0,
)
# this round scored no repos: repo-a is absent from repo_acc / open_counts.
scan_module._finalize_repo_issue_scores(ev, {}, {}, _mirror_repos('owner/repo-a'))

a = ev.repo_evaluations['owner/repo-a']
assert a.total_solved_issues == 0
assert a.total_valid_solved_issues == 0
assert a.total_closed_issues == 0
assert a.total_open_issues == 0
assert a.issue_discovery_score == 0.0
assert a.issue_token_score == 0.0
assert a.is_issue_eligible is False
assert a.issue_credibility == 0.0
# round-level roll-up reflects only this round (nothing), not the stale 7 / 8.12.
assert ev.total_solved_issues == 0
assert ev.total_valid_solved_issues == 0
assert ev.issue_discovery_score == 0.0
assert ev.issue_token_score == 0.0
assert ev.is_issue_eligible is False