Skip to content

Commit cb498c5

Browse files
SaboniAminebenoit-cty
authored andcommitted
feat: add BoAmps as output method
1 parent dfbd45a commit cb498c5

11 files changed

Lines changed: 2283 additions & 0 deletions

File tree

codecarbon/output.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
from codecarbon.output_methods.base_output import BaseOutput # noqa: F401
66

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

0 commit comments

Comments
 (0)