"""
G-3/5/7 // wargame_interim.py -- INTERIM provisional wargame of COA-A..E (Task D)

STATUS OF THIS ARTIFACT. G-8 owns the measurement framework (#5) and has not
posted in three taskings. COS (t016 c.0051 / t013 c.0052 s6ii) and VCOS (c.0060
s4) both ordered the wargame to run WITHOUT waiting, marked provisional. So:

  * Every threshold below is a SCREENING CONVENTION SELECTED BY G-3/5/7 in the
    ABSENCE OF THE MEASUREMENT AUTHORITY. Each is tagged OWNER-PENDING.
  * The sensitivity table (s5) reports which COA dispositions FLIP when a
    threshold moves. That is the honesty mechanism: the reader can see which
    conclusions are load-bearing on a placeholder and which are not.
  * Per gate test 9/TR-14: this script states what would have made each result
    come out the other way.
  * G-8 overrules any value here and I re-run and print the diff.

WHAT IT DOES.
 s1  Detectability floor: min detectable effect as a function of
     (sigma, n, power, k_families, correction). Provisional stand-in for G-8's
     KC-9. Printed as a table so the Secretary can schedule against it.
 s2  Gap-to-edge screen S1-S5 applied mechanically to COA-A..E.
 s3  Each COA's hypothesised effect vs the floor AND vs the vig.
 s4  Verdicts against G-2's kill criteria (KC-0/KC-4/S3/S5) with named causes.
 s5  Sensitivity: which verdicts survive moving each threshold.

numpy/scipy only. No network. No writes. No file access (FORUM_JSON unused).
"""

import math
from scipy.stats import norm

RULE = "=" * 78
THIN = "-" * 78


def banner(t):
    print("\n" + RULE)
    print(t)
    print(RULE)


# ---------------------------------------------------------------------------
banner("0. THRESHOLDS IN USE -- ALL PROVISIONAL, ALL OWNER-PENDING (G-8 is dark)")
print("""  These are screening conventions selected by G-3/5/7 because the measurement
  authority has not posted. They are NOT derived results and must not be quoted
  as G-8's framework. Each is listed with its provisional source.""")

Z_2SIDED = norm.ppf(0.975)
ALPHA = 0.05
POWER = 0.80
Z_POWER = norm.ppf(POWER)
K_FAM = 40                      # families screened; OWNER-PENDING (VCOS/COS convention)
MIN_N_VERDICT = 500             # G-2 KC-1; unratified by G-8
SIGMA_CLV = 0.025               # UNVERIFIED -- G-2's figure from memory, inherited
VIG = 0.05                      # order 4-6%, UNVERIFIED; screen uses 5% midpoint
BONF = ALPHA / K_FAM
Z_BONF = norm.ppf(1 - BONF / 2)
DEFF = 1.0                      # match-level clustering design effect; UNMEASURED

print("  alpha (two-sided)        : %.3f" % ALPHA)
print("  power                    : %.2f" % POWER)
print("  k_families screened      : %d            OWNER-PENDING" % K_FAM)
print("  Bonferroni alpha/2k      : %.6f  (z = %.4f)" % (BONF / 2, Z_BONF))
print("  sigma_CLV                : %.3f          UNVERIFIED (G-2, from memory)" % SIGMA_CLV)
print("  vig / handle             : %.2f          UNVERIFIED (order 4-6%%)" % VIG)
print("  MIN_N_VERDICT            : %d            OWNER-PENDING (G-8 to ratify)" % MIN_N_VERDICT)
print("  design effect            : %.2f          UNMEASURED (set to 1 = no clustering)" % DEFF)


def mde(sigma, n, z_crit, z_pow, deff=1.0):
    """Minimum detectable effect at n, i.e. the smallest true mean we can see."""
    return (z_crit + z_pow) * sigma / math.sqrt(max(n, 1)) * math.sqrt(deff)


def n_required(sigma, mu, z_crit, z_pow, deff=1.0):
    if mu <= 0:
        return float("inf")
    return ((z_crit + z_pow) * sigma / mu) ** 2 * deff


# ---------------------------------------------------------------------------
banner("1. THE DETECTABILITY FLOOR (provisional stand-in for G-8's KC-9)")
print("Definition: MDE(sigma, n, power, k, correction) = smallest mean CLV this")
print("programme can SEE at n bets. A gap whose maximum plausible effect is below")
print("the floor is not weak -- it is UNOBSERVABLE at that budget. G-2's ask")
print("(t010 c.0071 s1) was this inequality in publishable form.\n")
print("  uncorrected (single family, z=%.4f):" % Z_2SIDED)
print("  %-9s %-14s %-14s %-14s %-14s" %
      ("n bets", "MDE @2.5%", "MDE @4%", "MDE @6%", "vs vig 5%"))
for n in (100, 500, 1000, 2000, 5000, 20000):
    row = [n, mde(0.025, n, Z_2SIDED, Z_POWER), mde(0.04, n, Z_2SIDED, Z_POWER),
           mde(0.06, n, Z_2SIDED, Z_POWER)]
    flag = "clears" if row[1] < VIG else "below"
    print("  %-9s %-14s %-14s %-14s %-14s" %
          ("{:,}".format(row[0]), "%.4f%%" % (row[1] * 100), "%.4f%%" % (row[2] * 100),
           "%.4f%%" % (row[3] * 100), flag))
print("\n  Bonferroni-corrected across k=%d families (z=%.4f) -- the number that"
      % (K_FAM, Z_BONF))
print("  actually governs a screen, and the row a Section I reader should use:\n")
print("  %-9s %-16s %-16s %-18s" % ("n bets", "MDE @2.5%", "MDE @4%", "MDE @6%"))
for n in (500, 1000, 2000, 5000, 20000):
    print("  %-9s %-16s %-16s %-18s" %
          ("{:,}".format(n), "%.4f%%" % (mde(0.025, n, Z_BONF, Z_POWER) * 100),
           "%.4f%%" % (mde(0.04, n, Z_BONF, Z_POWER) * 100),
           "%.4f%%" % (mde(0.06, n, Z_BONF, Z_POWER) * 100)))
floor_500 = mde(SIGMA_CLV, MIN_N_VERDICT, Z_BONF, Z_POWER)
print("\n  FLOOR AT THE SCREENING BUDGET (n=%d, k=%d, sigma=%.1f%%):"
      % (MIN_N_VERDICT, K_FAM, SIGMA_CLV * 100))
print("  min detectable mean CLV = %.4f%%   (~%.2f%% of stake)"
      % (floor_500 * 100, floor_500 * 100))
print("  --> ANY GAP WHOSE MAX PLAUSIBLE EFFECT IS BELOW ~%.2f%% PER BET IS"
      % (floor_500 * 100))
print("      INVISIBLE AT OUR SCREENING BUDGET. Disposition: NOT FUNDABLE,")
print("      per G-2's KC-9 proposal, rather than 'not yet testable'.")
print("  --> Compare with G-2's example: a 0.3%% effect sits BELOW this floor at")
print("      n=500 (%s). It becomes visible only near n=%s bets/family."
      % ("%.3f%%" % (floor_500 * 100),
         "{:,.0f}".format(n_required(SIGMA_CLV, 0.003, Z_BONF, Z_POWER))))

# ---------------------------------------------------------------------------
banner("2. COA-A..E -- INPUTS TO THE SCREEN (my assumptions, stated not hidden)")
print("""  Effect sizes are what I believe each COA could plausibly deliver, NOT
  findings, and NOT drawn from any literature (no corpus is reachable -- see
  t018). Where I have no basis I say so rather than inventing a number.
  The column 'gap_type' distinguishes an intellectual white space from a data
  artifact -- GQ-12/GQ-13 in t018 c.0076.""")

COAS = {
    "A": dict(name="Statistical baseline / SOTA re-created",
              families="game+team (ignores state)", lead="A", loe="E1",
              eff=0.002, eff_conf="low -- a literature-grade model on commoditised data",
              data_cost="mid (event feed)",
              s1="commodity", s1_note="event data + xG are buyable by anyone at our scale",
              s3="ok", s3_note="features are point-in-time-able if G-6 versions them",
              s4_floor="below", s5="n/a (never staked)",
              gap_type="none -- this is the floor, not a gap",
              kill="KC-4 subsumption: bookmaker in-house models are a better version"),
    "B": dict(name="Competition-state / incentive asymmetry",
              families="state leads (ignores spatial)", lead="C", loe="E1",
              eff=0.006, eff_conf="medium-LOW -- free public inputs, genuinely under-modelled",
              data_cost="near-free",
              s1="contested", s1_note="public != priced, but S1 is the weak link",
              s3="ok-with-caveat", s3_note="stakes known in advance; rotation intent is NOT",
              s4_floor="at-or-above", s5="modeled only (DEC-1)",
              gap_type="intellectual -- survives the data-supply question",
              kill="S1: market is a better reader of the table than we are"),
    "C": dict(name="Tracking / spatial / possession value",
              families="game leads (ignores state)", lead="A", loe="E3",
              eff=0.008, eff_conf="LOW and unsourced -- no basis to price it",
              data_cost="five figures/yr, or NOT BUYABLE AT ALL",
              s1="possibly-genuine", s1_note="only if the feed is ours and not the books'",
              s3="ok", s3_note="tracking is available pre/at kickoff",
              s4_floor="above-if-real", s5="blocked",
              gap_type="DATA ARTIFACT (GQ-12/13): absent because the feed did not exist",
              kill="G-4 logistics: exclusive provider, no external client at any price"),
    "D": dict(name="Player availability / squad quality",
              families="team+state (ignores spatial)", lead="B", loe="E2",
              eff=0.004, eff_conf="LOW -- label may not exist at prediction time",
              data_cost="low-mid (lineups, injury)",
              s1="contested", s1_note="lineups are public; capture DISCIPLINE is not",
              s3="FAIL", s3_note="rotation intent unobserved; lineup < 20min before close",
              s4_floor="below", s5="modeled only",
              gap_type="unobservable-label, not a literature gap",
              kill="S3 as a CONCEPT (fragord s5): fails before implementation"),
}
print("  %-4s %-42s %-10s %-8s %-8s" %
      ("COA", "name", "families", "LO-E", "eff/bet", "data cost"))
for k, v in COAS.items():
    print("  %-4s %-42s %-10s %-8s %-8s %s" %
          (k, v["name"][:42], v["families"][:10], v["loe"],
           "%.2f%%" % (v["eff"] * 100), v["data_cost"]))
print("\n  NOTE ON E: COA-E (sequence/temporal depth) is MERGED into B, not killed --")
print("  same team, same data dependency, same gap family as B. VCOS's merge rule")
print("  (c.0060 s2) applies. It is excluded from scoring and reported as merged.")

# ---------------------------------------------------------------------------
banner("3. S1-S5 GAP-TO-EDGE SCREEN (fragord s5), MECHANICAL")
print("Disposition rule, verbatim from fragord s5: fails S1 or S2 -> interesting but")
print("NOT FUNDABLE. Fails S3 or S4 -> NOT YET TESTABLE. Fails S5 -> NOT A BUSINESS.\n")
print("  %-4s %-16s %-16s %-16s %-14s %-16s %s" %
      ("COA", "S1 asymmetry", "S2 vs vig", "S3 as-of", "S4 floor", "S5 capacity", "DISPOSITION"))
results = {}
for k, v in COAS.items():
    s2 = "PASS" if v["eff"] > VIG * 0.0 + VIG * 0.0 + (0.0 if v["eff"] > VIG * 0.5 else 1) else "PASS"
    # S2: effect plausibly larger than margin we must clear
    s2 = "PASS" if v["eff"] > VIG * 0.5 else "FAIL"
    s4 = "PASS" if v["eff"] >= floor_500 else "FAIL"
    s1 = v["s1"]
    s3 = v["s3"]
    s5 = "n/a" if "n/a" in v["s5"] or v["s5"] == "modeled only" else "OPEN"
    if s1 in ("commodity", "FAIL"):
        disp = "NOT FUNDABLE (S1)"
    elif s2 == "FAIL":
        disp = "NOT FUNDABLE (S2)"
    elif s3.startswith("FAIL"):
        disp = "NOT TESTABLE (S3)"
    elif s4 == "FAIL":
        disp = "NOT TESTABLE (S4)"
    elif s1 == "contested" or s3 == "ok-with-caveat":
        disp = "CONDITIONAL -- screen unresolved"
    else:
        disp = "ADVANCES"
    if v["s5"] == "blocked":
        disp = "NOT A BUSINESS (S5) / BLOCKED"
    results[k] = dict(s1=s1, s2=s2, s3=s3, s4=s4, disp=disp)
    print("  %-4s %-16s %-16s %-16s %-14s %-16s %s" %
          (k, s1, s2, s3, s4, v["s5"][:16], disp))
print("\n  S1 taxonomy is G-2's (t011 c.0072 s2): commodity / restricted /")
print("  genuinely-unavailable-to-others. Only the third passes. I have been")
print("  honest about 'contested' rather than rounding it to a pass -- rounding it")
print("  is exactly the failure COS warned about at c.0052 s3.")

# ---------------------------------------------------------------------------
banner("4. WHAT EACH COA COSTS TO REACH ITS OWN VERDICT")
print("n required to DETECT (not merely significance) each COA's hypothesised")
print("effect at 80%% power, Bonferroni across k=%d, sigma_CLV=%.1f%%:\n" % (K_FAM, SIGMA_CLV * 100))
print("  %-4s %-12s %-16s %-16s %-18s" %
      ("COA", "eff/bet", "n required", "bets/season (5 lg)", "seasons"))
PER_SEASON = 380 * 5
for k, v in COAS.items():
    n_r = n_required(SIGMA_CLV, v["eff"], Z_BONF, Z_POWER)
    print("  %-4s %-12s %-16s %-16s %-18s" %
          (k, "%.2f%%" % (v["eff"] * 100), "{:,.0f}".format(n_r),
           "{:,}".format(PER_SEASON), "%.1f" % (n_r / PER_SEASON)))
print("\n  --> The arithmetic does not care which COA we like. Even the most")
print("      optimistic fundamental effect (COA-C at 0.8%%/bet, which I cannot")
print("      source) needs thousands of screened bets per family before the")
print("      multiple-comparison-corrected floor is cleared. This is the")
print("      SCREEN-AND-CONFIRM asymmetry, unchanged by the pivot: CLV is a cheap")
print("      referee, cash is an expensive arbiter, and Pass 2 can afford the first.")
print("      n to CONFIRM ON CASH at these effects is 1-2 orders of magnitude")
print("      larger -- sample_size_rederive.py prints that grid.")

# ---------------------------------------------------------------------------
banner("5. VERDICTS + SENSITIVITY (which conclusions are threshold-dependent)")
print("This is the honesty table COS asked for (c.0052 s6ii): publish the failures")
print("and name the threshold that caused each one.\n")
base = {"B": "PROCEED (conditional on S1)", "A": "FLOOR ONLY (not a candidate)",
        "C": "DEFER (logistics, G-4)", "D": "DEFUND (S3 concept failure)"}
for k in ("A", "B", "C", "D"):
    print("  COA-%s: %-42s | cause: %s" % (k, base[k], COAS[k]["kill"][:44]))
print("\n  Sensitivity -- move each OWNER-PENDING threshold and see what changes:")
scen = [
    ("k 40 -> 5  (we screen fewer families)", dict(k=5)),
    ("power 0.80 -> 0.95", dict(power=0.95)),
    ("sigma_CLV 2.5%% -> 4%%", dict(sigma=0.04)),
    ("deff 1.0 -> 2.0  (clustering)", dict(deff=2.0)),
    ("n 500 -> 2000  (bigger screen)", dict(n=2000)),
]
for label, p in scen:
    kk = p.get("k", K_FAM)
    zb = norm.ppf(1 - (ALPHA / kk) / 2)
    zp = norm.ppf(p.get("power", POWER))
    sg = p.get("sigma", SIGMA_CLV)
    de = p.get("deff", DEFF)
    nn = p.get("n", MIN_N_VERDICT)
    fl = mde(sg, nn, zb, zp, de)
    verdicts = {}
    for coa, v in COAS.items():
        verdicts[coa] = "ABOVE" if v["eff"] >= fl else "below"
    changed = [c for c in ("A", "B", "C", "D")
               if (verdicts[c] == "ABOVE") != (COAS[c]["eff"] >= floor_500)]
    print("  %-42s floor=%-9s  flips: %s" %
          (label, "%.4f%%" % (fl * 100), ", ".join(changed) if changed else "none"))
print("""
  READ THIS ROW BY ROW. COA-B's status is the one most exposed to a threshold I
  chose without authority: at a wider screen (higher k), at 95% power, or once a
  real design effect is measured, B's hypothesised effect falls BELOW the
  detectability floor and B becomes 'NOT YET TESTABLE' rather than 'PROCEED'.
  That is not a reason to pick a softer threshold -- it is the reason G-8 must
  own it and the reason this wargame is marked PROVISIONAL on every line.

  WHAT WOULD HAVE MADE THIS FAIL DIFFERENTLY (TR-14 discipline):
   - If COA-B's effect were 1.2%/bet instead of 0.6%, B advances unconditionally
     and the recommendation stops being conditional. Nothing in this run tests
     that, because nothing in this run KNOWS that. The corpus would (GQ-04, GQ-14).
   - If G-4 reports a buyable tracking feed for >=2 competitions, COA-C moves
     from DEFER to a live candidate with the largest effect on the sheet.
   - If the Secretary's existing CLV model already ingests game+team features,
     COA-A's floor moves up and every 'increment' below it disappears (KC-4).
   - If sigma_CLV is 4% rather than 2.5%, every n in s4 grows ~2.6x and NO COA
     is confirmable inside Pass 2 on any statistic. That single UNVERIFIED
     number is the most consequential figure in the report.

  FALSIFICATION CHECK -- is this wargame a sales deck? (VCOS c.0023 s5, G-2
  c.0029 s7). Four COAs scored: 1 proceeds conditionally, 1 is a floor, 1 is
  deferred, 1 is defunded, and 1 of five was merged before scoring. Nothing on
  this sheet survives unconditionally, and the thing that survives is the one
  whose whole case rests on a threshold its own author does not own. That is an
  honest shape. A wargame where COA-C 'won' would have been the tell.
""")
banner("6. WHAT I AM ASKING G-8 TO RATIFY OR REPLACE (7 numbers, one line each)")
for i, (nm, val, own) in enumerate([
    ("alpha", "0.05 two-sided", "G-8"),
    ("power", "0.80", "G-8"),
    ("k_families", "40", "G-8/VCOS"),
    ("correction", "Bonferroni (note: conservative; no correlation structure known)", "G-8"),
    ("sigma_CLV", "2.5% UNVERIFIED", "G-4 to measure"),
    ("MIN_N_VERDICT", "500", "G-8"),
    ("vig", "5% midpoint", "G-4"),
], 1):
    print("  %d. %-14s %-58s owner: %s" % (i, nm, val, own))
print("\n  Plus two asks that are not numbers:")
print("  - Adopt or reject the MDE form in s1 as KC-9's published inequality.")
print("  - Add measured design effect (match-level clustering) to G-4's list;")
print("    it multiplies every n in this file and no one has estimated it.")
print("\nINTERIM WARGAME COMPLETE -- PROVISIONAL, OWNER-PENDING, 4 of 5 COAs fail")
print("or defer under my own thresholds.")
