# /// script
# requires-python = ">=3.10"
# dependencies = ["numpy"]
# ///
# Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT
# https://thedataengineering.com/lab-code/LICENSE.txt
"""The availability ceiling, and the benefit of cells.

a^N for N hard dependencies, checked by Monte Carlo; soft dependencies with a
fallback; then a poison tenant against no cells, 10 cells, and shuffle
sharding over 100 nodes with 2 nodes per tenant.
"""
import argparse, itertools, json, random
import numpy as np

p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
p.add_argument("--deps", type=int, default=30, help="Hard dependencies N.")
p.add_argument("--a", type=float, default=0.999, help="Availability of one dependency per minute.")
p.add_argument("--minutes", type=int, default=1_000_000, help="Simulated minutes.")
p.add_argument("--fallback", type=float, default=0.95, help="Share of failures a soft dependency's fallback covers.")
p.add_argument("--tenants", type=int, default=100, help="Tenants.")
p.add_argument("--nodes", type=int, default=100, help="Nodes for shuffle sharding.")
p.add_argument("--seed", type=int, default=0, help="Random seed.")
p.add_argument("--json", help="Also write the results to this JSON file.")
args = p.parse_args()
rng = np.random.default_rng(args.seed); random.seed(args.seed)

formulas = [{"case": f"{a}^{n}", "value": a ** n} for a, n in ((0.999, 30), (0.999, 100), (0.9999, 30))]
up = rng.random((args.minutes, args.deps)) < args.a
hard = float(np.mean(up.all(axis=1)))
covered = up | (rng.random((args.minutes, args.deps)) < args.fallback)
soft = float(np.mean(covered.all(axis=1)))
print(f"{args.deps} hard dependencies at {args.a}: measured {hard:.4f}, a^N = {args.a ** args.deps:.4f}")
print(f"with a fallback covering {args.fallback:.0%} of failures: {soft:.5f}")

poison = 0
cells = {t: t % 10 for t in range(args.tenants)}
hit_cells = sum(1 for t in range(args.tenants) if cells[t] == cells[poison]) / args.tenants
pairs = list(itertools.combinations(range(args.nodes), 2))
assign = {t: random.choice(pairs) for t in range(args.tenants * 50)}             # many tenants, each on 2 of 100 nodes
bad = set(assign[0])
full = sum(1 for t, pr in assign.items() if set(pr) == bad) / len(assign)
partial = sum(1 for t, pr in assign.items() if len(set(pr) & bad) == 1) / len(assign)
iso = [{"design": "no cells (one shared fleet)", "fully_hit_pct": 100.0, "partly_hit_pct": 0.0},
       {"design": "10 cells", "fully_hit_pct": hit_cells * 100, "partly_hit_pct": 0.0},
       {"design": f"shuffle sharding, {args.nodes} nodes, 2 per tenant", "fully_hit_pct": full * 100, "partly_hit_pct": partial * 100}]
for r in iso: print(f"poison tenant, {r['design']:<40} fully hit {r['fully_hit_pct']:6.2f}%   partly {r['partly_hit_pct']:5.2f}%")

if args.json:
    with open(args.json, "w") as f:
        json.dump({"tables": {"formulas": formulas, "isolation": iso, "summary": [{"hard": hard, "formula": args.a ** args.deps, "soft": soft,
                   "pairs": len(pairs)}]}}, f)
