diff --git a/gittensor/constants.py b/gittensor/constants.py index 9572ee9..fcb28b5 100644 --- a/gittensor/constants.py +++ b/gittensor/constants.py @@ -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 # ============================================================================= diff --git a/gittensor/utils/github_api_tools.py b/gittensor/utils/github_api_tools.py index b8a8dd4..4aeaecc 100644 --- a/gittensor/utils/github_api_tools.py +++ b/gittensor/utils/github_api_tools.py @@ -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 = """ @@ -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 """ @@ -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) @@ -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}") @@ -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 @@ -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 @@ -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 ( @@ -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() diff --git a/gittensor/validator/evaluation/reward.py b/gittensor/validator/evaluation/reward.py index 6c39afe..c8300da 100644 --- a/gittensor/validator/evaluation/reward.py +++ b/gittensor/validator/evaluation/reward.py @@ -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) diff --git a/gittensor/validator/utils/config.py b/gittensor/validator/utils/config.py index e38cae3..7d2af43 100644 --- a/gittensor/validator/utils/config.py +++ b/gittensor/validator/utils/config.py @@ -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') @@ -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}") diff --git a/tests/utils/test_github_api_tools.py b/tests/utils/test_github_api_tools.py index 06b3768..8475dc2 100644 --- a/tests/utils/test_github_api_tools.py +++ b/tests/utils/test_github_api_tools.py @@ -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, diff --git a/tests/validator/test_handle_exceptions.py b/tests/validator/test_handle_exceptions.py index 9757727..0fefbeb 100644 --- a/tests/validator/test_handle_exceptions.py +++ b/tests/validator/test_handle_exceptions.py @@ -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() diff --git a/tests/validator/test_timezone_consistency.py b/tests/validator/test_timezone_consistency.py new file mode 100644 index 0000000..ffcacd0 --- /dev/null +++ b/tests/validator/test_timezone_consistency.py @@ -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()