Skip to content

Commit b5ea5f5

Browse files
committed
feat: add BoAmps as output method
1 parent b335365 commit b5ea5f5

11 files changed

Lines changed: 2281 additions & 0 deletions

File tree

codecarbon/output.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@
2525

2626
# output is sent to metrics
2727
from codecarbon.output_methods.metrics.prometheus import PrometheusOutput # noqa: F401
28+
29+
# Output to BoAmps format
30+
from codecarbon.output_methods.boamps import BoAmpsOutput # noqa: F401
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""
2+
BoAmps output support for CodeCarbon.
3+
4+
Provides first-class support for generating BoAmps (Boavizta) standardized
5+
JSON reports from CodeCarbon emission tracking data.
6+
"""
7+
8+
from codecarbon.output_methods.boamps.models import ( # noqa: F401
9+
BoAmpsAlgorithm,
10+
BoAmpsDataset,
11+
BoAmpsEnvironment,
12+
BoAmpsHardware,
13+
BoAmpsHeader,
14+
BoAmpsInfrastructure,
15+
BoAmpsMeasure,
16+
BoAmpsPublisher,
17+
BoAmpsReport,
18+
BoAmpsSoftware,
19+
BoAmpsSystem,
20+
BoAmpsTask,
21+
)
22+
from codecarbon.output_methods.boamps.mapper import map_emissions_to_boamps # noqa: F401
23+
from codecarbon.output_methods.boamps.output import BoAmpsOutput # noqa: F401
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
"""
2+
Maps CodeCarbon EmissionsData to BoAmps report format.
3+
"""
4+
5+
import warnings
6+
from typing import Optional
7+
8+
from codecarbon.output_methods.boamps.models import (
9+
BoAmpsEnvironment,
10+
BoAmpsHeader,
11+
BoAmpsHardware,
12+
BoAmpsInfrastructure,
13+
BoAmpsMeasure,
14+
BoAmpsReport,
15+
BoAmpsSoftware,
16+
BoAmpsSystem,
17+
BoAmpsTask,
18+
)
19+
from codecarbon.output_methods.emissions_data import EmissionsData
20+
21+
BOAMPS_FORMAT_VERSION = "0.1"
22+
BOAMPS_FORMAT_SPEC_URI = (
23+
"https://github.com/Boavizta/BoAmps/tree/main/model"
24+
)
25+
26+
27+
def map_emissions_to_boamps(
28+
emissions: EmissionsData,
29+
task: Optional[BoAmpsTask] = None,
30+
header: Optional[BoAmpsHeader] = None,
31+
quality: Optional[str] = None,
32+
infra_overrides: Optional[dict] = None,
33+
environment_overrides: Optional[dict] = None,
34+
) -> BoAmpsReport:
35+
"""
36+
Map CodeCarbon EmissionsData to a BoAmps report.
37+
38+
Auto-fills fields from EmissionsData and merges with user-provided context.
39+
User-provided values take precedence over auto-detected values.
40+
41+
Args:
42+
emissions: CodeCarbon emissions data from a completed run.
43+
task: User-provided task context (required for schema-valid BoAmps).
44+
header: User-provided header overrides.
45+
quality: Quality assessment ("high", "medium", "low").
46+
infra_overrides: Additional infrastructure fields (cloud_instance, cloud_service).
47+
environment_overrides: Additional environment fields (power_source, etc.).
48+
49+
Returns:
50+
A BoAmpsReport populated with auto-detected and user-provided data.
51+
"""
52+
report_header = _build_header(emissions, header)
53+
measures = [_build_measure(emissions)]
54+
system = _build_system(emissions)
55+
software = _build_software(emissions)
56+
infrastructure = _build_infrastructure(emissions, infra_overrides)
57+
environment = _build_environment(emissions, environment_overrides)
58+
59+
if task is None:
60+
warnings.warn(
61+
"No BoAmps task context provided. The output will be missing required "
62+
"fields (taskStage, taskFamily, algorithms, dataset) and will not "
63+
"validate against the BoAmps schema.",
64+
UserWarning,
65+
stacklevel=2,
66+
)
67+
68+
return BoAmpsReport(
69+
header=report_header,
70+
task=task,
71+
measures=measures,
72+
system=system,
73+
software=software,
74+
infrastructure=infrastructure,
75+
environment=environment,
76+
quality=quality,
77+
)
78+
79+
80+
def _build_header(
81+
emissions: EmissionsData, user_header: Optional[BoAmpsHeader]
82+
) -> BoAmpsHeader:
83+
"""Build header from EmissionsData, merging with user overrides."""
84+
auto_header = BoAmpsHeader(
85+
format_version=BOAMPS_FORMAT_VERSION,
86+
format_version_specification_uri=BOAMPS_FORMAT_SPEC_URI,
87+
report_id=emissions.run_id,
88+
report_datetime=emissions.timestamp,
89+
)
90+
91+
if user_header is None:
92+
return auto_header
93+
94+
# User values override auto-detected values
95+
return BoAmpsHeader(
96+
licensing=user_header.licensing or auto_header.licensing,
97+
format_version=user_header.format_version or auto_header.format_version,
98+
format_version_specification_uri=(
99+
user_header.format_version_specification_uri
100+
or auto_header.format_version_specification_uri
101+
),
102+
report_id=user_header.report_id or auto_header.report_id,
103+
report_datetime=user_header.report_datetime or auto_header.report_datetime,
104+
report_status=user_header.report_status or auto_header.report_status,
105+
publisher=user_header.publisher or auto_header.publisher,
106+
)
107+
108+
109+
def _build_measure(emissions: EmissionsData) -> BoAmpsMeasure:
110+
"""Build a BoAmps measure from EmissionsData."""
111+
measure = BoAmpsMeasure(
112+
measurement_method="codecarbon",
113+
version=emissions.codecarbon_version,
114+
power_consumption=emissions.energy_consumed,
115+
measurement_duration=emissions.duration,
116+
measurement_date_time=emissions.timestamp,
117+
cpu_tracking_mode=emissions.tracking_mode,
118+
)
119+
120+
# CPU utilization as fraction (0-1)
121+
if emissions.cpu_utilization_percent > 0:
122+
measure.average_utilization_cpu = round(
123+
emissions.cpu_utilization_percent / 100.0, 4
124+
)
125+
126+
# GPU fields only if GPU is present
127+
if emissions.gpu_count and emissions.gpu_count > 0:
128+
measure.gpu_tracking_mode = emissions.tracking_mode
129+
if emissions.gpu_utilization_percent > 0:
130+
measure.average_utilization_gpu = round(
131+
emissions.gpu_utilization_percent / 100.0, 4
132+
)
133+
134+
return measure
135+
136+
137+
def _build_system(emissions: EmissionsData) -> BoAmpsSystem:
138+
"""Build system info from EmissionsData."""
139+
return BoAmpsSystem(os=emissions.os)
140+
141+
142+
def _build_software(emissions: EmissionsData) -> BoAmpsSoftware:
143+
"""Build software info from EmissionsData."""
144+
return BoAmpsSoftware(
145+
language="python",
146+
version=emissions.python_version,
147+
)
148+
149+
150+
def _build_infrastructure(
151+
emissions: EmissionsData, overrides: Optional[dict] = None
152+
) -> BoAmpsInfrastructure:
153+
"""Build infrastructure from EmissionsData hardware fields."""
154+
components = []
155+
156+
# CPU component (always present)
157+
cpu_component = BoAmpsHardware(
158+
component_type="cpu",
159+
component_name=emissions.cpu_model,
160+
nb_component=int(emissions.cpu_count) if emissions.cpu_count else 1,
161+
)
162+
components.append(cpu_component)
163+
164+
# GPU component (only if present)
165+
if emissions.gpu_count and emissions.gpu_count > 0:
166+
gpu_component = BoAmpsHardware(
167+
component_type="gpu",
168+
component_name=emissions.gpu_model if emissions.gpu_model else None,
169+
nb_component=int(emissions.gpu_count),
170+
)
171+
components.append(gpu_component)
172+
173+
# RAM component (always present)
174+
ram_component = BoAmpsHardware(
175+
component_type="ram",
176+
nb_component=1,
177+
memory_size=emissions.ram_total_size,
178+
)
179+
components.append(ram_component)
180+
181+
is_cloud = emissions.on_cloud == "Y"
182+
infra = BoAmpsInfrastructure(
183+
infra_type="publicCloud" if is_cloud else "onPremise",
184+
cloud_provider=emissions.cloud_provider if is_cloud and emissions.cloud_provider else None,
185+
components=components,
186+
)
187+
188+
# Apply overrides from context file
189+
if overrides:
190+
for attr in ("cloud_instance", "cloud_service", "infra_type"):
191+
if attr in overrides:
192+
setattr(infra, attr, overrides[attr])
193+
194+
return infra
195+
196+
197+
def _build_environment(
198+
emissions: EmissionsData, overrides: Optional[dict] = None
199+
) -> BoAmpsEnvironment:
200+
"""Build environment from EmissionsData location fields."""
201+
env = BoAmpsEnvironment(
202+
country=emissions.country_name or None,
203+
latitude=emissions.latitude or None,
204+
longitude=emissions.longitude or None,
205+
)
206+
207+
if overrides:
208+
for attr in ("location", "power_supplier_type", "power_source", "power_source_carbon_intensity"):
209+
if attr in overrides:
210+
setattr(env, attr, overrides[attr])
211+
212+
return env

0 commit comments

Comments
 (0)