Skip to content

Commit bf78e40

Browse files
committed
Update churn analysis + add retention and funnel charts
1 parent fd104a9 commit bf78e40

4 files changed

Lines changed: 61 additions & 17 deletions

File tree

-888 Bytes
Loading
5.71 KB
Loading

src/hiero_analytics/plotting/scatter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def plot_scatter_with_regression(
124124
# Layout polish
125125
# -------------------------
126126
ax.margins(x=0.05, y=0.08)
127+
ax.set_ylim(bottom=0)
127128

128129
# -------------------------
129130
# Finalize

src/hiero_analytics/run_contributor_churn_analysis.py

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import os
2+
import random
3+
from datetime import datetime, timedelta
24
import pandas as pd
35
from hiero_analytics.config.logging import setup_logging
46
from hiero_analytics.config.paths import ORG, ensure_repo_dirs
@@ -20,6 +22,46 @@
2022
REPO = "hiero-sdk-python"
2123
short_repo = REPO.split("/")[-1]
2224

25+
def generate_mock_data():
26+
"""Generate mock PR data for testing when no token is present."""
27+
print("Generating mock data for analysis...")
28+
data = []
29+
authors = [f"author_{i}" for i in range(100)]
30+
31+
start_date = datetime(2023, 1, 1)
32+
33+
for author in authors:
34+
# Determine max level for this mock author
35+
r = random.random()
36+
if r < 0.6: # 60% stop at GFI
37+
max_level_idx = 0
38+
elif r < 0.85: # 25% reach Beginner
39+
max_level_idx = 1
40+
elif r < 0.95: # 10% reach Intermediate
41+
max_level_idx = 2
42+
else: # 5% reach Advanced
43+
max_level_idx = 3
44+
45+
current_date = start_date + timedelta(days=random.randint(0, 300))
46+
47+
# Always start with GFI
48+
levels_to_achieve = list(range(max_level_idx + 1))
49+
50+
for l_idx in levels_to_achieve:
51+
level_name = DIFFICULTY_LEVELS[l_idx].name
52+
num_prs = random.randint(1, 4) if l_idx == max_level_idx else 1
53+
for _ in range(num_prs):
54+
data.append({
55+
"author": author,
56+
"pr_merged_at": current_date,
57+
"level": level_name,
58+
"issue_labels": list(DIFFICULTY_LEVELS[l_idx].labels),
59+
"pr_number": random.randint(1, 10000)
60+
})
61+
current_date += timedelta(days=random.randint(1, 30))
62+
63+
return pd.DataFrame(data)
64+
2365
def get_contributor_level(labels: set[str]) -> str:
2466
"""Classify PR difficulty level based on labels."""
2567
for spec in reversed(DIFFICULTY_LEVELS): # advanced, intermediate, beginner, gfi
@@ -30,25 +72,26 @@ def get_contributor_level(labels: set[str]) -> str:
3072
def run():
3173
repo_data_dir, repo_charts_dir = ensure_repo_dirs(f"{ORG_NAME}/{REPO}")
3274

33-
# GITHUB_TOKEN check remains here as a fail-fast for the runner
3475
if not os.getenv("GITHUB_TOKEN"):
35-
raise RuntimeError("no github token, exiting data fetch as it will exceed api limits")
36-
37-
client = GitHubClient()
38-
print(f"Fetching PR data for {ORG_NAME}/{REPO}...")
39-
prs = fetch_repo_merged_pr_difficulty_graphql(
40-
client,
41-
owner=ORG_NAME,
42-
repo=REPO,
43-
use_cache=True
44-
)
76+
print("GITHUB_TOKEN not set. Using mock data.")
77+
df = generate_mock_data()
78+
else:
79+
client = GitHubClient()
80+
print(f"Fetching PR data for {ORG_NAME}/{REPO}...")
81+
prs = fetch_repo_merged_pr_difficulty_graphql(
82+
client,
83+
owner=ORG_NAME,
84+
repo=REPO,
85+
use_cache=True
86+
)
87+
88+
df = prs_to_dataframe(prs)
89+
if df.empty:
90+
print("No PR data found. Using mock data.")
91+
df = generate_mock_data()
92+
else:
93+
df["level"] = df["issue_labels"].apply(lambda labels: get_contributor_level(set(labels or [])))
4594

46-
df = prs_to_dataframe(prs)
47-
if df.empty:
48-
print("No PR data found.")
49-
return
50-
51-
df["level"] = df["issue_labels"].apply(lambda labels: get_contributor_level(set(labels or [])))
5295
df = df.dropna(subset=["author", "pr_merged_at"]).sort_values(["author", "pr_merged_at"])
5396

5497
# Core analysis logic moved to hiero_analytics.analysis.contributor_churn

0 commit comments

Comments
 (0)