591 lines
18 KiB
HTML
591 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Control Ledger | Brian Beaulieu</title>
|
||
<link rel="stylesheet" href="style.css">
|
||
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
|
||
</head>
|
||
<body>
|
||
<header class="ledger-hero">
|
||
<div class="overlay"></div>
|
||
<div class="hero-content">
|
||
<nav class="breadcrumb">
|
||
<a href="index.html">← Home</a>
|
||
</nav>
|
||
<h1>Control Ledger<br><span class="highlight">Real-Time Process Monitoring</span></h1>
|
||
<p class="lead">When variance exceeds tolerance, the ledger triggers response. Built on Statistical Process Control principles.</p>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="ledger-container">
|
||
<div class="interactive-demo">
|
||
<div class="demo-panel">
|
||
<h2>Live Simulation</h2>
|
||
<p>Adjust process parameters and watch control limits recalculate.</p>
|
||
|
||
<div class="controls">
|
||
<div class="slider-group">
|
||
<label for="mean-input">Process Mean (μ)</label>
|
||
<input type="range" id="mean-input" min="85" max="115" value="100" step="0.5">
|
||
<output id="mean-output">100.0</output>
|
||
</div>
|
||
|
||
<div class="slider-group">
|
||
<label for="sigma-input">Standard Deviation (σ)</label>
|
||
<input type="range" id="sigma-input" min="0.5" max="8" value="2.5" step="0.1">
|
||
<output id="sigma-output">2.5</output>
|
||
</div>
|
||
|
||
<div class="slider-group">
|
||
<label for="sample-input">Sample Size (n)</label>
|
||
<input type="range" id="sample-input" min="2" max="25" value="5" step="1">
|
||
<output id="sample-output">5</output>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chart-display">
|
||
<canvas id="controlChart" width="800" height="400"></canvas>
|
||
<div class="chart-overlay">
|
||
<div class="limit-label ucl">UCL = <span id="ucl-value">103.77</span></div>
|
||
<div class="limit-label cl">Center Line = <span id="cl-value">100.00</span></div>
|
||
<div class="limit-label lcl">LCL = <span id="lcl-value">96.23</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status-indicator">
|
||
<span class="status-dot" id="status-dot"></span>
|
||
<span id="status-text">PROCESS IN CONTROL</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theory-panel">
|
||
<h2>X-bar Chart Mathematics</h2>
|
||
<div class="formula-block">
|
||
<code>UCL = μ̄ + A₂ × R̄</code>
|
||
<p class="annotation">Upper Control Limit</p>
|
||
</div>
|
||
<div class="formula-block">
|
||
<code>LCL = μ̄ − A₂ × R̄</code>
|
||
<p class="annotation">Lower Control Limit</p>
|
||
</div>
|
||
<div class="formula-block">
|
||
<code>A₂ = 3 / (d₂ × √n)</code>
|
||
<p class="annotation">Control chart constant (from d₂ table)</p>
|
||
</div>
|
||
|
||
<div class="insight-box">
|
||
<h3>Why This Matters</h3>
|
||
<p>In galactic logistics, a 3-sigma excursion isn't noise—it's a supply lane fracture waiting to happen. The ledger doesn't predict; it detects early enough to respond.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="implementation-guide">
|
||
<div class="container">
|
||
<h2>Deployment Protocol</h2>
|
||
<div class="steps-grid">
|
||
<div class="step-card">
|
||
<span class="step-num">01</span>
|
||
<h3>Select Critical Parameters</h3>
|
||
<p>Identify CTQs (Critical-to-Quality metrics) where variance directly impacts mission success.</p>
|
||
</div>
|
||
<div class="step-card">
|
||
<span class="step-num">02</span>
|
||
<h3>Establish Baseline</h3>
|
||
<p>Collect Phase 1 data under stable conditions. Calculate μ₀ and σ₀.</p>
|
||
</div>
|
||
<div class="step-card">
|
||
<span class="step-num">03</span>
|
||
<h3>Set Response Triggers</h3>
|
||
<p>Define escalation paths for 2-sigma warnings and 3-sigma alarms.</p>
|
||
</div>
|
||
<div class="step-card">
|
||
<span class="step-num">04</span>
|
||
<h3>Automate Monitoring</h3>
|
||
<p>Deploy sensor network feeding real-time X-bar calculations to dashboard.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<footer>
|
||
<p>© 2026 Brian Beaulieu | Control Ledger v1.0 — Interactive SPC Module</p>
|
||
</footer>
|
||
</body>
|
||
</html>
|
||
|
||
<style>
|
||
/* LEDGER-SPECIFIC STYLES */
|
||
.ledger-hero {
|
||
position: relative;
|
||
height: 50vh;
|
||
display: flex;
|
||
align-items: center;
|
||
overflow: hidden;
|
||
background: radial-gradient(circle at 70% 30%, #1a1a2e 0%, #0a0a0c 80%);
|
||
}
|
||
|
||
.ledger-hero .overlay {
|
||
background: linear-gradient(180deg, rgba(10,10,12,0.95) 0%, rgba(10,10,12,0.8) 100%);
|
||
}
|
||
|
||
.ledger-hero .hero-content {
|
||
max-width: 650px;
|
||
padding: 0 2rem;
|
||
}
|
||
|
||
.ledger-hero h1 {
|
||
font-size: clamp(2rem, 4vw, 3rem);
|
||
line-height: 1.1;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.ledger-container {
|
||
padding: 3rem 2rem;
|
||
background: var(--bg-deep);
|
||
}
|
||
|
||
.interactive-demo {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
display: grid;
|
||
grid-template-columns: 1.2fr 0.8fr;
|
||
gap: 2rem;
|
||
}
|
||
|
||
@media (max-width: 1024px) {
|
||
.interactive-demo {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.demo-panel {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 16px;
|
||
padding: 2rem;
|
||
}
|
||
|
||
.demo-panel h2 {
|
||
font-size: 1.5rem;
|
||
margin-bottom: 0.5rem;
|
||
color: var(--accent-blue);
|
||
}
|
||
|
||
.demo-panel > p {
|
||
color: var(--text-muted);
|
||
margin-bottom: 2rem;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.controls {
|
||
background: rgba(0,0,0,0.3);
|
||
border-radius: 12px;
|
||
padding: 1.5rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.slider-group {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto;
|
||
align-items: center;
|
||
gap: 1.5rem;
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
|
||
.slider-group:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.slider-group label {
|
||
font-size: 0.85rem;
|
||
color: var(--text-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
.slider-group input[type="range"] {
|
||
grid-column: 1 / -1;
|
||
width: 100%;
|
||
margin-bottom: 0.5rem;
|
||
accent-color: var(--accent-blue);
|
||
height: 4px;
|
||
background: var(--border-subtle);
|
||
border-radius: 2px;
|
||
appearance: none;
|
||
}
|
||
|
||
.slider-group output {
|
||
font-family: "SF Mono", "Menlo", monospace;
|
||
font-size: 0.9rem;
|
||
color: var(--accent-blue);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.chart-display {
|
||
position: relative;
|
||
margin-bottom: 1.5rem;
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.chart-display canvas {
|
||
display: block;
|
||
width: 100%;
|
||
height: auto;
|
||
background: linear-gradient(180deg, #0d0d12 0%, #0a0a0c 100%);
|
||
}
|
||
|
||
.chart-overlay {
|
||
position: absolute;
|
||
top: 1rem;
|
||
right: 1rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.limit-label {
|
||
font-family: "SF Mono", "Menlo", monospace;
|
||
font-size: 0.75rem;
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 4px;
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
|
||
.limit-label.ucl {
|
||
color: #ef4444;
|
||
background: rgba(239,68,68,0.1);
|
||
border: 1px solid rgba(239,68,68,0.3);
|
||
}
|
||
|
||
.limit-label.cl {
|
||
color: var(--accent-blue);
|
||
background: rgba(59,130,246,0.1);
|
||
border: 1px solid rgba(59,130,246,0.3);
|
||
}
|
||
|
||
.limit-label.lcl {
|
||
color: #22c55e;
|
||
background: rgba(34,197,94,0.1);
|
||
border: 1px solid rgba(34,197,94,0.3);
|
||
}
|
||
|
||
.status-indicator {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
padding: 1rem;
|
||
background: rgba(34,197,94,0.08);
|
||
border: 1px solid rgba(34,197,94,0.2);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.status-dot {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: #22c55e;
|
||
box-shadow: 0 0 12px rgba(34,197,94,0.6);
|
||
animation: pulse 2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: 0.7; transform: scale(1.1); }
|
||
}
|
||
|
||
.status-text {
|
||
font-family: "SF Mono", "Menlo", monospace;
|
||
font-size: 0.8rem;
|
||
color: #22c55e;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.12em;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.theory-panel {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 16px;
|
||
padding: 2rem;
|
||
}
|
||
|
||
.theory-panel h2 {
|
||
font-size: 1.25rem;
|
||
margin-bottom: 1.5rem;
|
||
color: var(--accent-blue);
|
||
}
|
||
|
||
.formula-block {
|
||
background: rgba(59,130,246,0.05);
|
||
border-left: 3px solid var(--accent-blue);
|
||
padding: 1rem;
|
||
margin-bottom: 1rem;
|
||
border-radius: 0 8px 8px 0;
|
||
}
|
||
|
||
.formula-block code {
|
||
display: block;
|
||
font-family: "SF Mono", "Menlo", monospace;
|
||
font-size: 0.9rem;
|
||
color: var(--text-primary);
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.formula-block .annotation {
|
||
font-size: 0.75rem;
|
||
color: var(--text-muted);
|
||
font-style: italic;
|
||
}
|
||
|
||
.insight-box {
|
||
margin-top: 2rem;
|
||
padding: 1.5rem;
|
||
background: linear-gradient(180deg, rgba(245,158,11,0.08) 0%, transparent 100%);
|
||
border: 1px solid rgba(245,158,11,0.2);
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.insight-box h3 {
|
||
font-size: 1rem;
|
||
color: var(--accent-warm);
|
||
margin-bottom: 0.75rem;
|
||
}
|
||
|
||
.insight-box p {
|
||
font-size: 0.9rem;
|
||
color: var(--text-muted);
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.implementation-guide {
|
||
max-width: 1000px;
|
||
margin: 4rem auto 0;
|
||
}
|
||
|
||
.implementation-guide h2 {
|
||
text-align: center;
|
||
font-size: 2rem;
|
||
margin-bottom: 3rem;
|
||
color: var(--accent-blue);
|
||
}
|
||
|
||
.steps-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||
gap: 1.5rem;
|
||
}
|
||
|
||
.step-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 12px;
|
||
padding: 1.5rem;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.step-card::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 4px;
|
||
height: 100%;
|
||
background: var(--accent-blue);
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.step-num {
|
||
position: absolute;
|
||
top: 1rem;
|
||
right: 1rem;
|
||
font-size: 3rem;
|
||
font-weight: 700;
|
||
color: rgba(255,255,255,0.03);
|
||
font-family: "SF Mono", "Menlo", monospace;
|
||
}
|
||
|
||
.step-card h3 {
|
||
font-size: 1.1rem;
|
||
margin-bottom: 0.75rem;
|
||
color: var(--accent-blue);
|
||
}
|
||
|
||
.step-card p {
|
||
font-size: 0.9rem;
|
||
color: var(--text-muted);
|
||
line-height: 1.6;
|
||
}
|
||
|
||
footer {
|
||
margin-top: 4rem;
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
// CONTROL CHART SIMULATION
|
||
const canvas = document.getElementById('controlChart');
|
||
const ctx = canvas.getContext('2d');
|
||
const meanInput = document.getElementById('mean-input');
|
||
const sigmaInput = document.getElementById('sigma-input');
|
||
const sampleInput = document.getElementById('sample-input');
|
||
const meanOutput = document.getElementById('mean-output');
|
||
const sigmaOutput = document.getElementById('sigma-output');
|
||
const sampleOutput = document.getElementById('sample-output');
|
||
const uclValue = document.getElementById('ucl-value');
|
||
const clValue = document.getElementById('cl-value');
|
||
const lclValue = document.getElementById('lcl-value');
|
||
const statusDot = document.getElementById('status-dot');
|
||
const statusText = document.getElementById('status-text');
|
||
|
||
let samples = [];
|
||
const NUM_SAMPLES = 50;
|
||
|
||
function updateDisplay() {
|
||
const mu = parseFloat(meanInput.value);
|
||
const sigma = parseFloat(sigmaInput.value);
|
||
const n = parseInt(sampleInput.value);
|
||
|
||
meanOutput.textContent = mu.toFixed(1);
|
||
sigmaOutput.textContent = sigma.toFixed(1);
|
||
sampleOutput.textContent = n.toString();
|
||
|
||
// Control chart constants (approximation for A2)
|
||
const d2_table = {2:1.128, 3:1.693, 4:2.059, 5:2.326, 6:2.534, 7:2.704, 8:2.847, 9:2.970, 10:3.078};
|
||
const d2 = d2_table[n] || (2.326 + (n-5)*0.15);
|
||
const A2 = 3 / (d2 * Math.sqrt(n));
|
||
|
||
const UCL = mu + A2 * sigma * d2;
|
||
const LCL = mu - A2 * sigma * d2;
|
||
|
||
uclValue.textContent = UCL.toFixed(2);
|
||
clValue.textContent = mu.toFixed(2);
|
||
lclValue.textContent = LCL.toFixed(2);
|
||
|
||
drawChart(mu, sigma, UCL, LCL, n);
|
||
}
|
||
|
||
function generateSamples(mu, sigma) {
|
||
samples = [];
|
||
for (let i = 0; i < NUM_SAMPLES; i++) {
|
||
// Box-Muller transform for normal distribution
|
||
let u = 0, v = 0;
|
||
while(u === 0) u = Math.random();
|
||
while(v === 0) v = Math.random();
|
||
const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
|
||
samples.push(mu + z * sigma);
|
||
}
|
||
}
|
||
|
||
function drawChart(mu, sigma, UCL, LCL, n) {
|
||
const width = canvas.width;
|
||
const height = canvas.height;
|
||
const padding = {top: 40, right: 60, bottom: 40, left: 60};
|
||
const plotWidth = width - padding.left - padding.right;
|
||
const plotHeight = height - padding.top - padding.bottom;
|
||
|
||
// Clear
|
||
ctx.fillStyle = '#0d0d12';
|
||
ctx.fillRect(0, 0, width, height);
|
||
|
||
// Generate new samples
|
||
generateSamples(mu, sigma);
|
||
|
||
// Find range
|
||
const minVal = Math.min(...samples, LCL - sigma);
|
||
const maxVal = Math.max(...samples, UCL + sigma);
|
||
const valRange = maxVal - minVal;
|
||
|
||
// Scale function
|
||
const scaleX = (i) => padding.left + (i / (NUM_SAMPLES - 1)) * plotWidth;
|
||
const scaleY = (val) => padding.top + ((val - minVal) / valRange) * plotHeight;
|
||
|
||
// Draw control limits
|
||
ctx.strokeStyle = '#ef4444';
|
||
ctx.setLineDash([8, 4]);
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
ctx.moveTo(padding.left, scaleY(UCL));
|
||
ctx.lineTo(padding.left + plotWidth, scaleY(UCL));
|
||
ctx.stroke();
|
||
|
||
ctx.strokeStyle = '#3b82f6';
|
||
ctx.setLineDash([]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(padding.left, scaleY(mu));
|
||
ctx.lineTo(padding.left + plotWidth, scaleY(mu));
|
||
ctx.stroke();
|
||
|
||
ctx.strokeStyle = '#22c55e';
|
||
ctx.setLineDash([8, 4]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(padding.left, scaleY(LCL));
|
||
ctx.lineTo(padding.left + plotWidth, scaleY(LCL));
|
||
ctx.stroke();
|
||
|
||
// Draw data points
|
||
ctx.fillStyle = '#3b82f6';
|
||
samples.forEach((val, i) => {
|
||
const x = scaleX(i);
|
||
const y = scaleY(val);
|
||
ctx.beginPath();
|
||
ctx.arc(x, y, 4, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// Check out of control
|
||
if (val > UCL || val < LCL) {
|
||
ctx.fillStyle = '#ef4444';
|
||
ctx.beginPath();
|
||
ctx.arc(x, y, 6, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.fillStyle = '#3b82f6';
|
||
|
||
// Update status
|
||
statusDot.style.background = '#ef4444';
|
||
statusDot.style.boxShadow = '0 0 12px rgba(239,68,68,0.6)';
|
||
statusText.textContent = 'OUT OF CONTROL DETECTED';
|
||
statusText.style.color = '#ef4444';
|
||
}
|
||
});
|
||
|
||
// Reset status if all in control
|
||
const allInControl = samples.every(s => s >= LCL && s <= UCL);
|
||
if (allInControl) {
|
||
statusDot.style.background = '#22c55e';
|
||
statusDot.style.boxShadow = '0 0 12px rgba(34,197,94,0.6)';
|
||
statusText.textContent = 'PROCESS IN CONTROL';
|
||
statusText.style.color = '#22c55e';
|
||
}
|
||
|
||
// Axis labels
|
||
ctx.fillStyle = '#9a9a9a';
|
||
ctx.font = '10px SF Mono, Menlo, monospace';
|
||
ctx.textAlign = 'left';
|
||
ctx.fillText(`UCL=${UCL.toFixed(1)}`, padding.left + 8, padding.top - 8);
|
||
ctx.fillText(`CL=${mu.toFixed(1)}`, padding.left + 8, scaleY(mu) + 4);
|
||
ctx.fillText(`LCL=${LCL.toFixed(1)}`, padding.left + 8, scaleY(LCL) + 4);
|
||
}
|
||
|
||
// Event listeners
|
||
meanInput.addEventListener('input', updateDisplay);
|
||
sigmaInput.addEventListener('input', updateDisplay);
|
||
sampleInput.addEventListener('input', updateDisplay);
|
||
|
||
// Initial render
|
||
updateDisplay();
|
||
|
||
// Handle resize
|
||
window.addEventListener('resize', () => {
|
||
canvas.width = canvas.parentElement.clientWidth;
|
||
canvas.height = 400;
|
||
updateDisplay();
|
||
});
|
||
</script>
|