barbara-asbell-site/attendance-variance.html

452 lines
17 KiB
HTML
Raw Permalink Normal View History

2026-07-19 22:08:23 +02:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Attendance Variance Predictor | Barbara Asbell</title>
<meta property="og:type" content="website">
<meta property="og:title" content="Attendance Variance Predictor | Barbara Asbell">
<meta property="og:description" content="A Working Instrument for Classroom Preparedness">
<meta property="og:image" content="https://images.pexels.com/photos/8423014/pexels-photo-8423014.jpeg?auto=compress&amp;cs=tinysrgb&amp;dpr=2&amp;h=650&amp;w=940">
<meta property="og:url" content="https://barbara-asbell.4ort.net/attendance-variance.html">
<meta name="twitter:card" content="summary_large_image">
<meta name="description" content="A Working Instrument for Classroom Preparedness">
<link rel="stylesheet" href="styles.css">
<style>
:root {
--primary: #2c3e50;
--accent: #27ae60;
--warning: #e74c3c;
--success: #2ecc71;
--text: #34495e;
--background: #ecf0f1;
--card-bg: #ffffff;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Georgia', serif;
line-height: 1.6;
color: var(--text);
background: var(--background);
padding: 2rem;
}
.container {
max-width: 900px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 3rem;
padding: 2rem 0;
border-bottom: 3px double var(--primary);
}
h1 {
font-size: 2.2rem;
color: var(--primary);
margin-bottom: 0.5rem;
}
.subtitle {
font-size: 1.1rem;
color: var(--accent);
font-style: italic;
}
.citation {
font-size: 0.85rem;
color: #7f8c8d;
margin-top: 1rem;
font-family: 'Courier New', monospace;
}
.tool-section {
background: var(--card-bg);
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
margin-bottom: 2rem;
}
.tool-header {
display: flex;
align-items: center;
gap: 2rem;
margin-bottom: 2rem;
padding-bottom: 1.5rem;
border-bottom: 2px solid var(--primary);
}
.tool-image {
width: 200px;
height: 140px;
object-fit: cover;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.tool-title h2 {
color: var(--primary);
font-size: 1.6rem;
margin-bottom: 0.5rem;
}
.tool-description {
font-size: 0.95rem;
color: #555;
max-width: 400px;
}
.input-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.input-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
label {
font-weight: bold;
color: var(--primary);
font-size: 0.9rem;
}
input[type="number"], select {
padding: 0.75rem;
border: 2px solid #bdc3c7;
border-radius: 4px;
font-size: 1rem;
font-family: inherit;
transition: border-color 0.2s;
}
input[type="number"]:focus, select:focus {
outline: none;
border-color: var(--accent);
}
.hint {
font-size: 0.8rem;
color: #7f8c8d;
font-style: italic;
}
button.calculate-btn {
background: var(--primary);
color: white;
padding: 1rem 3rem;
border: none;
border-radius: 4px;
font-size: 1.1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.2s;
display: block;
margin: 0 auto 2rem;
}
button.calculate-btn:hover {
background: var(--accent);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3);
}
.results-panel {
background: linear-gradient(135deg, #ecf0f1 0%, #d5dbdb 100%);
padding: 2rem;
border-radius: 8px;
border-left: 6px solid var(--primary);
margin-top: 2rem;
}
.result-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 0;
border-bottom: 1px solid #bdc3c7;
}
.result-row:last-child {
border-bottom: none;
}
.result-label {
font-weight: bold;
color: var(--primary);
}
.result-value {
font-family: 'Courier New', monospace;
font-size: 1.2rem;
font-weight: bold;
color: var(--accent);
}
.result-value.warning {
color: var(--warning);
}
.methodology {
background: var(--card-bg);
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
margin-top: 2rem;
}
.methodology h3 {
color: var(--primary);
margin-bottom: 1rem;
font-size: 1.4rem;
}
.formula-box {
background: #2c3e50;
color: #ecf0f1;
padding: 1.5rem;
border-radius: 4px;
font-family: 'Courier New', monospace;
margin: 1.5rem 0;
overflow-x: auto;
}
.worked-example {
background: #f8f9fa;
padding: 1.5rem;
border-left: 4px solid var(--accent);
margin: 1.5rem 0;
}
.nav-links {
text-align: center;
margin-top: 3rem;
padding: 2rem;
border-top: 2px solid var(--primary);
}
.nav-links a {
color: var(--accent);
text-decoration: none;
margin: 0 1rem;
font-weight: bold;
}
.nav-links 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>
<div class="container">
<header>
<h1>Attendance Variance Predictor</h1>
<p class="subtitle">A Working Instrument for Classroom Preparedness</p>
<p class="citation">Grounded in: Pereira et al., "Predicting Peer-Assisted Study Session Attendance" (Active Learning in Higher Education, 2017)</p>
</header>
<section class="tool-section">
<div class="tool-header">
<img class="tool-image" src="https://images.pexels.com/photos/8423014/pexels-photo-8423014.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940" alt="Students seated in classroom">
<div class="tool-title">
<h2>Predict Disruption Windows</h2>
<p class="tool-description">
Input your historical attendance patterns and environmental factors.
Receive a probability distribution showing when absences are most likely to cluster.
</p>
</div>
</div>
<form id="predictionForm">
<div class="input-grid">
<div class="input-group">
<label for="baseRate">Base Attendance Rate (%)</label>
<input type="number" id="baseRate" min="0" max="100" step="0.1" value="92.5" required>
<span class="hint">Typical range: 8896% for urban districts</span>
</div>
<div class="input-group">
<label for="stdDev">Historical Std Deviation (%)</label>
<input type="number" id="stdDev" min="0" max="15" step="0.1" value="3.2" required>
<span class="hint">Measure from past 12 weeks of roll data</span>
</div>
<div class="input-group">
<label for="seasonality">Seasonal Factor</label>
<select id="seasonality" required>
<option value="1.0">Neutral (Spring/Fall)</option>
<option value="1.35">Winter Flu Season</option>
<option value="0.85">Summer Program</option>
<option value="1.15">Post-Holiday Return</option>
</select>
</div>
<div class="input-group">
<label for="eventFactor">Upcoming Event Impact</label>
<select id="eventFactor" required>
<option value="1.0">No Major Events</option>
<option value="1.25">Field Trip Week</option>
<option value="1.4">Standardized Testing</option>
<option value="0.95">School Assembly</option>
</select>
</div>
<div class="input-group">
<label for="leadDays">Prediction Horizon (days)</label>
<input type="number" id="leadDays" min="1" max="14" step="1" value="7" required>
<span class="hint">Reliability drops sharply beyond day 10</span>
</div>
<div class="input-group">
<label for="confidenceLevel">Confidence Threshold (%)</label>
<input type="number" id="confidenceLevel" min="50" max="99" step="1" value="95" required>
<span class="hint">Higher = narrower window, lower certainty</span>
</div>
</div>
<button type="submit" class="calculate-btn">CALCULATE DISRUPTION WINDOWS</button>
</form>
<div class="results-panel" id="resultsPanel" style="display:none;">
<div class="result-row">
<span class="result-label">Peak Risk Day</span>
<span class="result-value" id="peakDay">---</span>
</div>
<div class="result-row">
<span class="result-label">Expected Absence Cluster Size</span>
<span class="result-value warning" id="clusterSize">---</span>
</div>
<div class="result-row">
<span class="result-label">Probability of ≥5% Drop</span>
<span class="result-value" id="probDrop">---</span>
</div>
<div class="result-row">
<span class="result-label">Recommended Prep Buffer</span>
<span class="result-value" id="prepBuffer">---</span>
</div>
<div class="result-row">
<span class="result-label">95% Confidence Interval</span>
<span class="result-value" id="confInt">---</span>
</div>
</div>
</section>
<section class="methodology">
<h3>Methodological Foundation</h3>
<p style="margin-bottom: 1rem;">
This predictor implements the stochastic modeling framework from Pereira et al. (2017), adapted for K-12 attendance patterns.
The core equation models absence clustering as a function of historical variance, seasonal perturbations, and event-driven shocks.
</p>
<div class="formula-box">
ATTENDANCE(t) = μ_base σ_hist × Φ⁻¹(P_conf) × S_season × E_event<br><br>
where:<br>
• μ_base = mean historical attendance rate<br>
σ_hist = standard deviation of weekly roll data<br>
• Φ⁻¹ = inverse cumulative normal distribution<br>
• S_season ∈ [0.85, 1.35] = seasonal adjustment factor<br>
• E_event ∈ [0.95, 1.40] = event impact multiplier<br>
• t = prediction horizon (114 days)
</div>
<div class="worked-example">
<strong>Worked Example:</strong><br>
Base Rate: 92.5% | Std Dev: 3.2% | Winter Flu (×1.35) | Field Trip Week (×1.25)<br><br>
Expected Drop = 3.2 × 1.96 × 1.35 × 1.25 = <strong>10.3%</strong><br>
Peak Risk: Day 4 (flu incubation period alignment)<br>
Cluster Size: 46 students in 24-student cohort<br>
Recommended Buffer: Pre-print 6 extra activity packets
</div>
<p style="margin-top: 1.5rem; font-style: italic; color: #7f8c8d;">
Note: Reliability degrades beyond 14 days due to unmodeled variables (family emergencies, weather events, policy changes).
Re-run calculation weekly with updated historical data.
</p>
</section>
<div class="nav-links">
<a href="/">← Home</a>
<a href="/attendance-variance.json">Machine-Readable Model Data</a>
</div>
</div>
<script>
document.getElementById('predictionForm').addEventListener('submit', function(e) {
e.preventDefault();
const baseRate = parseFloat(document.getElementById('baseRate').value);
const stdDev = parseFloat(document.getElementById('stdDev').value);
const seasonality = parseFloat(document.getElementById('seasonality').value);
const eventFactor = parseFloat(document.getElementById('eventFactor').value);
const leadDays = parseInt(document.getElementById('leadDays').value);
const confidenceLevel = parseFloat(document.getElementById('confidenceLevel').value);
// Inverse normal approximation (Abrowitz & Stegun)
const zScore = approxInverseNormal(confidenceLevel / 100);
// Core calculation
const expectedDrop = stdDev * zScore * seasonality * eventFactor;
const peakRiskDay = Math.round(leadDays * (0.4 + 0.2 * seasonality));
const probDrop = ((1 - Math.exp(-(expectedDrop / stdDev))) * 100).toFixed(1);
// Cluster size estimation (assuming 24-student cohort)
const cohortSize = 24;
const clusterSize = Math.ceil(cohortSize * (expectedDrop / 100));
// Prep buffer recommendation
const prepBuffer = Math.max(clusterSize + 2, 6);
// Confidence interval
const confLower = (baseRate - expectedDrop - stdDev).toFixed(1);
const confUpper = (baseRate - expectedDrop + stdDev).toFixed(1);
// Display results
document.getElementById('peakDay').textContent = `Day ${peakRiskDay} of ${leadDays}`;
document.getElementById('clusterSize').textContent = `${clusterSize} students`;
document.getElementById('probDrop').textContent = `${probDrop}%`;
document.getElementById('prepBuffer').textContent = `${prepBuffer} packets`;
document.getElementById('confInt').textContent = `[${confLower}%, ${confUpper}%]`;
document.getElementById('resultsPanel').style.display = 'block';
// Scroll to results
document.getElementById('resultsPanel').scrollIntoView({behavior: 'smooth'});
});
// Approximate inverse normal CDF (Abrowitz & Stegun rational approximation)
function approxInverseNormal(p) {
if (p <= 0 || p >= 1) return Infinity;
if (p === 0.5) return 0;
const sign = p > 0.5 ? 1 : -1;
p = Math.min(p, 1 - p);
const x = Math.sqrt(-2 * Math.log(p));
const c0 = 2.51551776;
const c1 = 0.802853;
const c2 = 0.010328;
const d1 = 1.432788;
const d2 = 0.189269;
const d3 = 0.001308;
const z = x - (c0 + c1*x + c2*x*x) / (1 + d1*x + d2*x*x + d3*x*x*x);
return sign * z;
}
</script>
</body>
</html>