// ... (Keep the global variable initializations as they are)
// let rawData = window.AT_DASHBOARD_DATA.csvData.rawData;
// let headers = window.AT_DASHBOARD_DATA.csvData.headers;
// ...
function handleFileSelect(e) {
const file = e.target.files[0];
if (!file) return;
const fileName = file.name.toLowerCase();
showLoading();
if (fileName.endsWith('.atdash')) {
const reader = new FileReader();
reader.onload = (event) => {
try { restoreFullDashboard(JSON.parse(event.target.result)); } // restoreFullDashboard will handle global vars
catch (err) { hideLoading(); alert('Error reading .atdash: ' + err.message); }
};
reader.readAsText(file);
} else if (fileName.endsWith('.csv')) {
Papa.parse(file, {
complete: (results) => {
hideLoading();
if (results.data && results.data.length > 1) {
const clean = results.data.filter(r => r.length > 20 && !r[0]?.toString().includes('console.log') && r.some(c => c && c.toString().trim() !== ''));
window.AT_DASHBOARD_DATA.csvData.headers = clean[0] || []; // Ensure it's an array
window.AT_DASHBOARD_DATA.csvData.rawData = clean.slice(1);
window.AT_DASHBOARD_DATA.csvData.dataLoadTime = new Date().toISOString();
// CRITICAL: Update global vars AFTER populating window object
headers = window.AT_DASHBOARD_DATA.csvData.headers;
rawData = window.AT_DASHBOARD_DATA.csvData.rawData;
storeCsvDataInLocalStorage();
updateDataStatus();
processData(); // This will now use the correctly updated globals
} else { alert('CSV empty or invalid.'); }
},
error: (err) => { hideLoading(); alert('Error parsing CSV: ' + err.message); }
});
} else { hideLoading(); alert('Unsupported file. Please use .csv or .atdash.'); }
}
// ...
function loadCsvDataFromLocalStorage() {
try {
const storedHeadersString = localStorage.getItem(LS_CSV_HEADERS_KEY);
const storedLoadTimeString = localStorage.getItem(LS_CSV_LOAD_TIME_KEY);
const numChunksString = localStorage.getItem(LS_CSV_CHUNK_COUNT_KEY);
if (!storedHeadersString || !numChunksString) { console.log("No main CSV data in localStorage or incomplete."); return false; }
window.AT_DASHBOARD_DATA.csvData.headers = JSON.parse(storedHeadersString);
window.AT_DASHBOARD_DATA.csvData.dataLoadTime = storedLoadTimeString || new Date().toISOString();
// CRITICAL: Update global var
headers = window.AT_DASHBOARD_DATA.csvData.headers;
const numChunks = parseInt(numChunksString);
if (isNaN(numChunks) || numChunks < 0) { throw new Error("Invalid chunk count."); }
if (numChunks === 0) {
window.AT_DASHBOARD_DATA.csvData.rawData = [];
rawData = []; // CRITICAL
console.log("Loaded empty main CSV rawData (with headers) from localStorage.");
return true;
}
let rawDataString = '';
for (let i = 0; i < numChunks; i++) {
const chunk = localStorage.getItem(LS_CSV_CHUNK_KEY_PREFIX + i);
if (chunk === null) throw new Error(`Missing localStorage chunk ${i}.`);
rawDataString += chunk;
}
window.AT_DASHBOARD_DATA.csvData.rawData = JSON.parse(rawDataString);
// CRITICAL: Update global var
rawData = window.AT_DASHBOARD_DATA.csvData.rawData;
console.log(`Main CSV data (${rawData.length} rows) loaded from localStorage.`);
return true;
} catch (e) {
console.error('Error loading main CSV data from localStorage:', e);
clearCsvDataFromLocalStorage(false);
window.AT_DASHBOARD_DATA.csvData = { headers: [], rawData: [], dataLoadTime: null };
// CRITICAL: Reset globals on error
headers = []; rawData = [];
return false;
}
}
// ...
function restoreFullDashboard(backup) {
try {
showLoading();
if (backup.csvData) {
window.AT_DASHBOARD_DATA.csvData = backup.csvData;
// CRITICAL: Update global vars
headers = window.AT_DASHBOARD_DATA.csvData.headers || [];
rawData = window.AT_DASHBOARD_DATA.csvData.rawData || [];
storeCsvDataInLocalStorage();
} else { // If no CSV data in backup, ensure globals are also empty
window.AT_DASHBOARD_DATA.csvData = { headers: [], rawData: [], dataLoadTime: null };
headers = [];
rawData = [];
}
if (backup.scenarioData) window.AT_DASHBOARD_DATA.scenarioData = backup.scenarioData;
updateDataStatus();
processData(); // Will use the correctly updated globals
setTimeout(() => { // ... (rest of timeout logic is likely fine)
if (backup.dashboardState) {
// ... (filter restoration) ...
const filters = backup.dashboardState.filters;
if (filters.tierFilter && document.getElementById('tierFilter')) document.getElementById('tierFilter').value = filters.tierFilter;
if (filters.deptFilter && document.getElementById('deptFilter')) document.getElementById('deptFilter').value = filters.deptFilter;
if (filters.managerLevel && document.getElementById('managerLevelSelect')) document.getElementById('managerLevelSelect').value = filters.managerLevel;
if (filters.deptLevel && document.getElementById('deptLevelSelect')) document.getElementById('deptLevelSelect').value = filters.deptLevel;
if (filters.deptMetric && document.getElementById('deptMetricSelect')) document.getElementById('deptMetricSelect').value = filters.deptMetric;
if (filters.scenarioView && document.getElementById('scenarioSelect')) document.getElementById('scenarioSelect').value = filters.scenarioView;
currentTierExplorerTier = backup.dashboardState.lastTierExplorerTier || null;
currentTierExplorerMetric = backup.dashboardState.lastTierExplorerMetric || 'FTE';
window.AT_DASHBOARD_DATA.tierExplorerState.currentTier = currentTierExplorerTier;
window.AT_DASHBOARD_DATA.tierExplorerState.currentMetric = currentTierExplorerMetric;
if (document.getElementById('metricSelectTierExplorer')) {
document.getElementById('metricSelectTierExplorer').value = currentTierExplorerMetric;
}
const lastViewId = backup.dashboardState.lastViewId;
if (lastViewId) {
let tierForNav = lastViewId === 'tierExplorer' ? currentTierExplorerTier : null;
// Construct selector carefully
let selector = `.nav-item[onclick*="showView('${lastViewId}'`;
if (tierForNav) {
selector += `, this, '${tierForNav}'`;
}
selector += `)"]`;
const navBtn = document.querySelector(selector);
if (navBtn) {
navBtn.click();
} else if (lastViewId === 'tierExplorer' && currentTierExplorerTier) { // Fallback
const genericTierExplorerButton = document.querySelector(`.nav-item[onclick*="'tierExplorer', this, '${currentTierExplorerTier}'"]`);
if (genericTierExplorerButton) genericTierExplorerButton.click();
} else { // Default to overview if specific button not found
document.querySelector('.nav-item[onclick*="showView(\'overview\'"]').click();
}
} else { // Default to overview if no lastViewId
document.querySelector('.nav-item[onclick*="showView(\'overview\'"]').click();
}
}
hideLoading();
alert(`Dashboard restored from .atdash (Backup: ${new Date(backup.timestamp).toLocaleString()}).`);
}, 300); // Slightly increased delay
} catch (e) { hideLoading(); console.error('Error restoring .atdash:', e); alert('Error restoring .atdash: ' + e.message); }
}
// ...
function clearData() {
if (confirm('Clear all data (CSV, session, browser storage) and reset dashboard? Download .atdash backup first if needed.')) {
clearCsvDataFromLocalStorage(false);
window.AT_DASHBOARD_DATA.csvData = { headers: [], rawData: [], dataLoadTime: null };
// CRITICAL: Reset globals
headers = []; rawData = [];
Object.values(charts).forEach(c => { if (c && typeof c.destroy === 'function') c.destroy(); });
charts = {}; window.AT_DASHBOARD_DATA.charts = {};
window.AT_DASHBOARD_DATA.tierExplorerState = { currentTier: null, currentMetric: 'FTE' };
currentTierExplorerTier = null; currentTierExplorerMetric = 'FTE';
location.reload();
}
}
Auckland Transport Analytics Dashboard
📊 Drop your CSV or .atdash file here or to begin analysis
Organization Overview
Total Positions
0
Across all departments
Total Headcount
0
Active employees
Average FTE
0
Full-time equivalent
Departments
0
Organizational units
Tier Distribution
Employee Group Distribution
Hierarchy Analysis
Span of Control by Manager
Organization Tree Depth
Department Metrics
Department Size Comparison
Department Tier Distribution
Workforce Analytics
Full-Time Positions
0
FTE = 1.0
Part-Time Positions
0
0 < FTE < 1.0
Vacant Positions
0
HC = 0
Fill Rate
0%
Positions filled
FTE Distribution (by Position)
Employee Group by Tier
Cost Center Analysis (Overall)
Top 15 Cost Centers by Headcount
Cost Center Count by Size (HC)
Key Insights & Recommendations
AT Reintegration Scenarios (2025-2027)
Total Staff Transition
1,804
Annual Savings
$18-22M
Target Public Trust
55%
Timeline
2027
Staff Reallocation by Function
Scenario Comparison
🎯 Alignment
Integration aligns transport with housing, climate, resilience under one LTP.
💰 Benefits
$18-22M annual savings via shared services for safety & transit.
📈 Targets
2030: Trust 55%, DSI <400, PT trips 140M, Farebox 40%.
🏗️ Plan
Phase 1 (Corp) mid-2026, Ph 2 (Ops) mid-2027; TCAMP in 2027-37 LTP.
Mayoral Expectations Analysis
LoE Dec 2023 - One Year Review
Expectations Met
3/15
Savings Target
$12M
vs $20M
Public Trust
27%
Enforcement Rev
+15%
Performance vs Mayoral Priorities
Detailed Assessment
Priority | Status | Evidence | Assessment |
---|---|---|---|
Listen to Akl | ❌ Fail | Trust 27% | No imprv. |
Fix roads | ❌ Fail | $37M capex low | Deteriorating |
$50 PT pass | ⚠️ Part | Low uptake | Price OK |
Savings ($20M) | ❌ Fail | $12M (Y1) | 40% short |
Enforce revenue | ✅ OK | +15% park rev | Success |
Cycling network | ❌ Fail | Cancelled | Unaffordable |
🚨 Gov. Disconnect
AT failed "take direction". 20% success shows misalignment. Validates reintegration.
💸 Financial Non-Comp.
Missed savings by 40%, $36M surplus, $37M capex underspend. Defiance.
Timeline: 1 Year Non-Delivery
Critical Verdict
AT's failure to deliver on mayoral expectations provides compelling evidence for reintegration.
- ✗ 3/15 expectations met
- ✗ Trust static at crisis levels
- ✗ Financial directives ignored
Conclusion: LoE shows arm's-length model enables defiance. Reintegration is a necessity.
Data Explorer
Position Details
0 recordsTier Cost Composition Explorer
0Tier X Positions
0Tier X FTE
0Tier X Headcount
0Cost Centers (Tier X)
Cost-Center Composition (Tier X Roles)
Cost Center ID | Cost Center Text | Tier X FTE | Tier X Headcount | Share of Total Tier X FTE |
---|