--- license: agpl-3.0 pipeline_tag: tabular-regression tags: - matrixai - onnx - tabular-regression - auditable-ai - reproducibility - provenance - synthetic-data - edge-inference --- # Celsius to Kelvin **A regression whose answer you check with a subtraction.** This is a one-input tabular regression: you give it a temperature in degrees Celsius and it returns the same temperature in kelvin. The point of publishing it is not that it predicts well — it is that you already know the right answer (`K = C + 273.15`), so you can check every number below with a calculator instead of trusting a metric. Built with [MatrixAI](https://github.com/robertollweb/matrixAI), which turns a written specification into a neural model, trains it, and exports a package that predicts with no MatrixAI installed. It is one of the three reproducible cases at [matrixaistudio.org/casos](https://matrixaistudio.org/casos#kelvin). MatrixAI can also build a model from a plain-language description. **This case was not made that way**: it was made from the `.mxai` and the recipe published here. --- ## 1. Check it yourself, before anything else Every number in this column comes out of a published file. The right-hand column is a subtraction anybody can do. | Input | `C + 273.15` | What the package returns | Difference | |---|---|---|---| | −40 °C | 233.15 | `233.15000236034393` | 2.36e−06 | | 0 °C | 273.15 | `273.15000146627426` | 1.47e−06 | | 50 °C | 323.15 | `323.1500059366226` | 5.94e−06 | | 100 °C | 373.15 | `373.1500029563904` | 2.96e−06 | Source: −40, 0 and 100 are the literal step-4 output in [`salida.txt`](https://matrixaistudio.org/casos/kelvin/salida.txt); 50 is `expected_output.json` in this package (sha256 `4b16dcefa0328bd4ce9e5cee18b52b7eb6b40d06d5873a8a38f705594bbc1476`), which is the value `predict.py --input example_input.json` is supposed to print. The **Difference** column is arithmetic done here on those two columns, not a figure any file publishes. The residual is float32 rounding, not model error, and you can confirm that too — see §6. **Where the four inputs come from.** These are simply the inputs the case publishes: `entrada.json` (0 °C), `entrada_100.json` and `entrada_-40.json` at `matrixaistudio.org/casos/kelvin/`, plus 50 °C, which is the `example_input.json` that ships inside the package. There is nothing special about them beyond the fact that you know the right answer by heart. The model declares `celsius: Scalar[-50, 150]` (`model.mxai`, `inference_spec.json`), and all four sit inside that range. **None of the four isolates a single parameter, and it is worth knowing why.** `W1` and `b1` are not a slope and an offset in degrees: `predict.py` normalizes the input to `(C + 50) / 200` first, so at 0 °C the model does not see 0, it sees 0.25 — and `W1` moves the answer there too. Measured on this package by perturbing `W1` by 1 % and recomputing in float32: `W1 = 0.8` gives `K(0) = 273.15000146627426`, while `W1 = 0.808` gives `273.6500024795532` — half a kelvin away at the input where a slope error is supposed to be invisible. What the four inputs together check is the whole line, not one coefficient each. (That perturbation was computed here; no published file contains it.) **How large the residual gets across the range.** The largest of the four deviations above is at 50 °C, which is not an extreme — but four points do not bound anything. Sweeping 201 points across the whole declared range [−50, 150] °C with this package's own `predict.py`, the largest deviation from `C + 273.15` is **1.4638900779573305e−05 at 69 °C** and the smallest is 5.96e−09 at −43 °C; at the two edges it is among the smallest (7.21e−07 at −50 °C, −2.38e−08 at 150 °C). So the error does not grow towards the edges, and it is about 2.5× the largest figure in the table above. It is float32 rounding noise, not model drift. That sweep was run here and is not published in any file; it is five lines of Python and you can redo it. --- ## 2. Use it without MatrixAI The package is self-usable: `predict.py` + `model.onnx` + `inference_spec.json`, and nothing from MatrixAI. Dependencies are numpy and onnxruntime (`requirements.txt`). ```bash python -m venv .venv . .venv/bin/activate # Windows: .venv\Scripts\activate pip install -r requirements.txt python predict.py --input example_input.json # 323.1500059366226 ``` From your own code: ```python from predict import MatrixAIModel model = MatrixAIModel() # loads inference_spec.json next to predict.py print(model.predict({"celsius": 50.0})) # 323.1500059366226 ``` > **If you plan to verify the package afterwards, do this on a copy.** Importing > `predict.py` from inside the bundle writes `__pycache__/predict.cpython-3XX.pyc` > into the package directory, and `matrixai verify` counts that as a file the > manifest does not name: measured, all four stages then come back `INCOMPARABLE` > with exit code 3 (`manifest INCOMPARABLE — the package ships files the manifest > does not cover`). Delete it (`rm -rf __pycache__`) before verifying, or use the > package somewhere else. Running `python predict.py --input …` as a script does > not create it; only importing does. That the check notices at all is what makes > `manifest PASS` worth anything. You feed **raw human values** (`50.0`, not `0.5`): `predict.py` applies the same normalization the model was trained with, and puts the answer back on the kelvin scale. **It clips out-of-range inputs, and you have to ask to be told.** The declared input range is [−50, 150] °C. Anything outside is clamped to the edge before the model sees it — measured by running this package's own `predict.py`: ``` {"celsius": 1000} -> 423.14999997615814 (i.e. 150 °C clamped, = 150 + 273.15) {"celsius": -273.15} -> 223.1500007212162 (i.e. -50 °C clamped, = -50 + 273.15) ``` The plain output does not say it clipped. `--meta` does — this is the literal output, as the command prints it: ```bash python predict.py --input out_of_range.json --meta ``` ```json { "prediction": 423.14999997615814, "meta": { "spec_version": 1, "warnings": [], "clipped": [ { "field": "celsius", "raw_value": 1000, "normalized_value": 1.0 } ] } } ``` If you wire this into anything, read `meta["clipped"]`. Those two numbers above are not in `salida.txt`; they were measured here by running the shipped `predict.py`, and the clipping itself is in `predict.py` (`_encode_scalar`). For the raw ONNX graph — input `Reading`, shape `[-1, 1]`; output `prediction`, shape `[-1]` (`export_manifest.json` for the names and shapes; the `float32` dtype is from `model_manifest.json` → `inputs[0].dtype`) — remember it expects an **already normalized** vector and returns an **un-denormalized** value. Prefer `predict.py`. --- ## 3. Reproduce the whole thing Nothing here is a "trust the card" step. All four verification stages are reported below in §4 — including the one that will *not* come back `PASS` on your machine, and why that is the correct answer rather than a fault. ### 3a. Check this package as it stands ```bash pip install "matrixai-core[export]" matrixai verify . # integrity + rebuild the dataset from the recipe matrixai verify . --retrain # …and train again (slow) matrixai verify . --json # same report, machine readable ``` Exit codes (the bundle's own `README.md`): `0` nothing failed · `2` something does not match · `3` it could not be checked. `verify` also accepts a `.zip` directly, and verifying a pristine copy is the right thing to do — see the note in §2 and the third note in §3b. **What you will actually get, measured.** Run on 2026-08-30 against the published `paquete.zip` with `matrixai-core` 1.7.0 — which is what the `pip install` line above installs today: ```console $ matrixai verify paquete.zip --retrain --locale en manifest PASS R1 PASS training PASS R3 INCOMPARABLE — the tolerance was measured for the package's own environment (f73756abfe0d07df…) and this one is different (364849887da933a3…), so a difference here would not prove the package wrong $ echo $? 3 ``` `INCOMPARABLE` is one of the four verdicts the tool defines (`PASS`, `FAIL`, `INCOMPARABLE`, `NOT_RUN`) and it means *"I could not check this"* — a different thing from `FAIL`. **Expect it, and expect exit 3.** It is not a defect in the package. The second digest will be different again on your machine: it is a digest of *your* environment. **Pinning the core version will not get you four `PASS`, and it would be dishonest to suggest it.** `environment_sha256` covers the matrixai version, the exact CPython build string, the kernel release and the numpy/onnx/onnxruntime/torch versions (`matrixai/export/reproduce.py`, `build_environment`). Measured here: recomputing that digest on this machine with only the version string forced to `1.6.0` reproduces `f73756abfe0d07df…` exactly — so on *this* machine the core version is the single field that moved, and 1.6.0 is still installable from PyPI. On any other machine the CPython build string and the kernel release move as well, and no pin brings those back. `R3`'s tolerance is scoped `same_environment_same_seed`; when the environment is not the same, the core says so instead of manufacturing a `PASS`. The four `PASS` lines quoted in §4 are the literal transcript of the run that built this package, on the machine that built it. They are true of that run. They are not a prediction about yours. ### 3b. Rebuild it from scratch, from the two source files `model.mxai` (the model) and `model.mxtrain` (the training contract) are in this repository, and they are text you can read in full. **Rename them first**: `model.mxtrain` names its model as `kelvin.mxai` in its first line, so the file has to be called that. ```bash cp model.mxai kelvin.mxai cp model.mxtrain kelvin.mxtrain cp data_recipe.txt receta.txt matrixai generate-dataset kelvin.mxai --training kelvin.mxtrain \ --rows 300 --seed 20260825 --mode coherent --recipe receta.txt -o datos matrixai train kelvin.mxai --training kelvin.mxtrain --output runs/v1 \ --recipe receta.txt --dataset-manifest datos/celsiustokelvin-synthetic-manifest.json matrixai export-bundle kelvin.mxai --params runs/v1/params.best.json \ --outdir paquete --training kelvin.mxtrain --data-recipe receta.txt --from-run runs/v1 cd paquete && python3 predict.py --input ../entrada.json cd .. && zip -r paquete.zip paquete matrixai verify paquete.zip --retrain ``` Those are the commands published with the case, plus one that is not: **`zip`**. `matrixai export-bundle` writes a *directory* and has no zip option (`--outdir` is the only output flag), so the zip has to be made separately — in this project it is made by the build script, not by the CLI. Three more notes: - `entrada.json` is the case's own example input, `{"celsius": 0}` — write that one line, or point `--input` at `example_input.json` from this package (`{"celsius": 50.0}`) and expect `323.1500059366226`. - `--dataset-manifest` is how the training run learns the generation seed, the mode and how many rows existed *before* the split. Without it the package cannot claim its dataset can be regenerated, and it says so instead of pretending. - **Verify the zip, not the directory you just built.** A freshly built bundle directory still has `datos/` sitting next to it, and a retrain will quietly use it — which is not the thing you wanted to test. ### 3c. The recipe is the data ``` predicted_kelvin = 1*celsius + 273.15 ``` That single line (`data_recipe.txt`, sha256 `7f332dbb7aa59ebf7518dba328d62599febc2e693fd528392dee33c43fe729de`) is what the 300 generated rows came from — 240 of which became the training CSV and 60 the eval CSV (§4). It ships inside the package, and `verify` uses it to **regenerate** the dataset and compare the digest — which is what stage R1 is. ### 3d. What you need Python 3.10 or newer (`requires-python = ">=3.10"`) and `pip install "matrixai-core[export]"`. No account, no LLM key, no GPU. It runs on a laptop. --- ## 4. Measured numbers, with where each one comes from Everything below is either copied from a file in this package or was measured by re-running the published files; each row says which. Nothing is rounded for presentation except where the source itself printed it rounded. ### Verification — `matrixai verify paquete.zip --retrain` Literal output of the run that built this package, from [`salida.txt`](https://matrixaistudio.org/casos/kelvin/salida.txt) step 5: ``` manifest PASS R1 PASS training PASS R3 PASS ``` For what you will get instead, and why, see §3a. | Stage | What it checks | |---|---| | `manifest` | Every artifact in the package matches the sha256 the manifest declares — and nothing travels that the manifest does not name. | | `R1` | The dataset rebuilt from the recipe has the full sha256 the package declares. | | `training` | Training runs to completion with what the package carries inside. | | `R3` | The metrics of that fresh run fall inside their declared tolerance. | Each stage reports one of four verdicts — `PASS`, `FAIL`, `INCOMPARABLE` (*"I could not check this"*), `NOT_RUN` — described at [matrixaistudio.org/manual/proof](https://matrixaistudio.org/manual/proof). ### Model metrics | Metric | Value | Split | Dataset sha256 | Source file | |---|---|---|---|---| | R² | `1.0` (printed as `1.000000`) | validation | `5827acf2b2b6058d90b6a70d959d882db080bac3ceb6bbe797a801d27201c180` | `reproduce.json` → `metrics[0]`; also `salida.txt` step 2 | | MAE | `6.505213034913027e-17` | validation | same | `reproduce.json` → `metrics[1]`; `salida.txt` prints `6.50521e-17` | | RMSE | `9.765943671129598e-17` | — | — | `params.best.json` → `metrics.rmse` | | Validation loss | `9.537365578767625e-33` | validation | — | `params.best.json`; `salida.txt` prints `0.000000` | Both published metrics declare `tolerance_abs: 0.0` and `tolerance_scope: "same_environment_same_seed"` — that is, the tolerance is only claimed for a rerun in the *same environment with the same seeds*, not for your machine in general. Both also carry `"incomplete": ["evaluator", "evaluator_version", "tolerance_rel"]` — the package names, itself, the three fields it could not fill. R² of exactly 1.0 and a MAE of 6.5e−17 mean the residuals are at the floating-point floor. That is what *should* happen when the data was generated by a rule the model can represent exactly. See §7. **Which rows those metrics are over.** The package does not record it (`artifacts.dataset.rows_used` is `null`), and a `null` is an answer, not a zero — but it is not a mystery either, and saying only "not recorded" would leave you believing it cannot be known. The 300 generated rows are split into 240 (`…-train.csv`) + 60 (`…-eval.csv`) by `generate-dataset` (`salida.txt` step 1). `model.mxtrain` reads **only** the 240-row train CSV and splits *that* again `train=0.8 validation=0.2 seed=42` (§5). Measured by re-running the published commands and reading `runs/v1/training_trace.json`: `rows_train: 192`, `rows_validation: 48`, `source: datos/celsiustokelvin-synthetic-train.csv`. **R² and MAE are over those 48 rows.** The 60-row eval CSV is generated and then never used by this pipeline; no metric on it is published. ### Data | | | |---|---| | Rows generated | 300 (`reproduce.json` → `artifacts.dataset.rows`) | | Train / eval split files | 240 rows / 60 rows (`salida.txt` step 1) | | Rows the model actually trained on / validated on | 192 / 48 — measured, `runs/v1/training_trace.json` of a re-run; the package itself does not record it | | Dataset sha256 | `5827acf2b2b6058d90b6a70d959d882db080bac3ceb6bbe797a801d27201c180` | | Rows the metric was computed over | `null` (`artifacts.dataset.rows_used`) — not recorded in the package | | Seeds | dataset `20260825`, split `42`, init `42` | | Generation mode | `coherent` | | Epochs declared | `200` (`RUN EPOCHS 200` in `model.mxtrain`) | | Epochs actually run | `null` for both `epochs_effective` and `epochs_ran` — not recorded in the package. `salida.txt` reports `Best epoch: 25`; measured on a re-run, the trace holds all 200 epochs, so all 200 ran and 25 was the best of them | | Backend / device | `stdlib` / `cpu` | ### ONNX equivalence From `export_manifest.json` → `equivalence_check`: ``` passed: true atol: 1e-05 rtol: 0.0001 max_abs_diff: 3.566741946237073e-08 max_rel_diff: 8.65425281073602e-08 n_samples: 20 n_outputs_per_sample: 1 ``` What that actually compares: 20 **already-normalized** input vectors drawn from `np.random.default_rng(seed)` with `seed = 42` by default — the *same* 20 points on every export, not a fresh sample — run through the core's own reference runtime and through onnxruntime, compared on the model's **raw** output, before `predict.py` puts it back on the kelvin scale (`matrixai/export/equivalence.py`, AGPL, in the core repo). Scaled up by the ×250 that denormalization applies, 3.57e−08 is about 8.9e−06 K — that conversion is arithmetic done here, not a published figure. It is a spot check on 20 fixed samples, not a proof of equivalence. ### Identity and environment | | | |---|---| | Project | `CelsiusToKelvin` | | Model hash | `mxai_fdbe5973e98a1123` | | Parameter schema hash | `params_e0a02353b5d9884e` | | Parameter set | `v1_best` | | Function kind | `linear_regression` (`model_manifest.json`) | | ONNX opset | 17 | | Exported at | `2026-08-26T15:55:05.170951+00:00` | | Manifest sha256 | `08f9d835c02574c4d6429063fa4787d08ae6bde432d9de56b3ed9cd97f30f465` | | Run capture sha256 | `141ef41bb4b29382561fe264dbdcd1d57e8ca70e7a0f1da8eca5432b251e04e8` | | Environment sha256 | `f73756abfe0d07dffabba936f5e66218dd56e5bf3ebc8ee0c507ac69bb3f2f15` | | Built with | matrixai-core `1.6.0`, CPython `3.12.3`, Linux `6.8.0-137-generic` x86_64 | | Packages | numpy `2.4.4`, onnx `1.21.0`, onnxruntime `1.26.0`, torch `2.11.0+cpu` | `reproduce.json` carries a sha256 for **16 of the 17 files** that travel, and reports `files_covered: 16`, `missing: []`, `conflicts: []`. Recomputed here: all 16 match. The one it does not cover is itself — a manifest cannot carry its own digest inside, so its integrity is declared by `manifest_sha256`, which is computed by whoever builds the package. What `verify` *does* catch, and why `manifest PASS` is worth something, is any extra file the manifest does not name (see the `__pycache__` note in §2). The `.mxai`, `.mxtrain` and recipe published at `matrixaistudio.org/casos/kelvin/` are byte-identical to `model.mxai`, `model.mxtrain` and `data_recipe.txt` here — same three sha256 values, checked with `sha256sum`. --- ## 5. What the model is The whole model, from `model.mxai`: ``` PROJECT CelsiusToKelvin VECTOR Reading[1] celsius: Scalar[-50, 150] END PARAM W1 Vector[1] END PARAM b1 Scalar END FUNCTION PredictedKelvinModel predicted_kelvin: Scalar = linear(W1 * Reading + b1) END GRAPH Reading -> PredictedKelvinModel END ``` One weight and one bias. The training contract (`model.mxtrain`) trains it with MSE and SGD at learning rate 0.5, batch 8, split 80/20 with seed 42, for up to 200 epochs, reading `datos/celsiustokelvin-synthetic-train.csv`. --- ## 6. The two numbers it learned — check them by hand `params.best.json` holds the entire trained model: ```json "W1": {"values": [0.8]}, "b1": {"values": 0.0926} ``` Those look like they have nothing to do with 273.15, and that is only because they live in normalized space. `inference_spec.json` gives the two ranges, and `predict.py` applies them: input `(C − (−50)) / 200`, output `y × 250 + 200`. Substitute: ``` K = (0.8 · (C + 50) / 200 + 0.0926) · 250 + 200 = 1.0 · (C + 50) + 23.15 + 200 = C + 273.15 ``` In decimal the slope comes out 1 and the offset 273.15 exactly. Stored in float32 they are `0.800000011920929` and `0.09260000288486481`, so the trained parameters are the physical constant to within float32 — and that "to within" is the whole source of the ~1e−06 residuals in §1. Recomputing the four predictions in float32 from those two numbers alone reproduces all four published outputs digit for digit — checked here with numpy, and something you can redo in five lines. That is the strongest statement this case can make, and it needs no metric. --- ## 7. What this does NOT prove Same weight as everything above. **It does not prove the model is good at anything.** It converts degrees. It is a control case: it exists so you can check the machinery, not to solve a problem. **R² = 1.0 is not an achievement.** The data was generated from `predicted_kelvin = 1*celsius + 273.15`, and the model is a single linear unit — the exact shape of the rule that made the data. A perfect fit is what should happen. It says nothing about how MatrixAI handles a problem where the answer is not already inside the model's hypothesis class. **The 48 validation rows came out of the same recipe as the training rows.** They are held out from the fitting, but not from the rule: there is no data of any other provenance anywhere in this case. R² = 1.0 says nothing about generalizing beyond that one line. **The data is synthetic.** No thermometer was involved, no observation of the physical world, no external dataset. Every row came out of that one line. **It says nothing about generalization, and the range is declared, not enforced.** Outside [−50, 150] °C the input is clipped to the edge (§2), so the package will answer for 1000 °C with the answer for 150 °C. Nothing here was measured outside the declared range. **Digests are not signatures.** `reproduce.json` states this in its own words: > This manifest proves the package is internally consistent and reproducible: > every artifact needed to rebuild this model travels here with its digest, and > each one matches the authoritative capture the core recorded while training it. > It does NOT prove authorship or authenticity: signatures are out of scope here. There is no signed receipt in this package. Anybody can change every file and recompute every digest; what the manifest catches is a package that is internally inconsistent, not one that was rebuilt from scratch by someone else. **`training PASS` means training completed, not that it was good.** And `R3` only compares a fresh run's metrics against a tolerance the package scopes to `same_environment_same_seed`. Off the machine that built it, `R3` comes back `INCOMPARABLE` — measured, see §3a — and that is the honest verdict, not a degraded one. **Some numbers in this package are absent, not zero.** `params.best.json` carries `accuracy: 0.0`, `macro_f1: 0.0`, `macro_precision: 0.0`, `macro_recall: 0.0`. Those are classification fields on a regression model. They are placeholders, not measurements — do not read them as "0 % accurate". Likewise `rows_used`, `epochs_effective`, `epochs_ran`, `evaluator` and `evaluator_version` are `null`: the package does not record them, and does not pretend to. **This is not a conformity assessment of anything.** Not a certification, not an audit in any regulatory sense, and not evidence for any regulatory regime in any jurisdiction. It is a package that can be rebuilt and checked, which is a different and much smaller claim. **This repository deliberately carries no `model-index` metrics block.** That widget presents a number as a checked result, and an R² of 1.0 on data generated from the model's own hypothesis class is not a result — it is arithmetic. **For the case that does not come out clean**, see the third one, [Will it rain tomorrow?](https://matrixaistudio.org/casos#lluvia): it fits *real* observations imperfectly (accuracy 0.762557), and separately it leaves two of the four verification stages `INCOMPARABLE` and a third `NOT_RUN`, because its data cannot be regenerated from any recipe. Those are two different limits — one of fit, one of verification — and it has both. --- ## 8. Files in this package | File | What it is | |---|---| | `model.mxai` | The model definition (source of truth) | | `model.mxtrain` | Training contract: dataset, split, loss, optimizer, epochs | | `params.best.json` | The trained weights — one weight, one bias | | `model.onnx` | ONNX model, opset 17 | | `model_manifest.json` | Model metadata, hashes, backend contract | | `export_manifest.json` | Export metadata, tolerance, equivalence check | | `data_recipe.txt` | The rule the training data was generated from | | `reproduce.json` | Whether this can be rebuilt, and the digest of every other artifact | | `inference_spec.json` | How a raw record maps to the model input | | `predict.py` | Standalone wrapper: raw values in, prediction out | | `requirements.txt` | numpy + onnxruntime, and nothing else | | `example_input.json` / `expected_output.json` | A runnable example and what it should print | | `space/` | A Hugging Face Space template the exporter emits | | `README.md` | The bundle's own readme | Seventeen files; `reproduce.json` covers the other sixteen. --- ## 9. License and links **AGPL-3.0.** The model, the package and the MatrixAI core are all AGPL-3.0-only; `predict.py` carries the SPDX header inside the bundle. - This case, with every artifact to download (`paquete.zip`, `kelvin.mxai`, `kelvin.mxtrain`, `receta.txt`, the three example inputs, `reproduce.json`, and `salida.txt` with the literal output of all five steps): - The other two cases: [Readmission risk (synthetic)](https://matrixaistudio.org/casos#clinico) · [Will it rain tomorrow?](https://matrixaistudio.org/casos#lluvia) - What a receipt and the four verification stages do and do not hold up: - Site: - Source (AGPL-3.0): - PyPI: — `pip install "matrixai-core[export]"` MatrixAI builds, trains, audits and deploys a neural network from a plain-language description or a CSV, and emits a cryptographic receipt at each step. It is open source under AGPL-3.0, and every exported package predicts with no MatrixAI installed. Built with matrixai-core `1.6.0`; the current release is `1.7.0`. A different version produces a different `environment_sha256` and therefore a different `R3` verdict — see §3a. That is worth knowing rather than hiding. --- ## Make your own This model is not a demo to look at — it is a case you can redo, and the tool that produced it is free. MatrixAI Studio turns a written description, or a CSV you already have, into a neural network you can question: it builds it, trains it, and emits a cryptographic receipt at every step, so anybody can re-check what you claim. It runs on your own machine — no account, no cloud, no API key. - **Download it and build your own:** - **Try it in the browser first, installing nothing:** - **This case, with every file and command:** - **What it does — and what it does not:** - **How the receipts work:** - **Source (AGPL-3.0):** · **PyPI:** `pip install matrixai-core` If something on this page is not true, it should be visible from the outside. That is the whole point of publishing the package and not just the numbers.