Skip to content

treat transferred issues as closed in discovery scoring#415

Closed
nudiltoys-cmyk wants to merge 8 commits intoentrius:testfrom
nudiltoys-cmyk:transferred-issues-as-closed
Closed

treat transferred issues as closed in discovery scoring#415
nudiltoys-cmyk wants to merge 8 commits intoentrius:testfrom
nudiltoys-cmyk:transferred-issues-as-closed

Conversation

@nudiltoys-cmyk
Copy link
Copy Markdown

closes #404
closes #405

summary

transferred issues were being counted as solved in discovery scoring, so a miner could file an issue, have it transferred, and still collect emissions
repo_scan never populated is_transferred even though the rest response carries state_reason, and the pr-linked path never had the data because the graphql query didn't select stateReason
this adds stateReason to the closingIssuesReferences selection, sets is_transferred in both construction paths, and routes transferred issues to closed_count in _collect_issues_from_prs and _merge_scan_issues

test plan

pytest covers transferred vs non-transferred in the pr-linked scoring path, the scan merge path, and the repo_scan field population from state_reason transferred/completed. ruff and pyright pass

nudiltoys-cmyk and others added 2 commits April 14, 2026 15:57
fixes the anti-gaming gap where transferred issues counted as solved
in the pr-linked path and were not flagged during repo scan. adds
stateReason to the closingIssuesReferences graphql selection,
populates is_transferred in both construction paths, and routes
transferred issues to closed_count in _collect_issues_from_prs and
_merge_scan_issues
@anderdc
Copy link
Copy Markdown
Collaborator

anderdc commented Apr 14, 2026

Review

What works

  • Transferred issues are correctly routed to closed_count in both _collect_issues_from_prs and _merge_scan_issues
  • stateReason added to GraphQL query, state_reason read from REST — both construction paths covered
  • Test coverage is solid: scoring paths, repo_scan field population, factory fixtures

Issues

NOT_PLANNED gap — GitHub stateReason has three values: COMPLETED, NOT_PLANNED, TRANSFERRED. The PR only handles TRANSFERRED. A NOT_PLANNED issue closed without a merged PR currently falls through to solved_count in _merge_scan_issues (line: if issue.state == 'CLOSED' and issue.closed_at). This is the same class of anti-gaming hole — a miner files an issue, it gets closed as not-planned, and they still get positive credibility. Recommendation: store state_reason on Issue instead of (or alongside) is_transferred, then gate on state_reason == 'COMPLETED' for solved classification, or at minimum add is_not_planned handling identical to transferred.

_merge_scan_issues solved classification is too loose — Any closed issue with closed_at set counts as solved. After the transferred check, a NOT_PLANNED issue still hits data.solved_count += 1. This predates the PR but the PR should fix it since it's the same category of bug and the data (stateReason) is now available.

Boolean field limits future extensibilityis_transferred: bool on Issue means if you later need NOT_PLANNED handling, you add another boolean. A state_reason: Optional[str] field would be more natural and let scoring logic branch on all three values. The boolean can stay as a convenience property if desired.

Minor: no test for NOT_PLANNED — Even if NOT_PLANNED handling is deferred, a test documenting current behavior for state_reason='not_planned' would make the gap explicit.

Verdict

The PR properly solves what it claims (transferred issues). The NOT_PLANNED gap is a real scoring loophole that should be addressed — either in this PR or as a fast-follow. Storing state_reason on Issue instead of only is_transferred would let both be handled cleanly.

replaces is_transferred: bool with state_reason: Optional[str] on Issue
and keeps is_transferred as a convenience property. gates solved
classification in _collect_issues_from_prs and _merge_scan_issues on
state_reason == 'COMPLETED', so NOT_PLANNED and TRANSFERRED both route
to closed_count. None is effectively unreachable inside the 35-day
lookback since github auto-populates stateReason on close, but the
strict gate is the safer default if that ever changes

tests cover COMPLETED, TRANSFERRED, NOT_PLANNED, and None in both the
pr-linked path and the scan path, plus the REST uppercase normalization
@nudiltoys-cmyk
Copy link
Copy Markdown
Author

thanks for the review

pushed a follow-up commit covering all four points:

  1. swapped is_transferred: bool for state_reason: Optional[str] on Issue, kept is_transferred as a @property returning self.state_reason == 'TRANSFERRED' so downstream readers are untouched
  2. populated state_reason from GraphQL stateReason and REST state_reason (normalized to uppercase for a single gate)
  3. gated solved classification in _collect_issues_from_prs and _merge_scan_issues on state_reason == 'COMPLETED' — anything else (NOT_PLANNED, TRANSFERRED, None) routes to closed_count. None is effectively unreachable inside the 35-day lookback since GitHub auto-populates stateReason on close, but treating it as not-solved is the safer default if that ever changes
  4. added COMPLETED, NOT_PLANNED, TRANSFERRED, and None test cases in both test files alongside the existing transferred ones

12/12 tests in the touched files pass, ruff clean. ready for another look

@nudiltoys-cmyk
Copy link
Copy Markdown
Author

pushed three style-only commits to clear the lint failure -just ruff format on the three files CI flagged, no logic changes. mind re-approving the workflow run?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants