Skip to content

Commit fd3f931

Browse files
committed
feat: added analyze_sponsors.py script
1 parent b9c9324 commit fd3f931

File tree

4 files changed

+190
-0
lines changed

4 files changed

+190
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
__pycache__
22
*.png
3+
output

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
output/sponsors.html: output/sponsors.md
2+
markdown2 $< -x tables > $@
3+
4+
output/sponsors.md: analyze_sponsors.py
5+
python3 analyze_sponsors.py > $@

analyze_sponsors.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
"""
2+
Analyze sponsor data to generate a sponsors table on the website
3+
"""
4+
5+
from dataclasses import dataclass, field
6+
from datetime import datetime, timedelta, timezone
7+
from typing import Optional
8+
9+
import pandas as pd
10+
11+
Amount = float
12+
Currency = str
13+
14+
15+
@dataclass
16+
class Sponsor:
17+
name: str
18+
github_username: Optional[str] = None
19+
donated: list[tuple[Amount, Currency, datetime]] = field(default_factory=list)
20+
source: str = "unknown"
21+
22+
def __repr__(self):
23+
return f"<Sponsor {self.name} ({self.github_username}) on {self.source} total_donated={self.total_donated}>"
24+
25+
@property
26+
def total_donated(self):
27+
# group donated by currency, aggregate by sum
28+
currencies = set([currency for _, currency, _ in self.donated])
29+
total_donated = {}
30+
for currency in currencies:
31+
total_donated[currency] = sum(
32+
[amount for amount, c, _ in self.donated if c == currency]
33+
)
34+
return total_donated
35+
36+
37+
def load_github_sponsors_csv(filename: str) -> list[Sponsor]:
38+
"""The CSV looks like the following:
39+
40+
Sponsor Handle,Sponsor Profile Name,Sponsor Public Email,Sponsorship Started On,Is Public?,Is Yearly?,Transaction ID,Tier Name,Tier Monthly Amount,Processed Amount,Is Prorated?,Status,Transaction Date,Metadata,Country,Region,VAT
41+
snehal-shekatkar,Snehal Shekatkar,,2023-05-07 21:45:50 +0200,true,false,ch_3N5DWnEQsq43iHhX1Svht2g7,$5 a month,$5.00,$4.52,true,settled,2023-05-07 21:46:01 +0200,"",AUT,undefined,
42+
justyn,Justyn Butler,,2023-02-15 12:14:28 +0100,true,false,ch_3NExsXEQsq43iHhX0Fmfcg6w,$3 a month,$3.00,$3.00,false,settled,2023-06-03 19:04:45 +0200,"",GBR,Kent,
43+
justyn,Justyn Butler,,2023-02-15 12:14:28 +0100,true,false,ch_3N3ikOEQsq43iHhX1eMXefVn,$3 a month,$3.00,$3.00,false,settled,2023-05-03 18:41:56 +0200,"",GBR,Kent,
44+
"""
45+
46+
df = pd.read_csv(
47+
filename, parse_dates=["Sponsorship Started On", "Transaction Date"]
48+
)
49+
df = df[df["Is Public?"] == True] # noqa: E712
50+
df = df[df["Status"] == "settled"]
51+
52+
sponsors = []
53+
handles = df["Sponsor Handle"].unique()
54+
for handle in handles:
55+
sponsor = Sponsor(
56+
name="placeholder",
57+
github_username=handle,
58+
donated=[],
59+
source="github",
60+
)
61+
for _, row in df.iterrows():
62+
if row["Sponsor Handle"] != handle:
63+
continue
64+
sponsor.name = row["Sponsor Profile Name"]
65+
sponsor.donated.append(
66+
(
67+
float(row["Processed Amount"].replace("$", "")),
68+
"USD",
69+
row["Transaction Date"],
70+
)
71+
)
72+
73+
sponsors.append(sponsor)
74+
75+
return sponsors
76+
77+
78+
def load_opencollective_csv(filename: str) -> list[Sponsor]:
79+
"""The CSV looks like this:
80+
81+
"datetime","shortId","shortGroup","description","type","kind","isRefund","isRefunded","shortRefundId","displayAmount","amount","paymentProcessorFee","netAmount","balance","currency","accountSlug","accountName","oppositeAccountSlug","oppositeAccountName","paymentMethodService","paymentMethodType","expenseType","expenseTags","payoutMethodType","merchantId","orderMemo"
82+
"2023-06-06T05:47:17","d3f98b95","f2238225","Contribution from Martin","CREDIT","CONTRIBUTION","","","","$50.00 USD",50,-2.5,47.5,3059.14,"USD","activitywatch","ActivityWatch","guest-cf5e5bf5","Martin","STRIPE","CREDITCARD",,"",,,
83+
"2023-06-06T05:47:17","e3c0d1f9","f2238225","Host Fee to Open Source Collective","DEBIT","HOST_FEE","","","","-$5.00 USD",-5,0,-5,3011.64,"USD","activitywatch","ActivityWatch","opensource","Open Source Collective",,,,"",,,
84+
"2023-06-01T04:03:23","80120e4e","9a189bcd","Yearly contribution from Olli Nevalainen","CREDIT","CONTRIBUTION","","","","$24.00 USD",24,-1.24,22.76,3016.64,"USD","activitywatch","ActivityWatch","olli-nevalainen","Olli Nevalainen","STRIPE","CREDITCARD",,"",,,
85+
"""
86+
87+
df = pd.read_csv(filename, parse_dates=["datetime"])
88+
df = df[df["type"] == "CREDIT"]
89+
90+
sponsors = []
91+
handles = df["oppositeAccountSlug"].unique()
92+
for handle in handles:
93+
sponsor = Sponsor(
94+
name="placeholder",
95+
github_username=handle,
96+
donated=[],
97+
source="opencollective",
98+
)
99+
for _, row in df.iterrows():
100+
if row["oppositeAccountSlug"] != handle:
101+
continue
102+
sponsor.name = row["oppositeAccountName"]
103+
sponsor.donated.append(
104+
(
105+
row["amount"],
106+
row["currency"],
107+
row["datetime"].replace(tzinfo=timezone.utc),
108+
)
109+
)
110+
111+
sponsors.append(sponsor)
112+
113+
# remove 'GitHub Sponsors' from sponsors
114+
sponsors = [sponsor for sponsor in sponsors if sponsor.name != "GitHub Sponsors"]
115+
116+
# subtract $3002 from Kerkko (actually FUUG.fi)
117+
for sponsor in sponsors:
118+
if sponsor.name == "Kerkko Pelttari":
119+
sponsor.donated.append((-3002, "USD", datetime.now(tz=timezone.utc)))
120+
121+
return sponsors
122+
123+
124+
def load_patreon_csv(filename: str) -> list[Sponsor]:
125+
"""The CSV looks like:
126+
127+
Name,Email,Twitter,Discord,Patron Status,Follows You,Lifetime Amount,Pledge Amount,Charge Frequency,Tier,Addressee,Street,City,State,Zip,Country,Phone,Patronage Since Date,Last Charge Date,Last Charge Status,Additional Details,User ID,Last Updated,Currency,Max Posts,Access Expiration,Next Charge Date
128+
Karan singh,[email protected],,kraft#9466,Declined patron,No,0.00,1.00,monthly,Time Tracker,,,,,,,,2023-04-08 17:45:28.731953,2023-05-31 07:46:37,Declined,,76783024,2023-05-31 08:01:37.517184,USD,,,2023-05-01 07:00:00
129+
Dan Thompson,[email protected],,,Declined patron,No,0.00,1.00,monthly,Time Tracker,,,,,,,,2023-02-28 20:00:03.756241,2023-03-15 12:03:01,Declined,,46598895,2023-03-15 12:18:01.633900,USD,,,2023-03-01 08:00:00
130+
"""
131+
132+
df = pd.read_csv(filename, parse_dates=["Patronage Since Date", "Last Charge Date"])
133+
134+
sponsors = []
135+
for _, row in df.iterrows():
136+
sponsor = Sponsor(
137+
name=row["Name"],
138+
github_username=None,
139+
donated=[
140+
(
141+
row["Lifetime Amount"],
142+
row["Currency"],
143+
row["Last Charge Date"].replace(tzinfo=timezone.utc),
144+
)
145+
],
146+
source="patreon",
147+
)
148+
sponsors.append(sponsor)
149+
150+
return sponsors
151+
152+
153+
if __name__ == "__main__":
154+
now = datetime.now(tz=timezone.utc)
155+
156+
sponsors = load_github_sponsors_csv(
157+
"data/sponsors/ActivityWatch-sponsorships-all-time.csv"
158+
)
159+
sponsors += load_opencollective_csv(
160+
"data/sponsors/opencollective-activitywatch-transactions.csv"
161+
)
162+
sponsors += load_patreon_csv("data/sponsors/patreon-members-866337.csv")
163+
sponsors = sorted(sponsors, key=lambda s: s.total_donated["USD"], reverse=True)
164+
165+
# filter out sponsors who have donated less than $10
166+
sponsors = [sponsor for sponsor in sponsors if sponsor.total_donated["USD"] >= 10]
167+
168+
# print as markdown table
169+
print("| Name | Active? | Total Donated |")
170+
print("| ---- |:-------:| -------------:|")
171+
for sponsor in sponsors:
172+
link = (
173+
f"([@{sponsor.github_username}](https://github.com/{sponsor.github_username}))"
174+
if sponsor.github_username
175+
else ""
176+
)
177+
# active if last donation was less than 3 months ago
178+
last_donation: datetime = max(timestamp for _, _, timestamp in sponsor.donated)
179+
is_active = (
180+
last_donation > now - timedelta(days=90) if sponsor.donated else False
181+
)
182+
print(
183+
f"| {sponsor.name} {link} | {'✔️' if is_active else ''} | {sponsor.total_donated['USD']:.2f} USD |"
184+
)

output/.empty

Whitespace-only changes.

0 commit comments

Comments
 (0)