enforces this separation at the type level, preventing accidental aggregation.
Step 3: Dual-Track Scope 2 with EAC and Residual Mix Tracking
The GHG Protocol and ESRS E1 require both location-based and market-based Scope 2 figures wherever data is available. Location-based uses average grid intensity. Market-based uses supplier-specific factors, Energy Attribute Certificates (EACs), or residual mix factors. A single scope2_total field destroys auditability. The schema stores both tracks independently, tracks EAC coverage percentages, and records the residual mix factor for uncovered consumption. This enables precise reconstruction during audit.
Step 4: Scope 3 Matrix with Mandatory/Optional Flags and Omission Logging
Scope 3 spans 15 value chain categories. Under CSRD, 12 are mandatory; 3 are optional unless material. The schema must track each category independently, record the calculation method (spend-based, activity-based, or hybrid), and maintain an explicit omission justification array. Auditors require documented reasons for any excluded category. The schema enforces this as a required field when a category is marked inactive.
Step 5: Ring-Fence Removals and Credits
Biological removals (forestry, soil sequestration) and engineered removals (DAC, BECCS) must never be netted against gross emissions in E1-6. They are disclosed separately in E1-7. Carbon credits are similarly isolated. The schema creates distinct ledger objects for removals and credits, enforces non-netting through type constraints, and tracks permanence risk notes for biological pathways. This prevents accidental offsetting of gross figures during report generation.
Step 6: Energy Mix and Intensity with Explicit Denominator Declaration
Energy consumption requires breakdown by source type (fossil, nuclear, renewable self-generated, renewable purchased, EAC-covered). Energy intensity is a calculated metric that requires an explicit denominator declaration. Intensity against revenue differs fundamentally from intensity against physical output. The schema stores the denominator unit and value alongside the intensity figure, ensuring regulatory transparency and preventing metric confusion.
Step 7: Target Schema with SBTi Validation and Offset Exclusion Flags
Climate targets must declare scope coverage, baseline year, target year, reduction percentage, and alignment framework. Science-Based Targets initiative (SBTi) standards explicitly prohibit counting offsets toward scope reduction targets. The schema includes a boolean flag to enforce this rule at the data level. It also tracks interim milestones and external validation status, enabling automated compliance checks during target lifecycle management.
Architecture Rationale
Each structural choice serves a specific compliance or engineering purpose:
- Explicit GWP context prevents historical recalculation debt and supports multi-framework reporting.
- Biogenic separation aligns with GHG Protocol accounting rules and prevents gross inflation.
- Dual Scope 2 tracking satisfies regulatory dual-disclosure requirements and enables EAC reconciliation.
- Scope 3 omission logging transforms audit requirements into schema constraints, eliminating manual justification tracking.
- Removal/credit ring-fencing enforces non-netting rules at the type level, preventing calculation errors during aggregation.
- Denominator declaration ensures intensity metrics are auditable and comparable across reporting periods.
- SBTi offset flags embed regulatory constraints directly into the data model, enabling automated validation.
Pitfall Guide
1. Netting Biological Removals Against Gross Emissions
Explanation: Developers often subtract removals from gross emissions to produce a "net" figure, then feed that into E1-6 fields. ESRS E1 requires gross figures to remain gross. Removals are disclosed separately in E1-7.
Fix: Enforce type-level separation. Create distinct GrossEmissionLedger and RemovalLedger objects. Never allow subtraction operations between them during aggregation. Validate at the schema layer.
2. Collapsing Scope 2 into a Single Metric
Explanation: Storing only location-based or market-based Scope 2 violates dual-disclosure requirements. Auditors require both figures with source attribution.
Fix: Implement parallel tracking fields for locationBased and marketBased. Store grid factor sources, EAC coverage percentages, and residual mix factors alongside each track. Validate that both are populated when data is available.
3. Ignoring the GWP Basis Shift (AR5 to AR6)
Explanation: Hardcoding a single GWP baseline forces mass recalculation when frameworks transition. CHβ and NβO values differ significantly between AR5 and AR6.
Fix: Attach a GwpContext object to every emission record. Store parallel values for AR5 and AR6. Declare the primary basis explicitly. Version the schema to support baseline transitions without data migration.
4. Omitting Scope 3 Categories Without Audit Trails
Explanation: Companies often skip optional or immaterial Scope 3 categories without documenting why. Auditors require explicit justification for every omission.
Fix: Include an omissionJustifications array in the Scope 3 schema. Require a reason string whenever a category is marked inactive. Validate that all mandatory categories are either populated or explicitly justified.
5. Hardcoding Energy Intensity Denominators
Explanation: Calculating intensity against revenue or physical output without declaring the denominator creates incomparable metrics. Regulators require explicit denominator disclosure.
Fix: Store denominatorUnit and denominatorValue alongside the intensity figure. Validate that the denominator matches the declared unit. Prevent implicit calculations that assume a default denominator.
6. Treating Carbon Credits as Emission Reductions
Explanation: Developers often apply credits to reduce gross emissions or scope targets. SBTi and ESRS E1 explicitly prohibit this. Credits are disclosure items, not reduction mechanisms.
Fix: Isolate credits in a CarbonCreditLedger object. Enforce a boolean flag excludedFromScopeTargets. Prevent any aggregation pipeline from subtracting credits from gross or target figures.
7. Missing SBTi Offset Exclusion Flags
Explanation: Targets that allow offsets violate SBTi criteria. Without explicit flags, compliance checks rely on manual review, increasing audit risk.
Fix: Add offsetsIncludedInTarget boolean to the target schema. Default to false. Validate against framework alignment rules during target creation. Block SBTi-aligned targets from enabling offset inclusion.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Early-stage startup with limited Scope 3 data | Granular schema with optional category flags | Prevents future schema migration; supports phased data collection | Low initial overhead, high long-term savings |
| Enterprise with AR5 legacy data | Parallel AR5/AR6 storage with explicit basis declaration | Avoids historical recalculation; supports multi-framework reporting | Moderate storage cost, eliminates migration debt |
| High Scope 3 complexity (manufacturing, retail) | Full 15-category matrix with method tracking | Enables audit traceability; supports spend/activity/hybrid calculations | Higher ingestion cost, reduces audit remediation |
| SBTi-aligned target management | Strict offset exclusion flags with validation pipeline | Ensures framework compliance; prevents target invalidation | Low implementation cost, high compliance assurance |
| Multi-jurisdictional reporting | GWP context + methodology versioning per record | Supports regional framework differences; enables automated disclosure routing | Moderate schema complexity, eliminates manual reconciliation |
Configuration Template
// Climate Disclosure Schema v1.0 β ESRS E1 Compliant
// TypeScript strict mode recommended
type GwpBasis = 'AR5' | 'AR6';
type CalculationMethod = 'spend' | 'activity' | 'hybrid';
type FrameworkAlignment = 'SBTi' | 'Paris_1.5C' | 'Paris_2C' | 'NetZero' | 'Custom';
interface GwpContext {
basis: GwpBasis;
primaryBasis: GwpBasis;
valueAr5: number;
valueAr6: number;
}
interface Scope1Breakdown {
stationaryCombustion: number;
mobileCombustion: number;
processEmissions: number;
fugitiveEmissions: number;
}
interface Scope1Record {
grossTco2e: number;
gwp: GwpContext;
biogenicCo2Tco2e: number; // Explicitly out-of-scope
includesBiogenicInGross: false; // Schema constraint
breakdown: Scope1Breakdown;
methodology: string;
auditTimestamp: string;
}
interface Scope2Record {
locationBasedTco2e: number;
marketBasedTco2e: number;
gwp: GwpContext;
gridFactorSource: string;
gridFactorCountry: string;
eacCoveragePercent: number;
residualMixFactor: number;
auditTimestamp: string;
}
interface Scope3Category {
tco2e: number;
method: CalculationMethod;
requiredCsrd: boolean;
isActive: boolean;
}
interface OmissionJustification {
category: string;
reason: string;
materialityAssessmentDate: string;
}
interface Scope3Record {
totalTco2e: number;
gwp: GwpContext;
categories: Record<string, Scope3Category>;
omissionJustifications: OmissionJustification[];
auditTimestamp: string;
}
interface RemovalLedger {
totalTco2eRemoved: number;
breakdown: {
forestryAfforestation: number;
forestryReforestation: number;
soilSequestration: number;
blueCarbon: number;
directAirCapture: number;
bioenergyCcs: number;
};
permanenceRiskNote: string;
nettedAgainstGross: false; // Schema constraint
auditTimestamp: string;
}
interface CarbonCreditLedger {
totalCreditsUsed: number;
standard: string;
vintageYear: number;
projectIds: string[];
usagePurpose: 'netZeroClaim' | 'carbonNeutralClaim' | 'offsettingOnly';
excludedFromScopeTargets: boolean;
auditTimestamp: string;
}
interface EnergyProfile {
totalConsumptionMwh: number;
breakdown: {
fossilFuelMwh: number;
nuclearMwh: number;
renewableSelfGeneratedMwh: number;
renewablePurchasedMwh: number;
renewableEacCoveredMwh: number;
};
renewableSharePercent: number;
energyIntensity: {
value: number;
denominatorUnit: string;
denominatorValue: number;
};
auditTimestamp: string;
}
interface ClimateTarget {
id: string;
scopeCoverage: ('scope1' | 'scope2' | 'scope3')[];
targetType: 'absolute' | 'intensity';
baselineYear: number;
baselineTco2e: number;
targetYear: number;
targetReductionPercent: number;
alignedFramework: FrameworkAlignment;
validatedBy: string | null;
interimMilestones: Array<{ year: number; reductionPercent: number }>;
offsetsIncludedInTarget: boolean; // Must be false for SBTi
auditTimestamp: string;
}
interface EsrsE1Disclosure {
reportingPeriod: string;
entityIdentifier: string;
scope1: Scope1Record;
scope2: Scope2Record;
scope3: Scope3Record;
removals: RemovalLedger;
carbonCredits: CarbonCreditLedger;
energy: EnergyProfile;
targets: ClimateTarget[];
schemaVersion: string;
complianceChecksum: string;
}
Quick Start Guide
- Initialize the schema: Copy the TypeScript interfaces into your project. Enable strict type checking and schema validation (e.g., Zod or TypeBox) to enforce constraints at ingestion.
- Inject GWP context: Attach a
GwpContext object to every emission record during data collection. Store parallel AR5/AR6 values and declare the primary basis explicitly.
- Wire dual-track Scope 2: Configure your ingestion pipeline to capture location-based and market-based figures separately. Populate EAC coverage and residual mix fields alongside each track.
- Validate against E1 checklist: Run automated schema validation before aggregation. Verify biogenic isolation, Scope 3 omission logging, removal ring-fencing, and SBTi offset flags. Flag violations at the pipeline level, not during report generation.