| Server IP : 35.80.110.71 / Your IP : 216.73.216.221 Web Server : Apache/2.4.58 (Ubuntu) System : Linux ip-172-31-21-44 6.17.0-1019-aws #19~24.04.1-Ubuntu SMP Tue Jun 23 18:53:06 UTC 2026 x86_64 User : ubuntu ( 1000) PHP Version : 8.3.31 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/splitstream-web/.next/server/app/ |
Upload File : |
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/chunks/002zte3riz0h6.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/2xx73w8k5o69w.js"/><script src="/_next/static/chunks/0_bmh1s0g_5m3.js" async=""></script><script src="/_next/static/chunks/390e59xl7grtx.js" async=""></script><script src="/_next/static/chunks/3ss6b_eo0gpqh.js" async=""></script><script src="/_next/static/chunks/turbopack-0frl60onblppy.js" async=""></script><script src="/_next/static/chunks/3u-4v10_yuvs8.js" async=""></script><script src="/_next/static/chunks/31jk64s9j6agx.js" async=""></script><script src="/_next/static/chunks/04th6t7cqv9wf.js" async=""></script><link rel="preload" href="https://www.googletagmanager.com/gtag/js?id=G-HZ8Z4XP611" as="script"/><title>Splitstream — A/B testing API with empirically calibrated Bayesian inference</title><meta name="description" content="Production-shaped A/B testing platform: sticky bucketing, mutex experiment groups, Sample Ratio Mismatch detection, guardrail metrics, an Octane carve-out for the hot path, and a Bayesian stats engine whose decision rule is empirically calibrated — not asserted."/><script src="/_next/static/chunks/0cz1d0mv5g_q7.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><header class="border-b border-(--color-paper-dim) bg-(--color-paper)"><div class="mx-auto flex max-w-6xl items-center justify-between px-6 py-5"><a class="text-lg font-bold tracking-tight text-(--color-ink) no-underline" href="/">Splitstream<span class="ml-2 rounded-full bg-(--color-accent-soft) px-2 py-0.5 text-xs font-medium text-(--color-accent) align-middle">portfolio demo</span></a><nav class="hidden md:flex items-center gap-5 text-sm text-(--color-ink-dim)"><a href="/docs">Docs</a><a href="/reference">API reference</a><a href="/sdks">SDKs</a><a href="/bucketing">Bucketing</a><a href="/stats-playground">Stats playground</a><a href="/with-pennant">With Pennant</a><a href="/about">About</a></nav><button type="button" aria-label="Open menu" aria-expanded="false" aria-controls="site-mobile-nav" class="md:hidden inline-flex items-center justify-center rounded-md border border-(--color-paper-dim) p-2 text-(--color-ink) cursor-pointer"><svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg></button></div></header><main class="mx-auto max-w-6xl px-6 py-10"><div class="space-y-20"><section class="space-y-6 pt-6"><div class="inline-block rounded-full border border-(--color-paper-dim) px-3 py-1 text-xs text-(--color-ink-dim)">A portfolio demonstration · production-shaped, not production-grade</div><h1 class="max-w-3xl text-5xl font-bold tracking-tight leading-tight">A/B testing that doesn't lie about the math.</h1><p class="max-w-2xl text-lg text-(--color-ink-dim)">Splitstream is an experimentation platform with sticky bucketing, mutex experiment groups, Sample Ratio Mismatch detection, guardrail metrics, and a Bayesian stats engine whose decision rule is<!-- --> <em>empirically calibrated</em>, not asserted. Pair with<!-- --> <a class="underline" href="/with-pennant">Pennant</a> for the full experimentation stack.</p><div class="flex flex-wrap items-center gap-4 pt-2"><a class="rounded-md bg-(--color-accent) px-5 py-2.5 text-sm font-medium text-white no-underline hover:bg-emerald-800" href="/docs">Read the docs</a><a class="rounded-md border border-(--color-paper-dim) px-5 py-2.5 text-sm font-medium text-(--color-ink) no-underline hover:bg-(--color-paper-dim)" href="/reference">API reference</a><a href="https://github.com/philiprehberger/splitstream" class="text-sm text-(--color-ink-dim)">Source on GitHub →</a></div></section><section class="rounded-lg border border-(--color-paper-dim) bg-(--color-paper) p-8 shadow-sm"><h2 class="text-2xl font-bold tracking-tight">The plan's draft defaults delivered <span class="text-(--color-bad)">66% empirical false-positive rate</span>.</h2><p class="mt-4 text-(--color-ink-dim) leading-relaxed">Bayesian threshold-stopping at <code>posterior_threshold=0.95</code> <!-- -->isn't safe under continuous peeking. We ran 360,000 null-effect simulations across a 36-cell grid before shipping. The calibrated tuple — the one Splitstream ships as the default — is<!-- --> <code>0.995 / 20,000 / 240 min</code>, which lands at<!-- --> <strong class="text-(--color-good)">4.77% empirical FP.</strong> <!-- -->The calibration is what lets the docs claim the math is right.</p><div class="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-4"><div class="rounded-md border border-(--color-paper-dim) p-4"><div class="text-xs uppercase tracking-wide text-(--color-ink-dim)">Original draft</div><div class="mt-2 text-2xl font-bold font-(--font-mono) text-(--color-bad)">66%</div></div><div class="rounded-md border border-(--color-paper-dim) p-4"><div class="text-xs uppercase tracking-wide text-(--color-ink-dim)">Threshold 0.99</div><div class="mt-2 text-2xl font-bold font-(--font-mono) text-(--color-warn)">~15%</div></div><div class="rounded-md border border-(--color-paper-dim) p-4"><div class="text-xs uppercase tracking-wide text-(--color-ink-dim)">Threshold 0.995</div><div class="mt-2 text-2xl font-bold font-(--font-mono) text-(--color-good)">4.77%</div></div><div class="rounded-md border border-(--color-paper-dim) p-4"><div class="text-xs uppercase tracking-wide text-(--color-ink-dim)">Threshold 0.999</div><div class="mt-2 text-2xl font-bold font-(--font-mono) ">1.4%</div></div></div><a class="mt-6 inline-block text-sm" href="/docs/concepts/bayesian-inference">Read the calibration table →</a></section><section class="space-y-6"><h2 class="text-3xl font-bold tracking-tight">Five lines per stack.</h2><p class="max-w-2xl text-(--color-ink-dim)">Splitstream ships SDKs for TypeScript, React, PHP (Laravel-ready), and Python (Django + FastAPI adapters). The bucketing function is guarded by a shared corpus — every SDK matches byte-for-byte.</p><div class="overflow-hidden rounded-lg border border-(--color-paper-dim)"><div class="flex flex-wrap border-b border-(--color-paper-dim) bg-(--color-paper)"><button class="px-4 py-2 text-sm font-medium transition border-b-2 border-(--color-accent) text-(--color-accent)">TypeScript</button><button class="px-4 py-2 text-sm font-medium transition text-(--color-ink-dim) hover:text-(--color-ink)">React</button><button class="px-4 py-2 text-sm font-medium transition text-(--color-ink-dim) hover:text-(--color-ink)">PHP / Laravel</button><button class="px-4 py-2 text-sm font-medium transition text-(--color-ink-dim) hover:text-(--color-ink)">Python (FastAPI)</button><button class="px-4 py-2 text-sm font-medium transition text-(--color-ink-dim) hover:text-(--color-ink)">With Pennant</button></div><pre class="m-0 overflow-x-auto bg-(--color-ink) p-5 text-[13px] leading-[1.55] text-emerald-50"><code>import { Splitstream } from "@philiprehberger/splitstream";
const splitstream = new Splitstream({
apiKey: process.env.SPLITSTREAM_KEY!,
context: { userId: "alice" },
});
const variant = await splitstream.assign("checkout-v2", { fallback: "control" });
splitstream.track("purchase-completed", { revenue: 49 });</code></pre></div></section><section class="space-y-6"><h2 class="text-3xl font-bold tracking-tight">What ships in v0.1.</h2><div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"><div class="rounded-lg border border-(--color-paper-dim) p-5"><h3 class="font-semibold">Sticky-forever assignment</h3><p class="mt-2 text-sm text-(--color-ink-dim) leading-relaxed">First /v1/assign call writes the variant to a PostgreSQL row partitioned by experiment_id. Subsequent calls — same user, same variant, same forever.</p></div><div class="rounded-lg border border-(--color-paper-dim) p-5"><h3 class="font-semibold">Empirically calibrated defaults</h3><p class="mt-2 text-sm text-(--color-ink-dim) leading-relaxed">360k null-effect simulations picked the threshold/sample/cadence tuple. No hand-waved priors. The calibration JSON ships in the repo.</p></div><div class="rounded-lg border border-(--color-paper-dim) p-5"><h3 class="font-semibold">Mutex experiment groups</h3><p class="mt-2 text-sm text-(--color-ink-dim) leading-relaxed">A unit assigned to one experiment in the group cannot be assigned to another. SELECT FOR UPDATE race-protects simultaneous calls; holdouts are surfaced.</p></div><div class="rounded-lg border border-(--color-paper-dim) p-5"><h3 class="font-semibold">Sample Ratio Mismatch detection</h3><p class="mt-2 text-sm text-(--color-ink-dim) leading-relaxed">Chi-squared test on observed vs declared weights, every 5 minutes. p < 0.001 raises a banner — most SRM is a bug, not a real traffic skew.</p></div><div class="rounded-lg border border-(--color-paper-dim) p-5"><h3 class="font-semibold">Guardrail metrics</h3><p class="mt-2 text-sm text-(--color-ink-dim) leading-relaxed">Lower bound on the 95% credible interval of the relative difference. If the regression bound crosses the threshold, the experiment auto-stops with reason "lost."</p></div><div class="rounded-lg border border-(--color-paper-dim) p-5"><h3 class="font-semibold">Peek-event audit</h3><p class="mt-2 text-sm text-(--color-ink-dim) leading-relaxed">Every dashboard open is logged. The audit log surfaces "you peeked 12 times before calling this experiment" alongside the outcome.</p></div><div class="rounded-lg border border-(--color-paper-dim) p-5"><h3 class="font-semibold">Octane hot-path carve-out</h3><p class="mt-2 text-sm text-(--color-ink-dim) leading-relaxed">/v1/assign and /v1/events run on Laravel Octane (RoadRunner) under an Apache mod_proxy split. mod_php handles low-volume admin + management routes.</p></div><div class="rounded-lg border border-(--color-paper-dim) p-5"><h3 class="font-semibold">Redis-buffered ingest</h3><p class="mt-2 text-sm text-(--color-ink-dim) leading-relaxed">Synchronous endpoint, async persistence via COPY. Cross-partition dedup via SETNX with a 30-day TTL. Backpressure 429 with Retry-After.</p></div><div class="rounded-lg border border-(--color-paper-dim) p-5"><h3 class="font-semibold">Cross-SDK bucketing corpus</h3><p class="mt-2 text-sm text-(--color-ink-dim) leading-relaxed">TS, PHP, and Python SDKs round-trip the same bucketing corpus in CI. Drift between any implementation silently poisons experiments — the corpus catches it.</p></div></div></section><section class="rounded-lg bg-(--color-ink) px-8 py-12 text-center text-white"><h2 class="text-3xl font-bold tracking-tight">Built by Philip Rehberger.</h2><p class="mt-3 text-(--color-paper-dim)">This is a portfolio demo, not a hosted service. The code is the artifact.</p><div class="mt-6 flex flex-wrap justify-center gap-4"><a href="https://philiprehberger.com" class="rounded-md bg-(--color-accent) px-5 py-2.5 text-sm font-medium no-underline text-white hover:bg-emerald-800">About Philip</a><a href="https://github.com/philiprehberger/splitstream" class="rounded-md border border-emerald-300 px-5 py-2.5 text-sm font-medium text-emerald-200 no-underline hover:bg-emerald-950">GitHub repo</a></div></section></div><!--$--><!--/$--></main><footer class="border-t border-(--color-paper-dim) mt-20"><div class="mx-auto flex max-w-6xl items-center justify-between px-6 py-8 text-sm text-(--color-ink-dim)"><div>Splitstream is a portfolio demonstration by<!-- --> <a href="https://philiprehberger.com">Philip Rehberger</a>.</div><div class="flex flex-wrap gap-4"><a href="https://github.com/philiprehberger/splitstream">GitHub</a><a href="/pricing">Pricing</a><a href="/downloads">Downloads</a><a href="/status">Status</a><a href="/about">About this demo</a></div></div></footer><script src="/_next/static/chunks/2xx73w8k5o69w.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[79404,[\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"/_next/static/chunks/31jk64s9j6agx.js\"],\"default\"]\n3:I[88287,[\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"/_next/static/chunks/31jk64s9j6agx.js\"],\"default\"]\n4:I[74895,[\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"/_next/static/chunks/31jk64s9j6agx.js\"],\"default\"]\n5:I[4561,[\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"/_next/static/chunks/31jk64s9j6agx.js\",\"/_next/static/chunks/04th6t7cqv9wf.js\"],\"\"]\n6:I[70805,[\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"/_next/static/chunks/31jk64s9j6agx.js\"],\"default\"]\ne:I[52576,[\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"/_next/static/chunks/31jk64s9j6agx.js\"],\"default\",1]\n:HL[\"/_next/static/chunks/002zte3riz0h6.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",16],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/002zte3riz0h6.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"async\":true,\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-1\",{\"src\":\"/_next/static/chunks/31jk64s9j6agx.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"children\":[[\"$\",\"$L2\",null,{}],[\"$\",\"main\",null,{\"className\":\"mx-auto max-w-6xl px-6 py-10\",\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}],[\"$\",\"footer\",null,{\"className\":\"border-t border-(--color-paper-dim) mt-20\",\"children\":[\"$\",\"div\",null,{\"className\":\"mx-auto flex max-w-6xl items-center justify-between px-6 py-8 text-sm text-(--color-ink-dim)\",\"children\":[[\"$\",\"div\",null,{\"children\":[\"Splitstream is a portfolio demonstration by\",\" \",[\"$\",\"a\",null,{\"href\":\"https://philiprehberger.com\",\"children\":\"Philip Rehberger\"}],\".\"]}],[\"$\",\"div\",null,{\"className\":\"flex flex-wrap gap-4\",\"children\":[[\"$\",\"a\",null,{\"href\":\"https://github.com/philiprehberger/splitstream\",\"children\":\"GitHub\"}],[\"$\",\"$L5\",null,{\"href\":\"/pricing\",\"children\":\"Pricing\"}],[\"$\",\"$L5\",null,{\"href\":\"/downloads\",\"children\":\"Downloads\"}],[\"$\",\"$L5\",null,{\"href\":\"/status\",\"children\":\"Status\"}],[\"$\",\"$L5\",null,{\"href\":\"/about\",\"children\":\"About this demo\"}]]}]]}]}],[\"$\",\"$L6\",null,{}]]}]}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"div\",null,{\"className\":\"space-y-20\",\"children\":[[\"$\",\"section\",null,{\"className\":\"space-y-6 pt-6\",\"children\":[[\"$\",\"div\",null,{\"className\":\"inline-block rounded-full border border-(--color-paper-dim) px-3 py-1 text-xs text-(--color-ink-dim)\",\"children\":\"A portfolio demonstration · production-shaped, not production-grade\"}],[\"$\",\"h1\",null,{\"className\":\"max-w-3xl text-5xl font-bold tracking-tight leading-tight\",\"children\":\"A/B testing that doesn't lie about the math.\"}],[\"$\",\"p\",null,{\"className\":\"max-w-2xl text-lg text-(--color-ink-dim)\",\"children\":[\"Splitstream is an experimentation platform with sticky bucketing, mutex experiment groups, Sample Ratio Mismatch detection, guardrail metrics, and a Bayesian stats engine whose decision rule is\",\" \",[\"$\",\"em\",null,{\"children\":\"empirically calibrated\"}],\", not asserted. Pair with\",\" \",[\"$\",\"$L5\",null,{\"href\":\"/with-pennant\",\"className\":\"underline\",\"children\":\"Pennant\"}],\" for the full experimentation stack.\"]}],[\"$\",\"div\",null,{\"className\":\"flex flex-wrap items-center gap-4 pt-2\",\"children\":[[\"$\",\"$L5\",null,{\"href\":\"/docs\",\"className\":\"rounded-md bg-(--color-accent) px-5 py-2.5 text-sm font-medium text-white no-underline hover:bg-emerald-800\",\"children\":\"Read the docs\"}],[\"$\",\"$L5\",null,{\"href\":\"/reference\",\"className\":\"rounded-md border border-(--color-paper-dim) px-5 py-2.5 text-sm font-medium text-(--color-ink) no-underline hover:bg-(--color-paper-dim)\",\"children\":\"API reference\"}],[\"$\",\"a\",null,{\"href\":\"https://github.com/philiprehberger/splitstream\",\"className\":\"text-sm text-(--color-ink-dim)\",\"children\":\"Source on GitHub →\"}]]}]]}],\"$L7\",\"$L8\",\"$L9\",\"$La\"]}],[\"$Lb\"],\"$Lc\"]}],{},null,false,null]},null,false,null],\"$Ld\",false]],\"m\":\"$undefined\",\"G\":[\"$e\",[\"$Lf\"]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"d4p1Nsp7dnkNrIm-L0oJl\"}\n"])</script><script>self.__next_f.push([1,"10:I[6437,[\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"/_next/static/chunks/31jk64s9j6agx.js\",\"/_next/static/chunks/04th6t7cqv9wf.js\"],\"CodeTabs\"]\n13:I[51858,[\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"/_next/static/chunks/31jk64s9j6agx.js\"],\"OutletBoundary\"]\n14:\"$Sreact.suspense\"\n16:I[51858,[\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"/_next/static/chunks/31jk64s9j6agx.js\"],\"ViewportBoundary\"]\n18:I[51858,[\"/_next/static/chunks/3u-4v10_yuvs8.js\",\"/_next/static/chunks/31jk64s9j6agx.js\"],\"MetadataBoundary\"]\n"])</script><script>self.__next_f.push([1,"7:[\"$\",\"section\",null,{\"className\":\"rounded-lg border border-(--color-paper-dim) bg-(--color-paper) p-8 shadow-sm\",\"children\":[[\"$\",\"h2\",null,{\"className\":\"text-2xl font-bold tracking-tight\",\"children\":[\"The plan's draft defaults delivered \",[\"$\",\"span\",null,{\"className\":\"text-(--color-bad)\",\"children\":\"66% empirical false-positive rate\"}],\".\"]}],[\"$\",\"p\",null,{\"className\":\"mt-4 text-(--color-ink-dim) leading-relaxed\",\"children\":[\"Bayesian threshold-stopping at \",[\"$\",\"code\",null,{\"children\":\"posterior_threshold=0.95\"}],\" \",\"isn't safe under continuous peeking. We ran 360,000 null-effect simulations across a 36-cell grid before shipping. The calibrated tuple — the one Splitstream ships as the default — is\",\" \",[\"$\",\"code\",null,{\"children\":\"0.995 / 20,000 / 240 min\"}],\", which lands at\",\" \",[\"$\",\"strong\",null,{\"className\":\"text-(--color-good)\",\"children\":\"4.77% empirical FP.\"}],\" \",\"The calibration is what lets the docs claim the math is right.\"]}],[\"$\",\"div\",null,{\"className\":\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-4\",\"children\":[[\"$\",\"div\",\"Original draft\",{\"className\":\"rounded-md border border-(--color-paper-dim) p-4\",\"children\":[[\"$\",\"div\",null,{\"className\":\"text-xs uppercase tracking-wide text-(--color-ink-dim)\",\"children\":\"Original draft\"}],[\"$\",\"div\",null,{\"className\":\"mt-2 text-2xl font-bold font-(--font-mono) text-(--color-bad)\",\"children\":\"66%\"}]]}],[\"$\",\"div\",\"Threshold 0.99\",{\"className\":\"rounded-md border border-(--color-paper-dim) p-4\",\"children\":[[\"$\",\"div\",null,{\"className\":\"text-xs uppercase tracking-wide text-(--color-ink-dim)\",\"children\":\"Threshold 0.99\"}],[\"$\",\"div\",null,{\"className\":\"mt-2 text-2xl font-bold font-(--font-mono) text-(--color-warn)\",\"children\":\"~15%\"}]]}],[\"$\",\"div\",\"Threshold 0.995\",{\"className\":\"rounded-md border border-(--color-paper-dim) p-4\",\"children\":[[\"$\",\"div\",null,{\"className\":\"text-xs uppercase tracking-wide text-(--color-ink-dim)\",\"children\":\"Threshold 0.995\"}],[\"$\",\"div\",null,{\"className\":\"mt-2 text-2xl font-bold font-(--font-mono) text-(--color-good)\",\"children\":\"4.77%\"}]]}],[\"$\",\"div\",\"Threshold 0.999\",{\"className\":\"rounded-md border border-(--color-paper-dim) p-4\",\"children\":[[\"$\",\"div\",null,{\"className\":\"text-xs uppercase tracking-wide text-(--color-ink-dim)\",\"children\":\"Threshold 0.999\"}],[\"$\",\"div\",null,{\"className\":\"mt-2 text-2xl font-bold font-(--font-mono) \",\"children\":\"1.4%\"}]]}]]}],[\"$\",\"$L5\",null,{\"href\":\"/docs/concepts/bayesian-inference\",\"className\":\"mt-6 inline-block text-sm\",\"children\":\"Read the calibration table →\"}]]}]\n"])</script><script>self.__next_f.push([1,"8:[\"$\",\"section\",null,{\"className\":\"space-y-6\",\"children\":[[\"$\",\"h2\",null,{\"className\":\"text-3xl font-bold tracking-tight\",\"children\":\"Five lines per stack.\"}],[\"$\",\"p\",null,{\"className\":\"max-w-2xl text-(--color-ink-dim)\",\"children\":\"Splitstream ships SDKs for TypeScript, React, PHP (Laravel-ready), and Python (Django + FastAPI adapters). The bucketing function is guarded by a shared corpus — every SDK matches byte-for-byte.\"}],[\"$\",\"$L10\",null,{}]]}]\n"])</script><script>self.__next_f.push([1,"9:[\"$\",\"section\",null,{\"className\":\"space-y-6\",\"children\":[[\"$\",\"h2\",null,{\"className\":\"text-3xl font-bold tracking-tight\",\"children\":\"What ships in v0.1.\"}],[\"$\",\"div\",null,{\"className\":\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\",\"children\":[[\"$\",\"div\",\"Sticky-forever assignment\",{\"className\":\"rounded-lg border border-(--color-paper-dim) p-5\",\"children\":[[\"$\",\"h3\",null,{\"className\":\"font-semibold\",\"children\":\"Sticky-forever assignment\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-sm text-(--color-ink-dim) leading-relaxed\",\"children\":\"First /v1/assign call writes the variant to a PostgreSQL row partitioned by experiment_id. Subsequent calls — same user, same variant, same forever.\"}]]}],[\"$\",\"div\",\"Empirically calibrated defaults\",{\"className\":\"rounded-lg border border-(--color-paper-dim) p-5\",\"children\":[[\"$\",\"h3\",null,{\"className\":\"font-semibold\",\"children\":\"Empirically calibrated defaults\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-sm text-(--color-ink-dim) leading-relaxed\",\"children\":\"360k null-effect simulations picked the threshold/sample/cadence tuple. No hand-waved priors. The calibration JSON ships in the repo.\"}]]}],[\"$\",\"div\",\"Mutex experiment groups\",{\"className\":\"rounded-lg border border-(--color-paper-dim) p-5\",\"children\":[[\"$\",\"h3\",null,{\"className\":\"font-semibold\",\"children\":\"Mutex experiment groups\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-sm text-(--color-ink-dim) leading-relaxed\",\"children\":\"A unit assigned to one experiment in the group cannot be assigned to another. SELECT FOR UPDATE race-protects simultaneous calls; holdouts are surfaced.\"}]]}],[\"$\",\"div\",\"Sample Ratio Mismatch detection\",{\"className\":\"rounded-lg border border-(--color-paper-dim) p-5\",\"children\":[[\"$\",\"h3\",null,{\"className\":\"font-semibold\",\"children\":\"Sample Ratio Mismatch detection\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-sm text-(--color-ink-dim) leading-relaxed\",\"children\":\"Chi-squared test on observed vs declared weights, every 5 minutes. p \u003c 0.001 raises a banner — most SRM is a bug, not a real traffic skew.\"}]]}],[\"$\",\"div\",\"Guardrail metrics\",{\"className\":\"rounded-lg border border-(--color-paper-dim) p-5\",\"children\":[[\"$\",\"h3\",null,{\"className\":\"font-semibold\",\"children\":\"Guardrail metrics\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-sm text-(--color-ink-dim) leading-relaxed\",\"children\":\"Lower bound on the 95% credible interval of the relative difference. If the regression bound crosses the threshold, the experiment auto-stops with reason \\\"lost.\\\"\"}]]}],[\"$\",\"div\",\"Peek-event audit\",{\"className\":\"rounded-lg border border-(--color-paper-dim) p-5\",\"children\":[[\"$\",\"h3\",null,{\"className\":\"font-semibold\",\"children\":\"Peek-event audit\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-sm text-(--color-ink-dim) leading-relaxed\",\"children\":\"Every dashboard open is logged. The audit log surfaces \\\"you peeked 12 times before calling this experiment\\\" alongside the outcome.\"}]]}],[\"$\",\"div\",\"Octane hot-path carve-out\",{\"className\":\"rounded-lg border border-(--color-paper-dim) p-5\",\"children\":[[\"$\",\"h3\",null,{\"className\":\"font-semibold\",\"children\":\"Octane hot-path carve-out\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-sm text-(--color-ink-dim) leading-relaxed\",\"children\":\"/v1/assign and /v1/events run on Laravel Octane (RoadRunner) under an Apache mod_proxy split. mod_php handles low-volume admin + management routes.\"}]]}],[\"$\",\"div\",\"Redis-buffered ingest\",{\"className\":\"rounded-lg border border-(--color-paper-dim) p-5\",\"children\":[[\"$\",\"h3\",null,{\"className\":\"font-semibold\",\"children\":\"Redis-buffered ingest\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-sm text-(--color-ink-dim) leading-relaxed\",\"children\":\"Synchronous endpoint, async persistence via COPY. Cross-partition dedup via SETNX with a 30-day TTL. Backpressure 429 with Retry-After.\"}]]}],[\"$\",\"div\",\"Cross-SDK bucketing corpus\",{\"className\":\"rounded-lg border border-(--color-paper-dim) p-5\",\"children\":[\"$L11\",\"$L12\"]}]]}]]}]\n"])</script><script>self.__next_f.push([1,"a:[\"$\",\"section\",null,{\"className\":\"rounded-lg bg-(--color-ink) px-8 py-12 text-center text-white\",\"children\":[[\"$\",\"h2\",null,{\"className\":\"text-3xl font-bold tracking-tight\",\"children\":\"Built by Philip Rehberger.\"}],[\"$\",\"p\",null,{\"className\":\"mt-3 text-(--color-paper-dim)\",\"children\":\"This is a portfolio demo, not a hosted service. The code is the artifact.\"}],[\"$\",\"div\",null,{\"className\":\"mt-6 flex flex-wrap justify-center gap-4\",\"children\":[[\"$\",\"a\",null,{\"href\":\"https://philiprehberger.com\",\"className\":\"rounded-md bg-(--color-accent) px-5 py-2.5 text-sm font-medium no-underline text-white hover:bg-emerald-800\",\"children\":\"About Philip\"}],[\"$\",\"a\",null,{\"href\":\"https://github.com/philiprehberger/splitstream\",\"className\":\"rounded-md border border-emerald-300 px-5 py-2.5 text-sm font-medium text-emerald-200 no-underline hover:bg-emerald-950\",\"children\":\"GitHub repo\"}]]}]]}]\nb:[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/04th6t7cqv9wf.js\",\"async\":true,\"nonce\":\"$undefined\"}]\nc:[\"$\",\"$L13\",null,{\"children\":[\"$\",\"$14\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@15\"}]}]\nd:[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L16\",null,{\"children\":\"$L17\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$L18\",null,{\"children\":[\"$\",\"$14\",null,{\"name\":\"Next.Metadata\",\"children\":\"$L19\"}]}]}],null]}]\nf:[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/002zte3riz0h6.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]\n"])</script><script>self.__next_f.push([1,"11:[\"$\",\"h3\",null,{\"className\":\"font-semibold\",\"children\":\"Cross-SDK bucketing corpus\"}]\n12:[\"$\",\"p\",null,{\"className\":\"mt-2 text-sm text-(--color-ink-dim) leading-relaxed\",\"children\":\"TS, PHP, and Python SDKs round-trip the same bucketing corpus in CI. Drift between any implementation silently poisons experiments — the corpus catches it.\"}]\n"])</script><script>self.__next_f.push([1,"17:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"15:null\n19:[[\"$\",\"title\",\"0\",{\"children\":\"Splitstream — A/B testing API with empirically calibrated Bayesian inference\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Production-shaped A/B testing platform: sticky bucketing, mutex experiment groups, Sample Ratio Mismatch detection, guardrail metrics, an Octane carve-out for the hot path, and a Bayesian stats engine whose decision rule is empirically calibrated — not asserted.\"}]]\n"])</script></body></html>