{"spec_id":"curve-oc","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ncurve-oc: Operating Characteristic (OC) Curve\nLibrary: matplotlib 3.11.0 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\nfrom math import comb\n\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as mticker\nimport numpy as np\n\n\n# Theme\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — first series is always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data\nfraction_defective = np.linspace(0, 0.15, 300)\n\nsampling_plans = [\n    {\"n\": 50, \"c\": 1, \"label\": \"n=50, c=1\"},\n    {\"n\": 80, \"c\": 2, \"label\": \"n=80, c=2\"},\n    {\"n\": 100, \"c\": 2, \"label\": \"n=100, c=2\"},\n]\n\noc_curves = {}\nfor plan in sampling_plans:\n    n, c = plan[\"n\"], plan[\"c\"]\n    prob_accept = sum(comb(n, k) * fraction_defective**k * (1 - fraction_defective) ** (n - k) for k in range(c + 1))\n    oc_curves[plan[\"label\"]] = prob_accept\n\naql = 0.02\nltpd = 0.10\n\n# Plot — 3200 × 1800 px canvas (landscape 16:9)\ncolors = IMPRINT_PALETTE[:3]\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nfor (label, prob_accept), color in zip(oc_curves.items(), colors, strict=False):\n    ax.plot(fraction_defective, prob_accept, linewidth=2.5, color=color, label=label)\n\n# Shaded risk regions\nn0, c0 = sampling_plans[0][\"n\"], sampling_plans[0][\"c\"]\nfirst_curve = oc_curves[sampling_plans[0][\"label\"]]\n\n# Producer's risk (α): probability of rejecting an acceptable lot — shade above curve at AQL\naql_mask = fraction_defective <= aql\nax.fill_between(\n    fraction_defective[aql_mask], first_curve[aql_mask], 1.0, alpha=0.25, color=colors[0], label=\"Producer's risk (α)\"\n)\n\n# Consumer's risk (β): probability of accepting a bad lot — #AE3030 for semantic error role\nltpd_mask = fraction_defective >= ltpd\nax.fill_between(\n    fraction_defective[ltpd_mask], 0, first_curve[ltpd_mask], alpha=0.40, color=\"#AE3030\", label=\"Consumer's risk (β)\"\n)\n\n# AQL and LTPD reference lines\nax.axvline(x=aql, color=INK_MUTED, linestyle=\"--\", linewidth=1.2, alpha=0.8)\nax.axvline(x=ltpd, color=INK_MUTED, linestyle=\"--\", linewidth=1.2, alpha=0.8)\n\nax.text(aql + 0.001, 0.97, \"AQL\", fontsize=8, fontweight=\"bold\", color=INK_SOFT, ha=\"left\", va=\"top\")\nax.text(ltpd + 0.001, 0.97, \"LTPD\", fontsize=8, fontweight=\"bold\", color=INK_SOFT, ha=\"left\", va=\"top\")\n\n# Producer's risk annotation at AQL for first plan\npa_at_aql = sum(comb(n0, k) * aql**k * (1 - aql) ** (n0 - k) for k in range(c0 + 1))\nalpha_value = 1 - pa_at_aql\nax.plot(aql, pa_at_aql, \"o\", color=colors[0], markersize=6, zorder=5)\nax.annotate(\n    f\"α = {alpha_value:.2f}\",\n    xy=(aql, pa_at_aql),\n    xytext=(aql + 0.016, pa_at_aql - 0.10),\n    fontsize=8,\n    color=colors[0],\n    fontweight=\"bold\",\n    arrowprops={\"arrowstyle\": \"->\", \"color\": colors[0], \"lw\": 1.2},\n)\n\n# Consumer's risk annotation at LTPD for first plan\nbeta_value = sum(comb(n0, k) * ltpd**k * (1 - ltpd) ** (n0 - k) for k in range(c0 + 1))\nax.plot(ltpd, beta_value, \"o\", color=\"#AE3030\", markersize=6, zorder=5)\nax.annotate(\n    f\"β = {beta_value:.2f}\",\n    xy=(ltpd, beta_value),\n    xytext=(ltpd + 0.010, beta_value + 0.12),\n    fontsize=8,\n    color=\"#AE3030\",\n    fontweight=\"bold\",\n    arrowprops={\"arrowstyle\": \"->\", \"color\": \"#AE3030\", \"lw\": 1.2},\n)\n\n# Axis formatting — percentage labels\nax.xaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f\"{x:.0%}\"))\nax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda y, _: f\"{y:.0%}\"))\nax.xaxis.set_major_locator(mticker.MultipleLocator(0.02))\n\n# Title: 44 chars < 67, so default 12pt\ntitle = \"curve-oc · python · matplotlib · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK)\nax.set_xlabel(\"Fraction Defective (p)\", fontsize=10, color=INK)\nax.set_ylabel(\"Probability of Acceptance P(accept)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)\n\n# Legend\nleg = ax.legend(fontsize=8, frameon=True, loc=\"upper right\")\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n\nax.set_xlim(0, 0.15)\nax.set_ylim(-0.02, 1.05)\n\n# Spines\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor s in (\"left\", \"bottom\"):\n    ax.spines[s].set_color(INK_SOFT)\n\n# Subtle y-axis grid\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\n\nfig.subplots_adjust(left=0.12, right=0.97, top=0.93, bottom=0.13)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}