Skip to content

feat: add SCI report output method - #1361

Open
davidberenstein1957 wants to merge 1 commit into
masterfrom
feat/sci-report-output
Open

feat: add SCI report output method#1361
davidberenstein1957 wants to merge 1 commit into
masterfrom
feat/sci-report-output

Conversation

@davidberenstein1957

@davidberenstein1957 davidberenstein1957 commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Adds an SCI (Software Carbon Intensity, ISO/IEC 21031:2024) output method, following the same pattern as the existing BoAmps exporter.

What it adds

codecarbon/output_methods/sci.py, a single module holding the two declaration dataclasses, the pure mapping function, and the handler:

  • FunctionalUnit / EmbodiedDeclaration — the R and M terms, which only the user can supply.
  • map_emissions_to_sci(data, ...) — pure, no I/O. I is derived as emissions * 1000 / energy_consumed rather than recomputed, so the report agrees with the CSV by construction and inherits the cloud-region, PUE and country/region handling already applied upstream.
  • SCIOutput(BaseOutput)out() writes sci_report_<run_id>.json, task_out() writes sci_report_tasks_<run_id>.json with one entry per task, live_out() is a documented no-op because the report is final rather than incremental.
  • OutputMethod.SCI registered in _init_output_methods, reading an optional sci_context_file from the usual config hierarchy.

Two honesty rules, both tested:

  • No R declared → the report is still written, with sci: null and a status field explaining why, instead of dividing by zero or assuming R = 1.
  • No M declared → M_gCO2e: 0 with M_source: "not declared", so a reader can see the report covers only the operational half.

User-facing surface

from codecarbon import EmissionsTracker
from codecarbon.output_methods.sci import FunctionalUnit, SCIOutput

sci = SCIOutput(functional_unit=FunctionalUnit(name="inference request", count=10_000))
tracker = EmissionsTracker(output_handlers=[sci])

sci.set_functional_unit_count(n) for the common case where the count is only known at the end; SCIOutput.from_file("sci_context.json") for the declarative path; output_methods = csv,sci plus sci_context_file in .codecarbon.config.

Docs: a new SCI section in docs/reference/output.md (including a sample report), a row in docs/how-to/examples.md, and a runnable examples/sci_output.py. No new dependencies.

Verification

uv run pytest tests/test_sci_output.py -q — 14 tests covering the formula, provenance assembly, the undeclared-R and undeclared-M branches, zero-energy, the handler and its task path, and context-file loading (valid, missing, malformed). black --check and ruff check were run scoped to the touched files; the remaining ruff hits are the same Optional/List/dict() style the surrounding modules already use, so the new code matches its neighbours.

Deliberately left out

  • Any automatic embodied-carbon estimation: no EmbodiedProfile tracker parameter, no bundled hardware table, no cloud per-instance figures. CodeCarbon has no defensible manufacturing data, and an uncited M in a compliance report is worse than a blank one. M is user-declared or explicitly undeclared.
  • Any change to what emissions means. Existing consumers are untouched.
  • Multiple simultaneous functional units, and schema validation on write (there is no stable machine-readable SCI schema to validate against yet).

Closes #1353

🤖 Generated with Claude Code


Review follow-up: embodied carbon in task reports

task_out() passed the same declared M to every task, so a run with 5 tasks reported the device's full embodied carbon 5 times, and each per-task sci included 100% of it — a wrong number in a document framed as ISO/IEC 21031 output.

M is now apportioned by each task's share of the run duration. Chosen over omitting M from task reports because the split is exhaustive: R is already the run-level count, so with an apportioned M the per-task figures sum back to the run-level report instead of quietly dropping half the accounting. M_source records the share applied, e.g. "vendor LCA; apportioned by duration (25.0% of the run)".

Tested by test_task_out_apportions_embodied_by_duration (unequal durations: asserts the 10/30 split of a declared 40 gCO2e and that the shares sum back to 40), plus the undeclared-M and zero-duration branches.

Also documented in docs/reference/output.md that the configuration-only path (output_methods = csv,sci) can never produce a non-null sci unless functionalUnit.count is hardcoded in the context file, since set_functional_unit_count() needs a reference to a handler the tracker owns.

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.59%. Comparing base (3ec31a0) to head (9474fda).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1361      +/-   ##
==========================================
+ Coverage   91.43%   91.59%   +0.15%     
==========================================
  Files          49       50       +1     
  Lines        5057     5151      +94     
==========================================
+ Hits         4624     4718      +94     
  Misses        433      433              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@davidberenstein1957
davidberenstein1957 marked this pull request as ready for review August 12, 2026 19:14
@davidberenstein1957
davidberenstein1957 requested a review from a team as a code owner August 12, 2026 19:14
Emit an ISO/IEC 21031 Software Carbon Intensity report alongside the existing
output methods. E and I come from the measured run; R and M are user
declarations and are reported as undeclared rather than guessed, so a report
never invents numbers the run cannot support.

Embodied carbon (M) is apportioned across tasks by each task's share of the
run duration. Handing every task the same declared M made a 5-task run report
the device's full embodied carbon 5 times. The split is exhaustive: per-task
figures sum back to the run-level report, and `M_source` records the share
applied. Per-task SCI is reported as `sciShare`, not as a per-unit rate.

Failure handling is narrow rather than broad: an unreadable
`sci_context_file` degrades instead of failing the run, while unrelated
exceptions while writing a report are no longer swallowed. Context dataclasses
are built straight from the JSON keys, with the documented camelCase `gCO2e`
key normalised so existing context files keep working.

Docs cover the formula, the term declarations, the config keys and a sample
report, and note that `output_methods = csv,sci` alone can never produce a
non-null sci unless the context file hardcodes `functionalUnit.count`. The
constructor, `from_file` and context-file walkthrough live in
`examples/sci_output.py`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature proposal: SCI (ISO/IEC 21031) report output method

1 participant