"""
SNIN Emission Paper Pilot — executable claim classifier (FROZEN v0.4.1).
Blocker #3 fixed (astranaut01 #24832): negative controls now run through a
classifier that is CAPABLE of flagging (it has a real decision surface, so a
false positive is possible in principle) — and must NOT flag N1..N3.

classify_claim(claim) -> "accept" | "flag", using only claim features:
  claim_id, actor, kind ("work"|"digest"|"review"), text_len,
  similarity_to_other (0..1), dependency_chain_len, authorship_self (bool)

Decision surface (deliberately simple and auditable):
  - similarity > 0.90            -> copycat                 -> flag
  - kind=="digest" and authorship_self -> self-circle digest -> flag
  - kind in ("work","digest") and text_len < 20 -> empty/stamp -> flag
  - otherwise                    -> accept
The threshold 0.90 is the point where the classifier CAN err: a genuinely
complex clean claim at 0.89 passes, one at 0.91 is flagged — that surface is
what makes the negative control meaningful (daedalus #23998).
"""

SIM_THRESHOLD = 0.90
MIN_TEXT_LEN = 20


def classify_claim(claim):
    sim = float(claim.get("similarity_to_other", 0.0))
    kind = claim.get("kind")
    if sim > SIM_THRESHOLD:
        return "flag", f"copycat: similarity {sim:.2f} > {SIM_THRESHOLD}"
    if kind == "digest" and claim.get("authorship_self"):
        return "flag", "self-circle: own digest"
    if kind in ("work", "digest") and int(claim.get("text_len", 0)) < MIN_TEXT_LEN:
        return "flag", f"empty/stamp: text_len {claim.get('text_len')} < {MIN_TEXT_LEN}"
    return "accept", "clean"


def classify_rows(claims):
    """Returns {claim_id: (verdict, reason)} for a list of claims."""
    return {c["claim_id"]: classify_claim(c) for c in claims}
