"""r08_tally.py: classify the pre-registered predictions and write the computed tally (part of the Phase 4 check).

q_rules.classify (frozen) applies the failure taxonomy of the plan to every prediction of inputs/plan.json
(frozen) with the population values of Phases 2 and 3 and the results of Phase 4; q_rules.tally counts the
classes. Registers h1_class to h3_class, their display text, where the interval of every applicable robustness
variant lies against its threshold (rob_*: 'below' or 'reaches'; a variant can change only the class of a
prediction that meets its rule), the tally names the plan lists (tally_*), and a few summaries of registered
values that the text quotes. Nothing here is typed: every number is counted or read from the values files.
"""
import sys, json
sys.path.insert(0, "/home/claude/mill/phase4/code")
sys.path.insert(0, "/home/claude/mill/phase3/code")
sys.path.insert(0, "/home/claude/mill/code")
import r_freeze
r_freeze.assert_freeze()

from vals import Values
import q_rules as QR

P3, P4 = "/home/claude/mill/phase3", "/home/claude/mill/phase4"
TEXT = {"CONFIRMED": "Confirmed", "CONFIRMED_NOT_ROBUST": "Confirmed, not robust", "FAILED_DIRECTION": "Failed: direction",
        "FAILED_MAGNITUDE": "Failed: magnitude", "FAILED_PRECISION": "Failed: precision", "NOT_TESTED": "Not tested"}
E = lambda s: {"population": "pre-registered predictions H1 to H3", "filter": "q_rules.classify over values.json, values3.json and values4.json", "statistic": s}


def run():
    V2 = json.load(open("/home/claude/mill/outputs/values.json"))
    V3 = json.load(open(f"{P3}/outputs/values3.json"))
    PLAN = json.load(open(f"{P3}/inputs/plan.json"))
    V = Values(path=f"{P4}/outputs/values4.json", script="r08_tally.py")
    V4 = {k: r for k, r in V.v.items() if r.get("source") != "r08_tally.py"}
    POPVALS = {**V2, **V3}
    VALS = {**V2, **V3, **V4}
    preds = PLAN["predictions"]
    out, below = {}, {}
    for pred in preds:
        h = pred["id"].lower()
        cls, against = QR.classify(pred, VALS, POPVALS)
        V.set(f"{h}_class", cls, "text", E(f"class of {pred['id']} under the registered taxonomy"), kind="text")
        V.set(f"{h}_class_text", TEXT[cls], "text", E(f"display text of the class of {pred['id']}"), kind="text")
        # q_rules.classify reaches the variants only for a prediction that meets its rule (plan section 12); for the
        # others the variants were not evaluated, which "none" would misstate (review item R7)
        if cls.startswith("FAILED") or cls == "NOT_TESTED":
            contra = "not evaluated: the decision rule failed" if cls.startswith("FAILED") else "not evaluated: not tested"
        else:
            contra = ", ".join(against) if against else "none"
        V.set(f"{h}_contradicting", contra, "text", E(f"applicable variants whose whole interval lies below the threshold of {pred['id']}, evaluated only when the rule holds"), kind="text")
        for v in pred["variants"]:
            if not QR.evaluate(v["applicable"], POPVALS):
                status = "not applicable"
            elif not QR.finite(v["contradicts"], VALS):
                status = "missing"
            else:   # neutral wording: the registered expression is 'the whole interval lies below the threshold'
                status = "below" if QR.evaluate(v["contradicts"], VALS) else "reaches"
            V.set(f"rob_{h}_{v['id'].lower()}", status, "text", E(f"position of the interval of variant {v['id']} on {pred['id']} against the threshold: 'below' when {v['contradicts']}, 'reaches' otherwise"), kind="text")
            if status == "below":
                below.setdefault(h, []).append(v["id"])
        lst = below.get(h, [])
        V.set(f"rob_{h}_below_list", "none" if not lst else (lst[0] if len(lst) == 1 else ", ".join(lst[:-1]) + " and " + lst[-1]), "text",
              E(f"variants of {pred['id']} whose whole interval lies below the threshold, in plan order"), kind="text")
        out[pred["id"]] = (cls, against)
    counts, cls = QR.tally(preds, VALS, POPVALS)
    for name, key in (("tally_confirmed", "CONFIRMED"), ("tally_confirmed_not_robust", "CONFIRMED_NOT_ROBUST"),
                      ("tally_failed", "FAILED"), ("tally_failed_direction", "FAILED_DIRECTION"),
                      ("tally_failed_magnitude", "FAILED_MAGNITUDE"), ("tally_failed_precision", "FAILED_PRECISION"),
                      ("tally_not_tested", "NOT_TESTED"), ("tally_total", "TOTAL")):
        V.set(name, int(counts[key]), "predictions", E(f"q_rules.tally count of {key}"))
    # summaries quoted in the text
    h3v = [v["id"].lower() for v in next(p for p in preds if p["id"] == "H3")["variants"]]
    ks = [VALS[f"h3_{v}_kappa"]["value"] for v in h3v]
    ts = [VALS[f"h3_{v}_thr"]["value"] for v in h3v]
    ev = lambda s: {"population": "labelled cutting paths (label.csv rows)", "filter": "imi_vm20i, label 1, Acc0 and Sound2 spectra, the H3 variants V1, V2, V6a, V6b, V6c, V7 and V9", "statistic": s}
    V.set("rob_h3_kappa_min", min(ks), "kappa", ev("smallest variant kappa"))
    V.set("rob_h3_kappa_max", max(ks), "kappa", ev("largest variant kappa"))
    V.set("rob_h3_thr_min", min(ts), "kappa", ev("smallest variant threshold"))
    V.set("rob_h3_thr_max", max(ts), "kappa", ev("largest variant threshold"))
    l0 = [VALS[f"e5_{ch}_{s}_l0"]["value"] for ch in ("acc0", "s2") for s in ("base", "comb1", "comb3", "rel", "band5k")]
    V.set("e5_label0_share_max", max(l0), "fraction", {"population": "labelled cutting paths (label.csv rows)", "filter": "imi_vm20i, label 0, spindle speed at least d5_min_rpm, Acc0 and Sound2, every E5 setting", "statistic": "largest off-harmonic share"})
    V.save()
    return out, counts


if __name__ == "__main__":
    o, c = run()
    print(o)
    print(c)
