ver display a number without its provenance.
interface GridIntensityRecord {
factor: number;
unit: 'kg CO2e per kWh';
source: 'IEA_2026' | 'DEFRA_2025' | 'EPA_2024';
year: number;
regionName: string;
subregionCode?: string;
generationMixNote?: string;
}
type GridRegistry = Record<string, GridIntensityRecord>;
Step 2: Construct the Lookup Registry
Store factors in a flat, key-value structure. Keys follow ISO 3166-1 alpha-2 for countries and EPA eGRID subregion codes for US states. A flat map guarantees O(1) resolution time and eliminates nested traversal logic.
const GRID_INTENSITY_REGISTRY: GridRegistry = {
// European Union & UK
GB: {
factor: 0.177,
unit: 'kg CO2e per kWh',
source: 'DEFRA_2025',
year: 2025,
regionName: 'United Kingdom',
generationMixNote: 'DEFRA 2025. -15% vs 2024 baseline.'
},
DE: {
factor: 0.364,
unit: 'kg CO2e per kWh',
source: 'IEA_2026',
year: 2026,
regionName: 'Germany'
},
FR: {
factor: 0.052,
unit: 'kg CO2e per kWh',
source: 'IEA_2026',
year: 2026,
regionName: 'France',
generationMixNote: '~70% nuclear generation'
},
// Asia-Pacific
IN: {
factor: 0.708,
unit: 'kg CO2e per kWh',
source: 'IEA_2026',
year: 2026,
regionName: 'India'
},
CN: {
factor: 0.581,
unit: 'kg CO2e per kWh',
source: 'IEA_2026',
year: 2026,
regionName: 'China',
generationMixNote: 'Declining intensity as solar/wind scales'
},
// US Subregions (EPA eGRID 2024)
US_NATIONAL: {
factor: 0.386,
unit: 'kg CO2e per kWh',
source: 'EPA_2024',
year: 2024,
regionName: 'United States (National Average)'
},
US_NYUP: {
factor: 0.1249,
unit: 'kg CO2e per kWh',
source: 'EPA_2024',
year: 2024,
regionName: 'NYUP — Upstate New York',
generationMixNote: 'Hydro 31% + Nuclear 31%'
},
US_WECC_CAMX: {
factor: 0.2265,
unit: 'kg CO2e per kWh',
source: 'EPA_2024',
year: 2024,
regionName: 'CAMX — California',
generationMixNote: 'Gas 46% + Solar 20%'
},
US_ERCT: {
factor: 0.3512,
unit: 'kg CO2e per kWh',
source: 'EPA_2024',
year: 2024,
regionName: 'ERCT — Texas (ERCOT)',
generationMixNote: 'Gas 47% + Wind 23%'
},
US_RFCW: {
factor: 0.4563,
unit: 'kg CO2e per kWh',
source: 'EPA_2024',
year: 2024,
regionName: 'RFCW — Ohio Valley',
generationMixNote: 'Coal 31% + Gas 32%'
},
US_SRMW: {
factor: 0.6260,
unit: 'kg CO2e per kWh',
source: 'EPA_2024',
year: 2024,
regionName: 'SRMW — SERC Midwest',
generationMixNote: 'Coal 59% — Highest subregion intensity'
}
} as const;
Step 3: Implement the Resolution Engine
The lookup function must fail explicitly. Returning undefined or NaN corrupts downstream financial models. Use a type guard to validate the registry state before resolution.
type ResolutionResult = GridIntensityRecord | null;
function resolveGridIntensity(
regionKey: string,
registry: GridRegistry
): ResolutionResult {
const normalizedKey = regionKey.trim().toUpperCase();
const record = registry[normalizedKey];
if (!record) {
console.warn(
`[CarbonEngine] No grid intensity record found for key: "${normalizedKey}". ` +
`Falling back to null to prevent silent NaN propagation.`
);
return null;
}
return record;
}
Step 4: Build the Calculation & Citation Payload
Scope 2 location-based emissions require precise unit conversion and mandatory source attribution. The function returns a structured report object that downstream UI or API layers can render without additional formatting logic.
interface Scope2LocationReport {
consumptionKwh: number;
intensityFactor: number;
emissionsKg: number;
emissionsTonnes: number;
citation: string;
generationContext?: string;
dataVersion: string;
}
function computeLocationBasedEmissions(
consumptionKwh: number,
regionKey: string,
registry: GridRegistry,
dataVersion: string
): Scope2LocationReport | null {
if (consumptionKwh < 0 || !Number.isFinite(consumptionKwh)) {
throw new Error('[CarbonEngine] Invalid consumption value. Must be a non-negative finite number.');
}
const record = resolveGridIntensity(regionKey, registry);
if (!record) return null;
const emissionsKg = consumptionKwh * record.factor;
return {
consumptionKwh,
intensityFactor: record.factor,
emissionsKg: parseFloat(emissionsKg.toFixed(4)),
emissionsTonnes: parseFloat((emissionsKg / 1000).toFixed(6)),
citation: `${record.source} (${record.year}) — ${record.regionName}`,
generationContext: record.generationMixNote,
dataVersion
};
}
Architecture Rationale
- Flat Registry over Nested Objects: O(1) key resolution eliminates traversal overhead. Carbon calculations often run in tight loops (e.g., processing thousands of meter readings). Nested structures introduce unnecessary cognitive and computational complexity.
- Server-Side Injection: The registry should be serialized into the initial HTML payload or injected via a build-time constant. This guarantees deterministic state across client, server, and edge runtimes. No network fetches, no race conditions.
- Explicit Citation Enforcement: By bundling
citation and dataVersion directly into the return payload, you prevent UI layers from displaying unattributed numbers. Auditors require source traceability at the point of display.
- Strict Type Guards: Returning
null on missing keys forces the calling code to handle the absence of data explicitly. This prevents NaN * 0 = 0 bugs that silently zero out emissions in financial reports.
Pitfall Guide
1. Conflating Location-Based and Market-Based Methods
Explanation: The GHG Protocol requires separate reporting for location-based (grid average) and market-based (contract-specific) emissions. Mixing them in a single calculation field violates disclosure standards.
Fix: Maintain separate calculation pipelines. Location-based uses the deterministic registry. Market-based requires user-supplied contract data, EAC/REGO certificates, or supplier-specific factors. Never average them.
2. Relying on National Averages for Site-Specific Calculations
Explanation: National averages mask subregional generation mix variations. A data center in a hydro-heavy region will appear artificially carbon-intensive if calculated against a coal-heavy national average.
Fix: Always resolve to the most granular subregion available. Use EPA eGRID subregions for the US, NUTS-2/3 for Europe, and state/province codes elsewhere. Fall back to national only when subregional data is genuinely unavailable.
3. Silent NaN Propagation in Calculation Chains
Explanation: Missing registry keys or malformed inputs produce NaN. JavaScript arithmetic with NaN propagates silently, corrupting downstream aggregations and financial totals.
Fix: Validate inputs with Number.isFinite(). Return null on missing data. Force calling code to handle the null case before aggregation. Use TypeScript strict mode to catch unhandled nulls at compile time.
4. Omitting Source Citations from Output Payloads
Explanation: Displaying emission totals without source attribution violates audit requirements. Regulators will reject filings that cannot trace numbers back to authoritative publications.
Fix: Bundle citation, source, year, and dataVersion into every calculation result. Render citations inline in the UI, not hidden in tooltips or footnotes.
5. Hardcoding Version Numbers Without CI Validation
Explanation: Grid factors update annually. Hardcoding a version string without automated validation leads to stale data deployments. A mismatch between the registry and the declared version creates compliance drift.
Fix: Store the version as a constant. Add a CI step that compares the registry's internal year metadata against the declared version. Fail the build if they diverge. Log a warning on every page load if a mismatch is detected at runtime.
6. Ignoring Unit Conversion Standards
Explanation: Emission factors are typically in kg CO₂e/kWh, but regulatory filings often require tonnes (tCO₂e). Manual division introduces rounding errors and inconsistent precision across reports.
Fix: Standardize precision in the calculation layer. Use toFixed(4) for kg and toFixed(6) for tonnes. Parse back to numbers with parseFloat() to avoid string concatenation bugs in downstream math.
7. Assuming Grid Factors Are Static Post-Deployment
Explanation: IEA publishes in April, DEFRA in June, EPA eGRID in January. Factors change annually. Treating the registry as immutable leads to outdated disclosures.
Fix: Implement a versioned update workflow. Tag each registry entry with its publication year. Schedule quarterly reviews aligned with source publication calendars. Automate diff checks between old and new factor sets to highlight material changes (>5% variance).
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Startup MVP / Internal Dashboard | National Average Registry | Fastest implementation, covers 80% of use cases, low maintenance overhead | $0 (Zero dependencies) |
| Enterprise CSRD/SEC Filing | Subregional Deterministic Registry | Meets GHG Protocol granularity, eliminates audit risk, ensures reproducibility | $0 (Zero dependencies) |
| Real-Time IoT Metering | Edge-Injected Registry + Local Cache | Sub-millisecond resolution, works offline, prevents network bottlenecks | $0 (Zero dependencies) |
| Market-Based Contract Tracking | External API + Manual Override Layer | Requires dynamic contract data, PPAs, and EAC certificates not available in static grids | $50-$200/mo (API fees) |
Configuration Template
// carbon-registry.config.ts
export const GRID_DATA_VERSION = '2025.6';
export const GRID_INTENSITY_REGISTRY = {
// Add entries following the GridIntensityRecord interface
// Ensure source, year, and regionName are always present
} as const;
export const GRID_UPDATE_CALENDAR = {
IEA: 'April (Global Energy Review)',
DEFRA: 'June (GHG Conversion Factors)',
EPA: 'January (eGRID Summary Tables)'
} as const;
// CI Validation Hook (pseudo-code)
export function validateRegistryVersion(registry: typeof GRID_INTENSITY_REGISTRY, declaredVersion: string): boolean {
const years = new Set(Object.values(registry).map(r => r.year));
const hasMismatch = Array.from(years).some(y => y.toString() !== declaredVersion.split('.')[0]);
if (hasMismatch) {
console.error(`[Registry] Version mismatch detected. Declared: ${declaredVersion}, Found years: ${Array.from(years)}`);
}
return !hasMismatch;
}
Quick Start Guide
- Initialize the Registry: Copy the configuration template into your project. Populate the
GRID_INTENSITY_REGISTRY with your target regions using the official IEA, DEFRA, and EPA datasets.
- Inject State: Serialize the registry into your application's initial state. For web apps, inject it via a
<script> tag or build-time constant. For Node.js, import it directly.
- Run Resolution: Call
resolveGridIntensity(regionKey, registry) to fetch the factor. Handle null returns explicitly to prevent calculation corruption.
- Compute & Report: Pass consumption data and the resolved record into
computeLocationBasedEmissions(). Extract the citation and emissionsTonnes fields for display or API transmission.
- Validate Deployment: Run the CI version check before merging. Ensure the declared
GRID_DATA_VERSION matches the publication years in the registry. Deploy with zero network dependencies.