puffin-app/server/utils/carbonComparisons.js
Matt 7bdd462be9
Some checks failed
Build and Push Docker Images / docker (push) Has been cancelled
Implement comprehensive email templates with SMTP integration
- Add beautiful HTML email templates for receipts, admin notifications, and contact forms
- Implement SMTP email service with Nodemailer and Handlebars templating
- Add carbon equivalency calculations with EPA/DEFRA/IMO 2024 conversion factors
- Add portfolio color palette system for project visualization
- Integrate Wren API portfolio fetching in webhook handler
- Add light mode enforcement for email client compatibility
- Include Puffin logo from MinIO S3 in all templates
- Add test email endpoint for template validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 20:09:31 +01:00

257 lines
6.7 KiB
JavaScript

/**
* Carbon Equivalencies - EPA/DEFRA/IMO 2024 Verified Conversion Factors
* Ported from frontend src/utils/carbonEquivalencies.ts and impactSelector.ts
*/
// Carbon equivalency conversion factors (units per metric ton of CO2e)
export const CARBON_EQUIVALENCIES = {
// TRANSPORTATION (EPA 2024)
MILES_DRIVEN_AVG_CAR: 2560,
GALLONS_GASOLINE: 113,
// AVIATION (DEFRA 2024)
FLIGHT_KM_ECONOMY_SHORT: 6667,
FLIGHT_KM_ECONOMY_LONG: 7692,
// NATURAL (EPA 2024)
TREE_SEEDLINGS_10YR: 16.7,
FOREST_ACRES_1YR: 1.0,
FOREST_ACRES_10YR: 1.32,
// ENERGY (EPA 2024)
ELECTRICITY_KWH: 2540,
HOME_ENERGY_YEARS: 0.134,
BARRELS_OIL: 2.33,
// YACHT-SPECIFIC (IMO 2024)
MGO_LITERS_AVOIDED: 373,
MGO_TONNES_AVOIDED: 0.312,
CRUISING_HOURS_60M: 1.24,
SUPERYACHT_NAUTICAL_MILES: 149,
SHORE_POWER_DAYS: 25.4,
// LIFESTYLE
SMARTPHONE_CHARGES: 115000,
POUNDS_COAL_BURNED: 1000,
};
// Comparison definitions with emoji icons (email-compatible)
const COMPARISON_DEFINITIONS = {
MILES_DRIVEN: {
icon: '🚗',
unit: 'miles',
label: 'Miles driven in an average car',
},
GALLONS_GASOLINE: {
icon: '⛽',
unit: 'gallons',
label: 'Gallons of gasoline not burned',
},
FLIGHT_KM_SHORT: {
icon: '✈️',
unit: 'passenger-km',
label: 'Short-haul flight distance (economy)',
},
FLIGHT_KM_LONG: {
icon: '🛫',
unit: 'passenger-km',
label: 'Long-haul flight distance (economy)',
},
TREE_SEEDLINGS: {
icon: '🌲',
unit: 'trees',
label: 'Tree seedlings grown for 10 years',
},
FOREST_ACRES_1YR: {
icon: '🌳',
unit: 'acres',
label: 'Acres of forest preserved for 1 year',
},
FOREST_ACRES_10YR: {
icon: '🌳',
unit: 'acres',
label: 'Acres of forest carbon sequestration over 10 years',
},
ELECTRICITY_KWH: {
icon: '⚡',
unit: 'kWh',
label: 'Kilowatt-hours of electricity',
},
HOME_ENERGY_YEARS: {
icon: '🏠',
unit: 'home-years',
label: 'Years of home electricity use',
},
BARRELS_OIL: {
icon: '💧',
unit: 'barrels',
label: 'Barrels of oil not consumed',
},
MGO_LITERS: {
icon: '🌊',
unit: 'liters',
label: 'Liters of Marine Gas Oil avoided',
},
MGO_TONNES: {
icon: '💧',
unit: 'tonnes',
label: 'Tonnes of Marine Gas Oil avoided',
},
CRUISING_HOURS: {
icon: '⚓',
unit: 'hours',
label: 'Hours of 60m yacht cruising avoided',
},
SUPERYACHT_NAUTICAL_MILES: {
icon: '🚢',
unit: 'nautical miles',
label: 'Nautical miles of superyacht voyaging',
},
SHORE_POWER_DAYS: {
icon: '🔋',
unit: 'days',
label: 'Days of shore power for large yacht',
},
SMARTPHONE_CHARGES: {
icon: '📱',
unit: 'charges',
label: 'Smartphone charges',
},
POUNDS_COAL: {
icon: '🔥',
unit: 'pounds',
label: 'Pounds of coal not burned',
},
};
// Smart scaling ranges - different comparisons for different CO2 amounts
const SCALING_RANGES = [
{
minTons: 0,
maxTons: 0.1,
comparisonKeys: ['MILES_DRIVEN', 'SMARTPHONE_CHARGES', 'TREE_SEEDLINGS'],
},
{
minTons: 0.1,
maxTons: 1,
comparisonKeys: ['MILES_DRIVEN', 'TREE_SEEDLINGS', 'GALLONS_GASOLINE'],
},
{
minTons: 1,
maxTons: 5,
comparisonKeys: ['FLIGHT_KM_SHORT', 'TREE_SEEDLINGS', 'ELECTRICITY_KWH'],
},
{
minTons: 5,
maxTons: 20,
comparisonKeys: ['FLIGHT_KM_LONG', 'FOREST_ACRES_1YR', 'MGO_LITERS'],
},
{
minTons: 20,
maxTons: 100,
comparisonKeys: ['CRUISING_HOURS', 'FOREST_ACRES_1YR', 'HOME_ENERGY_YEARS'],
},
{
minTons: 100,
maxTons: Infinity,
comparisonKeys: ['FOREST_ACRES_10YR', 'CRUISING_HOURS', 'SUPERYACHT_NAUTICAL_MILES'],
},
];
// Factor mapping for conversion
const FACTOR_MAP = {
MILES_DRIVEN: CARBON_EQUIVALENCIES.MILES_DRIVEN_AVG_CAR,
GALLONS_GASOLINE: CARBON_EQUIVALENCIES.GALLONS_GASOLINE,
FLIGHT_KM_SHORT: CARBON_EQUIVALENCIES.FLIGHT_KM_ECONOMY_SHORT,
FLIGHT_KM_LONG: CARBON_EQUIVALENCIES.FLIGHT_KM_ECONOMY_LONG,
TREE_SEEDLINGS: CARBON_EQUIVALENCIES.TREE_SEEDLINGS_10YR,
FOREST_ACRES_1YR: CARBON_EQUIVALENCIES.FOREST_ACRES_1YR,
FOREST_ACRES_10YR: CARBON_EQUIVALENCIES.FOREST_ACRES_10YR,
ELECTRICITY_KWH: CARBON_EQUIVALENCIES.ELECTRICITY_KWH,
HOME_ENERGY_YEARS: CARBON_EQUIVALENCIES.HOME_ENERGY_YEARS,
BARRELS_OIL: CARBON_EQUIVALENCIES.BARRELS_OIL,
MGO_LITERS: CARBON_EQUIVALENCIES.MGO_LITERS_AVOIDED,
MGO_TONNES: CARBON_EQUIVALENCIES.MGO_TONNES_AVOIDED,
CRUISING_HOURS: CARBON_EQUIVALENCIES.CRUISING_HOURS_60M,
SUPERYACHT_NAUTICAL_MILES: CARBON_EQUIVALENCIES.SUPERYACHT_NAUTICAL_MILES,
SHORE_POWER_DAYS: CARBON_EQUIVALENCIES.SHORE_POWER_DAYS,
SMARTPHONE_CHARGES: CARBON_EQUIVALENCIES.SMARTPHONE_CHARGES,
POUNDS_COAL: CARBON_EQUIVALENCIES.POUNDS_COAL_BURNED,
};
/**
* Calculate equivalency value
*/
function calculateEquivalency(tons, factor) {
return tons * factor;
}
/**
* Format large numbers with appropriate precision
*/
function formatEquivalencyValue(value) {
if (value < 0.01) {
return value.toExponential(2);
} else if (value < 1) {
return value.toFixed(2);
} else if (value < 100) {
return value.toFixed(1);
} else if (value < 1000) {
return Math.round(value).toLocaleString();
} else {
return Math.round(value).toLocaleString();
}
}
/**
* Select the most appropriate comparisons for the given CO2 amount
* @param {number} tons - Metric tons of CO2e to offset
* @param {number} count - Number of comparisons to return (default: 3)
* @returns {Array} Array of carbon comparisons with calculated values
*/
export function selectComparisons(tons, count = 3) {
// Find the appropriate range for this amount
const range = SCALING_RANGES.find(
(r) => tons >= r.minTons && tons < r.maxTons
);
if (!range) {
console.warn(`No comparison range found for ${tons} tons, using default`);
return getDefaultComparisons(tons, count);
}
// Get the comparison definitions for this range
const comparisons = range.comparisonKeys
.slice(0, count)
.map((key) => {
const definition = COMPARISON_DEFINITIONS[key];
const factor = FACTOR_MAP[key];
const value = calculateEquivalency(tons, factor);
return {
...definition,
value: formatEquivalencyValue(value),
};
});
return comparisons;
}
/**
* Get default comparisons when no range matches
*/
function getDefaultComparisons(tons, count) {
const defaultKeys = ['MILES_DRIVEN', 'TREE_SEEDLINGS', 'ELECTRICITY_KWH'];
return defaultKeys.slice(0, count).map((key) => {
const definition = COMPARISON_DEFINITIONS[key];
const factor = FACTOR_MAP[key];
const value = calculateEquivalency(tons, factor);
return {
...definition,
value: formatEquivalencyValue(value),
};
});
}