Spaces:
Sleeping
Sleeping
fix: reduce false empty years in activity (deeper scan + better parsing)
Browse files
app.py
CHANGED
|
@@ -31,6 +31,10 @@ REPO_KIND_PALETTES = {
|
|
| 31 |
"space": "orange",
|
| 32 |
}
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
# ============================================================
|
| 36 |
# API
|
|
@@ -54,6 +58,23 @@ def build_session(accept: str) -> requests.Session:
|
|
| 54 |
return session
|
| 55 |
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
# ============================================================
|
| 58 |
# Thread-safe rate limiters
|
| 59 |
# ============================================================
|
|
@@ -478,12 +499,14 @@ def fetch_user_discussion_activity(
|
|
| 478 |
dates = []
|
| 479 |
seen_ids = set()
|
| 480 |
skip = 0
|
| 481 |
-
limit =
|
| 482 |
cursor = None
|
| 483 |
pages_fetched = 0
|
|
|
|
|
|
|
| 484 |
|
| 485 |
-
MAX_PAGES =
|
| 486 |
-
MAX_DURATION =
|
| 487 |
OPEN_EVENT_EPSILON_SECONDS = 90
|
| 488 |
start = time.time()
|
| 489 |
|
|
@@ -491,22 +514,6 @@ def fetch_user_discussion_activity(
|
|
| 491 |
|
| 492 |
session = build_session("application/json")
|
| 493 |
|
| 494 |
-
def normalize_payload(payload):
|
| 495 |
-
if isinstance(payload, list):
|
| 496 |
-
return payload, None
|
| 497 |
-
|
| 498 |
-
if isinstance(payload, dict):
|
| 499 |
-
events = payload.get("recentActivity")
|
| 500 |
-
if isinstance(events, list):
|
| 501 |
-
return events, payload.get("cursor")
|
| 502 |
-
|
| 503 |
-
for key in ["items", "events", "activity"]:
|
| 504 |
-
value = payload.get(key)
|
| 505 |
-
if isinstance(value, list):
|
| 506 |
-
return value, payload.get("cursor")
|
| 507 |
-
|
| 508 |
-
return [], None
|
| 509 |
-
|
| 510 |
def extract_author_name(discussion: dict) -> str | None:
|
| 511 |
author = discussion.get("author")
|
| 512 |
if isinstance(author, str):
|
|
@@ -612,7 +619,7 @@ def fetch_user_discussion_activity(
|
|
| 612 |
payload = response.json()
|
| 613 |
pages_fetched += 1
|
| 614 |
|
| 615 |
-
events, next_cursor =
|
| 616 |
|
| 617 |
if not events:
|
| 618 |
logs.append("No more events.")
|
|
@@ -625,9 +632,17 @@ def fetch_user_discussion_activity(
|
|
| 625 |
continue
|
| 626 |
|
| 627 |
event_ts = parse_ts(event.get("time"))
|
| 628 |
-
if event_ts is not None
|
| 629 |
-
|
| 630 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 631 |
|
| 632 |
discussion = event.get("discussionData") or {}
|
| 633 |
if not isinstance(discussion, dict):
|
|
@@ -638,7 +653,12 @@ def fetch_user_discussion_activity(
|
|
| 638 |
continue
|
| 639 |
|
| 640 |
dt = extract_date_for_mode(event, discussion)
|
| 641 |
-
if dt is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 642 |
continue
|
| 643 |
|
| 644 |
if unique_id in seen_ids:
|
|
@@ -657,6 +677,12 @@ def fetch_user_discussion_activity(
|
|
| 657 |
skip += limit
|
| 658 |
|
| 659 |
logs.append(f"Fetched {pages_fetched} page(s) from recent-activity.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
logs.append(f"Found {len(dates)} unique {mode} event(s) in {selected_year}.")
|
| 661 |
|
| 662 |
return {
|
|
@@ -755,11 +781,13 @@ def fetch_bucketed_recent_activity(
|
|
| 755 |
|
| 756 |
cursor = None
|
| 757 |
skip = 0
|
| 758 |
-
limit =
|
| 759 |
pages_fetched = 0
|
|
|
|
|
|
|
| 760 |
|
| 761 |
-
MAX_PAGES =
|
| 762 |
-
MAX_DURATION =
|
| 763 |
start = time.time()
|
| 764 |
|
| 765 |
logs = []
|
|
@@ -799,8 +827,7 @@ def fetch_bucketed_recent_activity(
|
|
| 799 |
payload = response.json()
|
| 800 |
pages_fetched += 1
|
| 801 |
|
| 802 |
-
events =
|
| 803 |
-
next_cursor = payload.get("cursor")
|
| 804 |
|
| 805 |
if not events:
|
| 806 |
logs.append("No more events.")
|
|
@@ -821,11 +848,18 @@ def fetch_bucketed_recent_activity(
|
|
| 821 |
except Exception:
|
| 822 |
continue
|
| 823 |
|
| 824 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 825 |
stop_early = True
|
| 826 |
break
|
| 827 |
|
| 828 |
-
if
|
| 829 |
continue
|
| 830 |
|
| 831 |
bucket_key, unique_id = classifier(event)
|
|
@@ -867,6 +901,12 @@ def fetch_bucketed_recent_activity(
|
|
| 867 |
)
|
| 868 |
|
| 869 |
logs.append(f"Fetched {pages_fetched} page(s) from recent-activity.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 870 |
logs.append(
|
| 871 |
" | ".join(
|
| 872 |
f"{bucket_titles[key]}: {len(bucket_dates[key])}"
|
|
|
|
| 31 |
"space": "orange",
|
| 32 |
}
|
| 33 |
|
| 34 |
+
RECENT_ACTIVITY_LIMIT = 20
|
| 35 |
+
RECENT_ACTIVITY_MAX_PAGES = 200
|
| 36 |
+
RECENT_ACTIVITY_TIMEOUT_SECONDS = 45
|
| 37 |
+
|
| 38 |
|
| 39 |
# ============================================================
|
| 40 |
# API
|
|
|
|
| 58 |
return session
|
| 59 |
|
| 60 |
|
| 61 |
+
def normalize_recent_activity_payload(payload):
|
| 62 |
+
if isinstance(payload, list):
|
| 63 |
+
return payload, None
|
| 64 |
+
|
| 65 |
+
if isinstance(payload, dict):
|
| 66 |
+
events = payload.get("recentActivity")
|
| 67 |
+
if isinstance(events, list):
|
| 68 |
+
return events, payload.get("cursor")
|
| 69 |
+
|
| 70 |
+
for key in ["items", "events", "activity"]:
|
| 71 |
+
value = payload.get(key)
|
| 72 |
+
if isinstance(value, list):
|
| 73 |
+
return value, payload.get("cursor")
|
| 74 |
+
|
| 75 |
+
return [], None
|
| 76 |
+
|
| 77 |
+
|
| 78 |
# ============================================================
|
| 79 |
# Thread-safe rate limiters
|
| 80 |
# ============================================================
|
|
|
|
| 499 |
dates = []
|
| 500 |
seen_ids = set()
|
| 501 |
skip = 0
|
| 502 |
+
limit = RECENT_ACTIVITY_LIMIT
|
| 503 |
cursor = None
|
| 504 |
pages_fetched = 0
|
| 505 |
+
reached_selected_year = False
|
| 506 |
+
oldest_event_year_seen = None
|
| 507 |
|
| 508 |
+
MAX_PAGES = RECENT_ACTIVITY_MAX_PAGES
|
| 509 |
+
MAX_DURATION = RECENT_ACTIVITY_TIMEOUT_SECONDS
|
| 510 |
OPEN_EVENT_EPSILON_SECONDS = 90
|
| 511 |
start = time.time()
|
| 512 |
|
|
|
|
| 514 |
|
| 515 |
session = build_session("application/json")
|
| 516 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
def extract_author_name(discussion: dict) -> str | None:
|
| 518 |
author = discussion.get("author")
|
| 519 |
if isinstance(author, str):
|
|
|
|
| 619 |
payload = response.json()
|
| 620 |
pages_fetched += 1
|
| 621 |
|
| 622 |
+
events, next_cursor = normalize_recent_activity_payload(payload)
|
| 623 |
|
| 624 |
if not events:
|
| 625 |
logs.append("No more events.")
|
|
|
|
| 632 |
continue
|
| 633 |
|
| 634 |
event_ts = parse_ts(event.get("time"))
|
| 635 |
+
if event_ts is not None:
|
| 636 |
+
event_year = int(event_ts.year)
|
| 637 |
+
if oldest_event_year_seen is None or event_year < oldest_event_year_seen:
|
| 638 |
+
oldest_event_year_seen = event_year
|
| 639 |
+
|
| 640 |
+
if event_year == selected_year:
|
| 641 |
+
reached_selected_year = True
|
| 642 |
+
|
| 643 |
+
if event_year < selected_year:
|
| 644 |
+
stop_early = True
|
| 645 |
+
break
|
| 646 |
|
| 647 |
discussion = event.get("discussionData") or {}
|
| 648 |
if not isinstance(discussion, dict):
|
|
|
|
| 653 |
continue
|
| 654 |
|
| 655 |
dt = extract_date_for_mode(event, discussion)
|
| 656 |
+
if dt is None:
|
| 657 |
+
continue
|
| 658 |
+
|
| 659 |
+
if dt.year == selected_year:
|
| 660 |
+
reached_selected_year = True
|
| 661 |
+
if dt.year != selected_year:
|
| 662 |
continue
|
| 663 |
|
| 664 |
if unique_id in seen_ids:
|
|
|
|
| 677 |
skip += limit
|
| 678 |
|
| 679 |
logs.append(f"Fetched {pages_fetched} page(s) from recent-activity.")
|
| 680 |
+
if oldest_event_year_seen is not None:
|
| 681 |
+
logs.append(f"Oldest event year reached in feed: {oldest_event_year_seen}.")
|
| 682 |
+
if reached_selected_year:
|
| 683 |
+
logs.append(f"Selected year {selected_year} was reached in the feed.")
|
| 684 |
+
else:
|
| 685 |
+
logs.append(f"Selected year {selected_year} was NOT reached in the feed.")
|
| 686 |
logs.append(f"Found {len(dates)} unique {mode} event(s) in {selected_year}.")
|
| 687 |
|
| 688 |
return {
|
|
|
|
| 781 |
|
| 782 |
cursor = None
|
| 783 |
skip = 0
|
| 784 |
+
limit = RECENT_ACTIVITY_LIMIT
|
| 785 |
pages_fetched = 0
|
| 786 |
+
reached_selected_year = False
|
| 787 |
+
oldest_event_year_seen = None
|
| 788 |
|
| 789 |
+
MAX_PAGES = RECENT_ACTIVITY_MAX_PAGES
|
| 790 |
+
MAX_DURATION = RECENT_ACTIVITY_TIMEOUT_SECONDS
|
| 791 |
start = time.time()
|
| 792 |
|
| 793 |
logs = []
|
|
|
|
| 827 |
payload = response.json()
|
| 828 |
pages_fetched += 1
|
| 829 |
|
| 830 |
+
events, next_cursor = normalize_recent_activity_payload(payload)
|
|
|
|
| 831 |
|
| 832 |
if not events:
|
| 833 |
logs.append("No more events.")
|
|
|
|
| 848 |
except Exception:
|
| 849 |
continue
|
| 850 |
|
| 851 |
+
event_year = int(event_ts.year)
|
| 852 |
+
if oldest_event_year_seen is None or event_year < oldest_event_year_seen:
|
| 853 |
+
oldest_event_year_seen = event_year
|
| 854 |
+
|
| 855 |
+
if event_year == selected_year:
|
| 856 |
+
reached_selected_year = True
|
| 857 |
+
|
| 858 |
+
if event_year < selected_year:
|
| 859 |
stop_early = True
|
| 860 |
break
|
| 861 |
|
| 862 |
+
if event_year != selected_year:
|
| 863 |
continue
|
| 864 |
|
| 865 |
bucket_key, unique_id = classifier(event)
|
|
|
|
| 901 |
)
|
| 902 |
|
| 903 |
logs.append(f"Fetched {pages_fetched} page(s) from recent-activity.")
|
| 904 |
+
if oldest_event_year_seen is not None:
|
| 905 |
+
logs.append(f"Oldest event year reached in feed: {oldest_event_year_seen}.")
|
| 906 |
+
if reached_selected_year:
|
| 907 |
+
logs.append(f"Selected year {selected_year} was reached in the feed.")
|
| 908 |
+
else:
|
| 909 |
+
logs.append(f"Selected year {selected_year} was NOT reached in the feed.")
|
| 910 |
logs.append(
|
| 911 |
" | ".join(
|
| 912 |
f"{bucket_titles[key]}: {len(bucket_dates[key])}"
|