# Real public counts from three organization accounts on the Mastodon API (no login, read-only), to show how the numerator and the account size change an engagement rate by followers.
# Mastodon shows favourites, boosts and replies on every post, and the follower count of the account; it shows no reach or views to outsiders and has no saves.
# Usage: python3 -I real-data.py     Counts are read at run time and change; the output file is the record of what we read.
import json, time, datetime, urllib.request
from decimal import Decimal, ROUND_HALF_UP
def get(u):
    req = urllib.request.Request(u, headers={"User-Agent": "koldflux-research/1.0 (read-only, public API)"})
    return json.load(urllib.request.urlopen(req, timeout=25))
def pct(n, d):
    return str((Decimal(n) * 100 / Decimal(d)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)) + "%"
now = datetime.datetime.now(datetime.timezone.utc)
print("read at", now.strftime("%Y-%m-%dT%H:%M:%SZ"), "| posts: the 20 newest original posts (no replies, no boosts) that are at least 7 days old")
print("account | followers | posts | oldest..newest post | favourites | boosts | replies | favourites only | favourites+boosts | favourites+boosts+replies | lowest..highest post rate (all three)")
for inst, acct in (("mastodon.social", "Mastodon"), ("wikis.world", "wikipedia"), ("floss.social", "gnome")):
    a = get(f"https://{inst}/api/v1/accounts/lookup?acct={acct}")
    fol = a["followers_count"]
    st = get(f"https://{inst}/api/v1/accounts/{a['id']}/statuses?limit=40&exclude_replies=true&exclude_reblogs=true")
    old = [s for s in st if (now - datetime.datetime.fromisoformat(s["created_at"].replace("Z", "+00:00"))).days >= 7][:20]
    fav = sum(s["favourites_count"] for s in old); bo = sum(s["reblogs_count"] for s in old); rep = sum(s["replies_count"] for s in old)
    per = [Decimal(s["favourites_count"] + s["reblogs_count"] + s["replies_count"]) * 100 / Decimal(fol) for s in old]
    mean_rate = sum(per) / len(per)
    pooled = Decimal(fav + bo + rep) * 100 / Decimal(fol * len(old))
    assert abs(mean_rate - pooled) < Decimal("0.0000001"), "constant denominator: mean of rates must equal the pooled rate"
    n = len(old)
    print(f"@{acct}@{inst} | {fol:,} | {n} | {old[-1]['created_at'][:10]}..{old[0]['created_at'][:10]} | {fav} | {bo} | {rep} | "
          f"{pct(fav, fol * n)} | {pct(fav + bo, fol * n)} | {pct(fav + bo + rep, fol * n)} | {str(min(per).quantize(Decimal('0.01')))}%..{str(max(per).quantize(Decimal('0.01')))}%")
    print(f"   check: all three / favourites only = {Decimal(fav + bo + rep) / Decimal(fav):.2f} times; mean of per-post rates equals pooled rate: {mean_rate.quantize(Decimal('0.0001')) == pooled.quantize(Decimal('0.0001'))}")
    time.sleep(1)
