Project Overview
Developed a real-time Health, Safety & Environment (HSE) decision-support system that automates field operation safety assessments for environmental consulting teams. The dashboard transforms complex air quality data from multiple sources into clear Go/No-Go recommendations, eliminating manual analysis and reducing deployment risk.
Business Impact
Built for Corvus Consulting to address critical operational challenge: determining whether current air quality conditions are safe for outdoor fieldwork while maintaining regulatory compliance with CAAQS/WHO standards. The system processes environmental data in 500 milliseconds, providing instant safety guidance that protects field crews and reduces project delays.
The Business Problem
Environmental consulting firms face daily decisions about deploying field crews. Supervisors must answer: "Is it safe to send teams outdoors today?" This requires analyzing multiple air pollutants, weather conditions, regulatory compliance, and future forecasts—a time-consuming process prone to error when done manually.
Complex Data Sources
5+ pollutants (PM2.5, NO₂, O₃, CO, SO₂) with different health impacts and regulatory limits requiring specialized interpretation
Regulatory Compliance
Manual checking against CAAQS and WHO standards across multiple pollutant vectors
Time-Sensitive Decisions
Field crews need immediate go/no-go decisions for same-day deployment planning
Predictive Planning
Multi-day projects require forecasting to identify safe operational windows
System Architecture
1. Data Acquisition
OpenWeather API (5 endpoints)
Parallel requests for weather, pollution, forecasts2. Data Processing
TypeScript Business Logic
ERS calculation, compliance checks, trend analysis3. Decision Engine
Predictive Recommendation System
Go/Conditional/No-Go logic with rationale4. Visualization Layer
React + Material-UI + Recharts
Interactive dashboard with risk heatmaps5. User Interface
Responsive Web Dashboard
Desktop & mobile access for field supervisorsKey Technical Contributions
Environmental Risk Score (ERS) Algorithm
Challenge: Transform 5+ disparate pollutant measurements into single actionable risk metric for instant decision-making.
Solution: Engineered proprietary weighted scoring algorithm that converts pollutant concentrations to risk score (0-100):
Step 1: Normalize Against Regulatory Limits
Each pollutant converted to percentage of CAAQS/WHO threshold
PM2.5_pct = (current_pm2_5 / 25 µg/m³) × 100
NO2_pct = (current_no2 / 200 µg/m³) × 100
O3_pct = (current_o3 / 100 µg/m³) × 100
Step 2: Apply Health Impact Weights
Weights reflect respiratory risk during prolonged outdoor exposure
Step 3: Calculate Composite Score
ERS = (PM2.5_pct × 0.35) + (NO2_pct × 0.25) + (O3_pct × 0.25) + (CO_pct × 0.10) + (SO2_pct × 0.05)
Step 4: Map to Risk Categories
- 0-30: Low Risk (Safe for operations)
- 31-60: Moderate Risk (Caution advised)
- 61-100: High Risk (Operations not recommended)
// ERS Calculation Implementation
export function calculateRiskScore(pollutants: Pollutants): number {
// Normalize each pollutant against regulatory limits
const pm2_5_pct = (pollutants.pm2_5 / REGULATORY_LIMITS.pm2_5.limit) * 100;
const no2_pct = (pollutants.no2 / REGULATORY_LIMITS.no2.limit) * 100;
const o3_pct = (pollutants.o3 / REGULATORY_LIMITS.o3.limit) * 100;
const co_pct = (pollutants.co / REGULATORY_LIMITS.co.limit) * 100;
const so2_pct = (pollutants.so2 / REGULATORY_LIMITS.so2.limit) * 100;
// Apply health impact weights
const ers =
pm2_5_pct * RISK_WEIGHTS.pm2_5 + // 35%
no2_pct * RISK_WEIGHTS.no2 + // 25%
o3_pct * RISK_WEIGHTS.o3 + // 25%
co_pct * RISK_WEIGHTS.co + // 10%
so2_pct * RISK_WEIGHTS.so2; // 5%
return Math.min(Math.round(ers), 100); // Cap at 100
}
// Risk level categorization
export function getRiskLevel(ers: number): RiskLevel {
if (ers <= 30) return 'low';
if (ers <= 60) return 'moderate';
return 'high';
}
Automated Regulatory Compliance Engine
Challenge: Continuously monitor 5+ pollutants against CAAQS/WHO regulatory limits and provide early warning before non-compliance.
Solution: Built three-tier warning system that flags potential violations before they occur:
✓ Safe (<50% of limit)
Green status - Normal operations, no restrictions
⚠ Warning (50-80% of limit)
Yellow status - Early warning, enhanced monitoring required
✕ Danger (>80% of limit)
Red status - Approaching exceedance, operations should cease
Monitored Regulatory Limits
WHO 24-hour guideline
WHO 1-hour guideline
WHO 8-hour guideline
WHO 8-hour guideline
WHO 10-minute guideline
// Compliance Check Implementation
export function checkCompliance(pollutants: Pollutants): ComplianceStatus[] {
const compliance: ComplianceStatus[] = [];
Object.entries(pollutants).forEach(([key, value]) => {
const limit = REGULATORY_LIMITS[key];
const percentage = (value / limit.limit) * 100;
// Three-tier classification
let status: 'safe' | 'warning' | 'danger';
if (percentage <= 50) status = 'safe';
else if (percentage <= 80) status = 'warning';
else status = 'danger';
compliance.push({
pollutant: limit.name,
current: value,
limit: limit.limit,
percentage: Math.min(percentage, 100),
status,
unit: limit.unit
});
});
return compliance;
}
Predictive Recommendation Engine with 24-Hour Forecasting
Challenge: Enable proactive scheduling by identifying safe operational windows within next 24 hours, considering pollution trends and meteorological conditions.
Solution: Built multi-factor decision engine that analyzes current conditions, forecast trends, and wind dispersion to generate Go/Conditional/No-Go recommendations:
Factor 1: Current Risk Assessment
Real-time ERS score determines baseline safety level
Factor 2: Trend Analysis
24-hour forecast analyzed for improving/deteriorating conditions
Factor 3: Wind Dispersion Modeling
Wind speed ≥4 m/s significantly reduces pollutant exposure risk
Factor 4: Safe Window Identification
Algorithm identifies optimal time periods within forecast window
Example Recommendations Generated
Scenario: Low Risk (ERS = 24) + High Wind Speed (6.2 m/s)
Scenario: Moderate Risk (ERS = 48) + Adequate Wind (4.1 m/s)
Scenario: High Risk (ERS = 73) + Safe Window in 6 Hours
// Predictive Recommendation Engine
export function getSmartRecommendation(
currentERS: number,
forecast: ForecastData[],
currentWindSpeed: number
): string {
const riskLevel = getRiskLevel(currentERS);
// Analyze 24-hour forecast trends
const next24Hours = forecast.slice(0, 8); // 8 points = 24 hours
const avgFutureERS = next24Hours.reduce((sum, f) => sum + f.ers, 0) /
next24Hours.length;
const avgFutureWind = next24Hours.reduce((sum, f) => sum + f.windSpeed, 0) /
next24Hours.length;
// Identify safe operational windows
const safeWindows = next24Hours.filter(
f => f.ers <= 30 && f.windSpeed >= WIND_THRESHOLDS.medium
);
// HIGH RISK scenarios
if (riskLevel === 'high') {
if (safeWindows.length > 0) {
const hours = Math.round(
(safeWindows[0].timestamp - Date.now()) / (1000 * 60 * 60)
);
return `⚠️ UNSAFE - High Risk Detected. Operations NOT recommended. ` +
`Air quality expected to improve in approximately ${hours} hours.`;
}
return `⛔ UNSAFE - High Risk Detected. No improvement expected in ` +
`next 24 hours. Recommend postponing all non-essential operations.`;
}
// MODERATE RISK scenarios
if (riskLevel === 'moderate') {
if (currentWindSpeed >= WIND_THRESHOLDS.medium &&
avgFutureWind >= WIND_THRESHOLDS.medium) {
return `⚡ PROCEED WITH CAUTION - Moderate risk levels. Wind ` +
`conditions (${currentWindSpeed.toFixed(1)} m/s) support ` +
`dispersion. Limit exposure time.`;
}
if (avgFutureERS < currentERS) {
return `⏳ WAIT RECOMMENDED - Moderate risk with improving trends. ` +
`Air quality expected to improve over next 6-12 hours.`;
}
return `⚡ PROCEED WITH CAUTION - Moderate risk levels. Implement ` +
`enhanced safety protocols and monitor real-time conditions.`;
}
// LOW RISK scenarios
if (currentWindSpeed >= WIND_THRESHOLDS.high) {
return `✅ SAFE TO PROCEED - Low risk with excellent wind dispersion ` +
`(${currentWindSpeed.toFixed(1)} m/s). Ideal for field operations.`;
}
return `✅ SAFE TO PROCEED - Low risk conditions detected. Air quality ` +
`within acceptable limits. Maintain standard safety protocols.`;
}
Multi-Source API Integration & Data Pipeline
Challenge: Aggregate real-time environmental data from 5 separate OpenWeather API endpoints with minimal latency.
Solution: Architected parallel API request system that fetches all data sources simultaneously in ~500ms:
1. Current Weather API
- Temperature (°C)
- Wind speed (m/s)
- Humidity (%)
- General conditions
2. Current Air Pollution API
- PM2.5 (µg/m³)
- NO₂ (µg/m³)
- O₃ (µg/m³)
- CO, SO₂ (µg/m³)
- AQI (1-5 scale)
3. Pollution Forecast API
- 96-hour predictions
- Hourly granularity
- All 5 pollutants
- Trend analysis data
4. Weather Forecast API
- 5-day forecast
- Wind speed predictions
- Dispersion modeling
- 3-hour intervals
5. Geographic Data
- Calgary coordinates
- Edmonton coordinates
- Vancouver coordinates
- Toronto, Montreal
// Parallel API Integration
const fetchData = async (city: City) => {
try {
// Fetch all 4 APIs in parallel (~500ms total)
const [currentWeather, currentPollution, pollutionForecast, weatherForecast] =
await Promise.all([
openWeatherService.getCurrentWeather(city.lat, city.lon),
openWeatherService.getCurrentPollution(city.lat, city.lon),
openWeatherService.getPollutionForecast(city.lat, city.lon),
openWeatherService.getWeatherForecast(city.lat, city.lon)
]);
// Process current data
const currentData: CurrentData = {
temperature: currentWeather.main.temp,
windSpeed: currentWeather.wind.speed,
humidity: currentWeather.main.humidity,
aqi: currentPollution.list[0].main.aqi,
pollutants: {
pm2_5: currentPollution.list[0].components.pm2_5,
no2: currentPollution.list[0].components.no2,
o3: currentPollution.list[0].components.o3,
co: currentPollution.list[0].components.co,
so2: currentPollution.list[0].components.so2
}
};
// Calculate ERS and compliance
const ers = calculateRiskScore(currentData.pollutants);
const compliance = checkCompliance(currentData.pollutants);
// Process forecast (combine pollution + weather)
const forecastData: ForecastData[] = pollutionForecast.list.map((pollItem, index) => {
const weatherItem = weatherForecast.list[index];
return {
time: new Date(pollItem.dt * 1000).toLocaleTimeString(),
pm2_5: pollItem.components.pm2_5,
no2: pollItem.components.no2,
o3: pollItem.components.o3,
ers: calculateRiskScore(pollItem.components),
windSpeed: weatherItem.wind.speed,
aqi: pollItem.main.aqi
};
});
// Generate recommendation
const recommendation = getSmartRecommendation(ers, forecastData, currentData.windSpeed);
setDashboardData({ current, forecast, compliance, ers, recommendation });
} catch (err) {
setError('Failed to fetch environmental data');
}
};
"Digital Twin" Dashboard with Dynamic Risk Visualization
Challenge: Design intuitive interface that transforms complex environmental data into instantly understandable safety guidance for non-technical field supervisors.
Solution: Built "Digital Twin" style dashboard using Material-UI with color-coded states, risk heatmaps, and interactive trend charts:
Section 1: Environmental Risk Score
- Large 0-100 score display
- Color-coded by risk level (Green/Yellow/Red)
- Visual progress bar
- Risk category badge (Low/Moderate/High)
Section 2: Current Conditions
- Temperature, wind speed, humidity
- Air Quality Index with color coding
- Weather description with icon
- Material-UI icon system
Section 3: Operation Recommendation
- Prominent GO/CONDITIONAL/NO-GO badge
- Natural language explanation
- Decision rationale with context
- Disclaimer and safety notes
Section 4: Regulatory Compliance Grid
- All 5 pollutants with status icons
- Current vs. limit comparison
- Percentage bars with color coding
- Safe/Warning/Danger legend
Section 5: 24-Hour Trend Chart
- Interactive Recharts visualization
- PM2.5, NO₂, O₃ area charts
- ERS forecast line overlay
- Hover tooltips with data points
Corvus Consulting Brand-Aligned Color System
Kelly Green - Low Risk
Gold - Moderate Risk
Red - High Risk
Brand Anchor Color
Technology Stack
Frontend Framework
UI & Visualization
Data & APIs
Development Tools
Business Impact & Results
Operational Benefits
Field supervisors receive Go/No-Go recommendations in under 1 second, eliminating 15-20 minutes of manual data interpretation
5 API endpoints processed in parallel (500ms) providing complete environmental picture from weather, pollution, and forecasts
3-tier warning system (Safe/Warning/Danger) flags potential CAAQS/WHO violations before they occur, reducing liability exposure
24-hour forecasting identifies safe work windows hours in advance, enabling proactive scheduling and reducing project delays
ERS algorithm weighs health impacts appropriately, prioritizing PM2.5 (35%) for respiratory protection during prolonged outdoor exposure
Dashboard accessible from field on mobile devices, enabling on-site safety verification before crew deployment
Technical Learnings & Challenges
Algorithm Design for Domain Expertise
Designing the ERS algorithm required balancing mathematical rigor with practical field safety needs. The 35/25/25/10/5 weighting reflects actual respiratory risks, not arbitrary numbers—PM2.5 receives highest weight because fine particulates penetrate deepest into lungs.
Parallel API Optimization
Initial sequential API calls took 2+ seconds. Implementing Promise.all() reduced latency to 500ms, making the difference between "loading..." and "instant" in user perception. Real-time data requires real-time performance.
User-Centric Design for Non-Technical Users
Field supervisors don't need to know what "69.72 µg/m³ O₃" means—they need to know "Can I deploy crews today?" The Go/No-Go decision with natural language explanation transformed complex data into actionable guidance.
Conservative Safety Thresholds
The 50/80% warning tiers provide early alerts before regulatory exceedance. In safety-critical applications, false positives (unnecessary caution) are preferable to false negatives (missed hazards).
TypeScript for Complex Data Transformations
Strong typing prevented runtime errors when combining weather + pollution forecast data. Type safety caught mismatched units (m/s vs km/h) and incorrect API response parsing during development.
Visual Hierarchy in Safety Applications
Color coding must be immediate and universal: Green = Safe, Yellow = Caution, Red = Danger. Placing the Go/No-Go decision prominently ensures critical information visible without scrolling.
Environmental Technology & Data-Driven Safety
Real-time HSE decision-support system transforming complex environmental data into actionable field operation guidance.
Data Integration
5 API endpoints processed in parallel (500ms) - weather, pollution, forecasts aggregated automatically
Proprietary Algorithm
ERS scoring system with health-impact weighted pollutant vectors and CAAQS/WHO compliance monitoring
Predictive Intelligence
24-hour forecasting with safe window identification for proactive operational scheduling
Technologies: React 18 | TypeScript 5 | Material-UI 5 | Recharts | OpenWeather API | Vite | Axios