"""概率与阈值：python 06_postprocessing.py，改保留集的 prior_shift。"""
import math
from random import Random

rng = Random(53)
sigmoid = lambda z: 1 / (1 + math.exp(-z))
def sample(n, prior_shift=0.0):
    out = []
    for i in range(n):
        z = rng.gauss(0, 1.1)
        out.append((i, int(rng.random() < sigmoid(z + prior_shift)), 2.3*z))
    assert len({i for i, _, _ in out}) == len(out)
    return out

def nll(rows, temperature):
    return sum(math.log1p(math.exp(z/temperature)) - y*z/temperature
               for _, y, z in rows) / len(rows)

def f1(rows, temperature, cutoff):
    tp = fp = fn = 0
    for _, y, z in rows:
        pred = sigmoid(z / temperature) >= cutoff
        tp += y and pred
        fp += (not y) and pred
        fn += y and (not pred)
    return 2*tp / max(1, 2*tp+fp+fn)

dev, hold = sample(600), sample(600)
t = min((i/10 for i in range(8, 41)), key=lambda candidate: nll(dev, candidate))
cut = max((i/100 for i in range(10, 91)), key=lambda x: f1(dev,t,x))

def validate_submission(predictions, official_ids):
    ids = [key for key, _ in predictions]
    if len(ids) != len(set(ids)) or ids != official_ids:
        raise ValueError("duplicate, missing or out-of-order ID")
    if any(not math.isfinite(p) or not 0 <= p <= 1 for _, p in predictions):
        raise ValueError("invalid probability")

submission = [(i, sigmoid(z/t)) for i, _, z in hold]
validate_submission(submission, [i for i, _, _ in hold])
try:
    validate_submission(submission + [submission[0]], [i for i, _, _ in hold])
except ValueError as exc:
    print("expected submission failure:", exc)
else:
    raise AssertionError("duplicate ID escaped")

print("dev-chosen temperature and cutoff:", t, cut)
print("holdout NLL raw/calibrated:", nll(hold,1), nll(hold,t))
print("holdout F1 raw@0.5 / selected:", f1(hold,1,0.5), f1(hold,t,cut))
for lo in (0, 0.2, 0.4, 0.6, 0.8):
    bucket = [(y,sigmoid(z/t)) for _,y,z in hold if lo <= sigmoid(z/t) < lo+0.2]
    if bucket:
        print(f"bin {lo:.1f}: n={len(bucket)}, predicted={sum(p for _,p in bucket)/len(bucket):.3f}, "
              f"actual={sum(y for y,_ in bucket)/len(bucket):.3f}")

