backnforth/tools/stats_report.py
type-two 01b9f8097b First-party anonymous metrics: --stats JSONL + report tool
Page views and relay joins/session-lengths appended as JSON lines. Uniques
via daily-salted hash — no IPs, no cookies, no client-side code, no third
parties. tools/stats_report.py prints a per-day table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:18:57 +10:00

45 lines
1.4 KiB
Python

"""Traffic report from server.py --stats JSONL.
python3 tools/stats_report.py /path/to/stats.jsonl [days]
"""
import json
import sys
import time
from collections import defaultdict
path = sys.argv[1] if len(sys.argv) > 1 else 'stats.jsonl'
days_back = int(sys.argv[2]) if len(sys.argv) > 2 else 14
cutoff = time.time() - days_back * 86400
daily = defaultdict(lambda: {'views': 0, 'uniq': set(), 'hosts': 0, 'patients': 0})
sessions = []
for line in open(path, encoding='utf-8'):
try:
d = json.loads(line)
except ValueError:
continue
if d.get('t', 0) < cutoff:
continue
day = time.strftime('%Y-%m-%d', time.localtime(d['t']))
row = daily[day]
if d['e'] == 'page':
row['views'] += 1
row['uniq'].add(d.get('u'))
elif d['e'] == 'join':
row['hosts' if d.get('role') == 'admin' else 'patients'] += 1
elif d['e'] == 'session' and d.get('role') == 'admin':
sessions.append(d.get('secs', 0))
print(f"{'day':12} {'views':>6} {'uniques':>8} {'hosts':>6} {'patients':>9}")
for day in sorted(daily):
r = daily[day]
print(f"{day:12} {r['views']:>6} {len(r['uniq']):>8} {r['hosts']:>6} {r['patients']:>9}")
if sessions:
sessions.sort()
med = sessions[len(sessions) // 2]
print(f"\n{len(sessions)} hosted relay sessions · median {med // 60}m{med % 60:02d}s "
f"· longest {max(sessions) // 60}m")
else:
print('\nno completed relay sessions in range')