"""DVM census: is anyone using Nostr's job market (NIP-90)? Reads public relay data only (a few REQs, no scraping of pages, no posting). Counts job requests (5000-5999), results (6000-6999), feedback (7000) and handler announcements (31990) over the last N days, per kind, with distinct requesters/providers and payment amounts asked in feedback events. Usage: python census.py [days] -> out/census_.json + printed summary """ from __future__ import annotations import collections import json import sys import time from datetime import datetime, timezone from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "tools")) import _nostr_core as nc # noqa: E402 RELAYS = [ "wss://relay.damus.io", "wss://nos.lol", "wss://relay.primal.net", "wss://nostr.mom", "wss://relay.nostrdvm.com", "wss://relay.nostr.band", "wss://nostr.oxtr.dev", ] REQ_KINDS = list(range(5000, 6000)) RES_KINDS = list(range(6000, 7000)) def gather(flt: dict) -> tuple[dict[str, dict], dict[str, int]]: per_relay = nc.fetch(flt, RELAYS) seen: dict[str, dict] = {} counts = {} for r, evs in per_relay.items(): counts[r] = len(evs) for e in evs: if nc.verify_event(e): seen[e["id"]] = e return seen, counts def tag(e: dict, name: str) -> list[str] | None: for t in e.get("tags", []): if t and t[0] == name: return t return None def main() -> None: days = int(sys.argv[1]) if len(sys.argv) > 1 else 7 since = int(time.time()) - days * 86400 out = {"checked_at": datetime.now(timezone.utc).isoformat(timespec="seconds"), "days": days, "relays": RELAYS} reqs, rc = gather({"kinds": REQ_KINDS, "since": since, "limit": 2000}) ress, sc = gather({"kinds": RES_KINDS, "since": since, "limit": 2000}) fbs, fc = gather({"kinds": [7000], "since": since, "limit": 2000}) hnd, hc = gather({"kinds": [31990], "limit": 2000}) out["per_relay_counts"] = {"requests": rc, "results": sc, "feedback": fc, "handlers": hc} by_kind = collections.defaultdict(lambda: {"requests": 0, "requesters": set(), "results": 0, "providers": set(), "answered": 0, "bid_msats": []}) req_ids = {} for e in reqs.values(): k = by_kind[e["kind"]] k["requests"] += 1 k["requesters"].add(e["pubkey"]) bid = tag(e, "bid") if bid and len(bid) > 1 and bid[1].isdigit(): k["bid_msats"].append(int(bid[1])) req_ids[e["id"]] = e["kind"] answered = set() for e in ress.values(): k = by_kind[e["kind"] - 1000] k["results"] += 1 k["providers"].add(e["pubkey"]) et = tag(e, "e") if et and len(et) > 1 and et[1] in req_ids: answered.add(et[1]) for rid in answered: by_kind[req_ids[rid]]["answered"] += 1 pay = collections.Counter() amounts = [] for e in fbs.values(): st = tag(e, "status") if st and len(st) > 1: pay[st[1]] += 1 am = tag(e, "amount") if am and len(am) > 1 and am[1].isdigit() and st and st[1] == "payment-required": amounts.append(int(am[1])) hk = collections.Counter() hpub = set() recent_h = 0 for e in hnd.values(): hpub.add(e["pubkey"]) if e["created_at"] >= since: recent_h += 1 for t in e.get("tags", []): if t and t[0] == "k" and len(t) > 1: hk[t[1]] += 1 ts = sorted(e["created_at"] for e in reqs.values()) out["request_span_hours"] = round((ts[-1] - ts[0]) / 3600, 1) if ts else 0 out["unique"] = {"requests": len(reqs), "results": len(ress), "feedback": len(fbs), "handlers": len(hnd), "handler_pubkeys": len(hpub), "handlers_updated_in_window": recent_h} out["all_requesters"] = len({e["pubkey"] for e in reqs.values()}) out["all_providers"] = len({e["pubkey"] for e in ress.values()}) out["by_kind"] = { str(kk): {"requests": v["requests"], "requesters": len(v["requesters"]), "results": v["results"], "providers": len(v["providers"]), "requests_answered_in_window": v["answered"], "bids": len(v["bid_msats"]), "median_bid_sats": (sorted(v["bid_msats"])[len(v["bid_msats"]) // 2] // 1000 if v["bid_msats"] else None)} for kk, v in sorted(by_kind.items(), key=lambda kv: -kv[1]["requests"])} out["feedback_status"] = dict(pay) amounts.sort() out["payment_required"] = {"n": len(amounts), "median_sats": amounts[len(amounts) // 2] // 1000 if amounts else None, "max_sats": amounts[-1] // 1000 if amounts else None} out["handler_kinds_top"] = hk.most_common(15) od = Path(__file__).parent / "out" od.mkdir(exist_ok=True) p = od / f"census_{datetime.now(timezone.utc):%Y-%m-%d}_{days}d.json" p.write_text(json.dumps(out, indent=1), encoding="utf-8") print(json.dumps({k: out[k] for k in ("unique", "all_requesters", "all_providers", "per_relay_counts", "feedback_status", "payment_required")}, indent=1)) for kk, v in list(out["by_kind"].items())[:20]: print(kk, v) print("handler k tags:", out["handler_kinds_top"]) print("saved", p) if __name__ == "__main__": main()