supervisors-playbook/sentinel.html
2026-07-18 14:31:34 +00:00

302 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Stock-Out Sentinel | Brandon Buonaguro</title>
<style>
:root {
--bg: #0a0a0a;
--surface: #141414;
--primary: #ff4d00; /* Burnt orange */
--secondary: #00d9ff; /* Cyan accent */
--text: #e0e0e0;
--muted: #666;
--border: #333;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Inter', system-ui, -apple-system, sans-serif;
line-height: 1.6;
overflow-x: hidden;
}
header {
border-bottom: 1px solid var(--border);
padding: 2rem 1rem;
text-align: center;
position: relative;
}
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
letter-spacing: -0.05em;
font-weight: 800;
background: linear-gradient(180deg, var(--primary) 0%, #cc3700 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5rem;
}
.subtitle {
color: var(--muted);
font-size: 1.1rem;
max-width: 600px;
margin: 0 auto;
}
main {
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1rem;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
@media (max-width: 900px) { main { grid-template-columns: 1fr; } }
.panel {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 2rem;
box-shadow: 0 4px 24px rgba(0,0,0,0.5);
}
.panel-title {
color: var(--secondary);
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 1.5rem;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px dashed var(--border);
padding-bottom: 0.5rem;
}
/* Controls */
.control-group { margin-bottom: 1.5rem; }
label {
display: block;
font-size: 0.9rem;
color: var(--muted);
margin-bottom: 0.5rem;
font-weight: 600;
}
input[type="range"] {
width: 100%;
height: 6px;
background: var(--border);
border-radius: 3px;
appearance: none;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
background: var(--primary);
border-radius: 50%;
cursor: pointer;
transition: transform 0.2s;
}
input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.2); }
.val-display {
float: right;
color: var(--primary);
font-family: 'JetBrains Mono', monospace;
font-weight: 700;
}
/* Results */
.result-card {
background: rgba(255, 77, 0, 0.05);
border: 1px solid var(--primary);
border-radius: 8px;
padding: 1.5rem;
margin-top: 1rem;
text-align: center;
}
.prob-val {
font-size: 3rem;
font-weight: 800;
color: var(--primary);
line-height: 1;
}
.prob-label {
color: var(--muted);
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.1em;
}
.verdict {
margin-top: 1rem;
font-size: 1.2rem;
font-weight: 700;
color: var(--secondary);
}
.verdict.danger { color: #ff3333; }
.verdict.safe { color: #00ff88; }
/* Image */
.memory-shot {
grid-column: 1 / -1;
width: 100%;
height: 400px;
object-fit: cover;
border-radius: 12px;
filter: contrast(1.2) saturate(0.8);
opacity: 0.9;
margin: 2rem 0;
border: 1px solid var(--border);
}
.caption {
grid-column: 1 / -1;
text-align: center;
color: var(--muted);
font-style: italic;
font-size: 0.9rem;
margin-bottom: 2rem;
}
footer {
text-align: center;
padding: 3rem 1rem;
color: var(--muted);
font-size: 0.8rem;
border-top: 1px solid var(--border);
margin-top: 2rem;
}
a { color: var(--secondary); text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
</head>
<body>
<header>
<h1>The Stock-Out Sentinel</h1>
<div class="subtitle">
Calculate your probability of stock-out using real-world variance.
No spreadsheets. No guesswork. Just the math that keeps the lights on.
</div>
</header>
<main>
<!-- Input Panel -->
<section class="panel">
<div class="panel-title">Field Inputs</div>
<div class="control-group">
<label>Daily Demand (units/day)</label>
<input type="range" id="demand" min="10" max="500" value="120">
<span class="val-display" id="demandVal">120</span>
</div>
<div class="control-group">
<label>Lead Time (days)</label>
<input type="range" id="leadTime" min="1" max="30" value="7">
<span class="val-display" id="leadTimeVal">7</span>
</div>
<div class="control-group">
<label>Demand Std Dev (σ)</label>
<input type="range" id="sigma" min="1" max="100" value="18">
<span class="val-display" id="sigmaVal">18</span>
</div>
<div class="control-group">
<label>Service Level Target (%)</label>
<input type="range" id="serviceLevel" min="80" max="99.9" step="0.1" value="95">
<span class="val-display" id="serviceLevelVal">95%</span>
</div>
</section>
<!-- Result Panel -->
<section class="panel">
<div class="panel-title">The Verdict</div>
<div style="margin-bottom: 2rem;">
<div class="prob-label">Probability of Stock-Out</div>
<div class="prob-val" id="stockOutProb">5.0%</div>
</div>
<div class="result-card">
<div id="verdictText" class="verdict safe">SAFE TO RUN</div>
<div style="margin-top: 0.5rem; font-size: 0.9rem; color: var(--muted);">
Recommended Safety Stock: <span id="ssRec" style="color: var(--text); font-weight: 700;">245 units</span>
</div>
</div>
<div style="margin-top: 2rem; font-size: 0.85rem; color: var(--muted); line-height: 1.8;">
<strong>Logic:</strong><br>
SS = Z × σ × √LT<br>
Where Z is the standard normal deviate for your target service level.<br>
Stock-Out Prob = 1 - Service Level
</div>
</section>
<!-- The Memory Shot -->
<img src="https://images.pexels.com/photos/30341205/pexels-photo-30341205.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940" alt="Dark warehouse aisle with stacked inventory" class="memory-shot">
<div class="caption">
Aisle 4, Dover Distribution Center. November 2025. We missed $40k here because we trusted a forecast, not the variance.
</div>
<footer>
Built by Brandon Buonaguro | Dover, PA<br>
Data constants exposed at <a href="/sentinel.json">/sentinel.json</a><br>
<small>© 2026 The Supervisor's Playbook</small>
</footer>
</main>
<script>
// Constants
const Z_TABLE = {
80: 0.84, 85: 1.04, 90: 1.28, 95: 1.65, 97.5: 1.96, 99: 2.33, 99.9: 3.09
};
function interpolateZ(targetSL) {
const keys = Object.keys(Z_TABLE).map(Number).sort((a,b)=>a-b);
for (let i = 0; i < keys.length - 1; i++) {
if (targetSL >= keys[i] && targetSL <= keys[i+1]) {
const t = (targetSL - keys[i]) / (keys[i+1] - keys[i]);
return Z_TABLE[keys[i]] + t * (Z_TABLE[keys[i+1]] - Z_TABLE[keys[i]]);
}
}
return Z_TABLE[keys[keys.length-1]];
}
function calc() {
const D = parseFloat(document.getElementById('demand').value);
const LT = parseFloat(document.getElementById('leadTime').value);
const SIGMA = parseFloat(document.getElementById('sigma').value);
const SL = parseFloat(document.getElementById('serviceLevel').value);
document.getElementById('demandVal').textContent = D;
document.getElementById('leadTimeVal').textContent = LT;
document.getElementById('sigmaVal').textContent = SIGMA;
document.getElementById('serviceLevelVal').textContent = SL + '%';
const Z = interpolateZ(SL);
const SS = Math.round(Z * SIGMA * Math.sqrt(LT));
const prob = ((100 - SL)).toFixed(2);
document.getElementById('stockOutProb').textContent = prob + '%';
document.getElementById('ssRec').textContent = SS.toLocaleString() + ' units';
const verdictEl = document.getElementById('verdictText');
if (prob > 5) {
verdictEl.textContent = "CRITICAL: ORDER NOW";
verdictEl.className = "verdict danger";
} else if (prob > 1) {
verdictEl.textContent = "WATCH CLOSELY";
verdictEl.className = "verdict";
verdictEl.style.color = "#ffaa00";
} else {
verdictEl.textContent = "SAFE TO RUN";
verdictEl.className = "verdict safe";
}
}
document.querySelectorAll('input').forEach(el => el.addEventListener('input', calc));
calc();
</script>
</body>
</html>