focus-data-toolkit 0.11.0__py3-none-any.whl
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.
- focus_data_toolkit/__init__.py +69 -0
- focus_data_toolkit/__main__.py +6 -0
- focus_data_toolkit/_version.py +8 -0
- focus_data_toolkit/cli.py +968 -0
- focus_data_toolkit/context/__init__.py +88 -0
- focus_data_toolkit/context/billing.py +54 -0
- focus_data_toolkit/context/provider.py +90 -0
- focus_data_toolkit/convert/__init__.py +708 -0
- focus_data_toolkit/convert/billing_period.py +65 -0
- focus_data_toolkit/convert/contract_applied.py +235 -0
- focus_data_toolkit/convert/contract_commitment.py +182 -0
- focus_data_toolkit/convert/cost_and_usage.py +179 -0
- focus_data_toolkit/convert/detect.py +39 -0
- focus_data_toolkit/convert/invoice_detail.py +199 -0
- focus_data_toolkit/convert/streaming.py +1030 -0
- focus_data_toolkit/errors.py +145 -0
- focus_data_toolkit/focus_json.py +68 -0
- focus_data_toolkit/generators/__init__.py +61 -0
- focus_data_toolkit/generators/_shim.py +43 -0
- focus_data_toolkit/generators/engine/__init__.py +14 -0
- focus_data_toolkit/generators/engine/context.py +12 -0
- focus_data_toolkit/generators/engine/determinism.py +117 -0
- focus_data_toolkit/generators/engine/json_focus.py +63 -0
- focus_data_toolkit/generators/engine/ladder.py +71 -0
- focus_data_toolkit/generators/engine/scenarios_core.py +380 -0
- focus_data_toolkit/generators/engine/serialize.py +151 -0
- focus_data_toolkit/generators/generate_aws_focus_1_2.py +19 -0
- focus_data_toolkit/generators/generate_aws_focus_1_3.py +20 -0
- focus_data_toolkit/generators/generate_azure_focus_1_2.py +17 -0
- focus_data_toolkit/generators/generate_azure_focus_1_3.py +17 -0
- focus_data_toolkit/generators/generate_gcp_focus_1_2.py +17 -0
- focus_data_toolkit/generators/generate_gcp_focus_1_3.py +17 -0
- focus_data_toolkit/generators/providers/__init__.py +29 -0
- focus_data_toolkit/generators/providers/aws.py +186 -0
- focus_data_toolkit/generators/providers/azure.py +191 -0
- focus_data_toolkit/generators/providers/gcp.py +194 -0
- focus_data_toolkit/generators/providers/profile.py +123 -0
- focus_data_toolkit/generators/scenarios.py +178 -0
- focus_data_toolkit/generators/versions/__init__.py +17 -0
- focus_data_toolkit/generators/versions/adapter.py +41 -0
- focus_data_toolkit/generators/versions/v1_2.py +111 -0
- focus_data_toolkit/generators/versions/v1_3.py +154 -0
- focus_data_toolkit/io/__init__.py +1 -0
- focus_data_toolkit/io/atomic_writer.py +462 -0
- focus_data_toolkit/io/csv_io.py +128 -0
- focus_data_toolkit/io/parquet_io.py +528 -0
- focus_data_toolkit/io/records.py +92 -0
- focus_data_toolkit/io/row_source.py +117 -0
- focus_data_toolkit/lifecycle.py +342 -0
- focus_data_toolkit/manifest.py +114 -0
- focus_data_toolkit/model/__init__.py +43 -0
- focus_data_toolkit/model/capabilities.py +66 -0
- focus_data_toolkit/model/focus_1_4_decimal_scale.json +10 -0
- focus_data_toolkit/model/focus_1_4_model.json +1913 -0
- focus_data_toolkit/model/focus_1_4_servicesubcategory.json +84 -0
- focus_data_toolkit/model/focus_json_keys.py +112 -0
- focus_data_toolkit/model/iso_4217_currencies.json +23 -0
- focus_data_toolkit/model/json_schema_check.py +205 -0
- focus_data_toolkit/model/json_schemas/allocatedmethoddetailsobjectschema.json +82 -0
- focus_data_toolkit/model/json_schemas/commitmentprogrameligibilitydetailsobjectschema.json +41 -0
- focus_data_toolkit/model/json_schemas/contractappliedobjectschema.json +104 -0
- focus_data_toolkit/model/json_schemas/contractcommitmentapplicabilityobjectschema.json +290 -0
- focus_data_toolkit/model/json_schemas/json_schemas_provenance.json +38 -0
- focus_data_toolkit/model/model_provenance.json +58 -0
- focus_data_toolkit/model/validator.py +498 -0
- focus_data_toolkit/modes.py +18 -0
- focus_data_toolkit/official_validator.py +61 -0
- focus_data_toolkit/progress.py +89 -0
- focus_data_toolkit/provenance.py +106 -0
- focus_data_toolkit/py.typed +1 -0
- focus_data_toolkit/runtime.py +243 -0
- focus_data_toolkit/schema/__init__.py +17 -0
- focus_data_toolkit/schema/detection.py +274 -0
- focus_data_toolkit/schema/registry.py +127 -0
- focus_data_toolkit/storage/__init__.py +1 -0
- focus_data_toolkit/storage/external_index.py +99 -0
- focus_data_toolkit/storage/spill.py +150 -0
- focus_data_toolkit/studio/__init__.py +19 -0
- focus_data_toolkit/studio/app.py +467 -0
- focus_data_toolkit/studio/config.py +42 -0
- focus_data_toolkit/studio/frontend/app.js +214 -0
- focus_data_toolkit/studio/frontend/index.html +101 -0
- focus_data_toolkit/studio/frontend/style.css +60 -0
- focus_data_toolkit/studio/jobs.py +142 -0
- focus_data_toolkit/studio/preview.py +32 -0
- focus_data_toolkit/studio/security.py +125 -0
- focus_data_toolkit/studio/server.py +71 -0
- focus_data_toolkit/supplement/__init__.py +50 -0
- focus_data_toolkit/supplement/adapters/__init__.py +21 -0
- focus_data_toolkit/supplement/adapters/adapters_provenance.json +39 -0
- focus_data_toolkit/supplement/adapters/aws_invoice_summary.json +24 -0
- focus_data_toolkit/supplement/adapters/aws_savings_plans.json +31 -0
- focus_data_toolkit/supplement/adapters/azure_invoice.json +25 -0
- focus_data_toolkit/supplement/adapters/gcp_compute_commitments.json +28 -0
- focus_data_toolkit/supplement/adapters/registry.py +215 -0
- focus_data_toolkit/supplement/apply.py +318 -0
- focus_data_toolkit/supplement/gaps.py +219 -0
- focus_data_toolkit/supplement/kinds.py +118 -0
- focus_data_toolkit/supplement/loader.py +409 -0
- focus_data_toolkit/supplement/spec.py +74 -0
- focus_data_toolkit/supplement/validate.py +215 -0
- focus_data_toolkit/validate/__init__.py +15 -0
- focus_data_toolkit/validate/allocation.py +333 -0
- focus_data_toolkit/validate/bundle.py +254 -0
- focus_data_toolkit/validate/codes.py +93 -0
- focus_data_toolkit/validate/corrections.py +245 -0
- focus_data_toolkit/validate/reconciliation.py +98 -0
- focus_data_toolkit/validate/referential.py +289 -0
- focus_data_toolkit-0.11.0.dist-info/METADATA +519 -0
- focus_data_toolkit-0.11.0.dist-info/RECORD +116 -0
- focus_data_toolkit-0.11.0.dist-info/WHEEL +5 -0
- focus_data_toolkit-0.11.0.dist-info/entry_points.txt +2 -0
- focus_data_toolkit-0.11.0.dist-info/licenses/LICENSE +21 -0
- focus_data_toolkit-0.11.0.dist-info/licenses/LICENSES/CC-BY-4.0.txt +156 -0
- focus_data_toolkit-0.11.0.dist-info/licenses/NOTICE +60 -0
- focus_data_toolkit-0.11.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: focus-data-toolkit
|
|
3
|
+
Version: 0.11.0
|
|
4
|
+
Summary: Generate provider-realistic FOCUS 1.2/1.3 sample data (AWS, Azure, GCP) and convert it to the four FOCUS 1.4 datasets — strictly from source facts, completed by client supplements including native AWS/Azure/GCP exports — with schema detection, gap analysis, a cross-dataset validation gate and atomic writes.
|
|
5
|
+
Author: Guy-Hermann Adiko
|
|
6
|
+
License-Expression: MIT AND CC-BY-4.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/guymano/focus-data-toolkit
|
|
8
|
+
Project-URL: Source, https://github.com/guymano/focus-data-toolkit
|
|
9
|
+
Project-URL: Issues, https://github.com/guymano/focus-data-toolkit/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/guymano/focus-data-toolkit/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Documentation, https://github.com/guymano/focus-data-toolkit#readme
|
|
12
|
+
Keywords: finops,focus,billing,cost,sample-data
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
License-File: NOTICE
|
|
25
|
+
License-File: LICENSES/CC-BY-4.0.txt
|
|
26
|
+
Provides-Extra: parquet
|
|
27
|
+
Requires-Dist: pyarrow<26,>=15; extra == "parquet"
|
|
28
|
+
Requires-Dist: tzdata>=2024.1; sys_platform == "win32" and extra == "parquet"
|
|
29
|
+
Provides-Extra: scale
|
|
30
|
+
Requires-Dist: focus-data-toolkit[parquet]; extra == "scale"
|
|
31
|
+
Provides-Extra: validator
|
|
32
|
+
Requires-Dist: focus-validator<3,>=2.1; python_version >= "3.12" and extra == "validator"
|
|
33
|
+
Provides-Extra: studio
|
|
34
|
+
Requires-Dist: fastapi<1,>=0.110; extra == "studio"
|
|
35
|
+
Requires-Dist: uvicorn[standard]<1,>=0.29; extra == "studio"
|
|
36
|
+
Requires-Dist: python-multipart<1,>=0.0.9; extra == "studio"
|
|
37
|
+
Provides-Extra: studio-all
|
|
38
|
+
Requires-Dist: focus-data-toolkit[parquet,studio]; extra == "studio-all"
|
|
39
|
+
Provides-Extra: all
|
|
40
|
+
Requires-Dist: focus-data-toolkit[parquet,studio,validator]; extra == "all"
|
|
41
|
+
Provides-Extra: release
|
|
42
|
+
Requires-Dist: build<2,>=1; extra == "release"
|
|
43
|
+
Requires-Dist: twine<7,>=5; extra == "release"
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: focus-data-toolkit[parquet,release,studio]; extra == "dev"
|
|
46
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
47
|
+
Requires-Dist: pytest-cov<8,>=5; extra == "dev"
|
|
48
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
49
|
+
Requires-Dist: mypy<2.4,>=1.11; extra == "dev"
|
|
50
|
+
Requires-Dist: jsonschema<5,>=4.21; extra == "dev"
|
|
51
|
+
Requires-Dist: httpx<1,>=0.27; extra == "dev"
|
|
52
|
+
Dynamic: license-file
|
|
53
|
+
|
|
54
|
+
# focus-data-toolkit
|
|
55
|
+
|
|
56
|
+
Generate provider-realistic **FOCUS 1.2 / 1.3** sample data (AWS, Azure, GCP),
|
|
57
|
+
**migrate** a FOCUS 1.2/1.3 Cost and Usage table to **FOCUS 1.4**, optionally
|
|
58
|
+
**synthesize** the new 1.4 datasets for demos/tests, and **lint** the results —
|
|
59
|
+
in one dependency-free Python toolkit.
|
|
60
|
+
|
|
61
|
+
[FOCUS](https://focus.finops.org) (FinOps Open Cost and Usage Specification) is
|
|
62
|
+
the open standard for cloud cost and usage data. FOCUS 1.4 (June 2026) defines
|
|
63
|
+
four datasets: **Cost and Usage** (65 columns), **Contract Commitment**
|
|
64
|
+
(30 columns), **Billing Period** (6 columns, new) and **Invoice Detail**
|
|
65
|
+
(22 columns, new).
|
|
66
|
+
|
|
67
|
+
## What this toolkit does — and does not — claim
|
|
68
|
+
|
|
69
|
+
The toolkit is explicit about three very different operations:
|
|
70
|
+
|
|
71
|
+
- **Schema migration** — transforming columns/formats where the mapping is exact
|
|
72
|
+
or explicitly documented. *Cost and Usage* migrates 1.2/1.3 → 1.4 essentially
|
|
73
|
+
losslessly (1.4 only adds nullable columns and drops two deprecated ones).
|
|
74
|
+
- **Enrichment** — adding data from a complementary *authoritative* source
|
|
75
|
+
(e.g. a real invoice). Not yet ingested; the architecture reserves a place for it.
|
|
76
|
+
- **Synthetic projection** — inventing plausible values for demos/tests/learning.
|
|
77
|
+
These results are **never** presented as real financial facts or as fully
|
|
78
|
+
FOCUS-conformant.
|
|
79
|
+
|
|
80
|
+
The three new-in-1.4 datasets (**Billing Period**, **Invoice Detail**, and the
|
|
81
|
+
1.4-expanded **Contract Commitment**) contain **Mandatory columns that are
|
|
82
|
+
provider billing-system facts** — invoice status, payment terms, issuer-assigned
|
|
83
|
+
ids, provider record timestamps, commitment commercial terms. These are **not
|
|
84
|
+
present in, and not derivable from, a Cost and Usage table**. FOCUS 1.4 itself
|
|
85
|
+
treats them as provider-emitted (it adds an *Invoice Reconciliation* feature and
|
|
86
|
+
a *Rounding Variance Tolerance* appendix precisely because the issued invoice
|
|
87
|
+
legitimately differs from summed usage). So an aggregation of `BilledCost` is a
|
|
88
|
+
useful analytical summary — **not** a real invoice line.
|
|
89
|
+
|
|
90
|
+
A structurally valid file is **not** automatically FOCUS-conformant.
|
|
91
|
+
|
|
92
|
+
## Modes
|
|
93
|
+
|
|
94
|
+
| Mode | Behaviour |
|
|
95
|
+
|---|---|
|
|
96
|
+
| **`strict`** (default) | Never invents provider facts. A canonical FOCUS 1.4 dataset is produced only when every Mandatory non-nullable column has a factual lineage (observed / renamed / derived / enriched). From a Cost-and-Usage source that means **only Cost and Usage** is produced; the other three are reported `NOT_PRODUCED` in the manifest, with the exact blocking columns. |
|
|
97
|
+
| **`synthetic`** | Generates assumed values so you get all four datasets for demos/tests. Affected datasets are written with a `synthetic_` filename prefix and marked `PRODUCED_SYNTHETIC` in the manifest; the result is never labelled fully conformant. |
|
|
98
|
+
|
|
99
|
+
Every conversion writes a deterministic **manifest** (`focus_1_4_manifest.json`)
|
|
100
|
+
recording, per column, how each value was obtained
|
|
101
|
+
(`OBSERVED` / `RENAMED` / `DERIVED` / `ENRICHED` / `ASSUMED` / `UNAVAILABLE`).
|
|
102
|
+
|
|
103
|
+
The core package is **standard-library only** (Python ≥ 3.11).
|
|
104
|
+
|
|
105
|
+
## Installation
|
|
106
|
+
|
|
107
|
+
### Prerequisites
|
|
108
|
+
|
|
109
|
+
- **Python 3.11, 3.12 or 3.13** (the `validator` extra needs **3.12+**). Linux, macOS and
|
|
110
|
+
Windows are supported — CI tests Ubuntu and Windows across all three versions (see
|
|
111
|
+
[docs/compatibility.md](docs/compatibility.md)).
|
|
112
|
+
- `pip` (or [uv](https://docs.astral.sh/uv/)). **No compiler is needed**: the core package is
|
|
113
|
+
pure Python and standard-library only — zero runtime dependencies.
|
|
114
|
+
|
|
115
|
+
### Standard install
|
|
116
|
+
|
|
117
|
+
In a virtual environment (recommended):
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
python -m venv .venv
|
|
121
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
122
|
+
pip install focus-data-toolkit
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> **Until the first PyPI release**, install straight from GitHub instead:
|
|
126
|
+
> `pip install "git+https://github.com/guymano/focus-data-toolkit"`
|
|
127
|
+
> (append `@vX.Y.Z` to pin a tag once releases exist).
|
|
128
|
+
|
|
129
|
+
This installs the `focus-toolkit` command (alias of `python -m focus_data_toolkit`).
|
|
130
|
+
|
|
131
|
+
### Optional extras
|
|
132
|
+
|
|
133
|
+
The core covers generation, 1.2/1.3 → 1.4 conversion, client supplements + the AWS/Azure/GCP
|
|
134
|
+
provider-export adapters, bounded-memory CSV streaming, and every validation gate — all with
|
|
135
|
+
the standard library alone (the streaming state uses built-in `sqlite3`). Extras add:
|
|
136
|
+
|
|
137
|
+
| Command | Adds | Requires |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `pip install "focus-data-toolkit[parquet]"` | Parquet **input and output** (PyArrow; value-exact decimal128, Hive partitioning). On Windows, `tzdata` comes along automatically. | Python ≥ 3.11 |
|
|
140
|
+
| `pip install "focus-data-toolkit[validator]"` | the official FinOps `focus-validator`, used by `focus-toolkit validate --official` (rule models for FOCUS 1.2/1.3) | Python ≥ 3.12 |
|
|
141
|
+
| `pip install "focus-data-toolkit[all]"` | both of the above (on 3.11 the validator part is skipped automatically by its Python marker) | Python ≥ 3.11 |
|
|
142
|
+
|
|
143
|
+
### With uv
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
uv tool install focus-data-toolkit # standalone CLI on your PATH
|
|
147
|
+
uv add "focus-data-toolkit[parquet]" # as a project dependency
|
|
148
|
+
uvx --from focus-data-toolkit focus-toolkit --help # one-off run, nothing installed
|
|
149
|
+
# (--from is needed because the command name differs from the package name)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### From source
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
git clone https://github.com/guymano/focus-data-toolkit
|
|
156
|
+
cd focus-data-toolkit
|
|
157
|
+
pip install . # or: pip install ".[all]"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
For a development install (editable, with the test/lint toolchain), see
|
|
161
|
+
[Development](#development). Release artifacts (wheel, sdist) attached to GitHub Releases
|
|
162
|
+
ship with `SHA256SUMS`, two SBOM profiles and GitHub Artifact Attestations — verify a
|
|
163
|
+
downloaded wheel and install it directly:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# SHA256SUMS covers every release asset; check just the wheel's line:
|
|
167
|
+
grep "focus_data_toolkit-X.Y.Z-py3-none-any.whl" SHA256SUMS | sha256sum -c -
|
|
168
|
+
gh attestation verify focus_data_toolkit-X.Y.Z-py3-none-any.whl --repo guymano/focus-data-toolkit
|
|
169
|
+
pip install ./focus_data_toolkit-X.Y.Z-py3-none-any.whl
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
(To verify the whole asset set at once, download all files listed in `SHA256SUMS` into one
|
|
173
|
+
directory and run `sha256sum -c SHA256SUMS` there.)
|
|
174
|
+
|
|
175
|
+
### Verify the installation
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
focus-toolkit --help # CLI is on the PATH
|
|
179
|
+
# venv/project installs only — a `uv tool install` lives in its own isolated
|
|
180
|
+
# environment, so check it via the tool env instead:
|
|
181
|
+
python -c "import focus_data_toolkit as f; print(f.__version__)"
|
|
182
|
+
# uv tool run --from focus-data-toolkit python -c "import focus_data_toolkit as f; print(f.__version__)"
|
|
183
|
+
# end-to-end smoke test (deterministic, ~1 s):
|
|
184
|
+
focus-toolkit generate --provider aws --focus-version 1.3 --rows 10 --out /tmp/fdt-smoke
|
|
185
|
+
focus-toolkit convert --cost-and-usage /tmp/fdt-smoke/focus_1_3_cost_and_usage_aws.csv \
|
|
186
|
+
--out /tmp/fdt-smoke/focus-1.4
|
|
187
|
+
# exit code 3 is expected: the strict result is intentionally incomplete without supplements
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Upgrade / uninstall
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
pip install -U focus-data-toolkit
|
|
194
|
+
pip uninstall focus-data-toolkit
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Quickstart
|
|
198
|
+
|
|
199
|
+
Install first (see [Installation](#installation) above), then:
|
|
200
|
+
|
|
201
|
+
### 1. Generate FOCUS 1.2/1.3 sample data
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
focus-toolkit generate --provider aws --focus-version 1.3 --rows 1000 --seed 1302 --out ./out
|
|
205
|
+
# -> out/focus_1_3_cost_and_usage_aws.csv (65 columns)
|
|
206
|
+
# -> out/focus_1_3_contract_commitment_aws.csv (13 columns)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Providers: `aws`, `azure`, `gcp`. Versions: `1.2`, `1.3`. Same rows+seed →
|
|
210
|
+
byte-identical output.
|
|
211
|
+
|
|
212
|
+
### 2. Convert towards FOCUS 1.4
|
|
213
|
+
|
|
214
|
+
**Strict (default)** — migrate what is genuinely migratable:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
focus-toolkit convert \
|
|
218
|
+
--cost-and-usage out/focus_1_3_cost_and_usage_aws.csv \
|
|
219
|
+
--out ./focus-1.4
|
|
220
|
+
# -> focus-1.4/focus_1_4_cost_and_usage.csv (65 columns)
|
|
221
|
+
# -> focus-1.4/focus_1_4_manifest.json (why the other 3 were NOT produced)
|
|
222
|
+
# exit code 3: strict result is intentionally incomplete
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Synthetic** — also generate the provider-emitted datasets for demos/tests:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
focus-toolkit convert \
|
|
229
|
+
--cost-and-usage out/focus_1_3_cost_and_usage_aws.csv \
|
|
230
|
+
--contract-commitment out/focus_1_3_contract_commitment_aws.csv \
|
|
231
|
+
--out ./focus-1.4 --mode synthetic
|
|
232
|
+
# -> synthetic_focus_1_4_cost_and_usage.csv (migration + assumed InvoiceDetailId back-link)
|
|
233
|
+
# -> synthetic_focus_1_4_contract_commitment.csv (assumed terms)
|
|
234
|
+
# -> synthetic_focus_1_4_billing_period.csv (assumed status/timestamps)
|
|
235
|
+
# -> synthetic_focus_1_4_invoice_detail.csv (assumed invoice facts)
|
|
236
|
+
# -> focus_1_4_manifest.json
|
|
237
|
+
# exit code 4: synthetic result contains ASSUMED values
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
In synthetic mode Cost and Usage is also `synthetic_`-prefixed, because its
|
|
241
|
+
`InvoiceDetailId` back-links to the (synthetic) Invoice Detail — every other
|
|
242
|
+
column is a faithful migration, as the manifest's per-column lineage records. In
|
|
243
|
+
strict mode Cost and Usage is emitted unprefixed with its `InvoiceDetailId` left null.
|
|
244
|
+
|
|
245
|
+
Works the same on your **own** FOCUS 1.2/1.3 exports — the source version is
|
|
246
|
+
detected from the CSV header. CLI exit codes: `0` success without assumptions ·
|
|
247
|
+
`1` lint violation · `2` invalid arguments · `3` incomplete strict result ·
|
|
248
|
+
`4` synthetic result produced with assumptions.
|
|
249
|
+
|
|
250
|
+
### 3. Lint
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# built-in FOCUS 1.4 structural + semantic linter (not a full conformance validator)
|
|
254
|
+
focus-toolkit validate focus-1.4/focus_1_4_cost_and_usage.csv --dataset cost-and-usage
|
|
255
|
+
|
|
256
|
+
# official FinOps validator (optional extra, requires Python >= 3.12; supports 1.2/1.3)
|
|
257
|
+
pip install "focus-data-toolkit[validator]"
|
|
258
|
+
focus-toolkit validate --official --focus-version 1.2.0.1 my_focus_1_2_export.csv
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The official FinOps validator ships rule models for FOCUS 1.2/1.3 and does **not**
|
|
262
|
+
yet support 1.4. Until it does, the built-in linter provides a structural +
|
|
263
|
+
semantic check only — it cannot certify full FOCUS 1.4 conformance.
|
|
264
|
+
|
|
265
|
+
### Working with real client data (0.3.0 / P1)
|
|
266
|
+
|
|
267
|
+
For consolidated, multi-provider, multi-issuer, multi-currency exports:
|
|
268
|
+
|
|
269
|
+
- **Schema detection** identifies the dataset *and* version (1.2/1.3/1.4) with a confidence,
|
|
270
|
+
and reports missing / extension (`x_`) / unknown columns. Strict mode refuses an ambiguous
|
|
271
|
+
source; force it with `--source-version` / `--source-dataset`.
|
|
272
|
+
- **Grouping keys** use the full billing grain (issuer, invoice, account, currency, period,
|
|
273
|
+
charge category), so lines from different issuers/accounts/currencies are never merged.
|
|
274
|
+
Locally generated ids are `x_fdt_…`-prefixed and never presented as issuer-assigned.
|
|
275
|
+
- **Cross-dataset validation** (`validate_dataset_bundle`) checks referential integrity,
|
|
276
|
+
currency/period/issuer coherence, invoice reconciliation (with a rounding tolerance),
|
|
277
|
+
Split Cost Allocation, and correction/lifecycle integrity — separately from the
|
|
278
|
+
per-dataset linter. It runs as a **publication gate** on every conversion (eager and
|
|
279
|
+
streaming): an ERROR refuses to publish, the outcome is recorded in the manifest's
|
|
280
|
+
`bundle_validation` section, and `--no-validate` skips it (the skip is recorded too).
|
|
281
|
+
In the streaming path the checks re-read the staged files in bounded memory, spilling
|
|
282
|
+
per-key lookup state to a scratch SQLite database past a threshold.
|
|
283
|
+
- **Atomic writes**: output appears only after validation succeeds and checksums + manifest
|
|
284
|
+
are written; nothing partial is left on error. `--on-exists refuse|replace|version`.
|
|
285
|
+
A `replace` swap is **journaled**: if the process dies between its two renames, the next
|
|
286
|
+
publish to the same destination (or `focus-toolkit clean --out DIR`) reads the journal and
|
|
287
|
+
rolls the fully staged result forward — or the previous result back — so the destination
|
|
288
|
+
is never left missing. `clean` also removes orphan `.output.tmp-*` / `.trash-*` leftovers.
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
from focus_data_toolkit import convert_to_focus_1_4, detect_focus_schema, validate_bundle
|
|
292
|
+
|
|
293
|
+
detection = detect_focus_schema(headers) # dataset, version, confidence, ...
|
|
294
|
+
report = validate_bundle({"Cost and Usage": cu_rows, "Invoice Detail": invd_rows})
|
|
295
|
+
print(report.ok, report.counts()) # cross-dataset findings, JSON-serialisable
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Scale: streaming conversion and Parquet (0.3.0 / P1 Phase B)
|
|
299
|
+
|
|
300
|
+
For large client files, `convert_files` streams the Cost and Usage file **once** and stages
|
|
301
|
+
Invoice Detail aggregation / Billing Period dedup in a throwaway SQLite database, so peak
|
|
302
|
+
memory stays **flat regardless of row count** — a constant ~64 MB peak process RSS whether
|
|
303
|
+
converting 50k or 300k rows (6× the rows, ×1.05 the memory; `tools/benchmark_streaming.py`).
|
|
304
|
+
Its output is **byte-identical** to the in-memory path — both call the same pure
|
|
305
|
+
per-row/per-group functions and the same manifest assembler.
|
|
306
|
+
|
|
307
|
+
Sources may be **CSV (gzip ok) or Parquet** — the format is sniffed per file (magic bytes,
|
|
308
|
+
extension as fallback), so `--cost-and-usage export.parquet` works everywhere a CSV does
|
|
309
|
+
(`convert`, `gaps`, `supplements validate`), in both the eager and streaming paths.
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
# bounded-memory streaming to CSV (recommended for large exports)
|
|
313
|
+
focus-toolkit convert --cost-and-usage huge_cost_and_usage.csv.gz --out ./focus-1.4 \
|
|
314
|
+
--mode synthetic --stream
|
|
315
|
+
|
|
316
|
+
# Parquet output with exact decimal128 (requires the [parquet] extra)
|
|
317
|
+
focus-toolkit convert --cost-and-usage huge_cost_and_usage.csv --out ./focus-1.4 \
|
|
318
|
+
--mode synthetic --output-format parquet
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Gzip input is auto-detected. Exactness contract: **CSV is byte-exact** (the literal), while
|
|
322
|
+
**Parquet is decimal-value-exact** — decimal columns are written as `decimal128(precision,
|
|
323
|
+
scale)` (never binary float; a value needing more scale than the column allows raises with its
|
|
324
|
+
line number instead of rounding silently), dates as UTC timestamps, JSON/strings verbatim.
|
|
325
|
+
|
|
326
|
+
**Partitioning & compression** (Parquet): `--partition-by` writes the Cost and Usage dataset as
|
|
327
|
+
a Hive-partitioned tree on low-cardinality String / Date-Time columns; `--compression`
|
|
328
|
+
(snappy default / zstd / gzip / none) and `--target-file-size` (roll to a new part file per
|
|
329
|
+
partition) tune the layout:
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
focus-toolkit convert --cost-and-usage huge_cost_and_usage.csv --out ./focus-1.4 \
|
|
333
|
+
--mode synthetic --output-format parquet \
|
|
334
|
+
--partition-by BillingCurrency,BillingPeriodStart --compression zstd --target-file-size 128MB
|
|
335
|
+
# -> synthetic_focus_1_4_cost_and_usage/BillingCurrency=USD/BillingPeriodStart=.../part-0.parquet
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
The partition columns are stored in the paths (standard Hive), so any `pyarrow.dataset` reader
|
|
339
|
+
reconstructs full rows; a too-high-cardinality key is warned about and, past a hard cap,
|
|
340
|
+
refused (nothing partial is published).
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from focus_data_toolkit import convert_files
|
|
344
|
+
|
|
345
|
+
out = convert_files("huge_cost_and_usage.csv.gz", "./focus-1.4",
|
|
346
|
+
mode="synthetic", output_format="parquet",
|
|
347
|
+
partition_by=["BillingCurrency"]) # -> published Path
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`pip install "focus-data-toolkit[parquet]"` for Parquet; streaming CSV needs no extra
|
|
351
|
+
(the external state uses the standard-library `sqlite3`).
|
|
352
|
+
|
|
353
|
+
### Synthetic scenarios (SCA, corrections, billing lifecycle)
|
|
354
|
+
|
|
355
|
+
Deterministic, provider-agnostic scenario builders produce self-consistent data for tests and
|
|
356
|
+
demos, and typed lifecycle checks validate the full snapshot chains — status transitions plus
|
|
357
|
+
chain structure (id/order uniqueness, `previous_instance_id` resolution, cycle detection,
|
|
358
|
+
`last_updated` monotonicity, closed-instance immutability; `FDT-LIFE-001..006`):
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
from focus_data_toolkit.generators.scenarios import (
|
|
362
|
+
split_allocation_group, correction_set, billing_lifecycle_instances,
|
|
363
|
+
)
|
|
364
|
+
from focus_data_toolkit import check_dataset_instances, validate_bundle
|
|
365
|
+
|
|
366
|
+
alloc = split_allocation_group("origin-1", "100.00", weights=[3, 2, 1]) # ratios sum to 1,
|
|
367
|
+
validate_bundle({"Cost and Usage": alloc}).ok # costs sum to origin
|
|
368
|
+
|
|
369
|
+
corr = correction_set("chg-1", "100.00", ["-30.00"]) # original + signed Correction line,
|
|
370
|
+
validate_bundle({"Cost and Usage": corr}).ok # running net recorded in x_NetCharge
|
|
371
|
+
|
|
372
|
+
check_dataset_instances(billing_lifecycle_instances()) # [] — chains + transitions all valid
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Python API
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
from focus_data_toolkit import convert_to_focus_1_4, lint_focus_1_4_structure
|
|
379
|
+
from focus_data_toolkit.convert import read_csv_rows
|
|
380
|
+
from focus_data_toolkit.modes import Mode
|
|
381
|
+
|
|
382
|
+
result = convert_to_focus_1_4(read_csv_rows("focus_1_3_cost_and_usage.csv")) # strict
|
|
383
|
+
print(result.coverage) # ('Cost and Usage',)
|
|
384
|
+
print(result.not_produced) # ('Contract Commitment', 'Billing Period', 'Invoice Detail')
|
|
385
|
+
print(result.manifest["datasets"]["Invoice Detail"]["blocking_columns"])
|
|
386
|
+
|
|
387
|
+
result = convert_to_focus_1_4(read_csv_rows("focus_1_3_cost_and_usage.csv"), mode=Mode.SYNTHETIC)
|
|
388
|
+
rows = result.datasets["Invoice Detail"]
|
|
389
|
+
print(lint_focus_1_4_structure("Invoice Detail", rows).levels_passed)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Studio (local web UI)
|
|
393
|
+
|
|
394
|
+
For a no-CLI experience, launch the **Studio** — a local web app over the same engine:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
pip install "focus-data-toolkit[studio]" # or [studio-all] to also read/write Parquet
|
|
398
|
+
focus-toolkit ui # opens http://127.0.0.1:8765/?token=…
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
- **Local & private:** binds `127.0.0.1` by default, requires a fresh per-start **token** on every
|
|
402
|
+
request, validates `Host`/`Origin` (anti DNS-rebinding / CSRF), and confines file access to an
|
|
403
|
+
allowlisted `--root`. No telemetry, no external upload — data never leaves the machine.
|
|
404
|
+
- **Workflow:** pick a file already under `--root` (best for large files), upload a small/medium
|
|
405
|
+
file (capped), or generate synthetic test data → **Detect** → **Convert** with live per-phase
|
|
406
|
+
progress and **Cancel** → preview a **sampled** page (the full file is never loaded) → download
|
|
407
|
+
datasets, the manifest, diagnostics (JSON/CSV), `SHA256SUMS`, and an HTML summary.
|
|
408
|
+
- **Same Core:** every operation drives the same SDK the CLI and Runner use, so the Studio's
|
|
409
|
+
manifests, diagnostics and checksums are identical to a CLI run.
|
|
410
|
+
- Exposing it on a network needs `--allow-remote` (the token is still required). Generation is
|
|
411
|
+
capped in the UI (use the CLI/Runner for very large synthetic sets). See
|
|
412
|
+
[docs/studio.md](docs/studio.md).
|
|
413
|
+
|
|
414
|
+
## Runner (Docker / OCI)
|
|
415
|
+
|
|
416
|
+
For production, automation and large volumes, the toolkit ships as a container image whose
|
|
417
|
+
entrypoint **is** the `focus-toolkit` CLI — a container run is exactly a CLI run (same
|
|
418
|
+
manifests, diagnostics, checksums and exit codes; no FOCUS logic is duplicated). It is
|
|
419
|
+
**batch-only** (no HTTP server); status is the exit code, the logs, and the produced files.
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
docker run --rm \
|
|
423
|
+
--read-only --tmpfs /tmp \
|
|
424
|
+
-v "$PWD/input:/input:ro" \
|
|
425
|
+
-v "$PWD/output:/output" \
|
|
426
|
+
-v fdt-work:/work \
|
|
427
|
+
ghcr.io/guymano/focus-data-toolkit:0.10.0 \
|
|
428
|
+
convert --cost-and-usage /input/focus.csv \
|
|
429
|
+
--stream --output-format parquet --out /output/result \
|
|
430
|
+
--exit-policy pipeline
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
- **Non-root** (uid 65532) and **read-only-rootfs compatible** — only `/work` (scratch) and
|
|
434
|
+
`/output` are written. Because the atomic publish stages next to `--out`, **`/output` must be
|
|
435
|
+
writable** (mount a writable volume/dir; if you run as the image's non-root uid, ensure the
|
|
436
|
+
target is writable by it — or use a named volume, which Docker initialises writable for you).
|
|
437
|
+
- `/input` is meant to be mounted **read-only**. `FOCUS_TOOLKIT_WORK_DIR=/work` and `TMPDIR=/work`
|
|
438
|
+
are preset, so all scratch stays on the `/work` volume — point it at fast local storage for big
|
|
439
|
+
files, and size it (the SQLite aggregation + bundle spill scale with invoice/period/commitment
|
|
440
|
+
cardinalities, not with the Cost-and-Usage row count).
|
|
441
|
+
- **Signals**: `docker stop` sends SIGTERM to PID 1 (the CLI), which cancels cleanly — exit code
|
|
442
|
+
**130**, nothing partial published. Give it a grace period (`--stop-timeout`).
|
|
443
|
+
- **Exit codes** for orchestrators: use `--exit-policy pipeline` so a functional-but-incomplete
|
|
444
|
+
strict run (3) or a synthetic run (4) reports success (0); genuine failures (1/2/5/130) stay
|
|
445
|
+
non-zero. Disk budgets: `FOCUS_TOOLKIT_MAX_WORK_BYTES`, `FOCUS_TOOLKIT_MIN_WORK_FREE_BYTES`,
|
|
446
|
+
`FOCUS_TOOLKIT_MIN_OUTPUT_FREE_BYTES` (a shortfall fails fast with `FDT-IO-005/006`, exit 5).
|
|
447
|
+
|
|
448
|
+
**Single-node engine — pick the right method by scale:**
|
|
449
|
+
|
|
450
|
+
| Volume | Recommended method |
|
|
451
|
+
|---|---|
|
|
452
|
+
| Tests / ordinary files | CLI (or the forthcoming Studio) |
|
|
453
|
+
| Large files on one machine | Runner |
|
|
454
|
+
| Hundreds of GB | Runner with fast local `/work` + sized resources; Parquet + partitioning + zstd |
|
|
455
|
+
| Beyond a single node | Partition upstream / orchestrate multiple batches |
|
|
456
|
+
|
|
457
|
+
The image is not a distributed engine. **Immutable** tags — `<version>` (e.g. `0.10.0`) and
|
|
458
|
+
`sha-<full-commit>` — always identify the same bytes; the `<major>.<minor>` tag (e.g. `0.10`)
|
|
459
|
+
is a **rolling** convenience alias that advances with each patch release. Every release is
|
|
460
|
+
scanned (trivy) **before** its public tags are assigned, carries a CycloneDX SBOM, and is signed
|
|
461
|
+
(cosign) and attested (build provenance). See [docs/runner.md](docs/runner.md) for details.
|
|
462
|
+
|
|
463
|
+
## What is really migratable
|
|
464
|
+
|
|
465
|
+
| 1.4 dataset | Real migration? | Notes |
|
|
466
|
+
|---|---|---|
|
|
467
|
+
| Cost and Usage | **Yes** | Column mapping (1.2 lifted to 1.3 shape; deprecated provider columns dropped; nullable 1.4 additions null; `PricingCurrency*` backfilled; `ContractApplied` re-cased 1.3→1.4). |
|
|
468
|
+
| Contract Commitment | Partial → **synthetic** | The 13 source columns migrate; the 14 Mandatory 1.4 commercial terms are provider facts → assumed (synthetic only). |
|
|
469
|
+
| Billing Period | **No** (synthetic) | Period keys derive from Cost and Usage; `BillingPeriodStatus` and the timestamps are provider billing-cycle state. |
|
|
470
|
+
| Invoice Detail | **No** (synthetic) | `BilledCost` aggregates usage, but invoice status/terms/issuer-assigned id/timestamps are provider-issued. |
|
|
471
|
+
|
|
472
|
+
Conversion is pure (no clock, no RNG): the same input always produces the same
|
|
473
|
+
output bytes, in both modes.
|
|
474
|
+
|
|
475
|
+
## Development
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
git clone https://github.com/guymano/focus-data-toolkit && cd focus-data-toolkit
|
|
479
|
+
pip install -e .[dev] # includes pyarrow, so the Parquet suite runs (not skipped)
|
|
480
|
+
pytest -q # generators, detection, migration/lint, modes, manifest, CLI,
|
|
481
|
+
# streaming, Parquet, split-allocation, corrections, lifecycle
|
|
482
|
+
pytest -m slow # large-scale bounded-memory test (excluded by default)
|
|
483
|
+
python tools/benchmark_streaming.py --rows 100000 500000 # throughput + peak RSS
|
|
484
|
+
ruff check src tests
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
The FOCUS 1.4 model JSON (`src/focus_data_toolkit/model/focus_1_4_model.json`)
|
|
488
|
+
is the artifact of record, extracted from the FinOps Foundation "FOCUS 1.4
|
|
489
|
+
Data Model" workbook with `tools/extract_focus_1_4_model.py` (the workbook
|
|
490
|
+
itself is not redistributed here — download it from
|
|
491
|
+
[focus.finops.org](https://focus.finops.org)). Its machine-readable provenance
|
|
492
|
+
is recorded in `model_provenance.json` and can be checked with
|
|
493
|
+
`python scripts/verify_model_provenance.py`; see
|
|
494
|
+
[docs/model-provenance.md](docs/model-provenance.md).
|
|
495
|
+
|
|
496
|
+
## Contributing, security & docs
|
|
497
|
+
|
|
498
|
+
- **Contributing:** [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
499
|
+
- **Security policy / reporting:** [SECURITY.md](SECURITY.md) (private reporting;
|
|
500
|
+
a FOCUS conformance bug is a normal issue, not a security report)
|
|
501
|
+
- **Versioning & reproducibility:** [docs/versioning.md](docs/versioning.md)
|
|
502
|
+
- **Compatibility (Python / OS / FOCUS):** [docs/compatibility.md](docs/compatibility.md)
|
|
503
|
+
- **Security model:** [docs/security-model.md](docs/security-model.md)
|
|
504
|
+
- **Releasing:** [docs/releasing.md](docs/releasing.md)
|
|
505
|
+
- **Changelog:** [CHANGELOG.md](CHANGELOG.md)
|
|
506
|
+
|
|
507
|
+
## License and credits
|
|
508
|
+
|
|
509
|
+
The toolkit code is **MIT** (see [LICENSE](LICENSE)). The embedded FOCUS 1.4 data
|
|
510
|
+
model is a derivative of the FinOps FOCUS specification / data-model workbook,
|
|
511
|
+
which is © the FinOps Foundation and licensed **CC-BY-4.0**; it is redistributed
|
|
512
|
+
here with attribution (see [NOTICE](NOTICE) and
|
|
513
|
+
[docs/model-provenance.md](docs/model-provenance.md)). "FOCUS" and "FinOps" are
|
|
514
|
+
trademarks of the FinOps Foundation; the FOCUS specification, data-model workbook
|
|
515
|
+
and official validator are © the FinOps Foundation — this project is an
|
|
516
|
+
independent community toolkit and is not endorsed by the FinOps Foundation.
|
|
517
|
+
Related sample datasets from the same generators were contributed to
|
|
518
|
+
[FOCUS-Sample-Data](https://github.com/FinOps-Open-Cost-and-Usage-Spec/FOCUS-Sample-Data)
|
|
519
|
+
(PRs #6 and #7).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
focus_data_toolkit/__init__.py,sha256=tyd4C3tdJJM7mNU4oeGSc7BaxNwADHnfhtneocDhyxc,2538
|
|
2
|
+
focus_data_toolkit/__main__.py,sha256=BswQRTWsAPifuwP39nW8-evmiw2xKb1pB_uefWJp1-A,191
|
|
3
|
+
focus_data_toolkit/_version.py,sha256=LKhp8tqxoUCYHl3H3Mx2I9TfIZCx2mdjc-v61UYEnLk,323
|
|
4
|
+
focus_data_toolkit/cli.py,sha256=3PdZ8WUMuOfl3nqNzuyp6-4M19C_hDt3HLzTo_6MZU0,38677
|
|
5
|
+
focus_data_toolkit/errors.py,sha256=iiKdOu3YnHODNHQHLaDahUuUIwP22CF7XWXypw3aaz4,5161
|
|
6
|
+
focus_data_toolkit/focus_json.py,sha256=FBWGZzsVAZhjV3J7Q5sf0A5Ya7WKDfsDCe3aOMfrrjE,2806
|
|
7
|
+
focus_data_toolkit/lifecycle.py,sha256=jPwSyJM7vsGjDYAXxpsu90_O_8QeR8NV1t2uJokU3A8,14557
|
|
8
|
+
focus_data_toolkit/manifest.py,sha256=MNFrjmV7aZT5yPwDjvjC1Df8OVGMFPcVbG6Ve-YrvHs,4451
|
|
9
|
+
focus_data_toolkit/modes.py,sha256=fqIsS-Hy4eHSr8g3QeuOeV4Ur5Rhfb-7m-4OitLrJrI,686
|
|
10
|
+
focus_data_toolkit/official_validator.py,sha256=2-GqeucVazyzoe4pvLzNDl66uNIh0wbTBPLZJn27kIc,1987
|
|
11
|
+
focus_data_toolkit/progress.py,sha256=5cxjh0bwEEna4GKFP8rZingZ1k1qBtp6NrCtu_eyTQI,3372
|
|
12
|
+
focus_data_toolkit/provenance.py,sha256=C0wdde3uWPPBdyMlihwHU60IHoVI3b0ek_g8nd-oXf4,4122
|
|
13
|
+
focus_data_toolkit/py.typed,sha256=6s6t40Ie33L-SxfLsfkB0w0Y0WUMke9BmGXW2GdQdIQ,68
|
|
14
|
+
focus_data_toolkit/runtime.py,sha256=Dfse7ROzIJVW_jhl999ULx_f-QJtjHw9lBO3r_aSggs,9814
|
|
15
|
+
focus_data_toolkit/context/__init__.py,sha256=7Bo3atX4ABmPVc5oyY5-KywxZ8mbXi7KhIBgrz_1Hs8,3019
|
|
16
|
+
focus_data_toolkit/context/billing.py,sha256=HoCs4XtHbYp1lseO9oOyZ6V9Gib7p6Jhg_-xcz52FnM,1922
|
|
17
|
+
focus_data_toolkit/context/provider.py,sha256=mdH4cByNwNx40Qxlz_MUCVuE-9bJRzbryMaY812oy18,3834
|
|
18
|
+
focus_data_toolkit/convert/__init__.py,sha256=XePzx4VdbiMB_b-Wsb7b9_mxX4vAHLl-eBI7cchYQ88,30408
|
|
19
|
+
focus_data_toolkit/convert/billing_period.py,sha256=BoJfbTY06se6Vm6Gz7uLBYXlK_88DBpY207Oa2l3S0A,2892
|
|
20
|
+
focus_data_toolkit/convert/contract_applied.py,sha256=58vbVTYH4cSlZtWTT147-a4YHCnVMQvtW55XvTVNOuI,9787
|
|
21
|
+
focus_data_toolkit/convert/contract_commitment.py,sha256=SXMgB9flO-xLApeIj_D_WsFEZNtCwqz1ntFt04fQf4c,8601
|
|
22
|
+
focus_data_toolkit/convert/cost_and_usage.py,sha256=f3O1o8HmRLEeftyAOO_QKENKwPi1OAgpRk57UTd2B-E,7903
|
|
23
|
+
focus_data_toolkit/convert/detect.py,sha256=XoePBgzCGI7AaJi1uroovBoLXZ3huL5_k-CH5Z4mQes,1444
|
|
24
|
+
focus_data_toolkit/convert/invoice_detail.py,sha256=kmzcaeXKZmKfGAt956mO5wrAR9AQbuAwI3OOauNTFeg,8940
|
|
25
|
+
focus_data_toolkit/convert/streaming.py,sha256=KLw48NAQlIYvDBfG0P_uWdraPxXm8cjdqFioRdtOCzA,44030
|
|
26
|
+
focus_data_toolkit/generators/__init__.py,sha256=55H7mKoBdfkyRfjL0c3SCsvw46sZd3CsEBcPPJTvDTI,2591
|
|
27
|
+
focus_data_toolkit/generators/_shim.py,sha256=FdgvnvgnwhPDlbURd6WxOsyvIl0kImEXaSPJrgI15EU,2064
|
|
28
|
+
focus_data_toolkit/generators/generate_aws_focus_1_2.py,sha256=LpjkpGnvNGlV4-bhyHB4AGhbg6ac6SamrvJ5_oFHlW4,768
|
|
29
|
+
focus_data_toolkit/generators/generate_aws_focus_1_3.py,sha256=0_6_7oNLkxE7MPZzCsrOFp3cOYirzGkE7wS-WS4v2bA,845
|
|
30
|
+
focus_data_toolkit/generators/generate_azure_focus_1_2.py,sha256=OFYv6pZOI0qMPEWwhXK5Os1CERaDRAKActlXvtnW_yo,585
|
|
31
|
+
focus_data_toolkit/generators/generate_azure_focus_1_3.py,sha256=vCgnDgRm_anv6G5Po07ntH-EwAAfo5uoyQguOlow5ko,585
|
|
32
|
+
focus_data_toolkit/generators/generate_gcp_focus_1_2.py,sha256=EKIzHtiV283euQRF9pii5CnIUrhFhSCtV_nkZ3eUk28,574
|
|
33
|
+
focus_data_toolkit/generators/generate_gcp_focus_1_3.py,sha256=Q0vw8dSxqs_YHL5DvlWHq_Wifsu-FVQCI70-We2O56Y,574
|
|
34
|
+
focus_data_toolkit/generators/scenarios.py,sha256=HV7zGGb_qaXwD3ORtD25GWinqcABhMAt3AL6Xk0zxjA,7221
|
|
35
|
+
focus_data_toolkit/generators/engine/__init__.py,sha256=KMJ_dIXf1IThG8-ell-9A1_qCwh8bcQC-8Ok8Nta6rI,849
|
|
36
|
+
focus_data_toolkit/generators/engine/context.py,sha256=QSWR8AFmevrmzVmAO4xlrDD3TfjPntgBBYjYj4MLI3M,505
|
|
37
|
+
focus_data_toolkit/generators/engine/determinism.py,sha256=xeiqtOxqpipoS8RjphWbg8q5iF7jIPmh_JCltFQG9EA,4413
|
|
38
|
+
focus_data_toolkit/generators/engine/json_focus.py,sha256=o3j-GZr_V0XdR31eVhOxgdowNGRDoh9Xcld1Pj2YWKw,2567
|
|
39
|
+
focus_data_toolkit/generators/engine/ladder.py,sha256=bC2M2Hp-_dqTU80C6Wzlq7HGENK1CtUwyNSVn8An5gE,2578
|
|
40
|
+
focus_data_toolkit/generators/engine/scenarios_core.py,sha256=VXr0cFzZjoAAmzifnzOqtUPREsd2JL-gI4IQ5mV5UHc,16755
|
|
41
|
+
focus_data_toolkit/generators/engine/serialize.py,sha256=cW8x_VjYYsFlFcHFDBF4nRvWOBpdmnW0bhGsBvZ6sqg,5974
|
|
42
|
+
focus_data_toolkit/generators/providers/__init__.py,sha256=0WEjH3NpHK0hFl11weshUPSH9ADQPE2-YE8oM7mSKnI,892
|
|
43
|
+
focus_data_toolkit/generators/providers/aws.py,sha256=hoyObMVUo2L00NUAIIJq_Fz-zepYa6rPlDGqH2TY7Cw,7360
|
|
44
|
+
focus_data_toolkit/generators/providers/azure.py,sha256=v1HHmepy10et7KCrqyY_nVctXLegtG6Wg0U7M6CxicU,7920
|
|
45
|
+
focus_data_toolkit/generators/providers/gcp.py,sha256=PbHbRuU1GMBTmi76Gf6_GzanyQ37cVTed-_46TPVNq4,7761
|
|
46
|
+
focus_data_toolkit/generators/providers/profile.py,sha256=3KUiNzTAM206xXfBiTsk9s3VqSZjANmOiTMya3MZrfU,4967
|
|
47
|
+
focus_data_toolkit/generators/versions/__init__.py,sha256=tciA-qFbVzVbCKGhBK5chbL7ntfQi6m81lHY_-JMr7Y,798
|
|
48
|
+
focus_data_toolkit/generators/versions/adapter.py,sha256=HkfjLVUNA0f5XOXqq0egtIsUvJsAOOptmE8q3NFU3YU,1687
|
|
49
|
+
focus_data_toolkit/generators/versions/v1_2.py,sha256=QxWqN_cTF44JegCSHdergSyEHBh1QKNPmJpgLxxO-WE,3063
|
|
50
|
+
focus_data_toolkit/generators/versions/v1_3.py,sha256=w7Qirn1qQUfT_F83ns3Cipuing1HwI3lFI0oG10RmvI,4644
|
|
51
|
+
focus_data_toolkit/io/__init__.py,sha256=49aHPWaJ0PN9lYyxYX5pjpmc7UdurqaiSl1mzgG-xvk,51
|
|
52
|
+
focus_data_toolkit/io/atomic_writer.py,sha256=e_eKWMe25Ydwuyil93UPIakJiS1bhn-9d29suxPGi80,20471
|
|
53
|
+
focus_data_toolkit/io/csv_io.py,sha256=w9qOok9uf6ZlICOCfO2j3hAuVo8xS2aZSil5-l3bvU4,5002
|
|
54
|
+
focus_data_toolkit/io/parquet_io.py,sha256=E7BzbG4Kv4WKujMwOkq-GpzA5ZAD0zNh4x_Wi8pTZPw,22587
|
|
55
|
+
focus_data_toolkit/io/records.py,sha256=R78VfSqYHr2JPTso2ucUHe36AB5uS9ekbu9xJLdm95Y,2831
|
|
56
|
+
focus_data_toolkit/io/row_source.py,sha256=NTs2J4X945kJ2ezTP25_pWZ0QG2yuz_cAQCKOtJrhxI,4507
|
|
57
|
+
focus_data_toolkit/model/__init__.py,sha256=ezdCoMnn1-Z0ucqHJWuC-G818gBgqrd_zDEn_s6BwD8,1196
|
|
58
|
+
focus_data_toolkit/model/capabilities.py,sha256=zyNrXs5JNkSyf5oM8z8GNjdtVPKkbNVxGDBydLY3n7A,2576
|
|
59
|
+
focus_data_toolkit/model/focus_1_4_decimal_scale.json,sha256=d06EzrRi-nfl_sq5zMOsoWb_R0Y5uHFnU3lQnfAIpL0,893
|
|
60
|
+
focus_data_toolkit/model/focus_1_4_model.json,sha256=iwBuGja8lnj-9rechHZU-RTqer4lyCdHiqRO4dFbIUs,64971
|
|
61
|
+
focus_data_toolkit/model/focus_1_4_servicesubcategory.json,sha256=hTJGcJO-3R4iioLwfHgtiykobeddCnm8y_iW3r2Gf2Q,3244
|
|
62
|
+
focus_data_toolkit/model/focus_json_keys.py,sha256=5wBp_FmlJoyz7NpkSShbtuT87E9UKm48O28ZWNR8OEk,4164
|
|
63
|
+
focus_data_toolkit/model/iso_4217_currencies.json,sha256=ARakJ2kZJZvcKaqbDUnRpD0XXnhx-39TMefa6GR7us4,1446
|
|
64
|
+
focus_data_toolkit/model/json_schema_check.py,sha256=XCN7VBaHEVyPa8Wf0T6R6F8UD4hSKSiEYjMaYwkNr-w,8953
|
|
65
|
+
focus_data_toolkit/model/model_provenance.json,sha256=t4rHSZqcl15BgxOPjgdpZBw2WiJMeGDzEac_MX92hFQ,3848
|
|
66
|
+
focus_data_toolkit/model/validator.py,sha256=ttquxO8jJ5MYxpIGF6Wybn-WILNg5KTXevB5272NZYU,20534
|
|
67
|
+
focus_data_toolkit/model/json_schemas/allocatedmethoddetailsobjectschema.json,sha256=D1UkaZVEM8BKUa0YVh5LheE3h1cM2kpMxaczo5IU2P0,3250
|
|
68
|
+
focus_data_toolkit/model/json_schemas/commitmentprogrameligibilitydetailsobjectschema.json,sha256=DML8S_w6cgdUWRwBeliZ0hT3jWMBZ6FumcKHXLkd5XY,1647
|
|
69
|
+
focus_data_toolkit/model/json_schemas/contractappliedobjectschema.json,sha256=Jq04D4nBK0FPNoIvtPEnhuTqz7DQCsR4GoR1JIOl1z4,4352
|
|
70
|
+
focus_data_toolkit/model/json_schemas/contractcommitmentapplicabilityobjectschema.json,sha256=bDlqpawMz-0b-xxGTWaYaEUgVUrKUNyjJYMH_3eaVls,7143
|
|
71
|
+
focus_data_toolkit/model/json_schemas/json_schemas_provenance.json,sha256=oS7ns_LE2jTYbMsh-7w_6Bxd_EfXdIQ8sWFILi95auY,1778
|
|
72
|
+
focus_data_toolkit/schema/__init__.py,sha256=2zLyGC0WIDk1EJnBpWs3AnULjUbUUl77qumIDfeCCHk,718
|
|
73
|
+
focus_data_toolkit/schema/detection.py,sha256=oEhQsRJvNNOz7KFbtrDFDY9g8p1UvYLrGSjF6vfNxOE,10715
|
|
74
|
+
focus_data_toolkit/schema/registry.py,sha256=W4CHm6HavH_m8uh1Mv9UYDvhbUhh_ymYMehiSO2Ak3w,5223
|
|
75
|
+
focus_data_toolkit/storage/__init__.py,sha256=fq4abXU1niE3iASKD6YX1dv313ogXkaXxtuQd6lEWjU,80
|
|
76
|
+
focus_data_toolkit/storage/external_index.py,sha256=J1gSMJosSdLXuBnA0zb4yREt7v047lLHD-iAPC7ipp4,4090
|
|
77
|
+
focus_data_toolkit/storage/spill.py,sha256=E5ZannDqio3T0yaxwCa7kowXsqwONsV-_u6Bx-wzyQU,5435
|
|
78
|
+
focus_data_toolkit/studio/__init__.py,sha256=RA2hPijhhWjczdTyb-r0e9yFQDFeJMANlG3XoKi460E,958
|
|
79
|
+
focus_data_toolkit/studio/app.py,sha256=lU1m-UT_IOIRkxzOGLgFUWuuWwEquy_XGl9WPSQ0-v4,20472
|
|
80
|
+
focus_data_toolkit/studio/config.py,sha256=Adcslwc6TMWY0SGd2AvtavQMft_8__-s_XZezTgCk7Y,1654
|
|
81
|
+
focus_data_toolkit/studio/jobs.py,sha256=ALf2L_WCMAXnvwplCrpDEtE6F7HjfWg4dHrMUoj-LWw,5527
|
|
82
|
+
focus_data_toolkit/studio/preview.py,sha256=aNar6QMey_7oV7TPgMLDLWHvp7R9EFw9GYGk7_mL6eE,1229
|
|
83
|
+
focus_data_toolkit/studio/security.py,sha256=5LUReMjwdXwyF-eP9p2SZtjxrm9Ieta905yFD-FVXng,5457
|
|
84
|
+
focus_data_toolkit/studio/server.py,sha256=WkAb5FBudFg3Wh0HktDjd_eaFccA0Gm25BhBCIBaP0o,2211
|
|
85
|
+
focus_data_toolkit/studio/frontend/app.js,sha256=zioD0JAY5RE30WNIPUx3sW5Yq0mA2FsLM_9fIToVjL4,9731
|
|
86
|
+
focus_data_toolkit/studio/frontend/index.html,sha256=9YtZuY9RAL67hj6wlkQlx4HD-l2iMgLR3STtgI95sjg,3606
|
|
87
|
+
focus_data_toolkit/studio/frontend/style.css,sha256=KLfzs335aTfs7-xkx_cT9rAr66NjaQ6hhlRIOpDYHE8,3906
|
|
88
|
+
focus_data_toolkit/supplement/__init__.py,sha256=Py15wzVq6jVt2Avd88dcUvoxNpbmfr38B2tzUoZ0bNU,1845
|
|
89
|
+
focus_data_toolkit/supplement/apply.py,sha256=gQPrSeC29ApSvTZrFVQdvJEjURON6kxry3TcP45cVCQ,12981
|
|
90
|
+
focus_data_toolkit/supplement/gaps.py,sha256=Lov9yU5OEKgNKwPVyBvgqYQ4AVviAYZqKKGlMWpmh44,9502
|
|
91
|
+
focus_data_toolkit/supplement/kinds.py,sha256=LePJW3QZmqvPYDP2fhNDFX0MDr1ymVGb_Jk0ooKDsbM,3986
|
|
92
|
+
focus_data_toolkit/supplement/loader.py,sha256=og8UVY_sGL2G1dJ5R5vviFoQYkZVG4V0r1F6ZW-5_1U,16650
|
|
93
|
+
focus_data_toolkit/supplement/spec.py,sha256=hEjVMSKNM_8Z7j07S2z7PsQF89jAOJY3zSZCXT65OmU,2959
|
|
94
|
+
focus_data_toolkit/supplement/validate.py,sha256=XqiZ3CGShcR-KfxpVhM3OSHQELDOilbQ9gVzOKz2CX4,8977
|
|
95
|
+
focus_data_toolkit/supplement/adapters/__init__.py,sha256=VGo1WZSo595WbdyBH9nDycJTf3cX54dXy2hGxe_rK7M,428
|
|
96
|
+
focus_data_toolkit/supplement/adapters/adapters_provenance.json,sha256=Ddu1sYYkSqB55X2G-_DoMfwpdyG3p-mrdFhNz2KVsqk,1965
|
|
97
|
+
focus_data_toolkit/supplement/adapters/aws_invoice_summary.json,sha256=9p0xkUaU6c_X-ew746_yc9I0Ck-DaChdhsMctpY-4K4,1385
|
|
98
|
+
focus_data_toolkit/supplement/adapters/aws_savings_plans.json,sha256=uvCZuhd1NnLFK6UE2d7vl4wnnEoWjIvnKwVQGoeN9vs,2390
|
|
99
|
+
focus_data_toolkit/supplement/adapters/azure_invoice.json,sha256=NTm-up2EyrWuf6j34sNfsy08Mz6FoB_4h3SK1qR4DoU,1852
|
|
100
|
+
focus_data_toolkit/supplement/adapters/gcp_compute_commitments.json,sha256=itwVsiIRTvb5DeygWq1EgBi1aipyFMQBOp42A5uzSsM,2179
|
|
101
|
+
focus_data_toolkit/supplement/adapters/registry.py,sha256=xJfewTrtsutS4ilCxn2hRC0UjzvgXfKpcL4wgscFbIQ,8240
|
|
102
|
+
focus_data_toolkit/validate/__init__.py,sha256=q26nUoQXAp3ikNbyvE9wNPpsif4Mk5i5ZOMHGPec9O8,399
|
|
103
|
+
focus_data_toolkit/validate/allocation.py,sha256=bMAOwo-d_iuJnCaohSV7IYmbxtXJ7_31sYp79Q6YoSM,13211
|
|
104
|
+
focus_data_toolkit/validate/bundle.py,sha256=OI-MaIKUp1EHqJBS9aTcGmkn7YRZTXnBCeJMg4rfGcQ,9906
|
|
105
|
+
focus_data_toolkit/validate/codes.py,sha256=EUukxWGTopAouuQBdq551qY5v8Kwxzp-ZiyUv7UiLF4,5762
|
|
106
|
+
focus_data_toolkit/validate/corrections.py,sha256=klLSgorVh7ST9mSbPkLCIVmmRtRpa4qli1CzOAk-nok,10164
|
|
107
|
+
focus_data_toolkit/validate/reconciliation.py,sha256=qNBEObAtQG_VLCAm0JF9AnYAZlEdI_D5fZdbM6ZvrkQ,4119
|
|
108
|
+
focus_data_toolkit/validate/referential.py,sha256=dO_uXy6mIwLfxPnwsFKSE7fRiBw-K55XK_De-KzoXsc,11472
|
|
109
|
+
focus_data_toolkit-0.11.0.dist-info/licenses/LICENSE,sha256=NtTLMNmquvok1ABHks0U0ap9Kvz9PWA0JLg5PtQUMi4,1088
|
|
110
|
+
focus_data_toolkit-0.11.0.dist-info/licenses/NOTICE,sha256=J51RSKahdwtYmrhwgKyGK_mZjaJc04zTjguO5TJUgSg,2841
|
|
111
|
+
focus_data_toolkit-0.11.0.dist-info/licenses/LICENSES/CC-BY-4.0.txt,sha256=1VdTnfaOdxzB7tzJHRP3D8qTDlCNEe7cr6SxXbSeN0Q,17023
|
|
112
|
+
focus_data_toolkit-0.11.0.dist-info/METADATA,sha256=meeu3PZZ_w98Vo79s8PBBKUdfVL5oQanEFwID35NtqY,27002
|
|
113
|
+
focus_data_toolkit-0.11.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
114
|
+
focus_data_toolkit-0.11.0.dist-info/entry_points.txt,sha256=MiGCjfdjj1I73tNzREaD1X28p59PJ-nkboal8xXQ6hg,62
|
|
115
|
+
focus_data_toolkit-0.11.0.dist-info/top_level.txt,sha256=tLPrj44kcwFwJfhRB-fXgP-z_V4e6sPBmCwRBtf7rEw,19
|
|
116
|
+
focus_data_toolkit-0.11.0.dist-info/RECORD,,
|