"""Turn Study C's traces into its published results.

    python3 research/studies/market-perception/executive-endgames/analyze.py \\
        research/studies/market-perception/executive-endgames/results/main [--out results.json]

Reads ``summary.json`` and ``seed-*/<arm>.jsonl`` from a run directory and writes:

- ``results.json``: terminal-state frequencies per arm with Wilson intervals, the
  preregistered contrasts (paired by seed: an exact sign-flip test and a seed bootstrap,
  Holm-adjusted), the negative control, time to decision, disagreement, per-role
  elicited assessments, stage-to-stage transition counts, a leave-one-seed-out factor
  ablation and the common-random-numbers accounting;
- ``tree.json``: the trajectory tree (``scripts/society/trajectory.py``) with each
  branch's runs, events, artifacts and trace references;
- ``runs.csv``: one row per run.

Everything here is computed from the traces; nothing is typed in by hand. Pure Python,
no model calls.
"""

import argparse
import csv
import itertools
import json
import math
import pathlib
import random
import statistics
import sys

HERE = pathlib.Path(__file__).resolve().parent
ROOT = HERE.parents[3]
sys.path.insert(0, str(ROOT / "scripts" / "society"))

import trajectory  # noqa: E402

ROLE_NAMES = {"sponsor": "business sponsor", "eng": "engineering exec", "fin": "finance exec",
              "proc": "procurement lead", "platform": "competing internal team"}
TERMINALS = ["pilot", "partnership", "acquisition_exploration", "internal_build", "rejected", "no_decision"]
STAGES = [("first_response", "Initial response"), ("circulation", "Circulation by week 2"),
          ("diligence", "Diligence before proposal"), ("first_proposal", "First proposal"),
          ("alignment", "Fate of first proposal"), ("terminal", "Terminal state")]
STAGE_NOTES = {
    "first_response": "The pitch reaches only the business sponsor's inbox. The label classifies the sponsor's first turn "
                      "(the first week they were available): a proposal, mail to colleagues, diligence requests, or nothing.",
    "circulation": "Who had seen the pitch itself by the end of week 2: the sponsor, plus anyone sent the deck or the fact "
                   "sheet, or a mail that is or forwards the pitch. The competing internal team sees it only if a colleague sends it.",
    "diligence": "Kinds of vendor diligence (technical, financial, security, reference) requested before the first "
                 "proposal was filed. The vendor's answers are fixed text, identical in every arm, delivered a week later.",
    "first_proposal": "The first proposal filed and who filed it. Only roles with the right may file a kind; partnership "
                      "needs technical and financial diligence delivered first, acquisition exploration needs financial.",
    "alignment": "What happened to that first proposal: ratified (every required approver approved or approved with "
                 "conditions), blocked by a role holding a block right, superseded by another ratified proposal, or still open at week 10.",
    "terminal": "The first ratified proposal's terminal state, or no_decision if nothing was ratified in ten simulated weeks.",
}


# -- statistics ----------------------------------------------------------------------

def wilson(k, n, z=1.959964):
    if n == 0:
        return [0.0, 0.0]
    p = k / n
    centre = (p + z * z / (2 * n)) / (1 + z * z / n)
    half = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / (1 + z * z / n)
    return [round(max(0.0, centre - half), 4), round(min(1.0, centre + half), 4)]


def sign_flip_p(diffs):
    """Exact two-sided p for a paired mean difference: every sign assignment of the
    non-zero differences (enumerated when there are at most 20, else 200,000 draws)."""
    nz = [d for d in diffs if d != 0]
    observed = abs(sum(nz))
    if not nz:
        return 1.0
    if len(nz) <= 20:
        hits = total = 0
        for signs in itertools.product((1, -1), repeat=len(nz)):
            total += 1
            hits += abs(sum(s * d for s, d in zip(signs, nz))) >= observed - 1e-12
        return hits / total
    rng = random.Random(0)
    hits = sum(abs(sum(d if rng.random() < .5 else -d for d in nz)) >= observed - 1e-12 for _ in range(200_000))
    return (hits + 1) / 200_001


def bootstrap_mean_ci(values, draws=10_000, seed=0):
    if not values:
        return [None, None]
    rng = random.Random(seed)
    n = len(values)
    means = sorted(sum(values[rng.randrange(n)] for _ in range(n)) / n for _ in range(draws))
    return [round(means[int(.025 * draws)], 4), round(means[int(.975 * draws) - 1], 4)]


def holm(pvalues):
    order = sorted(range(len(pvalues)), key=lambda i: pvalues[i])
    adjusted = [0.0] * len(pvalues)
    running = 0.0
    for rank, i in enumerate(order):
        running = max(running, min(1.0, (len(pvalues) - rank) * pvalues[i]))
        adjusted[i] = running
    return adjusted


def tvd(a, b):
    na, nb = sum(a.values()) or 1, sum(b.values()) or 1
    keys = set(a) | set(b)
    return round(.5 * sum(abs(a.get(k, 0) / na - b.get(k, 0) / nb) for k in keys), 4)


def entropy(counts):
    n = sum(counts.values())
    return round(-sum(c / n * math.log2(c / n) for c in counts.values() if c), 4) if n else 0.0


# -- reading runs ----------------------------------------------------------------------

def load_run(path, seed, arm, engaged_set):
    records = [json.loads(line) for line in path.read_text().splitlines()]
    ref = f"seed-{seed}/{arm}.jsonl"
    lines = {id(r): i + 1 for i, r in enumerate(records)}
    turns = [r for r in records if r["type"] == "turn"]
    ends = {r["round"]: r for r in records if r["type"] == "round_end"}
    final = next(r for r in records if r["type"] == "terminal")
    inst = final["institution"]
    events = [e for t in turns for e in t["events"]]
    proposals = sorted(inst["proposals"].values(), key=lambda p: int(p["id"][1:]))
    first = proposals[0] if proposals else None
    first_round = first["round"] if first else None
    dil_before = sorted({e["kind"] for t in turns for e in t["events"]
                         if e["event"] == "diligence_requested" and (first_round is None or e["round"] <= first_round)})
    aware_by = {}
    for r in sorted(ends):
        for role in ends[r]["aware"]:
            aware_by.setdefault(role, r)
    circ_round = min(1, max(ends)) if ends else 0
    aware2 = ends[circ_round]["aware"] if ends else []
    sponsor_first = next((t for t in turns if t["role"] == "sponsor"), None)
    first_support = {}
    last_support = {}
    for t in turns:
        s = (t.get("assessment") or {}).get("support")
        if s is not None:
            first_support.setdefault(t["role"], s)
            last_support[t["role"]] = s
    spread = statistics.pstdev(last_support.values()) if len(last_support) >= 2 else 0.0
    terminal = final["terminal"]
    weeks = final["round"] + 1
    run = {
        "run": f"s{seed}-{arm}", "seed": seed, "arm": arm, "terminal": terminal,
        "engaged": terminal in engaged_set, "weeks_to_decision": weeks, "decided": terminal != "no_decision",
        "turns": len(turns), "invalid": sum(len(t["invalid"]) for t in turns),
        "unreadable": sum(1 for t in turns if t["reply"] is None),
        "messages": sum(len(t["applied"]["messages"]) for t in turns),
        "forwards": sum(1 for t in turns for m in t["applied"]["messages"] if m["forward_of"]),
        "aware_week2": len(aware2), "platform_aware_week2": "platform" in aware2,
        "platform_aware_round": aware_by.get("platform"),
        "diligence_kinds_before_proposal": dil_before,
        "proposals": [{"id": p["id"], "kind": p["kind"], "by": p["by"], "round": p["round"], "status": p["status"]}
                      for p in proposals],
        "blocks": inst["blocks"], "objections": inst["objections"], "refusals": inst["refusals"],
        "contested": len(final["contested"]), "support_spread": round(spread, 4),
        "first_support": first_support, "last_support": last_support,
        "sponsor_first_support": first_support.get("sponsor"),
        "first_assessment": {t["role"]: t["assessment"] for t in reversed(turns) if t.get("assessment")},
        "state_hash": final["state_hash"], "keys": [(t["round"], t["role"], t["key"]) for t in turns],
    }
    # The path through the stages, with evidence.

    def turn_ref(t):
        return f"{ref}#L{lines[id(t)]}"

    def describe(t):
        a = t["applied"]
        bits = []
        for m in a["messages"]:
            bits.append(f"mailed {', '.join(m['to'])}" + (f" forwarding {m['forward_of']}" if m["forward_of"] else "")
                        + (f", sharing {', '.join(m['shared'])}" if m["shared"] else "") + f" ({m['id']}: \"{m['subject']}\")")
        if a["diligence"]:
            bits.append("requested " + ", ".join(d["kind"] for d in a["diligence"]) + " diligence")
        if a["proposal"]:
            bits.append(f"filed {a['proposal']['id']} ({a['proposal']['kind']}; {a['proposal']['doc']})")
        for s in a["stances"]:
            bits.append(f"{s['stance']} on {s['proposal']}" + (" (binding)" if s["binding"] else " (not binding)"))
        return (f"week {t['round'] + 1}, {ROLE_NAMES[t['role']]}: " + ("; ".join(bits) or "no action")
                + (f". Rationale (simulated): {t['reply'].get('rationale')}" if t.get("reply") and t["reply"].get("rationale") else ""))

    def artifacts(t):
        a = t["applied"]
        out = [m["id"] for m in a["messages"]] + [d["mail"] for d in a["diligence"]]
        if a["proposal"]:
            out.append(a["proposal"]["doc"])
        return out

    path = []
    if sponsor_first is None:
        label = "no response"
        path.append({"stage": "first_response", "label": label, "round": None, "events": ["the sponsor never took a turn"],
                     "refs": [], "artifacts": []})
    else:
        a = sponsor_first["applied"]
        if a["proposal"]:
            label = f"proposed {a['proposal']['kind']}"
        elif a["messages"] and a["diligence"]:
            label = "circulated + diligence"
        elif a["messages"]:
            label = "circulated"
        elif a["diligence"]:
            label = "diligence only"
        else:
            label = "held"
        path.append({"stage": "first_response", "label": label, "round": sponsor_first["round"],
                     "events": [describe(sponsor_first)], "refs": [turn_ref(sponsor_first)],
                     "artifacts": artifacts(sponsor_first)})
    early = [t for t in turns if t["round"] <= circ_round]
    path.append({"stage": "circulation",
                 "label": f"{len(aware2)} aware" + (", incl. competing team" if "platform" in aware2 else ""),
                 "round": circ_round, "events": [f"aware by end of week {circ_round + 1}: " + ", ".join(ROLE_NAMES[x] for x in aware2)]
                 + [describe(t) for t in early if t is not sponsor_first],
                 "refs": [turn_ref(t) for t in early] + ([f"{ref}#L{lines[id(ends[circ_round])]}"] if ends else []),
                 "artifacts": [x for t in early for x in artifacts(t)]})
    n = len(dil_before)
    dil_turns = [t for t in turns if t["applied"]["diligence"] and (first_round is None or t["round"] <= first_round)]
    path.append({"stage": "diligence", "label": {0: "no diligence", 4: "all 4 kinds"}.get(n, f"{n} kind" + ("s" if n > 1 else "")),
                 "round": first_round, "events": [f"kinds: {', '.join(dil_before) or 'none'}"] + [describe(t) for t in dil_turns],
                 "refs": [turn_ref(t) for t in dil_turns], "artifacts": [x for t in dil_turns for x in artifacts(t)]})
    if first:
        filer = next(t for t in turns if (t["applied"]["proposal"] or {}).get("id") == first["id"])
        path.append({"stage": "first_proposal", "label": f"{first['kind']} by {ROLE_NAMES[first['by']]}",
                     "round": first["round"], "events": [describe(filer), f"terms: {first.get('terms', '')}"]
                     if "terms" in first else [describe(filer)],
                     "refs": [turn_ref(filer)], "artifacts": artifacts(filer)})
        status = first["status"]
        stance_turns = [t for t in turns if any(s["proposal"] == first["id"] for s in t["applied"]["stances"])]
        if status == "blocked":
            blocker = next(e["role"] for e in events if e["event"] == "blocked" and e["proposal"] == first["id"])
            fate = f"blocked by {ROLE_NAMES[blocker]}"
        elif status == "superseded":
            fate = f"superseded by {terminal}"
        elif status == "open":
            fate = "open at week 10"
        else:
            fate = status
        path.append({"stage": "alignment", "label": fate, "round": None,
                     "events": [describe(t) for t in stance_turns] or ["no stances taken"],
                     "refs": [turn_ref(t) for t in stance_turns],
                     "artifacts": [f"decision-{first['id']}"]})
    else:
        path.append({"stage": "first_proposal", "label": "none filed", "round": None,
                     "events": ["nobody filed a proposal in ten weeks"], "refs": [], "artifacts": []})
    path.append({"stage": "terminal", "label": terminal, "round": final["round"],
                 "events": [f"{terminal} in week {weeks}" + (" (horizon)" if terminal == "no_decision" else
                                                              f", by ratifying {final['proposal']}")],
                 "refs": [f"{ref}#L{lines[id(final)]}"], "artifacts": [f"decision-{final['proposal']}"] if final["proposal"] else []})
    for step in path:
        step["events"] = [e[:600] for e in step["events"]]
    run["path"] = path
    full = {p["id"]: p for p in inst["proposals"].values()}
    for p in run["proposals"]:
        p["terms"] = full[p["id"]]["terms"]
    return run


# -- the ablation ------------------------------------------------------------------------

def ablation(runs, arms):
    """Held-out prediction of `endgame_engaged` with the shared registry's benchmark
    (scripts/society/factors.py): leave one seed (scenario) out, baseline = the
    positioning arm, expanded = arm + factors observed by week 2 + the sponsor's first
    elicited support; and each candidate's incremental value over the arm alone."""
    import factors
    rows = []
    for r in runs:
        rows.append({"seed": r["seed"], "endgame_engaged": int(r["engaged"]),
                     "arm_research": float(r["arm"] == "research"), "arm_upside": float(r["arm"] == "upside"),
                     "exec_forwarding_breadth": r["aware_week2"],
                     "competing_team_exposure": float(r["platform_aware_week2"]),
                     "diligence_breadth": len(r["diligence_kinds_before_proposal"]),
                     "stakeholder_support": r["sponsor_first_support"]})
    baseline = ["arm_research", "arm_upside"]
    candidates = ["exec_forwarding_breadth", "competing_team_exposure", "diligence_breadth", "stakeholder_support"]
    result = factors.ablation(rows, "endgame_engaged", baseline, baseline + candidates, "seed", "seed", reps=1000)
    for data in result["sets"].values():
        data.pop("per_group", None)
    result["incremental"] = factors.incremental_value(rows, "endgame_engaged", baseline, candidates, "seed", "seed", reps=500)
    result["note"] = ("the expanded factors are measured during the process (by week 2), so this is early-signal "
                      "prediction inside the simulator, not prediction from the pitch alone")
    return result


# -- the whole thing -----------------------------------------------------------------------

def analyze(directory, spec):
    directory = pathlib.Path(directory)
    summary = json.loads((directory / "summary.json").read_text())
    arms = [a["id"] for a in spec["interventions"]["arms"]]
    engaged_set = set(spec["study"]["engaged_terminals"])
    runs = []
    for seed in summary["seeds"]:
        for arm in arms:
            runs.append(load_run(directory / f"seed-{seed}" / f"{arm}.jsonl", seed, arm, engaged_set))
    by_arm = {a: [r for r in runs if r["arm"] == a] for a in arms}
    seeds = summary["seeds"]
    result = {"study": spec["id"], "engine": summary["engine"], "model": summary["model"],
              "preregistration": summary["preregistration"], "seeds": seeds, "n_seeds": len(seeds),
              "arms": arms, "n_runs": len(runs), "journal": summary["journal"], "elapsed_s": summary["elapsed_s"],
              "stages": [{"id": s, "title": t, "note": STAGE_NOTES[s]} for s, t in STAGES]}
    # Terminal frequencies.
    freq = {}
    for a in arms:
        n = len(by_arm[a])
        counts = {t: sum(1 for r in by_arm[a] if r["terminal"] == t) for t in TERMINALS}
        freq[a] = {"n": n, "counts": counts,
                   "share": {t: round(c / n, 4) for t, c in counts.items()},
                   "ci": {t: wilson(c, n) for t, c in counts.items()},
                   "engaged": sum(r["engaged"] for r in by_arm[a]),
                   "engaged_ci": wilson(sum(r["engaged"] for r in by_arm[a]), n),
                   "entropy_bits": entropy(counts)}
    result["terminals"] = freq
    # Paired contrasts.
    seed_runs = {(r["seed"], r["arm"]): r for r in runs}

    def contrast(a, b, field="engaged"):
        diffs = [float(seed_runs[(s, a)][field]) - float(seed_runs[(s, b)][field]) for s in seeds]
        return {"a": a, "b": b, "outcome": field, "difference": round(sum(diffs) / len(diffs), 4),
                "ci": bootstrap_mean_ci(diffs), "p": round(sign_flip_p(diffs), 4),
                "discordant": sum(1 for d in diffs if d != 0), "n_pairs": len(diffs)}
    prereg = spec["outcomes"]["preregistered"][0]
    primary = [contrast(a, b) for a, b in prereg["contrasts"]]
    for c, adj in zip(primary, holm([c["p"] for c in primary])):
        c["p_holm"] = round(adj, 4)
    result["primary"] = primary
    neg = (spec["outcomes"].get("negative_control") or {}).get("arms")
    result["negative_control"] = None
    if neg:
        result["negative_control"] = contrast(neg[0], neg[1])
        result["negative_control"]["concordance"] = round(
            sum(seed_runs[(s, neg[0])]["terminal"] == seed_runs[(s, neg[1])]["terminal"] for s in seeds) / len(seeds), 4)
    result["concordance"] = {f"{a}|{b}": round(sum(seed_runs[(s, a)]["terminal"] == seed_runs[(s, b)]["terminal"]
                                                   for s in seeds) / len(seeds), 4)
                             for a, b in itertools.combinations(arms, 2)}
    result["tvd"] = {f"{a}|{b}": tvd(freq[a]["counts"], freq[b]["counts"]) for a, b in itertools.combinations(arms, 2)}
    secondary = {}
    for t in ("acquisition_exploration", "internal_build", "rejected", "no_decision", "pilot"):
        for a, b in prereg["contrasts"] + ([neg] if neg else []):
            diffs = [float(seed_runs[(s, a)]["terminal"] == t) - float(seed_runs[(s, b)]["terminal"] == t) for s in seeds]
            secondary[f"{t}:{a}-{b}"] = {"difference": round(sum(diffs) / len(diffs), 4), "ci": bootstrap_mean_ci(diffs),
                                         "p": round(sign_flip_p(diffs), 4)}
    result["secondary_contrasts"] = secondary
    # Process measures.
    process = {}
    for a in arms:
        rs = by_arm[a]
        weeks = [r["weeks_to_decision"] for r in rs if r["decided"]]
        process[a] = {
            "weeks_to_decision_mean": round(sum(weeks) / len(weeks), 3) if weeks else None,
            "weeks_to_decision_ci": bootstrap_mean_ci(weeks),
            "weeks_to_decision_median": statistics.median(weeks) if weeks else None,
            "weeks_hist": {str(w): sum(1 for x in weeks if x == w) for w in range(1, 11)},
            "decided": len(weeks),
            "support_spread_mean": round(sum(r["support_spread"] for r in rs) / len(rs), 4),
            "support_spread_ci": bootstrap_mean_ci([r["support_spread"] for r in rs]),
            "blocks_per_run": round(sum(r["blocks"] for r in rs) / len(rs), 4),
            "objections_per_run": round(sum(r["objections"] for r in rs) / len(rs), 4),
            "contested_share": round(sum(1 for r in rs if r["contested"]) / len(rs), 4),
            "proposals_per_run": round(sum(len(r["proposals"]) for r in rs) / len(rs), 4),
            "turns_per_run": round(sum(r["turns"] for r in rs) / len(rs), 3),
            "messages_per_run": round(sum(r["messages"] for r in rs) / len(rs), 3),
            "forwards_per_run": round(sum(r["forwards"] for r in rs) / len(rs), 3),
            "aware_week2_mean": round(sum(r["aware_week2"] for r in rs) / len(rs), 3),
            "platform_aware_week2_share": round(sum(r["platform_aware_week2"] for r in rs) / len(rs), 4),
            "platform_ever_aware_share": round(sum(r["platform_aware_round"] is not None for r in rs) / len(rs), 4),
            "internal_build_proposed_share": round(sum(any(p["kind"] == "internal_build" for p in r["proposals"]) for r in rs) / len(rs), 4),
            "acquisition_proposed_share": round(sum(any(p["kind"] == "acquisition_exploration" for p in r["proposals"]) for r in rs) / len(rs), 4),
            "invalid_actions": sum(r["invalid"] for r in rs), "unreadable_replies": sum(r["unreadable"] for r in rs),
        }
    result["process"] = process
    # Elicited assessments (simulated hypotheses, not observations).
    elicited = {}
    for a in arms:
        elicited[a] = {}
        for role in ROLE_NAMES:
            for scale in ("support", "technical_merit", "vendor_risk", "strategic_value", "cost_justified"):
                vals = [r["first_assessment"][role][scale] for r in by_arm[a]
                        if role in r["first_assessment"] and scale in r["first_assessment"][role]]
                elicited[a].setdefault(role, {})[scale] = {
                    "mean": round(sum(vals) / len(vals), 3) if vals else None, "n": len(vals),
                    "ci": bootstrap_mean_ci(vals, 4000, 2) if vals else [None, None]}
    result["elicited_first_assessment"] = elicited
    # Proposals filed and their fates, per kind and arm.
    fates = {}
    for r in runs:
        for p in r["proposals"]:
            fates.setdefault(r["arm"], {}).setdefault(p["kind"], {}).setdefault(p["status"], 0)
            fates[r["arm"]][p["kind"]][p["status"]] += 1
    result["proposal_fates"] = fates
    # Transitions and the tree.
    paths = [{"run": r["run"], "arm": r["arm"], "path": r["path"]} for r in runs]
    trans = trajectory.transitions(paths)
    result["transitions"] = [{"from": a, "to": b, "arms": c, "total": sum(c.values())}
                             for (a, b), c in sorted(trans.items(), key=lambda kv: (-sum(kv[1].values()), kv[0]))]
    tree = trajectory.build_tree(paths, root_label="pitch delivered to the sponsor")
    result["leaves"] = len(trajectory.leaves(tree))
    # CRN accounting: turns in two arms of one seed that put the identical prompt.
    crn = {"pairs_same_prompt": 0}
    for s in seeds:
        keys = {a: {(rd, role): k for rd, role, k in seed_runs[(s, a)]["keys"]} for a in arms}
        for a, b in itertools.combinations(arms, 2):
            for slot, k in keys[a].items():
                if keys[b].get(slot) == k:
                    crn["pairs_same_prompt"] += 1
    result["crn"] = crn
    result["ablation"] = ablation(runs, arms)
    result["examples"] = pick_examples(runs)
    return result, tree, runs


def pick_examples(runs):
    """For each arm, the first seed's run plus the first run of each terminal state
    not already shown: real traces for the page, chosen by rule, not by taste."""
    out = []
    seen = set()
    for r in sorted(runs, key=lambda r: (r["seed"], r["arm"])):
        if r["terminal"] not in seen:
            seen.add(r["terminal"])
            out.append(r["run"])
    return out


def main(argv=None):
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("run_dir")
    parser.add_argument("--spec", default=str(HERE / "spec.json"))
    args = parser.parse_args(argv)
    spec = json.loads(pathlib.Path(args.spec).read_text())
    directory = pathlib.Path(args.run_dir)
    result, tree, runs = analyze(directory, spec)
    (directory / "results.json").write_text(json.dumps(result, indent=1, sort_keys=True) + "\n")
    (directory / "tree.json").write_text(json.dumps(tree, sort_keys=True) + "\n")
    with open(directory / "runs.csv", "w", newline="") as f:
        cols = ["run", "seed", "arm", "terminal", "engaged", "weeks_to_decision", "turns", "messages", "forwards",
                "aware_week2", "platform_aware_week2", "blocks", "objections", "contested", "support_spread",
                "sponsor_first_support", "invalid", "state_hash"]
        w = csv.writer(f)
        w.writerow(cols + ["diligence_before_proposal", "proposals"])
        for r in runs:
            w.writerow([r[c] for c in cols] + ["+".join(r["diligence_kinds_before_proposal"]),
                                               ";".join(f"{p['id']}:{p['kind']}:{p['by']}:{p['status']}" for p in r["proposals"])])
    print(json.dumps({"terminals": {a: v["counts"] for a, v in result["terminals"].items()},
                      "primary": result["primary"], "negative_control": result["negative_control"],
                      "ablation": result["ablation"]}, indent=1))


if __name__ == "__main__":
    sys.exit(main())
