"""Project 101: reply rates by feature, within follower bands. Reads posts.json, writes features.json + prints tables. 'answered' = at least one top-level reply by someone other than the author (self-threads don't count).""" import json, re from collections import defaultdict D = json.load(open("posts.json", encoding="utf-8")); P = D["posts"] def band(f): return "<100" if f < 100 else "100-999" if f < 1000 else "1000+" def feats(p): t = p["text"]; tl = t.lower() tags = len(re.findall(r"#\w+", t)) return { "question": "?" in t, "asks_reader": bool(re.search(r"\b(you|your|anyone|what do|how do you|would you|thoughts|feedback|which)\b", tl)) and "?" in t, "image/video": p["image"], "link": p["link"], "number": bool(re.search(r"\d", t)), "first person story (I/my)": bool(re.search(r"\b(i|i'm|i've|my)\b", tl)), "money ($/MRR/revenue)": bool(re.search(r"\$|mrr|revenue|sales|paying|customers", tl)), "launch/promo words": bool(re.search(r"\b(launch|launched|check out|try it|sign up|waitlist|link in|free trial)\b", tl)), "mentions AI": bool(re.search(r"\b(ai|llm|gpt|claude|agent)\b", tl)), "5+ hashtags": tags >= 5, "short (<120 chars)": len(t) < 120, "long (>250 chars)": len(t) > 250, "bot label": p["bot"], } ok = [p for p in P if p.get("others") is not None] for p in ok: p["answered"] = p["others"] > 0 out = {"n": len(ok), "base": {}, "features": {}} print(f"posts with known repliers: {len(ok)} of {len(P)}") for b in ["<100", "100-999", "1000+"]: s = [p for p in ok if band(p["followers"]) == b] r = sum(p["answered"] for p in s) / max(1, len(s)) out["base"][b] = {"n": len(s), "answered": round(r, 3)} print(f"band {b:8} n={len(s):3} answered {r:.0%} median likes {sorted(p['likes'] for p in s)[len(s)//2] if s else 0}") allr = sum(p["answered"] for p in ok) / len(ok); print(f"ALL answered {allr:.0%}") names = list(feats(ok[0]).keys()) print(f"\n{'feature':28} {'with: n / answered':>20} {'without':>16} per band with-vs-without") for f in names: w = [p for p in ok if feats(p)[f]]; wo = [p for p in ok if not feats(p)[f]] rate = lambda s: sum(p["answered"] for p in s) / len(s) if s else float("nan") per = [] for b in ["<100", "100-999", "1000+"]: bw = [p for p in w if band(p["followers"]) == b]; bwo = [p for p in wo if band(p["followers"]) == b] per.append(f"{b}:{rate(bw):.0%}({len(bw)})/{rate(bwo):.0%}({len(bwo)})" if bw and bwo else f"{b}:-") out["features"][f] = {"with_n": len(w), "with": round(rate(w), 3) if w else None, "without_n": len(wo), "without": round(rate(wo), 3) if wo else None, "bands": per} print(f"{f:28} {len(w):4} / {rate(w):4.0%} {len(wo):4} / {rate(wo):4.0%} {' '.join(per)}") # who answers: bots among repliers reps = [r for p in ok for r in p.get("other_replies", [])] print(f"\nreplies read: {len(reps)}, by bot-labelled accounts: {sum(r['bot'] for r in reps)}") # concentration: authors posting many times from collections import Counter c = Counter(p["handle"] for p in P) print("most active authors:", c.most_common(6)) json.dump(out, open("features.json", "w"), indent=1)