Source code for sw_metadata_bot.incremental

"""Decision engine for incremental issue lifecycle handling."""

from dataclasses import dataclass


[docs] @dataclass(frozen=True) class Decision: """Decision outcome for a repository in incremental mode.""" action: str reason: str
[docs] def evaluate( *, previous_exists: bool, unsubscribed: bool, repo_updated: bool, has_findings: bool, identical_findings: bool, previous_issue_open: bool, codemeta_missing: bool, previous_codemeta_missing: bool, ) -> Decision: """Evaluate the configured decision tree and return action + reason. This function implements a cascading decision tree that determines whether to create a new issue, update an existing one with a comment, close it, or stop (skip). The logic prioritizes certain conditions to prevent unnecessary noise. Decision Tree (evaluated in order):: 1. NO PREVIOUS ANALYSIS action="create" (first-time analysis, always create issue) 2. UNSUBSCRIBE DETECTED action="stop" (user explicitly unsubscribed, respect their choice) 3. REPOSITORY NOT UPDATED action="stop" (no changes since last analysis, skip) 4. MISSING CODEMETA WITHOUT OTHER FINDINGS Check if codemeta status changed: - If issue open AND codemeta status unchanged: action="stop" (already reported, issue still relevant) - If issue open AND codemeta status changed: action="comment" (report that codemeta was added/removed) - If no issue open: action="create" (new codemeta issue) 5. NO FINDINGS (REPO IS CLEAN) - If issue is open: action="close" (metadata quality improved, close issue) - If no issue: action="stop" (nothing to report) 6. FINDINGS IDENTICAL TO PREVIOUS Check issue state: - If issue open: action="stop" (same issue already posted) - If issue closed: action="create" (quality got worse again after improvements) 7. FINDINGS CHANGED (DEFAULT CASE) Check issue state: - If issue open: action="comment" (update existing issue with new findings) - If no issue: return "create" (quality changed while issue is closed) Args: previous_exists: Whether a previous analysis snapshot exists unsubscribed: Whether unsubscribe comment detected on existing issue repo_updated: Whether repository has new commits since last analysis has_findings: Whether current analysis found metadata issues identical_findings: Whether findings are identical to previous run previous_issue_open: Whether previously opened issue is still open codemeta_missing: Whether codemeta.json is missing in current analysis previous_codemeta_missing: Whether codemeta.json was missing in previous analysis Returns: Decision object with action and reason explaining the choice Note: For research software: This decision tree is intentionally conservative, favoring skipping unnecessary issues over creating duplicate noise. Changes to this logic should be discussed as they affect user experience. """ if not previous_exists: return Decision(action="create", reason="no_previous_analysis") if unsubscribed: return Decision(action="stop", reason="unsubscribe") if not repo_updated: return Decision(action="stop", reason="repo_not_updated") if codemeta_missing and not has_findings: if previous_issue_open and previous_codemeta_missing: return Decision( action="stop", reason="missing_codemeta_identical_and_issue_open", ) if previous_issue_open: return Decision( action="comment", reason="missing_codemeta_changed_and_issue_open", ) return Decision(action="create", reason="missing_codemeta") if not has_findings: if previous_issue_open: return Decision(action="close", reason="no_findings_close_open_issue") return Decision(action="stop", reason="no_findings") if identical_findings: if previous_issue_open: return Decision(action="stop", reason="identical_and_issue_open") return Decision(action="create", reason="identical_but_issue_closed") if previous_issue_open: return Decision(action="comment", reason="changed_and_issue_open") return Decision(action="create", reason="changed_and_issue_closed")