Early Access - The Dcycle CLI is currently available for enterprise customers.
Contact us to learn more about access.
Multi-Organization Consolidated Reporting
For holding companies and enterprise groups, this guide shows how to automate consolidated sustainability reporting across multiple organizations.Use Case
Your company structure:Copy
Acme Holding Corp
├── Acme Spain (manufacturing)
├── Acme France (manufacturing)
├── Acme UK (sales)
└── Acme Logistics (transport)
- Generate a single consolidated report
- Compare performance across subsidiaries
- Track group-wide reduction targets
Quick Start: Export All Organizations
Copy
#!/bin/bash
# export_all_orgs.sh - Export data from all organizations
YEAR=${1:-2024}
OUTPUT_DIR="reports/$YEAR"
mkdir -p "$OUTPUT_DIR"
echo "📊 Exporting data for all organizations ($YEAR)..."
# Get list of all organizations
ORG_IDS=$(dc org list --format json | jq -r '.[].id')
for org_id in $ORG_IDS; do
# Get organization name (sanitized for filename)
org_name=$(dc org show $org_id --format json | jq -r '.name' | tr ' /' '_')
echo " Exporting: $org_name"
# Set organization context
dc org set $org_id
# Export key data
dc facility list --format json > "$OUTPUT_DIR/${org_name}_facilities.json"
dc vehicle list --format json > "$OUTPUT_DIR/${org_name}_vehicles.json"
echo " ✓ Exported to $OUTPUT_DIR/${org_name}_*.json"
done
echo "✅ Export complete: $OUTPUT_DIR/"
Consolidated Emissions Report
Generate a single report with emissions from all subsidiaries:Copy
#!/bin/bash
# consolidated_emissions.sh
YEAR=${1:-2024}
OUTPUT_FILE="reports/consolidated_emissions_$YEAR.json"
echo "📊 Generating consolidated emissions report for $YEAR..."
# Start JSON array
echo "[" > "$OUTPUT_FILE"
first=true
for org_id in $(dc org list --format json | jq -r '.[].id'); do
# Set context
dc org set $org_id > /dev/null
# Get org details
org_info=$(dc org show $org_id --format json)
org_name=$(echo $org_info | jq -r '.name')
org_country=$(echo $org_info | jq -r '.country')
# Get emissions (assuming this command exists)
# Adapt to actual CLI commands available
emissions=$(dc emissions summary --year $YEAR --format json 2>/dev/null || echo '{}')
# Add comma separator
if [ "$first" = true ]; then
first=false
else
echo "," >> "$OUTPUT_FILE"
fi
# Write organization data
cat >> "$OUTPUT_FILE" << EOF
{
"organization_id": "$org_id",
"name": "$org_name",
"country": "$org_country",
"year": $YEAR,
"emissions": $emissions
}
EOF
echo " ✓ $org_name"
done
# Close JSON array
echo "]" >> "$OUTPUT_FILE"
echo "✅ Report saved to: $OUTPUT_FILE"
Comparison Report
Compare performance across subsidiaries:Copy
#!/bin/bash
# compare_subsidiaries.sh
YEAR=${1:-2024}
echo "═══════════════════════════════════════════════════════════════════"
echo " SUSTAINABILITY PERFORMANCE BY SUBSIDIARY - $YEAR"
echo "═══════════════════════════════════════════════════════════════════"
echo ""
printf "%-25s %10s %10s %10s %12s\n" "Organization" "Scope 1" "Scope 2" "Scope 3" "Total"
printf "%-25s %10s %10s %10s %12s\n" "────────────" "───────" "───────" "───────" "─────"
TOTAL_S1=0
TOTAL_S2=0
TOTAL_S3=0
TOTAL_ALL=0
for org_id in $(dc org list --format json | jq -r '.[].id'); do
dc org set $org_id > /dev/null
name=$(dc org show $org_id --format json | jq -r '.name' | cut -c1-25)
# Get emissions by scope
# Adapt these commands to your actual CLI
scope1=$(dc vehicle list --format json | jq '[.[].emissions_tco2e // 0] | add // 0')
scope2=$(dc invoice list --year $YEAR --format json 2>/dev/null | jq '[.[].emissions_tco2e // 0] | add // 0' || echo "0")
scope3=$(dc purchase list --year $YEAR --format json 2>/dev/null | jq '[.[].emissions_tco2e // 0] | add // 0' || echo "0")
total=$(echo "$scope1 + $scope2 + $scope3" | bc)
printf "%-25s %10.1f %10.1f %10.1f %12.1f\n" "$name" "$scope1" "$scope2" "$scope3" "$total"
TOTAL_S1=$(echo "$TOTAL_S1 + $scope1" | bc)
TOTAL_S2=$(echo "$TOTAL_S2 + $scope2" | bc)
TOTAL_S3=$(echo "$TOTAL_S3 + $scope3" | bc)
TOTAL_ALL=$(echo "$TOTAL_ALL + $total" | bc)
done
echo "────────────────────────────────────────────────────────────────────"
printf "%-25s %10.1f %10.1f %10.1f %12.1f\n" "GROUP TOTAL" "$TOTAL_S1" "$TOTAL_S2" "$TOTAL_S3" "$TOTAL_ALL"
echo ""
echo "All values in tCO2e"
Copy
═══════════════════════════════════════════════════════════════════
SUSTAINABILITY PERFORMANCE BY SUBSIDIARY - 2024
═══════════════════════════════════════════════════════════════════
Organization Scope 1 Scope 2 Scope 3 Total
──────────── ─────── ─────── ─────── ─────
Acme Spain 456.2 234.5 678.9 1369.6
Acme France 234.1 189.3 456.7 880.1
Acme UK 98.4 123.4 234.5 456.3
Acme Logistics 987.6 145.2 567.8 1700.6
────────────────────────────────────────────────────────────────────
GROUP TOTAL 1776.3 692.4 1937.9 4406.6
All values in tCO2e
Python Consolidation Script
For more complex reporting needs:Copy
#!/usr/bin/env python3
# consolidate_report.py
import json
import subprocess
from dataclasses import dataclass
from typing import Optional
@dataclass
class OrgEmissions:
org_id: str
name: str
country: str
scope_1: float = 0
scope_2: float = 0
scope_3: float = 0
@property
def total(self) -> float:
return self.scope_1 + self.scope_2 + self.scope_3
def run_dc(*args) -> dict:
"""Run dc command and return JSON output"""
result = subprocess.run(
['dc'] + list(args) + ['--format', 'json'],
capture_output=True,
text=True
)
if result.returncode != 0:
return {}
return json.loads(result.stdout) if result.stdout else {}
def get_all_organizations() -> list[dict]:
"""Get list of all accessible organizations"""
return run_dc('org', 'list')
def set_organization(org_id: str):
"""Set active organization context"""
subprocess.run(['dc', 'org', 'set', org_id], capture_output=True)
def get_org_emissions(org_id: str, year: int) -> OrgEmissions:
"""Get emissions for a single organization"""
set_organization(org_id)
# Get org info
org_info = run_dc('org', 'show', org_id)
emissions = OrgEmissions(
org_id=org_id,
name=org_info.get('name', 'Unknown'),
country=org_info.get('country', '??')
)
# Get scope 1 (vehicles)
vehicles = run_dc('vehicle', 'list')
if isinstance(vehicles, list):
emissions.scope_1 = sum(v.get('emissions_tco2e', 0) for v in vehicles)
# Get scope 2 (invoices) - adapt to actual commands
# emissions.scope_2 = ...
# Get scope 3 (purchases, logistics) - adapt to actual commands
# emissions.scope_3 = ...
return emissions
def generate_consolidated_report(year: int) -> dict:
"""Generate consolidated emissions report"""
orgs = get_all_organizations()
all_emissions = []
for org in orgs:
emissions = get_org_emissions(org['id'], year)
all_emissions.append(emissions)
# Calculate totals
total_s1 = sum(e.scope_1 for e in all_emissions)
total_s2 = sum(e.scope_2 for e in all_emissions)
total_s3 = sum(e.scope_3 for e in all_emissions)
return {
'year': year,
'generated_at': datetime.now().isoformat(),
'organizations': [
{
'id': e.org_id,
'name': e.name,
'country': e.country,
'scope_1_tco2e': e.scope_1,
'scope_2_tco2e': e.scope_2,
'scope_3_tco2e': e.scope_3,
'total_tco2e': e.total,
}
for e in all_emissions
],
'group_totals': {
'scope_1_tco2e': total_s1,
'scope_2_tco2e': total_s2,
'scope_3_tco2e': total_s3,
'total_tco2e': total_s1 + total_s2 + total_s3,
}
}
if __name__ == '__main__':
import sys
from datetime import datetime
year = int(sys.argv[1]) if len(sys.argv) > 1 else 2024
report = generate_consolidated_report(year)
print(json.dumps(report, indent=2))
Year-over-Year Tracking
Track progress against reduction targets:Copy
#!/bin/bash
# yoy_tracking.sh
CURRENT_YEAR=2024
BASELINE_YEAR=2020
TARGET_REDUCTION=50 # percent by 2030
echo "═══════════════════════════════════════════════════════════════════"
echo " YEAR-OVER-YEAR PROGRESS TRACKING"
echo " Baseline: $BASELINE_YEAR | Current: $CURRENT_YEAR | Target: -${TARGET_REDUCTION}% by 2030"
echo "═══════════════════════════════════════════════════════════════════"
echo ""
printf "%-20s %12s %12s %10s %10s\n" "Organization" "$BASELINE_YEAR" "$CURRENT_YEAR" "Change" "On Track?"
printf "%-20s %12s %12s %10s %10s\n" "────────────" "────────" "────────" "──────" "─────────"
for org_id in $(dc org list --format json | jq -r '.[].id'); do
dc org set $org_id > /dev/null
name=$(dc org show $org_id --format json | jq -r '.name' | cut -c1-20)
# Get emissions for both years (adapt to actual commands)
baseline=1000 # placeholder - replace with actual query
current=800 # placeholder - replace with actual query
if [ "$baseline" -gt 0 ]; then
change=$(echo "scale=1; (($current - $baseline) / $baseline) * 100" | bc)
# Required rate to hit target by 2030
years_remaining=$((2030 - CURRENT_YEAR))
years_elapsed=$((CURRENT_YEAR - BASELINE_YEAR))
required_rate=$(echo "scale=1; $TARGET_REDUCTION / (2030 - $BASELINE_YEAR) * $years_elapsed" | bc)
actual_reduction=$(echo "scale=1; -1 * $change" | bc)
if (( $(echo "$actual_reduction >= $required_rate" | bc -l) )); then
status="✅ Yes"
else
status="⚠️ Behind"
fi
printf "%-20s %12.1f %12.1f %9.1f%% %10s\n" "$name" "$baseline" "$current" "$change" "$status"
fi
done
Automated Monthly Report
Schedule a monthly consolidated report:Copy
# .github/workflows/monthly-consolidated-report.yml
name: Monthly Consolidated Report
on:
schedule:
- cron: '0 7 1 * *' # 1st of each month at 7 AM
jobs:
generate-report:
runs-on: ubuntu-latest
env:
DCYCLE_API_KEY: ${{ secrets.DCYCLE_API_KEY }}
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: pip install dcycle-cli
- name: Generate consolidated report
run: |
python scripts/consolidate_report.py $(date +%Y) > report.json
- name: Upload report artifact
uses: actions/upload-artifact@v4
with:
name: consolidated-report-${{ github.run_number }}
path: report.json
- name: Send to stakeholders
run: |
# Send via email, Slack, or upload to shared drive
curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-H 'Content-Type: application/json' \
-d @- << EOF
{
"text": "📊 Monthly consolidated sustainability report is ready",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Monthly Consolidated Report*\nGenerated: $(date)\nDownload from: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
}
]
}
EOF
Best Practices
Consistent Data Periods
Ensure all subsidiaries report for the same time period to enable accurate consolidation.
Currency Normalization
If using spend-based factors, normalize currencies before consolidating.
Boundary Definitions
Document which entities are included/excluded from consolidated reports.
Intercompany Elimination
Avoid double-counting internal logistics or shared services.

