saggio 1.4.2__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 (98) hide show
  1. saggio-1.4.2/LICENSE +29 -0
  2. saggio-1.4.2/PKG-INFO +411 -0
  3. saggio-1.4.2/README.md +371 -0
  4. saggio-1.4.2/pyproject.toml +158 -0
  5. saggio-1.4.2/saggio/__init__.py +198 -0
  6. saggio-1.4.2/saggio/__main__.py +32 -0
  7. saggio-1.4.2/saggio/analyze/__init__.py +125 -0
  8. saggio-1.4.2/saggio/analyze/apple.py +542 -0
  9. saggio-1.4.2/saggio/analyze/capability.py +469 -0
  10. saggio-1.4.2/saggio/analyze/cpu.py +389 -0
  11. saggio-1.4.2/saggio/analyze/llm.py +396 -0
  12. saggio-1.4.2/saggio/analyze/power/__init__.py +133 -0
  13. saggio-1.4.2/saggio/analyze/power/accelerator.py +223 -0
  14. saggio-1.4.2/saggio/analyze/power/graphics.py +185 -0
  15. saggio-1.4.2/saggio/analyze/power/meter.py +391 -0
  16. saggio-1.4.2/saggio/analyze/power/rapl.py +156 -0
  17. saggio-1.4.2/saggio/analyze/power/reading.py +97 -0
  18. saggio-1.4.2/saggio/analyze/power/soc.py +72 -0
  19. saggio-1.4.2/saggio/analyze/power/sysfs.py +66 -0
  20. saggio-1.4.2/saggio/analyze/power/tables.py +196 -0
  21. saggio-1.4.2/saggio/analyze/run.py +1117 -0
  22. saggio-1.4.2/saggio/analyze/static/__init__.py +123 -0
  23. saggio-1.4.2/saggio/analyze/static/archetype.py +197 -0
  24. saggio-1.4.2/saggio/analyze/static/calls.py +223 -0
  25. saggio-1.4.2/saggio/analyze/static/findings.py +312 -0
  26. saggio-1.4.2/saggio/analyze/static/languages.py +44 -0
  27. saggio-1.4.2/saggio/analyze/static/read.py +83 -0
  28. saggio-1.4.2/saggio/analyze/static/running.py +267 -0
  29. saggio-1.4.2/saggio/analyze/static/tables.py +358 -0
  30. saggio-1.4.2/saggio/analyze/static/walking.py +238 -0
  31. saggio-1.4.2/saggio/analyze/static/worksize.py +213 -0
  32. saggio-1.4.2/saggio/auditor/__init__.py +74 -0
  33. saggio-1.4.2/saggio/auditor/assumptions.py +230 -0
  34. saggio-1.4.2/saggio/auditor/blocks.py +169 -0
  35. saggio-1.4.2/saggio/auditor/build.py +331 -0
  36. saggio-1.4.2/saggio/auditor/measuring.py +172 -0
  37. saggio-1.4.2/saggio/auditor/naming.py +134 -0
  38. saggio-1.4.2/saggio/auditor/options.py +144 -0
  39. saggio-1.4.2/saggio/auditor/paths.py +56 -0
  40. saggio-1.4.2/saggio/auditor/projections.py +287 -0
  41. saggio-1.4.2/saggio/catalog/__init__.py +71 -0
  42. saggio-1.4.2/saggio/catalog/pricing.py +410 -0
  43. saggio-1.4.2/saggio/catalog/refresh/__init__.py +132 -0
  44. saggio-1.4.2/saggio/catalog/refresh/carbon.py +408 -0
  45. saggio-1.4.2/saggio/catalog/refresh/embodied.py +296 -0
  46. saggio-1.4.2/saggio/catalog/refresh/tariffs.py +350 -0
  47. saggio-1.4.2/saggio/catalog/refresh/timezones.py +233 -0
  48. saggio-1.4.2/saggio/catalog/registry.py +804 -0
  49. saggio-1.4.2/saggio/cli/__init__.py +38 -0
  50. saggio-1.4.2/saggio/cli/app.py +726 -0
  51. saggio-1.4.2/saggio/cli/commands.py +1151 -0
  52. saggio-1.4.2/saggio/cli/exit_codes.py +52 -0
  53. saggio-1.4.2/saggio/data/__init__.py +27 -0
  54. saggio-1.4.2/saggio/data/grid.yaml +553 -0
  55. saggio-1.4.2/saggio/data/hardware.yaml +334 -0
  56. saggio-1.4.2/saggio/data/instances.yaml +119 -0
  57. saggio-1.4.2/saggio/data/providers.yaml +74 -0
  58. saggio-1.4.2/saggio/data/report/i18n.yaml +120 -0
  59. saggio-1.4.2/saggio/data/report/logo.png +0 -0
  60. saggio-1.4.2/saggio/data/report/report.css +245 -0
  61. saggio-1.4.2/saggio/data/report/report.html +38 -0
  62. saggio-1.4.2/saggio/data/report/report.js +168 -0
  63. saggio-1.4.2/saggio/data/services.yaml +111 -0
  64. saggio-1.4.2/saggio/data/templates/annotated.yaml +183 -0
  65. saggio-1.4.2/saggio/data/templates/minimal.yaml +85 -0
  66. saggio-1.4.2/saggio/diff.py +529 -0
  67. saggio-1.4.2/saggio/estimate/__init__.py +124 -0
  68. saggio-1.4.2/saggio/estimate/context.py +618 -0
  69. saggio-1.4.2/saggio/estimate/embodied.py +329 -0
  70. saggio-1.4.2/saggio/estimate/energy.py +580 -0
  71. saggio-1.4.2/saggio/estimate/equivalences.py +263 -0
  72. saggio-1.4.2/saggio/estimate/extrapolate.py +939 -0
  73. saggio-1.4.2/saggio/estimate/machine.py +359 -0
  74. saggio-1.4.2/saggio/estimate/scaling.py +527 -0
  75. saggio-1.4.2/saggio/fold.py +453 -0
  76. saggio-1.4.2/saggio/model/__init__.py +135 -0
  77. saggio-1.4.2/saggio/model/cost_model.py +614 -0
  78. saggio-1.4.2/saggio/model/derivation.py +487 -0
  79. saggio-1.4.2/saggio/model/dimensions.py +371 -0
  80. saggio-1.4.2/saggio/model/quantity.py +482 -0
  81. saggio-1.4.2/saggio/model/results.py +279 -0
  82. saggio-1.4.2/saggio/model/schema.py +170 -0
  83. saggio-1.4.2/saggio/model/taxonomy.py +186 -0
  84. saggio-1.4.2/saggio/model/validate.py +1001 -0
  85. saggio-1.4.2/saggio/report/__init__.py +60 -0
  86. saggio-1.4.2/saggio/report/dashboard.py +273 -0
  87. saggio-1.4.2/saggio/report/figures.py +835 -0
  88. saggio-1.4.2/saggio/report/html.py +1168 -0
  89. saggio-1.4.2/saggio/report/markdown.py +1001 -0
  90. saggio-1.4.2/saggio/report/office.py +146 -0
  91. saggio-1.4.2/saggio/templates.py +112 -0
  92. saggio-1.4.2/saggio.egg-info/PKG-INFO +411 -0
  93. saggio-1.4.2/saggio.egg-info/SOURCES.txt +96 -0
  94. saggio-1.4.2/saggio.egg-info/dependency_links.txt +1 -0
  95. saggio-1.4.2/saggio.egg-info/entry_points.txt +2 -0
  96. saggio-1.4.2/saggio.egg-info/requires.txt +13 -0
  97. saggio-1.4.2/saggio.egg-info/top_level.txt +1 -0
  98. saggio-1.4.2/setup.cfg +4 -0
saggio-1.4.2/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Warith Harchaoui
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
saggio-1.4.2/PKG-INFO ADDED
@@ -0,0 +1,411 @@
1
+ Metadata-Version: 2.4
2
+ Name: saggio
3
+ Version: 1.4.2
4
+ Summary: What it costs to run your code, per unit of work, with every number saying how far it can be trusted: money, time, energy, carbon, water, and any dimension you register.
5
+ Author-email: Warith HARCHAOUI <warith.harchaoui@sev7n.io>
6
+ License: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/warith-harchaoui/saggio
8
+ Project-URL: Source, https://github.com/warith-harchaoui/saggio
9
+ Project-URL: Issues, https://github.com/warith-harchaoui/saggio/issues
10
+ Keywords: cost of running,green software,carbon footprint,energy measurement,sustainability,FinOps,Green Algorithms,provenance,reproducibility
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: BSD License
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Operating System :: MacOS :: MacOS X
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Classifier: Topic :: System :: Benchmark
25
+ Requires-Python: <3.14,>=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: os-helper<3,>=2.0.0
29
+ Requires-Dist: PyYAML>=6.0
30
+ Requires-Dist: platformdirs>=4.0
31
+ Provides-Extra: office
32
+ Requires-Dist: md2star>=1.0; extra == "office"
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=8.0; extra == "dev"
35
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
36
+ Requires-Dist: pytest-xdist>=3.6; extra == "dev"
37
+ Requires-Dist: ruff==0.16.10; extra == "dev"
38
+ Requires-Dist: pillow>=10.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # saggio
42
+
43
+ [🇫🇷 LISEZMOI.md](https://github.com/warith-harchaoui/saggio/blob/master/LISEZMOI.md) · 🇬🇧 English
44
+
45
+ **What does it cost to run your code?** Money, time, energy, carbon, water, and
46
+ any other dimension you decide to watch, per unit of work, with every number
47
+ saying how far it can be trusted.
48
+
49
+ <p>
50
+ <img src="https://raw.githubusercontent.com/warith-harchaoui/saggio/master/assets/logo.png" alt="saggio logo">
51
+ </p>
52
+
53
+ The answer is a YAML file you commit next to the code, and reports rendered from
54
+ it. The file is reviewable in a pull request, the reports are readable by people
55
+ who will never open a terminal, and neither of them is allowed to state a number
56
+ without saying where it came from.
57
+
58
+ ```bash
59
+ pip install saggio
60
+
61
+ saggio audit . --country FR --run -o cost_of_running.yaml
62
+ saggio render cost_of_running.yaml -f html -o cost_of_running.html
63
+ ```
64
+
65
+ ## The one idea
66
+
67
+ Every number carries a **status**:
68
+
69
+ | Status | What it means |
70
+ |---|---|
71
+ | `measured` | A counter on the machine said so. |
72
+ | `estimated` | A sourced formula or a published figure said so. |
73
+ | `placeholder` | The field is being held open. It is not a number. |
74
+ | `TODO` | A human has to supply this before the model can be trusted. |
75
+
76
+ And every derived number names the numbers it came from:
77
+
78
+ ```yaml
79
+ costs:
80
+ carbon:
81
+ value: 0.0014
82
+ unit: "gCO2e"
83
+ status: "estimated"
84
+ derived_from: ["scenarios[0].costs.energy", "assumptions.grid_carbon_intensity"]
85
+ ```
86
+
87
+ From there the **weakest-link rule** follows, and the validator enforces it: a
88
+ derived value may never claim to be better founded than the worst of its inputs.
89
+ Measure the runtime and the energy becomes measured on its own. Leave the country
90
+ unstated and the carbon figure stays open, because nobody knows it yet.
91
+
92
+ And the derivation is **checked, not just declared**. The validator parses every
93
+ unit down to its base dimensions — a watt *is* a joule per second — and recomputes
94
+ the value from its named inputs, multiplying them and, where that cannot reach the
95
+ stated unit, dividing: amortising an embodied footprint over a lifetime is a
96
+ division, and the model names the lifetime among its inputs just the same. A value
97
+ more than a percent from what its inputs give is an error. Where several readings
98
+ reach the unit and the units cannot say which was meant, a value matching none of
99
+ them is still an error, because it is wrong under every reading. A unit no
100
+ arrangement of the inputs can produce is an error needing no arithmetic at all.
101
+ The mistake this closes is the worst-looking one: a number orders of magnitude
102
+ out, carrying a perfectly correct list of the inputs it supposedly came from,
103
+ reads as better founded than anything else on the page. And where any unit
104
+ involved is one the package does not know — a dimension you registered this
105
+ morning — it says nothing rather than inventing a rule.
106
+
107
+ Three properties fall out of writing it this way, and they are the reason for the
108
+ design:
109
+
110
+ - **Nothing can hide.** The validator walks the whole file. Any number outside a
111
+ quantity is an error, wherever it is and whatever block it is in, so an extra
112
+ block cannot smuggle a figure past the rules.
113
+ - **The rule is general.** Because the derivation is data rather than code, a
114
+ dimension you invented this morning is checked exactly as carefully as carbon.
115
+ - **Nothing is invented.** An unresolved country gives a `TODO`, not a zero. A
116
+ provider that publishes no water figure gives a `TODO`, not a plausible one. A
117
+ run that failed projects to nothing at all.
118
+
119
+ ## What it does
120
+
121
+ **Reads your repository.** Languages, workload shape, frameworks, how much work a
122
+ full run performs, which paid APIs you call and on which line. All of it
123
+ deterministic, all of it quoting its evidence.
124
+
125
+ **Runs a slice of it, if you let it.** With `--run`, and after you have agreed
126
+ once, it executes a capped slice of your real entry point, times it, reads
127
+ whatever energy counters the machine publishes to an ordinary user, and profiles
128
+ where the time went. On Linux that is the powercap tree, memory zone included,
129
+ and the graphics driver's own sensor; on an Apple Silicon Mac it is the chip's
130
+ own counters, read without a password; on any machine with an NVIDIA board it is
131
+ the driver. `saggio power` says which of them answer here, and prints what it
132
+ would take to open the ones that do not — without ever opening them itself. The
133
+ size the slice covers is read from your own configuration, so projecting to a
134
+ whole run is arithmetic rather than a guess.
135
+
136
+ **Looks things up rather than assuming them.** What a GPU draws, what a kilowatt
137
+ hour emits in Poland, what overhead a datacenter adds, where an API publishes its
138
+ prices: sourced YAML catalogues, each row carrying the URL it came from and the
139
+ date somebody read it, each one going stale on a schedule that matches how fast
140
+ that kind of fact actually moves.
141
+
142
+ **Projects, and says what it assumed — or measures the assumption away.** From a
143
+ measured slice to a whole run.
144
+ From one accelerator to another, and all the way to money and carbon rather than
145
+ stopping at a duration. The first projection rests on the work being uniform, and `--scaling-steps 3`
146
+ replaces that assumption with a number: three slices of different sizes, a fitted
147
+ exponent, and a refusal to project at all when the fit says the slices are not
148
+ measuring one consistent behaviour. That second projection is a bracket, not a number: work is
149
+ limited by arithmetic throughput or by memory bandwidth, the two ratios differ by
150
+ more than a factor of two between an A100 and an H100, and reporting the compute
151
+ ratio alone would understate the bill by a third. It refuses outright when the
152
+ catalogue has no throughput figure for the precision the work runs in.
153
+
154
+ **Counts the hardware, not only the electricity.** Manufacturing one HGX H100
155
+ baseboard emits 1,312 kgCO2e before it computes anything, and a model that
156
+ reports only the energy is claiming that figure is zero. The `embodied_carbon`
157
+ dimension amortises a published product carbon footprint over the share of the
158
+ hardware's life one unit of work reserved — which completes the four terms of
159
+ [ISO/IEC 21031:2024](https://greensoftware.foundation/standards/sci/), the
160
+ Software Carbon Intensity standard, whose shape this package already had.
161
+
162
+ The arithmetic is complete; **the catalogue is not, and will not pretend to
163
+ be.** Seven footprints ship: three accelerators out of 25 rows and four
164
+ processors out of 12. Everything else reports `TODO` with a sentence saying
165
+ nobody has read a footprint for it, which is not the same as it having been free
166
+ to build. The reason the gap is that wide is that most vendors publish a
167
+ footprint for a whole server and not for the part inside it, and the one open
168
+ database that covers processors answers for a chip it does not have by
169
+ substituting the nearest one it does — asked for an Apple M4 Max it returns an
170
+ Apple M1 Max, four generations earlier, with no warning. Those answers are
171
+ refused by name rather than imported.
172
+ [`STANDARDS.md`](https://github.com/warith-harchaoui/saggio/blob/master/STANDARDS.md) has the table, the boundary of every figure, and
173
+ the eight refusals with their reasons.
174
+
175
+ **Writes reports that people read.** Markdown for a pull request. A single
176
+ self-contained HTML page for everyone else: offline, light and dark, English and
177
+ French, with a panel that recomputes the model for a different country in the
178
+ browser and a chart of how the grid would move the carbon elsewhere. Word and PDF
179
+ through `md2star` when a document is what somebody wants. Every known carbon
180
+ figure is also restated in terms a reader can feel — tree-months of
181
+ sequestration, kilometres in an average car, a fraction of a Paris–London
182
+ flight — with the [Green Algorithms](https://doi.org/10.1002/advs.202100707)
183
+ coefficients, and without gaining any confidence in the restating: an open figure
184
+ stays open, and a measured one reads `estimated`, because the tree is an average
185
+ tree.
186
+
187
+ **Shows the team, not just the project.** `saggio dashboard` renders every
188
+ committed cost model on one page. It leads with the one comparison that is honest
189
+ across projects — how much of each model is measured, estimated, or still open —
190
+ and says plainly that the cost rows, each per its own unit of work, do not
191
+ compare with each other.
192
+
193
+ **Fails your build when a cost drifts.** `diff` compares two models and fails on a
194
+ cost that worsened past a threshold, on a status that weakened, and on a quantity
195
+ that quietly disappeared.
196
+
197
+ ## Install
198
+
199
+ ```bash
200
+ pip install saggio
201
+ ```
202
+
203
+ **Linux and macOS.** Windows is out of scope, deliberately and for good: it
204
+ publishes no vendor-neutral processor energy counter to an unprivileged process,
205
+ no machine-wide processor-time total this package can read, and no POSIX resource
206
+ accounting for a child. A tool whose whole proposition is that a number says how
207
+ far it can be trusted should not pretend to support a platform where it could
208
+ only ever estimate, so `import saggio` there raises rather than half-working.
209
+
210
+ Three runtime dependencies, on purpose. [`os-helper`](https://pypi.org/project/os-helper/)
211
+ answers every question about the machine and the operating system on both of
212
+ them. PyYAML parses the models and the catalogues.
213
+ `platformdirs` finds the per-user config directory. Everything else this package
214
+ does, it does itself.
215
+
216
+ Word and PDF need one more thing, and only if you want them:
217
+
218
+ ```bash
219
+ pip install "saggio[office]"
220
+ ```
221
+
222
+ For conda:
223
+
224
+ ```bash
225
+ conda env create -f environment.yaml
226
+ conda activate env-for-saggio
227
+ ```
228
+
229
+ ## Use it
230
+
231
+ ```bash
232
+ # Start from a worked example, or from a scaffold with everything left open.
233
+ saggio init --template annotated -o cost_of_running.yaml
234
+
235
+ # Check it against the schema and the honesty rules.
236
+ saggio validate cost_of_running.yaml
237
+
238
+ # Read a repository and write a model for it.
239
+ saggio audit . --country FR -o cost_of_running.yaml
240
+
241
+ # Read it, and run a capped slice to measure what it really costs.
242
+ saggio audit . --country FR --run -o cost_of_running.yaml
243
+
244
+ # What would this cost on an H100, from a measurement taken on a 4090?
245
+ saggio audit . --country FR --run \
246
+ --source-accelerator RTX-4090 --target-accelerator H100
247
+
248
+ # Measure one command of your own.
249
+ saggio measure -- python train.py --steps 100
250
+
251
+ # What is this machine, and does the catalogue know its parts?
252
+ saggio machine
253
+
254
+ # Turn the model into something a person reads.
255
+ saggio render cost_of_running.yaml -f html -o report.html
256
+
257
+ # Fail the build when a cost has drifted.
258
+ saggio diff main.yaml branch.yaml --threshold 10
259
+ ```
260
+
261
+ The library is the same thing without the printing:
262
+
263
+ ```python
264
+ import saggio
265
+
266
+ result = saggio.audit(".", options=saggio.AuditOptions(country="FR"))
267
+ print(result.report.summary())
268
+ print(saggio.render_markdown(result.model))
269
+ ```
270
+
271
+ [`EXAMPLES.md`](https://github.com/warith-harchaoui/saggio/blob/master/EXAMPLES.md) is the cookbook, [`MEASURING.md`](https://github.com/warith-harchaoui/saggio/blob/master/MEASURING.md) is
272
+ which counters this machine will let you read and what each of them covers,
273
+ [`GALLERY.md`](https://github.com/warith-harchaoui/saggio/blob/master/GALLERY.md) is what it says about nanoGPT, Whisper, DINOv2,
274
+ FastAPI and Airflow with the files committed beside it,
275
+ [`docs/api.md`](https://github.com/warith-harchaoui/saggio/blob/master/docs/api.md) is every name `import saggio` gives you, and
276
+ [`docs/`](https://github.com/warith-harchaoui/saggio/blob/master/docs/README.md) is the map of the rest.
277
+
278
+ [`ANALYSIS.md`](https://github.com/warith-harchaoui/saggio/blob/master/ANALYSIS.md) is the investigation behind the division of labour
279
+ above: what static and dynamic analysis have actually been shown to deliver for
280
+ complexity and for consumption, which of it belongs here, and which of it is
281
+ refused and why.
282
+
283
+ ## Running your code, and what that means
284
+
285
+ Measuring what code costs to run means running it. There is no sandbox here and
286
+ none is pretended: your repository under study executes as you, with your
287
+ permissions and your network access.
288
+
289
+ So consent is explicit, asked for once, and recorded where you can find and
290
+ revoke it:
291
+
292
+ ```bash
293
+ saggio consent grant
294
+ saggio consent revoke
295
+ ```
296
+
297
+ Without it, `--run` does nothing and the audit proceeds on reading alone. A
298
+ session with no terminal is refused rather than defaulted, so a build server can
299
+ never agree on your behalf.
300
+
301
+ ## What it never does
302
+
303
+ It never puts a number in a file that nobody chose. The country is stated by a
304
+ person or inferred from the machine's timezone and labelled as an inference; it is
305
+ never read off a locale and written down as fact.
306
+
307
+ It never parses a pricing page, and never asks a model what one says. With
308
+ `--fetch-prices` it reads rates from sources published *as data*, records which
309
+ model the code names and on which line, and stamps every rate with where it was
310
+ read and when. A rate from the vendor's own price API and a rate from somebody
311
+ else's transcription are both `estimated`, so each one also carries a
312
+ `source_kind` saying which it is, and the drift gate fails when that weakens.
313
+ Without the flag nothing reaches the network and the price stays open, pointing
314
+ at the page where the current number lives.
315
+
316
+ It never lets a language model supply a number. A local model, when you have one
317
+ running, is asked what shape of work your repository does and nothing else. Its
318
+ answer is labelled with the model's name and marked low confidence. Every number
319
+ comes from a file you can open or a counter you can read.
320
+
321
+ And it never counts what it cannot count. Making the hardware, the people, the
322
+ office, the idle capacity: all of it is listed in the report as excluded, because
323
+ a footprint that quietly leaves out the largest term is worse than no footprint.
324
+
325
+ ## Where the numbers come from
326
+
327
+ The method is [Green Algorithms](https://doi.org/10.1002/advs.202100707)
328
+ (Lannelongue, Grealey, and Inouye, 2021): power multiplied by time is energy,
329
+ energy multiplied by a grid intensity is carbon, energy multiplied by a tariff is
330
+ money, energy multiplied by a water usage effectiveness is water.
331
+
332
+ One distinction is kept that is easy to lose. The machine draws one amount; the
333
+ building draws that amount multiplied by its power usage effectiveness. Carbon and
334
+ money follow the building, because that is what the meter counts. Water follows
335
+ the machine, because water usage effectiveness is defined per kilowatt-hour of IT
336
+ load and using the building figure would count the cooling twice.
337
+
338
+ Hardware wattages, grid intensities, tariffs, and datacenter overheads live in
339
+ [`saggio/data/`](https://github.com/warith-harchaoui/saggio/tree/master/saggio/data), one row each
340
+ with its source and its date. Missing a row is a normal outcome, and the tool
341
+ tells you which one by name:
342
+
343
+ ```bash
344
+ saggio catalog list gpu
345
+ saggio catalog add gpu H300 \
346
+ --source-url https://www.nvidia.com/... --retrieved-date 2026-09-12 \
347
+ --field tdp_w=800 --field peak_bf16_tflops=2400
348
+ saggio catalog freshness # exits 1 when a number has gone stale
349
+ ```
350
+
351
+ A row cannot be added without a source and a date. That rule is what keeps the
352
+ catalogues worth trusting.
353
+
354
+ ## How it is put together
355
+
356
+ ```
357
+ model/ What a cost model means: the taxonomy, the quantity, the dimensions,
358
+ the schema, the validation. No I/O, no network, no subprocess.
359
+ catalog/ Sourced facts about the world, and the rule that a row without
360
+ provenance does not enter.
361
+ estimate/ Facts and measurements into numbers: the machine, the deployment,
362
+ the Green Algorithms chain, the projections.
363
+ analyze/ What a repository is: reading it, running a slice of it, and asking a
364
+ local model about its shape (never about its numbers).
365
+ auditor.py The whole job, in one function.
366
+ diff.py What changed between two models, and whether it fails the gate.
367
+ templates.py The starter models the wheel ships.
368
+ report/ Markdown, HTML, Word, PDF.
369
+ cli/ Argument parsing and printing. Nothing else.
370
+ ```
371
+
372
+ The dependency direction only points one way, so the command line can do nothing a
373
+ library caller cannot.
374
+
375
+ The HTML report's own pieces are authored outside the package, in
376
+ [`reporting/`](https://github.com/warith-harchaoui/saggio/tree/master/reporting): the document shell with the tokens the renderer
377
+ fills, the stylesheet, the script, the translations. They are a stylesheet and a
378
+ script there rather than strings quoted inside Python, and `reporting/sync.py`
379
+ copies them into the package that ships them, with a test that fails the build if
380
+ the two ever drift apart. Copy that trio to render reports of your own shape.
381
+
382
+ ## Contributing
383
+
384
+ [`CONTRIBUTING.md`](https://github.com/warith-harchaoui/saggio/blob/master/CONTRIBUTING.md) has the details. The short version: add a
385
+ catalogue row with its source and its date, or a test that pins a behaviour you
386
+ care about. [`CODING.md`](https://github.com/warith-harchaoui/saggio/blob/master/CODING.md) is the style this repository is written in.
387
+
388
+ ```bash
389
+ pip install -e ".[dev]"
390
+ pytest # 1000+ checks, including every example in every docstring
391
+ ruff check .
392
+ ruff format --check .
393
+ ```
394
+
395
+ ## Related work
396
+
397
+ [`LANDSCAPE.md`](https://github.com/warith-harchaoui/saggio/blob/master/LANDSCAPE.md) places this alongside CodeCarbon, Green Algorithms,
398
+ Scaphandre, PowerAPI, Cloud Carbon Footprint, and the rest of the field, and is
399
+ honest about where each of them is the better tool.
400
+
401
+ ## The name
402
+
403
+ *Saggio* is Italian for the assay of a metal: you draw a sample, you determine
404
+ its fineness, and the result is stamped with who determined it and when. It also
405
+ means an essay, and it means judicious. All three are the point. This tool draws
406
+ a capped sample of a real run, reports how well founded each number is, and
407
+ records where every figure came from and on what date somebody read it.
408
+
409
+ ## Licence
410
+
411
+ [BSD 3-Clause](https://github.com/warith-harchaoui/saggio/blob/master/LICENSE). Warith Harchaoui, Ph.D.