auraagent 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. auraagent-0.1.0/.github/workflows/publish.yml +36 -0
  2. auraagent-0.1.0/.gitignore +47 -0
  3. auraagent-0.1.0/LICENSE +21 -0
  4. auraagent-0.1.0/PKG-INFO +140 -0
  5. auraagent-0.1.0/README.md +70 -0
  6. auraagent-0.1.0/aura/__init__.py +30 -0
  7. auraagent-0.1.0/aura/carbon/__init__.py +33 -0
  8. auraagent-0.1.0/aura/carbon/decorators.py +54 -0
  9. auraagent-0.1.0/aura/carbon/output.py +129 -0
  10. auraagent-0.1.0/aura/carbon/tracker.py +158 -0
  11. auraagent-0.1.0/aura/cli.py +73 -0
  12. auraagent-0.1.0/aura/core/__init__.py +20 -0
  13. auraagent-0.1.0/aura/core/auth.py +164 -0
  14. auraagent-0.1.0/aura/core/config.py +120 -0
  15. auraagent-0.1.0/aura/core/constants.py +19 -0
  16. auraagent-0.1.0/aura/core/exceptions.py +24 -0
  17. auraagent-0.1.0/aura/core/models.py +184 -0
  18. auraagent-0.1.0/aura/web/__init__.py +22 -0
  19. auraagent-0.1.0/aura/web/app.py +73 -0
  20. auraagent-0.1.0/aura/web/main.py +49 -0
  21. auraagent-0.1.0/aura/web/routers/__init__.py +1 -0
  22. auraagent-0.1.0/aura/web/routers/auth.py +71 -0
  23. auraagent-0.1.0/aura/web/routers/config.py +48 -0
  24. auraagent-0.1.0/aura/web/routers/tracker.py +71 -0
  25. auraagent-0.1.0/aura/web/services/__init__.py +1 -0
  26. auraagent-0.1.0/aura/web/services/tracker_service.py +88 -0
  27. auraagent-0.1.0/aura/web/templates/base.html +99 -0
  28. auraagent-0.1.0/aura/web/templates/dashboard.html +136 -0
  29. auraagent-0.1.0/aura/web/templates/login.html +92 -0
  30. auraagent-0.1.0/aura/web/templates/settings.html +60 -0
  31. auraagent-0.1.0/installer/build.py +58 -0
  32. auraagent-0.1.0/installer/install.py +152 -0
  33. auraagent-0.1.0/pyproject.toml +105 -0
  34. auraagent-0.1.0/tests/__init__.py +0 -0
  35. auraagent-0.1.0/tests/test_carbon/__init__.py +0 -0
  36. auraagent-0.1.0/tests/test_carbon/test_tracker.py +48 -0
  37. auraagent-0.1.0/tests/test_web/__init__.py +0 -0
@@ -0,0 +1,36 @@
1
+ name: Publish to GitHub Packages
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ packages: write
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.11"
22
+
23
+ - name: Install build tools
24
+ run: pip install hatchling build twine
25
+
26
+ - name: Build package
27
+ run: python -m build
28
+
29
+ - name: Publish to GitHub Packages (PyPI)
30
+ env:
31
+ TWINE_USERNAME: __token__
32
+ TWINE_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
33
+ run: |
34
+ twine upload \
35
+ --repository-url https://pypi.pkg.github.com/tombouVincentA/ \
36
+ dist/*
@@ -0,0 +1,47 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .eggs/
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # Tests & coverage
17
+ .coverage
18
+ .coverage.*
19
+ htmlcov/
20
+ .pytest_cache/
21
+ .mypy_cache/
22
+ .ruff_cache/
23
+
24
+ # Aura config locale (contient des tokens — NE JAMAIS COMMITER)
25
+ .aura.config
26
+ *.aura.config
27
+ .env
28
+ .env.*
29
+
30
+ # PyInstaller
31
+ installer/dist/
32
+ installer/build/
33
+ installer/*.spec
34
+
35
+ # IDE
36
+ .vscode/
37
+ .idea/
38
+ *.swp
39
+ *.swo
40
+
41
+ # OS
42
+ .DS_Store
43
+ Thumbs.db
44
+
45
+ # Logs
46
+ *.log
47
+ logs/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CEVIA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: auraagent
3
+ Version: 0.1.0
4
+ Summary: Aura — Carbon tracking and monitoring toolkit
5
+ Project-URL: Homepage, https://cevia.io
6
+ Project-URL: Repository, https://github.com/tombouVincentA/auraagent
7
+ Project-URL: Documentation, https://docs.cevia.io/aura
8
+ Project-URL: Issues, https://github.com/tombouVincentA/auraagent/issues
9
+ Author-email: CEVIA <info@cevia.ai>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2025 CEVIA
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: ai,carbon,emissions,sustainability,tracking
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Intended Audience :: Science/Research
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.9
39
+ Classifier: Programming Language :: Python :: 3.10
40
+ Classifier: Programming Language :: Python :: 3.11
41
+ Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
43
+ Classifier: Topic :: System :: Monitoring
44
+ Requires-Python: >=3.9
45
+ Requires-Dist: click>=8.1.0
46
+ Requires-Dist: codecarbon>=2.4.0
47
+ Requires-Dist: pydantic>=2.0.0
48
+ Requires-Dist: python-dotenv>=1.0.0
49
+ Requires-Dist: requests>=2.31.0
50
+ Requires-Dist: rich>=13.0.0
51
+ Provides-Extra: all
52
+ Requires-Dist: aura[installer,web]; extra == 'all'
53
+ Provides-Extra: dev
54
+ Requires-Dist: httpx>=0.27.0; extra == 'dev'
55
+ Requires-Dist: mypy>=1.9.0; extra == 'dev'
56
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
57
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
58
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
59
+ Requires-Dist: ruff>=0.3.0; extra == 'dev'
60
+ Provides-Extra: installer
61
+ Requires-Dist: pyinstaller>=6.0.0; extra == 'installer'
62
+ Provides-Extra: web
63
+ Requires-Dist: fastapi>=0.110.0; extra == 'web'
64
+ Requires-Dist: httpx>=0.27.0; extra == 'web'
65
+ Requires-Dist: jinja2>=3.1.0; extra == 'web'
66
+ Requires-Dist: python-multipart>=0.0.9; extra == 'web'
67
+ Requires-Dist: uvicorn[standard]>=0.27.0; extra == 'web'
68
+ Requires-Dist: websockets>=12.0; extra == 'web'
69
+ Description-Content-Type: text/markdown
70
+
71
+ # Aura
72
+
73
+ Carbon tracking and monitoring toolkit for AI engineers.
74
+
75
+ ## Composants
76
+
77
+ | Composant | Description |
78
+ |-----------|-------------|
79
+ | `aura.carbon` | Mesure d'émissions CO2 (wrapper CodeCarbon) |
80
+ | `aura.web` | Interface web locale (FastAPI) |
81
+ | `aura.core` | Utilitaires partagés (config, auth, models) |
82
+
83
+ ## Installation
84
+
85
+ ```bash
86
+ # Depuis GitHub Packages
87
+ pip install aura --index-url https://pip.pkg.github.com/cevia
88
+
89
+ # Avec le composant web
90
+ pip install "aura[web]" --index-url https://pip.pkg.github.com/cevia
91
+ ```
92
+
93
+ ## Usage rapide
94
+
95
+ ```python
96
+ from aura.carbon import AuraCarbon
97
+
98
+ # Context manager
99
+ with AuraCarbon() as tracker:
100
+ train_model()
101
+
102
+ # Decorator
103
+ from aura.carbon import track_emissions
104
+
105
+ @track_emissions()
106
+ def train_model():
107
+ pass
108
+ ```
109
+
110
+ ## Interface web locale
111
+
112
+ ```bash
113
+ aura-web
114
+ # Ouvre http://localhost:8765 dans le navigateur
115
+ ```
116
+
117
+ ## Structure
118
+
119
+ ```
120
+ aura/
121
+ ├── carbon/ # Mesure CO2
122
+ │ ├── tracker.py # AuraCarbon (wrapper CodeCarbon)
123
+ │ ├── output.py # AuraOutput (envoi vers serveur)
124
+ │ └── decorators.py
125
+ ├── web/ # Interface locale
126
+ │ ├── main.py # Point d'entrée (FastAPI + navigateur)
127
+ │ ├── app.py # Application FastAPI
128
+ │ ├── routers/ # Endpoints
129
+ │ ├── services/ # Logique métier
130
+ │ └── templates/ # HTML
131
+ └── core/ # Partagé
132
+ ├── config.py # ~/.aura.config
133
+ ├── auth.py # Auth serveur
134
+ ├── models.py # Modèles Pydantic
135
+ └── exceptions.py
136
+ ```
137
+
138
+ ## Licence
139
+
140
+ MIT — CEVIA
@@ -0,0 +1,70 @@
1
+ # Aura
2
+
3
+ Carbon tracking and monitoring toolkit for AI engineers.
4
+
5
+ ## Composants
6
+
7
+ | Composant | Description |
8
+ |-----------|-------------|
9
+ | `aura.carbon` | Mesure d'émissions CO2 (wrapper CodeCarbon) |
10
+ | `aura.web` | Interface web locale (FastAPI) |
11
+ | `aura.core` | Utilitaires partagés (config, auth, models) |
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ # Depuis GitHub Packages
17
+ pip install aura --index-url https://pip.pkg.github.com/cevia
18
+
19
+ # Avec le composant web
20
+ pip install "aura[web]" --index-url https://pip.pkg.github.com/cevia
21
+ ```
22
+
23
+ ## Usage rapide
24
+
25
+ ```python
26
+ from aura.carbon import AuraCarbon
27
+
28
+ # Context manager
29
+ with AuraCarbon() as tracker:
30
+ train_model()
31
+
32
+ # Decorator
33
+ from aura.carbon import track_emissions
34
+
35
+ @track_emissions()
36
+ def train_model():
37
+ pass
38
+ ```
39
+
40
+ ## Interface web locale
41
+
42
+ ```bash
43
+ aura-web
44
+ # Ouvre http://localhost:8765 dans le navigateur
45
+ ```
46
+
47
+ ## Structure
48
+
49
+ ```
50
+ aura/
51
+ ├── carbon/ # Mesure CO2
52
+ │ ├── tracker.py # AuraCarbon (wrapper CodeCarbon)
53
+ │ ├── output.py # AuraOutput (envoi vers serveur)
54
+ │ └── decorators.py
55
+ ├── web/ # Interface locale
56
+ │ ├── main.py # Point d'entrée (FastAPI + navigateur)
57
+ │ ├── app.py # Application FastAPI
58
+ │ ├── routers/ # Endpoints
59
+ │ ├── services/ # Logique métier
60
+ │ └── templates/ # HTML
61
+ └── core/ # Partagé
62
+ ├── config.py # ~/.aura.config
63
+ ├── auth.py # Auth serveur
64
+ ├── models.py # Modèles Pydantic
65
+ └── exceptions.py
66
+ ```
67
+
68
+ ## Licence
69
+
70
+ MIT — CEVIA
@@ -0,0 +1,30 @@
1
+ """
2
+ Aura — Carbon tracking and monitoring toolkit.
3
+
4
+ Composants disponibles :
5
+ - aura.carbon : Mesure d'émissions CO2 (wrapper CodeCarbon)
6
+ - aura.web : Interface web locale (FastAPI)
7
+ - aura.core : Utilitaires partagés (config, auth, logging)
8
+
9
+ Usage rapide :
10
+ from aura.carbon import AuraCarbon
11
+
12
+ tracker = AuraCarbon()
13
+ tracker.start()
14
+ # ... ton code ...
15
+ tracker.stop()
16
+ """
17
+
18
+ __version__ = "0.1.0"
19
+ __author__ = "CEVIA"
20
+ __email__ = "contact@cevia.io"
21
+
22
+ # Exports publics principaux
23
+ from aura.carbon import AuraCarbon
24
+ from aura.carbon.decorators import track_emissions
25
+
26
+ __all__ = [
27
+ "AuraCarbon",
28
+ "track_emissions",
29
+ "__version__",
30
+ ]
@@ -0,0 +1,33 @@
1
+ """
2
+ aura.carbon — Composant de mesure d'émissions CO2.
3
+
4
+ Wrapper autour de CodeCarbon avec sortie vers le serveur Aura/Django.
5
+
6
+ Usage :
7
+ from aura.carbon import AuraCarbon
8
+
9
+ # Context manager
10
+ with AuraCarbon() as tracker:
11
+ train_model()
12
+
13
+ # Decorator
14
+ from aura.carbon import track_emissions
15
+
16
+ @track_emissions()
17
+ def train_model():
18
+ pass
19
+
20
+ # Start/Stop explicite
21
+ tracker = AuraCarbon()
22
+ tracker.start()
23
+ train_model()
24
+ emissions = tracker.stop()
25
+ """
26
+
27
+ from aura.carbon.tracker import AuraCarbon
28
+ from aura.carbon.decorators import track_emissions
29
+
30
+ __all__ = [
31
+ "AuraCarbon",
32
+ "track_emissions",
33
+ ]
@@ -0,0 +1,54 @@
1
+ """
2
+ Décorateurs Aura pour le tracking d'émissions.
3
+
4
+ Usage :
5
+ from aura.carbon import track_emissions
6
+
7
+ @track_emissions()
8
+ def train_model():
9
+ pass
10
+
11
+ @track_emissions(project_name="GPT Training", measure_power_secs=5)
12
+ def fine_tune(model, dataset):
13
+ pass
14
+ """
15
+
16
+ import functools
17
+ from typing import Optional, Callable, Any
18
+
19
+ from aura.carbon.tracker import AuraCarbon
20
+
21
+
22
+ def track_emissions(
23
+ project_name: Optional[str] = None,
24
+ measure_power_secs: float = 15,
25
+ save_to_file: bool = False,
26
+ **kwargs,
27
+ ) -> Callable:
28
+ """
29
+ Décorateur pour tracker les émissions d'une fonction.
30
+
31
+ Args:
32
+ project_name: Nom du projet (défaut : depuis ~/.aura.config).
33
+ measure_power_secs: Fréquence de mesure en secondes.
34
+ save_to_file: Sauvegarder aussi dans un CSV local.
35
+ **kwargs: Autres paramètres transmis à AuraCarbon / EmissionsTracker.
36
+
37
+ Example :
38
+ @track_emissions(project_name="Training", measure_power_secs=5)
39
+ def train():
40
+ ...
41
+ """
42
+ def decorator(func: Callable) -> Callable:
43
+ @functools.wraps(func)
44
+ def wrapper(*args, **func_kwargs) -> Any:
45
+ tracker = AuraCarbon(
46
+ project_name=project_name or func.__name__,
47
+ measure_power_secs=measure_power_secs,
48
+ save_to_file=save_to_file,
49
+ **kwargs,
50
+ )
51
+ with tracker:
52
+ return func(*args, **func_kwargs)
53
+ return wrapper
54
+ return decorator
@@ -0,0 +1,129 @@
1
+ """
2
+ AuraOutput — Sortie HTTP vers le serveur Aura/Django.
3
+
4
+ Hérite de BaseOutput (CodeCarbon) et envoie les données
5
+ d'émissions à l'endpoint /api/emissions/ du serveur.
6
+ """
7
+
8
+ import requests
9
+ from typing import Optional
10
+
11
+ from codecarbon.output_methods.base_output import BaseOutput
12
+ from codecarbon.output_methods.emissions_data import EmissionsData
13
+
14
+ from aura.core.config import AuraConfig
15
+ from aura.core.exceptions import AuraAPIError
16
+
17
+
18
+ class AuraOutput(BaseOutput):
19
+ """
20
+ Output CodeCarbon qui envoie les données vers le serveur Aura.
21
+
22
+ Appelé automatiquement par CodeCarbon à chaque :
23
+ - live_out() : toutes les api_call_interval mesures (temps réel)
24
+ - out() : à stop() ou flush()
25
+ """
26
+
27
+ def __init__(self, config: Optional[AuraConfig] = None):
28
+ """
29
+ Args:
30
+ config: AuraConfig chargée. Si None, chargée automatiquement
31
+ depuis ~/.aura.config.
32
+ """
33
+ if config is None:
34
+ config = AuraConfig().load()
35
+
36
+ self._endpoint = f"{config.api_endpoint.rstrip('/')}/emissions/"
37
+ self._headers = {
38
+ "Authorization": f"Token {config.api_key}",
39
+ "Content-Type": "application/json",
40
+ }
41
+
42
+ # ─── Interface BaseOutput ─────────────────────────────────────────────────
43
+
44
+ def out(self, total: EmissionsData, delta: EmissionsData) -> None:
45
+ """Appelé à stop() ou flush() — envoie les données cumulatives."""
46
+ self._send(total)
47
+
48
+ def live_out(self, total: EmissionsData, delta: EmissionsData) -> None:
49
+ """Appelé périodiquement pendant le run — envoie le delta."""
50
+ self._send(delta)
51
+
52
+ def task_out(self, data, experiment_name: str) -> None:
53
+ """Appelé à stop() si des tâches ont été trackées."""
54
+ # Optionnel : envoyer les tâches séparément
55
+ pass
56
+
57
+ def exit(self) -> None:
58
+ """Cleanup à la fin du tracking."""
59
+ pass
60
+
61
+ # ─── Envoi HTTP ───────────────────────────────────────────────────────────
62
+
63
+ def _send(self, data: EmissionsData) -> None:
64
+ """
65
+ Envoie un objet EmissionsData au serveur.
66
+
67
+ En cas d'erreur réseau ou serveur, on logue sans planter
68
+ le code utilisateur.
69
+ """
70
+ try:
71
+ payload = self._to_dict(data)
72
+ response = requests.post(
73
+ self._endpoint,
74
+ json=payload,
75
+ headers=self._headers,
76
+ timeout=10,
77
+ )
78
+ if not response.ok:
79
+ print(
80
+ f"[Aura] Avertissement : l'envoi des émissions a échoué "
81
+ f"({response.status_code})"
82
+ )
83
+ except requests.ConnectionError:
84
+ print("[Aura] Avertissement : impossible de joindre le serveur.")
85
+ except requests.Timeout:
86
+ print("[Aura] Avertissement : timeout lors de l'envoi des émissions.")
87
+ except Exception as e:
88
+ print(f"[Aura] Avertissement : erreur inattendue lors de l'envoi : {e}")
89
+
90
+ @staticmethod
91
+ def _to_dict(data: EmissionsData) -> dict:
92
+ """Convertit EmissionsData en dict JSON-sérialisable."""
93
+ return {
94
+ "run_id": str(data.run_id),
95
+ "project_name": data.project_name,
96
+ "experiment_id": str(data.experiment_id) if data.experiment_id else None,
97
+ "timestamp": data.timestamp,
98
+ "duration": data.duration,
99
+ "emissions": data.emissions,
100
+ "emissions_rate": data.emissions_rate,
101
+ "energy_consumed": data.energy_consumed,
102
+ "cpu_power": data.cpu_power,
103
+ "gpu_power": data.gpu_power,
104
+ "ram_power": data.ram_power,
105
+ "cpu_energy": data.cpu_energy,
106
+ "gpu_energy": data.gpu_energy,
107
+ "ram_energy": data.ram_energy,
108
+ "cpu_utilization_percent": getattr(data, "cpu_utilization_percent", None),
109
+ "gpu_utilization_percent": getattr(data, "gpu_utilization_percent", None),
110
+ "ram_utilization_percent": getattr(data, "ram_utilization_percent", None),
111
+ "ram_used_gb": getattr(data, "ram_used_gb", None),
112
+ "country_name": data.country_name,
113
+ "country_iso_code": data.country_iso_code,
114
+ "region": data.region,
115
+ "cloud_provider": data.cloud_provider,
116
+ "cloud_region": data.cloud_region,
117
+ "on_cloud": data.on_cloud,
118
+ "os": data.os,
119
+ "python_version": data.python_version,
120
+ "codecarbon_version": data.codecarbon_version,
121
+ "cpu_model": data.cpu_model,
122
+ "cpu_count": data.cpu_count,
123
+ "gpu_model": data.gpu_model,
124
+ "gpu_count": data.gpu_count,
125
+ "ram_total_size": data.ram_total_size,
126
+ "tracking_mode": data.tracking_mode,
127
+ "pue": data.pue,
128
+ "wue": getattr(data, "wue", 0.0),
129
+ }