Skip to content
Closed
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
5 changes: 5 additions & 0 deletions gittensor/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
SECONDS_PER_DAY = 86400
SECONDS_PER_HOUR = 3600

# =============================================================================
# PR Scoring & Lookback Windows
# =============================================================================
MERGED_PR_LOOKBACK_DAYS = 30 # how many days a merged pr will count for scoring

# =============================================================================
# Temp Vars
# =============================================================================
Expand Down
33 changes: 21 additions & 12 deletions gittensor/utils/github_api_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
from gittensor.constants import (
BASE_GITHUB_API_URL,
MERGE_SUCCESS_RATIO_APPLICATION_DATE,
MERGED_PR_LOOKBACK_DAYS,
)
from gittensor.validator.utils.config import MERGED_PR_LOOKBACK_DAYS
from gittensor.validator.utils.datetime_utils import CHICAGO_TZ

# core github graphql query
QUERY = """
Expand Down Expand Up @@ -97,10 +98,10 @@

def normalize_repo_name(repo_name: str) -> str:
"""Normalize repository name to lowercase for case-insensitive comparison.

Args:
repo_name (str): Repository name in format 'owner/repo'

Returns:
str: Lowercase repository name
"""
Expand Down Expand Up @@ -180,9 +181,7 @@ def get_github_user(token: str) -> Optional[Dict[str, Any]]:
time.sleep(2)

except Exception as e:
bt.logging.warning(
f"Could not fetch GitHub user (attempt {attempt + 1}/6): {e}"
)
bt.logging.warning(f"Could not fetch GitHub user (attempt {attempt + 1}/6): {e}")
if attempt < 5: # Don't sleep on last attempt
time.sleep(2)

Expand Down Expand Up @@ -242,8 +241,8 @@ def get_github_account_age_days(token: str) -> Optional[int]:
return None

try:
created_dt = datetime.fromisoformat(created_at.rstrip("Z")).replace(tzinfo=timezone.utc)
now_dt = datetime.now(timezone.utc)
created_dt = datetime.fromisoformat(created_at.rstrip("Z")).replace(tzinfo=timezone.utc).astimezone(CHICAGO_TZ)
now_dt = datetime.now(CHICAGO_TZ)
return (now_dt - created_dt).days
except Exception as e:
bt.logging.warning(f"Could not parse GitHub account creation date: {e}")
Expand Down Expand Up @@ -372,7 +371,13 @@ def _process_non_merged_pr(
# Handle CLOSED (not merged) PRs - count if within lookback period
if pr_state == 'CLOSED' and not pr_raw['mergedAt']:
if pr_raw.get('closedAt'):
closed_dt = datetime.fromisoformat(pr_raw['closedAt'].rstrip("Z")).replace(tzinfo=timezone.utc)
from gittensor.validator.utils.datetime_utils import CHICAGO_TZ

closed_dt = (
datetime.fromisoformat(pr_raw['closedAt'].rstrip("Z"))
.replace(tzinfo=timezone.utc)
.astimezone(CHICAGO_TZ)
)
if (
normalized_repo in active_repositories
and closed_dt >= date_filter
Expand Down Expand Up @@ -408,7 +413,7 @@ def _should_skip_merged_pr(
normalized_repo = normalize_repo_name(repository_full_name)
if normalized_repo not in master_repositories:
return (True, f"Skipping PR #{pr_raw['number']} in {repository_full_name} - ineligible repo")

repo_key = normalized_repo

# Filter by lookback window
Expand Down Expand Up @@ -482,7 +487,11 @@ def _should_skip_merged_pr(
repo_metadata = master_repositories[repo_key]
inactive_at = repo_metadata.get("inactiveAt")
if inactive_at is not None:
inactive_dt = datetime.fromisoformat(inactive_at.rstrip("Z")).replace(tzinfo=timezone.utc)
from gittensor.validator.utils.datetime_utils import CHICAGO_TZ

inactive_dt = (
datetime.fromisoformat(inactive_at.rstrip("Z")).replace(tzinfo=timezone.utc).astimezone(CHICAGO_TZ)
)
# Skip PR if it was merged at or after the repo became inactive
if merged_dt >= inactive_dt:
return (
Expand Down Expand Up @@ -521,7 +530,7 @@ def get_user_merged_prs_graphql(
return PRCountResult(valid_prs=[], open_pr_count=0, merged_pr_count=0, closed_pr_count=0)

# Calculate date filter
date_filter = datetime.now(timezone.utc) - timedelta(days=MERGED_PR_LOOKBACK_DAYS)
date_filter = datetime.now(CHICAGO_TZ) - timedelta(days=MERGED_PR_LOOKBACK_DAYS)

# Convert numeric user ID to GraphQL global node ID
global_user_id = base64.b64encode(f"04:User{user_id}".encode()).decode()
Expand Down
3 changes: 0 additions & 3 deletions gittensor/validator/evaluation/reward.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,6 @@ async def get_rewards(
# Apply all multipliers and calculate final scores
apply_cross_miner_multipliers_and_finalize(miner_evaluations)

# store all miner evaluations after adjusting score
await self.bulk_store_evaluation(miner_evaluations)

# Normalize the rewards between [0,1]
normalized_rewards = normalize_rewards_linear(miner_evaluations)

Expand Down
5 changes: 3 additions & 2 deletions gittensor/validator/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import bittensor as bt

# from gittensor.constants import MERGED_PR_LOOKBACK_DAYS

# NOTE: bump this number when we make new updates
__version__ = "2.0.7"


VALIDATOR_WAIT = 60 # 60 seconds
VALIDATOR_STEPS_INTERVAL = 120 # 2 hours, every time a scoring round happens
MERGED_PR_LOOKBACK_DAYS = 30 # how many days a merged pr will count for scoring

# required env vars
WANDB_API_KEY = os.getenv('WANDB_API_KEY')
Expand All @@ -17,5 +18,5 @@
# log values
bt.logging.info(f"VALIDATOR_WAIT: {VALIDATOR_WAIT}")
bt.logging.info(f"VALIDATOR_STEPS_INTERVAL: {VALIDATOR_STEPS_INTERVAL}")
bt.logging.info(f"MERGED_PR_LOOKBACK_DAYS: {MERGED_PR_LOOKBACK_DAYS}")
# bt.logging.info(f"MERGED_PR_LOOKBACK_DAYS: {MERGED_PR_LOOKBACK_DAYS}")
bt.logging.info(f"WANDB_PROJECT: {WANDB_PROJECT}")
1 change: 0 additions & 1 deletion tests/utils/test_github_api_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
sys.modules['gittensor.validator'] = Mock()
sys.modules['gittensor.validator.utils'] = Mock()
sys.modules['gittensor.validator.utils.config'] = Mock()
sys.modules['gittensor.validator.utils.config'].MERGED_PR_LOOKBACK_DAYS = 30

from gittensor.utils.github_api_tools import (
get_user_merged_prs_graphql,
Expand Down
1 change: 0 additions & 1 deletion tests/validator/test_handle_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
sys.modules["gittensor.validator"] = Mock()
sys.modules["gittensor.validator.utils"] = Mock()
sys.modules["gittensor.validator.utils.config"] = Mock()
sys.modules["gittensor.validator.utils.config"].MERGED_PR_LOOKBACK_DAYS = 30
sys.modules["gittensor.validator.utils.config"].WANDB_PROJECT = "gittensor-validators"
sys.modules["gittensor.validator.utils.config"].__version__ = "2.0.5"
sys.modules["gittensor.validator.utils.storage"] = Mock()
Expand Down
41 changes: 41 additions & 0 deletions tests/validator/test_timezone_consistency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import unittest
from datetime import datetime, timezone, timedelta
from unittest.mock import MagicMock
from gittensor.validator.evaluation.scoring import calculate_time_decay_multiplier
from gittensor.classes import PullRequest
from gittensor.validator.utils.datetime_utils import CHICAGO_TZ, parse_github_timestamp


class TestTimezoneConsistency(unittest.TestCase):
def test_calculate_time_decay_multiplier_chicago(self):
# Create a mock PR with merged_at in Chicago timezone
merged_at = datetime.now(CHICAGO_TZ) - timedelta(hours=10)
pr = MagicMock(spec=PullRequest)
pr.merged_at = merged_at

# Calculate multiplier
multiplier = calculate_time_decay_multiplier(pr)

# We expect some decay after 10 hours (grace period is 4 hours)
self.assertLess(multiplier, 1.0)
self.assertGreater(multiplier, 0.0)

def test_parse_github_timestamp_returns_chicago(self):
timestamp_str = "2024-01-15T10:30:00Z"
dt = parse_github_timestamp(timestamp_str)

# Check if it's Chicago
# pytz timezones can be tricky to compare directly with == due to dst,
# but checking the zone name or using .zone attribute works.
self.assertEqual(dt.tzinfo.zone, CHICAGO_TZ.zone)

# 10:30 UTC is 04:30 Chicago (CST, UTC-6) in January
self.assertEqual(dt.year, 2024)
self.assertEqual(dt.month, 1)
self.assertEqual(dt.day, 15)
self.assertEqual(dt.hour, 4)
self.assertEqual(dt.minute, 30)


if __name__ == '__main__':
unittest.main()