|
11 | 11 | from pathlib import Path |
12 | 12 | from urllib.parse import quote |
13 | 13 |
|
14 | | -SCRIPT_VERSION = 4 |
| 14 | +SCRIPT_VERSION = 5 |
15 | 15 | QUALIFYING_KIND_LABELS = ("bug", "enhancement") |
16 | 16 | REACTION_KEYS = ("+1", "-1", "laugh", "hooray", "confused", "heart", "rocket", "eyes") |
17 | 17 | BASE_ATTENTION_WINDOW_HOURS = 24.0 |
@@ -393,9 +393,15 @@ def is_bot_login(login): |
393 | 393 | return bool(login) and login.lower().endswith("[bot]") |
394 | 394 |
|
395 | 395 |
|
396 | | -def is_human_user(user_obj): |
| 396 | +def human_login_key(user_obj): |
397 | 397 | login = extract_login(user_obj) |
398 | | - return bool(login) and not is_bot_login(login) |
| 398 | + if not login or is_bot_login(login): |
| 399 | + return "" |
| 400 | + return login.casefold() |
| 401 | + |
| 402 | + |
| 403 | +def is_human_user(user_obj): |
| 404 | + return bool(human_login_key(user_obj)) |
399 | 405 |
|
400 | 406 |
|
401 | 407 | def label_names(issue): |
@@ -467,22 +473,26 @@ def reaction_summary(item): |
467 | 473 | def reaction_event_summary(reactions, since, until): |
468 | 474 | counts = {} |
469 | 475 | total = 0 |
| 476 | + users = set() |
470 | 477 | for reaction in reactions or []: |
471 | 478 | if not isinstance(reaction, dict): |
472 | 479 | continue |
473 | 480 | if not is_in_window(str(reaction.get("created_at") or ""), since, until): |
474 | 481 | continue |
475 | | - if not is_human_user(reaction.get("user")): |
| 482 | + user_key = human_login_key(reaction.get("user")) |
| 483 | + if not user_key: |
476 | 484 | continue |
477 | 485 | content = str(reaction.get("content") or "") |
478 | 486 | if not content: |
479 | 487 | continue |
480 | 488 | counts[content] = counts.get(content, 0) + 1 |
481 | 489 | total += 1 |
| 490 | + users.add(user_key) |
482 | 491 | return { |
483 | 492 | "total": total, |
484 | 493 | "counts": counts, |
485 | 494 | "upvotes": counts.get("+1", 0), |
| 495 | + "users": sorted(users, key=str.casefold), |
486 | 496 | } |
487 | 497 |
|
488 | 498 |
|
@@ -618,13 +628,21 @@ def summarize_issue( |
618 | 628 | new_comment_reaction_total = sum( |
619 | 629 | comment["reaction_total"] for comment in new_comments |
620 | 630 | ) |
621 | | - new_issue_user_interaction = new_issue and is_human_user(issue.get("user")) |
| 631 | + new_issue_user_key = human_login_key(issue.get("user")) if new_issue else "" |
| 632 | + new_issue_user_interaction = bool(new_issue_user_key) |
622 | 633 | new_comment_user_interactions = sum( |
623 | 634 | 1 for comment in new_comments if comment["human_user_interaction"] |
624 | 635 | ) |
625 | | - user_interactions = ( |
626 | | - int(new_issue_user_interaction) + new_comment_user_interactions + new_reactions |
| 636 | + interaction_user_keys = set(issue_reaction_events_summary["users"]) |
| 637 | + interaction_user_keys.update(comment_reaction_events_summary["users"]) |
| 638 | + if new_issue_user_key: |
| 639 | + interaction_user_keys.add(new_issue_user_key) |
| 640 | + interaction_user_keys.update( |
| 641 | + comment["author"].casefold() |
| 642 | + for comment in new_comments |
| 643 | + if comment["human_user_interaction"] |
627 | 644 | ) |
| 645 | + user_interactions = len(interaction_user_keys) |
628 | 646 | attention_level = attention_level_for(user_interactions, attention_thresholds) |
629 | 647 | attention_marker = attention_marker_for(user_interactions, attention_thresholds) |
630 | 648 | updated_without_visible_new_post = ( |
@@ -957,6 +975,7 @@ def collect_digest(args): |
957 | 975 | "New issue comments are filtered by comment creation time within the window from the fetched comment set.", |
958 | 976 | "Reaction events are counted by GitHub reaction created_at timestamps for hydrated issues and fetched comments.", |
959 | 977 | "Current reaction totals are standing engagement signals; new_reactions and new_upvotes are windowed activity.", |
| 978 | + "user_interactions counts unique human users per issue across new issues, new comments, and new reactions; repeated actions by the same user count once.", |
960 | 979 | "The collector does not assign semantic clusters; use summary_inputs as model-ready evidence for report-time clustering.", |
961 | 980 | "Pure reaction-only issues may be missed if GitHub issue search does not surface them via updated_at.", |
962 | 981 | "Issues updated during the window without a new issue body or new comment are retained because label/status edits can still be useful owner signals.", |
|
0 commit comments