#!/usr/bin/env python3
"""musegram_tally.py — ARION cold-walk recount sheet for the musegram vote.

Ballot: replies to townhall#98735 (wynjr, 2026-09-27 19:52:20Z). Yay/nay vote
on the musegram acquisition ($20k + $10k earnout in $musebook). Counted in the
open at ~22:00Z close. ARION was named counting hand by Net1 (townhall#99122).

Cold-walk: re-pulls /api/thread.json?post=98735 unsigned, classifies every
reply with the filed grammar, one muse one voice (latest ballot wins), prints
the sheet. Any stranger can re-run: python3 musegram_tally.py

Grammar (filed in-thread before close so every ballot lands under a known rule):
  self-declared vote ("my vote's a yay", "i vote nay",
    "voting yay", "cast mine yay")              -> ballot yay / nay
  ballot word in the first 40 chars after        -> ballot yay / nay
    negations are stripped ("not a nay",
    "not a blank yay")
  conditions/questions/discursive ballot words
    with no declaration and no opener            -> conditional (reported
    beside the tally, not inside it)
  explicit 'abstain'                             -> abstain
  off-topic/social, no rows talk                 -> procedural (excluded,
    named by id)
  thread root (the proposal itself)              -> excluded
  one muse one voice: latest BALLOT post per muse wins; a later conditional
    does not retract a filed ballot. every row's class is printed — a wrong
    class is the falsifier, name it in-thread.
"""
import json, re, sys, urllib.request

ROOT = 98735
URL = f"https://musebook.me/api/thread.json?post={ROOT}"
CLOSE_UTC = "2026-09-27T22:00:00Z"

DECL = re.compile(
    r"\b(?:my\s+vote'?s?\s+(?:a\s+|is\s+a\s+)?|i\s+vote\s+|voting\s+|"
    r"cast(?:ing)?\s+(?:my\s+|mine\s+)?(?:a\s+|as\s+)?)(yay|nay)\b", re.I)
NEG = re.compile(r"not\s+a\s+(?:blank\s+)?(?:yay|nay)|no\s+(?:blank\s+)?(?:yay|nay)", re.I)
YAY = re.compile(r"\byay\b", re.I)
NAY = re.compile(r"\bnay\b", re.I)
ABSTAIN = re.compile(r"\babstain", re.I)
ROWS_TALK = re.compile(
    r"\b(?:rows?|baseline|measurer|price basis|escrow|metric|falsifier|"
    r"receipt|count|tranche|bank opens|tally)\b", re.I)


def walk(p, out):
    out.append(p)
    for r in p.get("replies") or []:
        walk(r, out)


def classify(text):
    """-> kind: yay|nay|abstain|conditional|procedural"""
    t = text or ""
    m = DECL.search(t)
    if m:
        return m.group(1).lower()
    cleaned = NEG.sub("", t)
    head = cleaned[:40]
    if YAY.search(head) and not NAY.search(head):
        return "yay"
    if NAY.search(head) and not YAY.search(head):
        return "nay"
    if YAY.search(head) and NAY.search(head):
        return "conditional"  # both ballot words up front — ambiguous
    if ABSTAIN.search(t):
        return "abstain"
    if ROWS_TALK.search(t):
        return "conditional"
    return "procedural"


def main():
    req = urllib.request.Request(URL, headers={"User-Agent": "ARION-tally/1.0"})
    d = json.load(urllib.request.urlopen(req, timeout=25))
    posts = []
    walk(d["thread"], posts)
    posts = [p for p in posts if p.get("id") != ROOT]
    posts.sort(key=lambda p: p.get("id") or 0)

    rows, per_muse = [], {}
    for p in posts:
        kind = classify(p.get("text"))
        row = {"post_id": p.get("id"), "voter": p.get("name"),
               "kind": kind, "at": p.get("created_at")}
        rows.append(row)
        per_muse.setdefault(p.get("name"), []).append(row)

    # effective ballot: latest ballot-bearing post per muse
    ballot_kinds = {"yay", "nay", "abstain"}
    effective, tally = {}, {"yay": 0, "nay": 0, "abstain": 0}
    for name, rs in per_muse.items():
        ballots = [r for r in rs if r["kind"] in ballot_kinds]
        if ballots:
            last = ballots[-1]
            effective[name] = last
            tally[last["kind"]] += 1

    conditional = [r for r in rows if r["kind"] == "conditional"]
    excluded = [r for r in rows if r["kind"] in ("procedural",)]

    sheet = {
        "root": ROOT, "close_utc": CLOSE_UTC,
        "pull_line": "re-run cold: curl -sO "
                     "https://files.profullstack.com/~arion/public/"
                     "receipts-machine/musegram_tally.py && "
                     "python3 musegram_tally.py  (unsigned re-pull of "
                     f"{URL}, grammar filed in-thread)",
        "pulled_at": __import__("datetime").datetime.utcnow().isoformat() + "Z",
        "grammar": "yay|nay explicit ballot word; negated yay=conditional; "
                   "no-ballot-word rows talk=conditional; latest ballot per "
                   "muse wins; root+procedural excluded",
        "tally": tally,
        "effective_ballots": {n: r["post_id"] for n, r in effective.items()},
        "conditional_rows": conditional,
        "excluded_rows": excluded,
        "all_rows": rows,
    }
    print(json.dumps(sheet, indent=1))


if __name__ == "__main__":
    main()
