From 9fc2f2df71d759838ff9c0ede91269b6beca26ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:23:41 +0000 Subject: [PATCH 1/3] Initial plan From 68afea27ce912c5931d47dbefee1f37b8f710495 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:27:27 +0000 Subject: [PATCH 2/3] fix: query each repo individually to capture all PRs across the org Co-authored-by: A1L13N <193832434+A1L13N@users.noreply.github.com> --- .../generate_leaderboard.cpython-312.pyc | Bin 0 -> 14511 bytes scripts/generate_leaderboard.py | 210 ++++++++++++------ 2 files changed, 136 insertions(+), 74 deletions(-) create mode 100644 scripts/__pycache__/generate_leaderboard.cpython-312.pyc diff --git a/scripts/__pycache__/generate_leaderboard.cpython-312.pyc b/scripts/__pycache__/generate_leaderboard.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95e6773961abce7419ac7d396e7a495b128b9718 GIT binary patch literal 14511 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 str: def build_search_query( - org: str, + repo: str, state: str, start_date: datetime, end_date: Optional[datetime], date_field: str, ) -> str: terms = [ - f"org:{org}", + f"repo:{repo}", "is:pr", f"state:{state}", f"{date_field}:>={format_query_date(start_date)}", @@ -91,6 +91,47 @@ def fetch_search_pulls(query_text: str, page: int): DELAY_SECONDS = 0.35 +def fetch_org_repos(org: str) -> list: + """Return a list of full repository names (owner/repo) for the given org.""" + repos = [] + page = 1 + while True: + query = urllib.parse.urlencode( + {"per_page": PER_PAGE, "page": page, "type": "public"} + ) + url = f"https://api.github.com/orgs/{org}/repos?{query}" + headers = { + "Accept": "application/vnd.github.v3+json", + "User-Agent": "alphaonelabs-leaderboard-generator", + } + if GITHUB_TOKEN: + headers["Authorization"] = f"Bearer {GITHUB_TOKEN}" + request = urllib.request.Request(url, headers=headers) + try: + with urllib.request.urlopen(request, timeout=30) as response: + data = json.loads(response.read().decode("utf-8")) + except urllib.error.HTTPError as error: + if error.code == 403: + raise RuntimeError( + "GitHub API access is temporarily restricted (403)." + ) from error + raise RuntimeError(f"GitHub API error: {error.code}") from error + except urllib.error.URLError as error: + raise RuntimeError("Unable to fetch repository list.") from error + + if not isinstance(data, list) or len(data) == 0: + break + for repo in data: + full_name = repo.get("full_name") + if full_name: + repos.append(full_name) + if len(data) < PER_PAGE: + break + page += 1 + time.sleep(DELAY_SECONDS) + return repos + + def parse_cli_date(value: str): try: return datetime.strptime(value, "%Y-%m-%d").replace( @@ -192,85 +233,106 @@ def is_within_date_range( return True +def get_pr_id(pr: dict): + """Return a stable unique identifier for a PR search result, or None.""" + return pr.get("node_id") or pr.get("id") or None + + def build_leaderboard(start_date: datetime, end_date: Optional[datetime]): contributor_stats = {} + seen_pr_ids: set[str] = set() - closed_query = build_search_query( - GITHUB_ORG, - "closed", - start_date, - end_date, - "closed", - ) + repos = fetch_org_repos(GITHUB_ORG) - closed_prs = [] - page = 1 - while page <= MAX_CLOSED_PAGES: - rows = fetch_search_pulls(closed_query, page) - if not isinstance(rows, list) or len(rows) == 0: - break - closed_prs.extend(rows) - if len(rows) < PER_PAGE: - break - page += 1 - time.sleep(DELAY_SECONDS) + for repo_full_name in repos: + closed_query = build_search_query( + repo_full_name, + "closed", + start_date, + end_date, + "closed", + ) - for pr in closed_prs: - user = pr.get("user") - if ( - not user - or not user.get("login") - or should_exclude(user.get("login")) - ): - continue - - merged_at = parse_github_date(pr.get("pull_request", {}).get("merged_at")) - closed_at = parse_github_date(pr.get("closed_at")) - relevant_date = merged_at or closed_at - if not is_within_date_range(relevant_date, start_date, end_date): - continue - - ensure_contributor(contributor_stats, user) - if merged_at: - contributor_stats[user["login"]]["merged_pr_count"] += 1 - else: - contributor_stats[user["login"]]["closed_pr_count"] += 1 - - open_query = build_search_query( - GITHUB_ORG, - "open", - start_date, - end_date, - "created", - ) + closed_prs = [] + page = 1 + while page <= MAX_CLOSED_PAGES: + rows = fetch_search_pulls(closed_query, page) + if not isinstance(rows, list) or len(rows) == 0: + break + closed_prs.extend(rows) + if len(rows) < PER_PAGE: + break + page += 1 + time.sleep(DELAY_SECONDS) + + for pr in closed_prs: + pr_id = get_pr_id(pr) + if pr_id is None or pr_id in seen_pr_ids: + continue + seen_pr_ids.add(pr_id) + + user = pr.get("user") + if ( + not user + or not user.get("login") + or should_exclude(user.get("login")) + ): + continue + + merged_at = parse_github_date(pr.get("pull_request", {}).get("merged_at")) + closed_at = parse_github_date(pr.get("closed_at")) + relevant_date = merged_at or closed_at + if not is_within_date_range(relevant_date, start_date, end_date): + continue + + ensure_contributor(contributor_stats, user) + if merged_at: + contributor_stats[user["login"]]["merged_pr_count"] += 1 + else: + contributor_stats[user["login"]]["closed_pr_count"] += 1 + + open_query = build_search_query( + repo_full_name, + "open", + start_date, + end_date, + "created", + ) - open_prs = [] - page = 1 - while page <= MAX_OPEN_PAGES: - rows = fetch_search_pulls(open_query, page) - if not isinstance(rows, list) or len(rows) == 0: - break - open_prs.extend(rows) - if len(rows) < PER_PAGE: - break - page += 1 - time.sleep(DELAY_SECONDS) + open_prs = [] + page = 1 + while page <= MAX_OPEN_PAGES: + rows = fetch_search_pulls(open_query, page) + if not isinstance(rows, list) or len(rows) == 0: + break + open_prs.extend(rows) + if len(rows) < PER_PAGE: + break + page += 1 + time.sleep(DELAY_SECONDS) + + for pr in open_prs: + pr_id = get_pr_id(pr) + if pr_id is None or pr_id in seen_pr_ids: + continue + seen_pr_ids.add(pr_id) + + user = pr.get("user") + if ( + not user + or not user.get("login") + or should_exclude(user.get("login")) + ): + continue + + created_at = parse_github_date(pr.get("created_at")) + if not is_within_date_range(created_at, start_date, end_date): + continue + + ensure_contributor(contributor_stats, user) + contributor_stats[user["login"]]["open_pr_count"] += 1 - for pr in open_prs: - user = pr.get("user") - if ( - not user - or not user.get("login") - or should_exclude(user.get("login")) - ): - continue - - created_at = parse_github_date(pr.get("created_at")) - if not is_within_date_range(created_at, start_date, end_date): - continue - - ensure_contributor(contributor_stats, user) - contributor_stats[user["login"]]["open_pr_count"] += 1 + time.sleep(DELAY_SECONDS) contributors = [] for item in contributor_stats.values(): From f9deaee5fab8923315e25ccbc09c4d567b9a076c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:27:41 +0000 Subject: [PATCH 3/3] chore: add .gitignore to exclude __pycache__ Co-authored-by: A1L13N <193832434+A1L13N@users.noreply.github.com> --- .gitignore | 1 + .../generate_leaderboard.cpython-312.pyc | Bin 14511 -> 0 bytes 2 files changed, 1 insertion(+) create mode 100644 .gitignore delete mode 100644 scripts/__pycache__/generate_leaderboard.cpython-312.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/scripts/__pycache__/generate_leaderboard.cpython-312.pyc b/scripts/__pycache__/generate_leaderboard.cpython-312.pyc deleted file mode 100644 index 95e6773961abce7419ac7d396e7a495b128b9718..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14511 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