focus-data-toolkit 0.13.0__tar.gz → 0.14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/CHANGELOG.md +170 -1
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/PKG-INFO +35 -7
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/README.md +33 -5
- focus_data_toolkit-0.14.0/docs/conversion-rules.md +176 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/supplements.md +28 -4
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/versioning.md +9 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/pyproject.toml +1 -1
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/__init__.py +27 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/_version.py +1 -1
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/cli.py +23 -1
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/context/__init__.py +9 -3
- focus_data_toolkit-0.14.0/src/focus_data_toolkit/context/provider.py +173 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/__init__.py +46 -23
- focus_data_toolkit-0.14.0/src/focus_data_toolkit/convert/contract_commitment.py +296 -0
- focus_data_toolkit-0.14.0/src/focus_data_toolkit/convert/cost_and_usage.py +732 -0
- focus_data_toolkit-0.14.0/src/focus_data_toolkit/convert/exceptions.py +20 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/streaming.py +42 -9
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/validator.py +164 -8
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/adapters_provenance.json +18 -2
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/aws_savings_plans.json +7 -5
- focus_data_toolkit-0.14.0/src/focus_data_toolkit/supplement/adapters/azure_reservation_orders.json +33 -0
- focus_data_toolkit-0.14.0/src/focus_data_toolkit/supplement/adapters/azure_savings_plan_orders.json +35 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/registry.py +45 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/apply.py +2 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/gaps.py +18 -1
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/kinds.py +4 -1
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/loader.py +10 -1
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/codes.py +22 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/PKG-INFO +35 -7
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/SOURCES.txt +9 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/requires.txt +1 -1
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/client_like/SOURCES.md +1 -1
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/client_like/consolidated_multi_provider_1_3.csv +4 -4
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/aws_1.2_CostAndUsage.log +625 -625
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/aws_1.3_ContractCommitment.log +117 -117
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/aws_1.3_CostAndUsage.log +865 -865
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/azure_1.2_CostAndUsage.log +617 -617
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/azure_1.3_ContractCommitment.log +117 -117
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/azure_1.3_CostAndUsage.log +857 -857
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/baseline.json +10 -10
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/gcp_1.2_CostAndUsage.log +617 -617
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/gcp_1.3_ContractCommitment.log +117 -117
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/gcp_1.3_CostAndUsage.log +857 -857
- focus_data_toolkit-0.14.0/tests/test_adapters_aws.py +321 -0
- focus_data_toolkit-0.14.0/tests/test_adapters_azure_gcp.py +402 -0
- focus_data_toolkit-0.14.0/tests/test_contract_commitment_duration.py +357 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_contract_commitment_semantics.py +17 -7
- focus_data_toolkit-0.14.0/tests/test_focus_1_4_value_migrations.py +276 -0
- focus_data_toolkit-0.14.0/tests/test_lint_focus_1_4_rules.py +322 -0
- focus_data_toolkit-0.14.0/tests/test_provider_roles_1_2.py +230 -0
- focus_data_toolkit-0.14.0/tests/test_public_api.py +83 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_streaming.py +63 -0
- focus_data_toolkit-0.13.0/src/focus_data_toolkit/context/provider.py +0 -90
- focus_data_toolkit-0.13.0/src/focus_data_toolkit/convert/contract_commitment.py +0 -182
- focus_data_toolkit-0.13.0/src/focus_data_toolkit/convert/cost_and_usage.py +0 -212
- focus_data_toolkit-0.13.0/tests/test_adapters_aws.py +0 -179
- focus_data_toolkit-0.13.0/tests/test_adapters_azure_gcp.py +0 -142
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/CONTRIBUTING.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/LICENSE +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/MANIFEST.in +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/NOTICE +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/SECURITY.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/audit/2026-07-improvement-plan.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/audit/2026-07-repo-audit.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/compatibility.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/generator-corrections.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/generator-statistics.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/model-provenance.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/pr53-review-followup.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/releasing.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/runner.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/security-model.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/studio.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/schema/model_provenance.schema.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/check_pinned_actions.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/describe_generated_samples.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/generate_resolved_sbom.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/generate_sbom.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/regenerate_golden_fixtures.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/sample_audit.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/validate_official_samples.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/verify_model_provenance.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/verify_release.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/setup.cfg +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/__main__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/context/billing.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/billing_period.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/contract_applied.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/detect.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/invoice_detail.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/errors.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/focus_json.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/_shim.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/allocation_math.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/context.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/determinism.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/json_focus.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/ladder.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/scenarios_core.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/serialize.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/generate_aws_focus_1_2.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/generate_aws_focus_1_3.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/generate_azure_focus_1_2.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/generate_azure_focus_1_3.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/generate_gcp_focus_1_2.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/generate_gcp_focus_1_3.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/providers/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/providers/aws.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/providers/azure.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/providers/gcp.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/providers/profile.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/scenarios.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/versions/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/versions/adapter.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/versions/v1_2.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/versions/v1_3.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/atomic_writer.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/csv_io.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/parquet_io.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/records.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/row_source.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/lifecycle.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/manifest.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/capabilities.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/focus_1_4_decimal_scale.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/focus_1_4_model.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/focus_1_4_servicesubcategory.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/focus_json_keys.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/iso_4217_currencies.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schema_check.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schemas/allocatedmethoddetailsobjectschema.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schemas/commitmentprogrameligibilitydetailsobjectschema.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schemas/contractappliedobjectschema.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schemas/contractcommitmentapplicabilityobjectschema.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schemas/json_schemas_provenance.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/model_provenance.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/modes.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/official_report.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/official_validator.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/progress.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/provenance.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/py.typed +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/runtime.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/schema/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/schema/detection.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/schema/registry.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/storage/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/storage/external_index.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/storage/spill.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/app.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/config.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/frontend/app.js +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/frontend/index.html +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/frontend/style.css +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/jobs.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/preview.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/security.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/server.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/aws_invoice_summary.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/azure_invoice.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/gcp_compute_commitments.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/spec.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/validate.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/__init__.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/allocation.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/bundle.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/corrections.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/reconciliation.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/referential.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/dependency_links.txt +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/entry_points.txt +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/top_level.txt +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/conftest.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/README.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows100_seed42.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows25_seed7.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows25_seed7_credits.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/aws_1_3_contract_commitment_rows100_seed42.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows100_seed42.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows25_seed7.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows25_seed7_credits.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows100_seed42.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows25_seed7.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows25_seed7_credits.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/azure_1_3_contract_commitment_rows100_seed42.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows100_seed42.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows25_seed7.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows25_seed7_credits.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows100_seed42.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows25_seed7.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows25_seed7_credits.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/gcp_1_3_contract_commitment_rows100_seed42.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows100_seed42.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows25_seed7.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows25_seed7_credits.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/scenarios_correction_set.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_equal.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_negative.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_weighted.csv +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/correctness_migration/README.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/correctness_migration/pr67_before.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/SOURCES.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/README.md +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/aws_1.2_CostAndUsage.stderr.log +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/aws_1.3_ContractCommitment.stderr.log +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/aws_1.3_CostAndUsage.stderr.log +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/azure_1.2_CostAndUsage.stderr.log +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/azure_1.3_ContractCommitment.stderr.log +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/azure_1.3_CostAndUsage.stderr.log +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/gcp_1.2_CostAndUsage.stderr.log +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/gcp_1.3_ContractCommitment.stderr.log +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/gcp_1.3_CostAndUsage.stderr.log +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/invoice_detail_grain_example.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/numeric_format_examples.json +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_atomic_write.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_billing_lifecycle.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_bundle_gate.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_bundle_validation.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_capabilities.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_cli.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_client_like.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_container.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_contract_applied.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_convert_roundtrip.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_corrections.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_cross_provider.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_detect.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_fake_provider.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_fixtures_are_synthetic.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_focus_json.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_gaps.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_generated_conformance.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_generator_golden.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_generators.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_grouping_keys.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_lifecycle_chains.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_lineage_counters.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_lint_focus.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_manifest.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_model_provenance.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_multi_provider.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_official_json_schemas.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_official_report.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_official_validator.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_packaging.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_parquet.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_parquet_input.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_participant_entities.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_partitioning.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_progress_cancel.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_release_tooling.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_replace_recovery.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_reviewed_roundtrip.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_reviewed_sample_semantics.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_runtime.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_schema_detection.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_single_source_focus_rules.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_spill.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_split_allocation.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_strict_conversion.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_studio.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_supplement_apply.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_supplement_loader.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_supplement_streaming.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_synthetic_conversion.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_validator.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_workflow_pins.py +0 -0
- {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tools/extract_focus_1_4_model.py +0 -0
|
@@ -9,6 +9,174 @@ policy.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.14.0] — 2026-10-06
|
|
13
|
+
|
|
14
|
+
FOCUS 1.2/1.3 to 1.4 conversion follows the specification more closely, and commitment
|
|
15
|
+
terms and provider-native commitment inventories can complete the 1.4 datasets.
|
|
16
|
+
|
|
17
|
+
- **Breaking:**
|
|
18
|
+
- the `aws-savings-plans` adapter (v2) is keyed by `savingsPlanArn`;
|
|
19
|
+
- a unit-price column the source does not carry is omitted from the 1.4 Cost and Usage
|
|
20
|
+
output instead of being written null.
|
|
21
|
+
- **New byte baselines for converted output:**
|
|
22
|
+
- conversion manifests label migrated columns `DERIVED`;
|
|
23
|
+
- `ContractCommitmentDurationType` is written in whole years (`1 Year`, `3 Years`).
|
|
24
|
+
|
|
25
|
+
Generated samples are unchanged.
|
|
26
|
+
- **Stricter lint:** output that passed with 0.13.0 can now be refused; see the upgrade
|
|
27
|
+
notes under Changed.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- The supplement API and the documented failures are now public, re-exported from the
|
|
32
|
+
package root: `SupplementBundle`, `SupplementFileSpec`, `load_bundle_dir`,
|
|
33
|
+
`compute_gaps`, `GapReport`, `Mode`, `ConversionError`, `SupplementError` and
|
|
34
|
+
`AtomicWriteError`. Library callers no longer import internal modules, which
|
|
35
|
+
docs/versioning.md leaves free to change. Existing import paths keep working.
|
|
36
|
+
- `ContractCommitmentDurationType` is a `contract_commitment` supplement column. A
|
|
37
|
+
supplied term wins over the derived value. It must follow the format v1.4 recommends, a
|
|
38
|
+
positive whole number and a listed unit (`1 Year`, `3 Years`, `36 Months`); anything
|
|
39
|
+
else is refused (`FDT-SUPP-004`). That format is a SHOULD in the specification, so the
|
|
40
|
+
lint of a 1.4 file does not fail on it.
|
|
41
|
+
- `fdt gaps` reports `ContractCommitmentDurationType` as a conditional advisory: it is
|
|
42
|
+
derived only for periods spanning whole calendar months, so the other terms must be
|
|
43
|
+
supplied. Without a commitment period in the source it is a blocking gap.
|
|
44
|
+
- A supplement that almost matches a provider adapter now names what it lacks, e.g.
|
|
45
|
+
`aws-savings-plans@2 requires savingsPlanArn` for an export from the v1 adapter era.
|
|
46
|
+
- Azure commitment adapters `azure-reservation-orders` and `azure-savings-plan-orders`
|
|
47
|
+
(REST shape, api-version 2022-11-01). One row per order, keyed by the lowercased order
|
|
48
|
+
ARM id. Each provides the term (`1 Year`, `3 Years`, `5 Years`, or `1 Month` for a
|
|
49
|
+
monthly savings plan), the payment model and interval, the lifecycle status where the
|
|
50
|
+
provisioning state determines one, the creation time where the response carries it,
|
|
51
|
+
and the benefit category. The commitment model is
|
|
52
|
+
not emitted: the order does not carry the commitment grain. A savings plan order is
|
|
53
|
+
recognised by its `sku.name`; a new `none_of` detection clause rules out the plan- and
|
|
54
|
+
reservation-level lists, which share it. A `lower` adapter transform supports
|
|
55
|
+
case-insensitive provider identifiers.
|
|
56
|
+
- A FOCUS 1.2 source can declare who issued it (`provider_role="csp"` or `"msp"`, CLI
|
|
57
|
+
`--provider-role`), with the cloud provider's own publisher names
|
|
58
|
+
(`first_party_publishers`, CLI `--first-party-publisher`).
|
|
59
|
+
- With `csp`, a row whose `PublisherName` differs from `ProviderName` and is no declared
|
|
60
|
+
first-party name is a Marketplace charge. Its `ServiceProviderName` and
|
|
61
|
+
`HostProviderName` become the seller, as FOCUS 1.4 requires (`FDT-MIG-005`).
|
|
62
|
+
- With `msp`, they stay `ProviderName`, the MSP.
|
|
63
|
+
- Without a declaration, `ProviderName` is kept as before, and those rows are reported
|
|
64
|
+
(`FDT-CTX-005`). Nothing is refused.
|
|
65
|
+
- Only official FOCUS columns are read. See
|
|
66
|
+
[conversion rules](docs/conversion-rules.md#participating-entities-of-a-12-source),
|
|
67
|
+
including why `HostProviderName` = `ServiceProviderName` is an interpretation for a 1.2
|
|
68
|
+
source.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- **Breaking:** the `aws-savings-plans` adapter (now v2) is keyed by `savingsPlanArn`,
|
|
73
|
+
the fully qualified identifier FOCUS recommends and the one Contract Commitment and
|
|
74
|
+
`CommitmentDiscountId` carry. v1 keyed by the bare `savingsPlanId`, so its rows could
|
|
75
|
+
never join. Exports without `savingsPlanArn` are no longer auto-detected; re-export
|
|
76
|
+
with the ARN (as `describe-savings-plans` returns it). The adapter also maps
|
|
77
|
+
`termDurationInSeconds` (1 and 3 years) to the duration.
|
|
78
|
+
- **New byte baseline:** `ContractCommitmentDurationType` is written in whole years when
|
|
79
|
+
the term is a multiple of 12 months (`1 Year`, `3 Years`), as in the specification's
|
|
80
|
+
examples, instead of `12 Months` / `36 Months`. Other terms stay in months.
|
|
81
|
+
- The FOCUS 1.4 linter now enforces more of the static Cost and Usage rules of the v1.4
|
|
82
|
+
requirements model. Output that passed before can now fail the lint, and strict
|
|
83
|
+
publication refuses it:
|
|
84
|
+
- Tax and Credit `EffectiveCost` equals `BilledCost` (CAU-EffectiveCost-C-017);
|
|
85
|
+
- `ListCost` and `ContractedCost` equal unit price × `PricingQuantity` when both are
|
|
86
|
+
present, Correction rows included (C-011). The relative tolerance is the official
|
|
87
|
+
focus-validator's (1e-9 × max(|cost|, 1)), and the arithmetic is exact whatever the
|
|
88
|
+
caller's Decimal context. An operand whose exponent lies beyond ±1000 is reported as
|
|
89
|
+
not computable instead of being computed;
|
|
90
|
+
- a unit is null exactly when its quantity is (`PricingUnit`, `ConsumedUnit`,
|
|
91
|
+
`CommitmentDiscountUnit`, C-005/C-006);
|
|
92
|
+
- `ListUnitPrice` and `ContractedUnitPrice` are present when `SkuPriceId` is set
|
|
93
|
+
(C-013/C-015). This applies in every profile, as these rules have no applicability
|
|
94
|
+
criteria, except for a column the source did not carry.
|
|
95
|
+
`lint_focus_1_4_structure(source_absent_columns=...)` lets a producer name such
|
|
96
|
+
columns that it writes all null, and a column missing from the rows counts as absent;
|
|
97
|
+
- under a declared `SupportsUnitPricing` condition, the pricing and quantity columns
|
|
98
|
+
are null when `SkuPriceId` is null (C-009 to C-016).
|
|
99
|
+
|
|
100
|
+
Other static rules of the model are not checked. These include the pricing-currency
|
|
101
|
+
unit prices' C-012, whose presence is not visible on a row and whose model condition
|
|
102
|
+
for `PricingCurrencyContractedUnitPrice` contradicts its text.
|
|
103
|
+
**Upgrade note:** expect refusals for:
|
|
104
|
+
- provider costs rounded to the cent (exact identity), and 1.2 Correction rows, which
|
|
105
|
+
1.2 exempted from the identity;
|
|
106
|
+
- Credit rows whose `EffectiveCost` differs from `BilledCost` (C-017), which the
|
|
107
|
+
converter copies as is (Tax rows are migrated, `FDT-MIG-001`);
|
|
108
|
+
- rows with a `SkuPriceId` but a null `ListUnitPrice` or `ContractedUnitPrice` in a
|
|
109
|
+
column the source carries (C-013/C-015).
|
|
110
|
+
- **Breaking (output layout):** a unit-price column the source does not carry
|
|
111
|
+
(`ListUnitPrice`, `ContractedUnitPrice`, `PricingCurrencyListUnitPrice`,
|
|
112
|
+
`PricingCurrencyContractedUnitPrice`) is no longer written all null into the 1.4 Cost
|
|
113
|
+
and Usage output; it is omitted, as Invoice Detail already omits its unfilled
|
|
114
|
+
conditional columns. Its presence condition is not met, and written null it would break
|
|
115
|
+
the "MUST NOT be null when SkuPriceId is not null" rules in any validator, including
|
|
116
|
+
`focus-toolkit validate` on the published file. A column counts as carried when any
|
|
117
|
+
source row carries it. Sources that carry all four columns, such as the generated
|
|
118
|
+
samples, are unchanged.
|
|
119
|
+
- The client-like test fixture violated the cost identity by a factor of 1000; its
|
|
120
|
+
quantities are corrected. Its Tax row no longer carries a pricing quantity, and its
|
|
121
|
+
pricing-currency effective cost now matches its effective cost.
|
|
122
|
+
- **New byte baseline for conversion manifests.** On sources that can trigger those
|
|
123
|
+
migrations, `EffectiveCost` and the `SkuPriceId`-dependent columns are now labelled
|
|
124
|
+
`DERIVED` at column level. This follows the "weakest lineage the rule can produce"
|
|
125
|
+
convention, and `lineage_summary` records the per-value mix. Converted values of the
|
|
126
|
+
generated samples are unchanged.
|
|
127
|
+
- `ConversionError` and `ConversionCancelled` are defined in `focus_data_toolkit.convert.exceptions`
|
|
128
|
+
and re-exported unchanged from `focus_data_toolkit.convert`.
|
|
129
|
+
|
|
130
|
+
### Fixed
|
|
131
|
+
|
|
132
|
+
- `focus-toolkit convert --stream` printed each diagnostic as `note None:`; it now prints
|
|
133
|
+
its code, as the eager path does.
|
|
134
|
+
- `ContractCommitmentDurationType` is no longer rounded from `days / 30.44`, which turned
|
|
135
|
+
any span into a plausible-looking term. It is derived (`DERIVED`) only when the
|
|
136
|
+
commitment period spans a whole number of calendar months, the day clamped at month
|
|
137
|
+
end. Otherwise strict mode leaves it empty (`UNAVAILABLE`): Contract Commitment is not
|
|
138
|
+
produced until the term is supplied, and the other datasets are unaffected. Synthetic
|
|
139
|
+
mode assumes the nearest whole-month value (`ASSUMED`). `FDT-CC-001` lists the
|
|
140
|
+
commitments concerned, at the same point in the eager and streaming pipelines.
|
|
141
|
+
- The duration's lineage is settled per row. A partial supplement no longer blanks, or
|
|
142
|
+
counts as supplied, the terms derived for the rows it does not cover.
|
|
143
|
+
- A naive commitment timestamp is read as UTC, as FOCUS requires, instead of failing to
|
|
144
|
+
compare with an offset one; months are counted in the start's frame.
|
|
145
|
+
- 1.2/1.3 Cost and Usage values that break a FOCUS 1.4 rule the converter can meet without
|
|
146
|
+
inventing a fact are migrated, instead of being copied into non-conformant output. Each
|
|
147
|
+
migration is counted and reported, and each migrated value counts as `DERIVED` in
|
|
148
|
+
`lineage_summary`:
|
|
149
|
+
- Tax `EffectiveCost` equals `BilledCost` (CAU-EffectiveCost-C-017), with the exact net
|
|
150
|
+
change per billing currency (`FDT-MIG-001`). Credit rows, also covered by C-017, are not
|
|
151
|
+
rewritten;
|
|
152
|
+
- pricing and quantity columns and their units are nulled where `SkuPriceId` is null, on
|
|
153
|
+
Tax, Credit, Adjustment and Correction rows (`FDT-MIG-002`). For a 1.2 source this
|
|
154
|
+
migrates values 1.2 allowed; a 1.3 source already broke its own version's rule;
|
|
155
|
+
- on Usage/Purchase rows that are not corrections, FOCUS 1.4 requires the same columns to
|
|
156
|
+
be non-null, so their values are kept and the conflict is reported (`FDT-MIG-004`); so
|
|
157
|
+
are rows whose `ChargeCategory` is missing or not an allowed value;
|
|
158
|
+
- null source pricing-currency values are backfilled with a warning (`FDT-MIG-003`).
|
|
159
|
+
- A null `PricingCurrencyEffectiveCost` is no longer backfilled from `EffectiveCost` when
|
|
160
|
+
`PricingCurrency` differs from `BillingCurrency`, or when `BillingCurrency` is null. The
|
|
161
|
+
copy labelled a billing-currency amount with another, or an unknown, currency.
|
|
162
|
+
**Upgrade note:** a multi-currency source with such nulls used to convert; it now stops
|
|
163
|
+
with `FDT-MIG-011` (CLI exit 2). A Tax restatement across currencies stops with
|
|
164
|
+
`FDT-MIG-010`. See [conversion rules](docs/conversion-rules.md).
|
|
165
|
+
|
|
166
|
+
### Security
|
|
167
|
+
|
|
168
|
+
- The Runner image upgrades `perl-base` to 5.36.0-7+deb12u4 (bookworm-security). The
|
|
169
|
+
pinned base digest ships 5.36.0-7+deb12u3, which the container scan flags with three
|
|
170
|
+
CRITICAL (CVE-2026-13221, CVE-2026-42496, CVE-2026-8376) and four HIGH
|
|
171
|
+
(CVE-2026-42497, CVE-2026-48962, CVE-2026-57432, CVE-2026-57433) vulnerabilities.
|
|
172
|
+
- The Runner image no longer ships the `libpcre2-8-0` 10.42-1 package flagged HIGH by
|
|
173
|
+
the container scan (CVE-2026-103111, CVE-2026-86145, CVE-2026-89157, CVE-2026-89161).
|
|
174
|
+
The base image digest (`python:3.12-slim-bookworm`) is refreshed, bringing
|
|
175
|
+
10.42-1+deb12u1. The runtime stage then installs the pinned bookworm-security fix
|
|
176
|
+
10.42-1+deb12u2, which no base digest ships yet.
|
|
177
|
+
- Lock `urllib3` 2.8.0 (PYSEC-2026-4175, -4176, -4177). It reaches the lock only
|
|
178
|
+
transitively, through the optional `validator` extra (`focus-validator` -> `requests`)
|
|
179
|
+
and the release tooling (`twine`); the core toolkit has no runtime dependency.
|
|
12
180
|
## [0.13.0] — 2026-09-05
|
|
13
181
|
|
|
14
182
|
Prepared in source; publishing is a separate release step.
|
|
@@ -716,7 +884,8 @@ conformance defects.
|
|
|
716
884
|
|
|
717
885
|
<!-- Reference links. 0.2.0/0.3.0 were pre-release development milestones and were never tagged
|
|
718
886
|
or published, so only the first public release (0.9.0) has a tag link. -->
|
|
719
|
-
[Unreleased]: https://github.com/guymano/focus-data-toolkit/compare/v0.
|
|
887
|
+
[Unreleased]: https://github.com/guymano/focus-data-toolkit/compare/v0.14.0...HEAD
|
|
888
|
+
[0.14.0]: https://github.com/guymano/focus-data-toolkit/compare/v0.13.0...v0.14.0
|
|
720
889
|
[0.13.0]: https://github.com/guymano/focus-data-toolkit/compare/v0.12.0...v0.13.0
|
|
721
890
|
[0.12.0]: https://github.com/guymano/focus-data-toolkit/compare/v0.11.0...v0.12.0
|
|
722
891
|
[0.11.0]: https://github.com/guymano/focus-data-toolkit/compare/v0.11.0rc1...v0.11.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: focus-data-toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
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
5
|
Author: Guy-Hermann Adiko
|
|
6
6
|
License-Expression: MIT AND CC-BY-4.0
|
|
@@ -46,7 +46,7 @@ Requires-Dist: focus-data-toolkit[parquet,release,studio]; extra == "dev"
|
|
|
46
46
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
47
47
|
Requires-Dist: pytest-cov<8,>=5; extra == "dev"
|
|
48
48
|
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
49
|
-
Requires-Dist: mypy<2.
|
|
49
|
+
Requires-Dist: mypy<2.5,>=1.11; extra == "dev"
|
|
50
50
|
Requires-Dist: jsonschema<5,>=4.21; extra == "dev"
|
|
51
51
|
Requires-Dist: httpx<1,>=0.27; extra == "dev"
|
|
52
52
|
Dynamic: license-file
|
|
@@ -63,10 +63,16 @@ Generate realistic **FOCUS 1.2 / 1.3** cost and usage data (AWS, Azure, GCP), co
|
|
|
63
63
|
|
|
64
64
|
[FOCUS](https://focus.finops.org) is the open standard for cloud cost and usage data. FOCUS 1.4 defines four datasets: Cost and Usage, Contract Commitment, Billing Period, and Invoice Detail. The toolkit is honest by design: any value it cannot derive from your source is left empty (**strict** mode) or filled with clearly labelled assumptions (**synthetic** mode).
|
|
65
65
|
|
|
66
|
-
The source prepares **0.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
The source prepares **0.14.0**, which converts FOCUS 1.2/1.3 sources toward 1.4 more
|
|
67
|
+
faithfully:
|
|
68
|
+
- values are migrated to the 1.4 rules, and a stricter 1.4 lint checks the output;
|
|
69
|
+
- commitment terms can be supplied explicitly;
|
|
70
|
+
- Azure reservation and savings plan orders have adapters;
|
|
71
|
+
- a 1.2 source can declare who issued it, to name its Service Provider.
|
|
72
|
+
|
|
73
|
+
See the [conversion rules](docs/conversion-rules.md) and the [changelog](CHANGELOG.md).
|
|
74
|
+
The [generator corrections and validation evidence](docs/generator-corrections.md)
|
|
75
|
+
describe the generated samples, unchanged since 0.13.0. The Docker examples below
|
|
70
76
|
continue to pin the previously released 0.12.0 image.
|
|
71
77
|
|
|
72
78
|
## Three ways to use it
|
|
@@ -118,9 +124,31 @@ from focus_data_toolkit import convert_files
|
|
|
118
124
|
convert_files("cost_and_usage.csv", "focus-1.4", mode="strict")
|
|
119
125
|
```
|
|
120
126
|
|
|
127
|
+
Everything a library caller needs is importable from the package root, including
|
|
128
|
+
supplements and the failures to handle:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from pathlib import Path
|
|
132
|
+
|
|
133
|
+
from focus_data_toolkit import (
|
|
134
|
+
AtomicWriteError, ConversionError, Mode, SupplementBundle, SupplementError,
|
|
135
|
+
SupplementFileSpec, convert_files,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
bundle = SupplementBundle.load([SupplementFileSpec(path=Path("invoices.json"))])
|
|
140
|
+
convert_files("cost_and_usage.csv", "focus-1.4", mode=Mode.STRICT, supplements=bundle)
|
|
141
|
+
except (ConversionError, SupplementError):
|
|
142
|
+
... # invalid source or unusable supplement: nothing was written
|
|
143
|
+
except AtomicWriteError:
|
|
144
|
+
... # the lint or cross-dataset gate refused publication: nothing was written
|
|
145
|
+
```
|
|
146
|
+
|
|
121
147
|
## What it does, and what it doesn't
|
|
122
148
|
|
|
123
|
-
- Converts FOCUS 1.2/1.3 Cost and Usage into the four FOCUS 1.4 datasets.
|
|
149
|
+
- Converts FOCUS 1.2/1.3 Cost and Usage into the four FOCUS 1.4 datasets. Values a 1.4 rule
|
|
150
|
+
fixes deterministically are migrated and reported (`FDT-MIG-*`). Values it cannot fix without
|
|
151
|
+
inventing a fact are refused. See [docs/conversion-rules.md](docs/conversion-rules.md).
|
|
124
152
|
- The three other datasets come only from [supplements](docs/supplements.md) or synthetic mode.
|
|
125
153
|
There is no FOCUS 1.4 generator and no invalid-data generator.
|
|
126
154
|
- Built for real data: bounded-memory streaming (`--stream`), atomic writes, a deterministic
|
|
@@ -10,10 +10,16 @@ Generate realistic **FOCUS 1.2 / 1.3** cost and usage data (AWS, Azure, GCP), co
|
|
|
10
10
|
|
|
11
11
|
[FOCUS](https://focus.finops.org) is the open standard for cloud cost and usage data. FOCUS 1.4 defines four datasets: Cost and Usage, Contract Commitment, Billing Period, and Invoice Detail. The toolkit is honest by design: any value it cannot derive from your source is left empty (**strict** mode) or filled with clearly labelled assumptions (**synthetic** mode).
|
|
12
12
|
|
|
13
|
-
The source prepares **0.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
The source prepares **0.14.0**, which converts FOCUS 1.2/1.3 sources toward 1.4 more
|
|
14
|
+
faithfully:
|
|
15
|
+
- values are migrated to the 1.4 rules, and a stricter 1.4 lint checks the output;
|
|
16
|
+
- commitment terms can be supplied explicitly;
|
|
17
|
+
- Azure reservation and savings plan orders have adapters;
|
|
18
|
+
- a 1.2 source can declare who issued it, to name its Service Provider.
|
|
19
|
+
|
|
20
|
+
See the [conversion rules](docs/conversion-rules.md) and the [changelog](CHANGELOG.md).
|
|
21
|
+
The [generator corrections and validation evidence](docs/generator-corrections.md)
|
|
22
|
+
describe the generated samples, unchanged since 0.13.0. The Docker examples below
|
|
17
23
|
continue to pin the previously released 0.12.0 image.
|
|
18
24
|
|
|
19
25
|
## Three ways to use it
|
|
@@ -65,9 +71,31 @@ from focus_data_toolkit import convert_files
|
|
|
65
71
|
convert_files("cost_and_usage.csv", "focus-1.4", mode="strict")
|
|
66
72
|
```
|
|
67
73
|
|
|
74
|
+
Everything a library caller needs is importable from the package root, including
|
|
75
|
+
supplements and the failures to handle:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from pathlib import Path
|
|
79
|
+
|
|
80
|
+
from focus_data_toolkit import (
|
|
81
|
+
AtomicWriteError, ConversionError, Mode, SupplementBundle, SupplementError,
|
|
82
|
+
SupplementFileSpec, convert_files,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
bundle = SupplementBundle.load([SupplementFileSpec(path=Path("invoices.json"))])
|
|
87
|
+
convert_files("cost_and_usage.csv", "focus-1.4", mode=Mode.STRICT, supplements=bundle)
|
|
88
|
+
except (ConversionError, SupplementError):
|
|
89
|
+
... # invalid source or unusable supplement: nothing was written
|
|
90
|
+
except AtomicWriteError:
|
|
91
|
+
... # the lint or cross-dataset gate refused publication: nothing was written
|
|
92
|
+
```
|
|
93
|
+
|
|
68
94
|
## What it does, and what it doesn't
|
|
69
95
|
|
|
70
|
-
- Converts FOCUS 1.2/1.3 Cost and Usage into the four FOCUS 1.4 datasets.
|
|
96
|
+
- Converts FOCUS 1.2/1.3 Cost and Usage into the four FOCUS 1.4 datasets. Values a 1.4 rule
|
|
97
|
+
fixes deterministically are migrated and reported (`FDT-MIG-*`). Values it cannot fix without
|
|
98
|
+
inventing a fact are refused. See [docs/conversion-rules.md](docs/conversion-rules.md).
|
|
71
99
|
- The three other datasets come only from [supplements](docs/supplements.md) or synthetic mode.
|
|
72
100
|
There is no FOCUS 1.4 generator and no invalid-data generator.
|
|
73
101
|
- Built for real data: bounded-memory streaming (`--stream`), atomic writes, a deterministic
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# FOCUS 1.2/1.3 to 1.4 conversion rules
|
|
2
|
+
|
|
3
|
+
This page states what the converter does to each Cost and Usage value. It also lists the 1.4 rules
|
|
4
|
+
it cannot meet from a 1.2 or 1.3 source. Every rule cites the FOCUS specification
|
|
5
|
+
([FOCUS_Spec](https://github.com/FinOps-Open-Cost-and-Usage-Spec/FOCUS_Spec), tags `v1.2`
|
|
6
|
+
`7296e31`, `v1.3` `c67b95b`, `v1.4` `f1eeb30`) and, where one exists, the rule identifier of the
|
|
7
|
+
v1.4 requirements model (`specification/requirements_model/releases/1.4`).
|
|
8
|
+
|
|
9
|
+
## Copied, renamed and dropped columns
|
|
10
|
+
|
|
11
|
+
- Every 1.4 column present in the source is copied verbatim. Allowed values did not change between
|
|
12
|
+
1.2 and 1.4 for any enumerated column, so enum values are never rewritten.
|
|
13
|
+
- `ProviderName` and `PublisherName` (deprecated in 1.3, removed in 1.4) are dropped. For a 1.2
|
|
14
|
+
source, `ServiceProviderName` and `HostProviderName` are derived as described in
|
|
15
|
+
[Participating entities of a 1.2 source](#participating-entities-of-a-12-source).
|
|
16
|
+
- Columns new in 1.3 or 1.4 that the source does not carry are emitted null, for example
|
|
17
|
+
`CommitmentProgramEligibilityDetails` and `InvoiceDetailId`. The manifest records them as
|
|
18
|
+
`UNAVAILABLE`.
|
|
19
|
+
- A unit-price column the source does not carry is **omitted** instead: `ListUnitPrice`,
|
|
20
|
+
`ContractedUnitPrice`, `PricingCurrencyListUnitPrice` and
|
|
21
|
+
`PricingCurrencyContractedUnitPrice`.
|
|
22
|
+
- These conditional columns MUST NOT be null when `SkuPriceId` is set
|
|
23
|
+
(CAU-ListUnitPrice-C-013, CAU-ContractedUnitPrice-C-015, and C-012 of both pricing-currency
|
|
24
|
+
unit prices).
|
|
25
|
+
- A source that does not carry one has not met its presence condition, and an all-null
|
|
26
|
+
column would fail those rules in any validator.
|
|
27
|
+
- A column counts as carried when any source row carries it.
|
|
28
|
+
- The manifest lists no rule for an omitted column.
|
|
29
|
+
|
|
30
|
+
## Participating entities of a 1.2 source
|
|
31
|
+
|
|
32
|
+
FOCUS 1.2 has `ProviderName`, `PublisherName` and `InvoiceIssuerName`. FOCUS 1.3 introduced
|
|
33
|
+
`ServiceProviderName` "as a replacement for ProviderName", added `HostProviderName`, and
|
|
34
|
+
deprecated `ProviderName` and `PublisherName`. FOCUS 1.4 removes those two; there
|
|
35
|
+
`ServiceProviderName` is Mandatory and not nullable, and `HostProviderName` is Mandatory and
|
|
36
|
+
nullable. Only these official columns are read; provider-specific `x_` columns are not.
|
|
37
|
+
|
|
38
|
+
**What the specification says.**
|
|
39
|
+
|
|
40
|
+
- FOCUS 1.2 appendix *Origination of Cost Data*: when `PublisherName` differs from
|
|
41
|
+
`ProviderName`, the row is either a cloud marketplace purchase (Provider = the cloud provider,
|
|
42
|
+
Publisher = the seller; scenarios 3.1 to 3.3) or cloud services bought through an MSP
|
|
43
|
+
(Provider = the MSP, Publisher = the cloud provider; scenario 2.1).
|
|
44
|
+
- FOCUS 1.4 `ServiceProviderName`: "In marketplace scenarios, the Service Provider represents the
|
|
45
|
+
seller rather than the marketplace operator"; a reseller of white-labeled services is the
|
|
46
|
+
Service Provider. The appendix *Participating Entity Identification* gives the Marketplace
|
|
47
|
+
Seller in scenarios 3.1.1 to 3.3.2 and the MSP in scenarios 2.1 and 2.2.
|
|
48
|
+
- FOCUS 1.4 `HostProviderName` MUST reflect the host when the customer selected it or the
|
|
49
|
+
service provider exposes it, and MUST match `ServiceProviderName` in all other cases.
|
|
50
|
+
|
|
51
|
+
**What the converter does.** A 1.2 row cannot tell the marketplace case from the MSP case, so the
|
|
52
|
+
caller declares who issued the file (`provider_role`, CLI `--provider-role`):
|
|
53
|
+
|
|
54
|
+
| Declaration | Row whose `PublisherName` differs from `ProviderName` | Code |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| none (default) | `ServiceProviderName` = `ProviderName`, as before; the rows are counted | `FDT-CTX-005` (warning) |
|
|
57
|
+
| `csp`: a cloud provider issued the file | Marketplace charge: `ServiceProviderName` = `PublisherName` (the seller) | `FDT-MIG-005` (info) |
|
|
58
|
+
| `msp`: an MSP or reseller issued the file | `ServiceProviderName` = `ProviderName` (the MSP) | none |
|
|
59
|
+
|
|
60
|
+
- **First-party publishers.** A cloud provider may spell itself differently in the two columns (for
|
|
61
|
+
example `ProviderName` "Microsoft Azure" and `PublisherName` "Microsoft"). With `csp`, such names
|
|
62
|
+
are declared as first-party publishers (`first_party_publishers`, CLI
|
|
63
|
+
`--first-party-publisher`, case-insensitive), and their rows are not treated as Marketplace
|
|
64
|
+
charges. The comparison with `ProviderName` is case-insensitive as well.
|
|
65
|
+
- **Nothing is refused.** Without a declaration, strict mode still produces the dataset; the
|
|
66
|
+
warning names the publishers concerned.
|
|
67
|
+
- **1.3 sources.** They carry `ServiceProviderName` themselves; the declaration does not apply.
|
|
68
|
+
- **Manifest.** The declaration is recorded in the `ServiceProviderName` provenance note.
|
|
69
|
+
|
|
70
|
+
**`HostProviderName` is an interpretation.** A 1.2 source has no host column, so it never exposes
|
|
71
|
+
the host, and `HostProviderName` takes the `ServiceProviderName` value. The specification does
|
|
72
|
+
not settle this for a 1.2 source, and the true 1.4 value can differ in two cases:
|
|
73
|
+
|
|
74
|
+
- a Marketplace offering that runs on the cloud provider's infrastructure, or that exposes its
|
|
75
|
+
host, has the cloud provider as host (appendix 3.1.x and 3.2.x), where the converter writes the
|
|
76
|
+
seller (3.3.x, without visibility);
|
|
77
|
+
- in an MSP file, the 1.2 `PublisherName` is the cloud provider, which could be read as exposing
|
|
78
|
+
the host (appendix 2.1), where the converter writes the MSP (2.2). The publisher is never taken
|
|
79
|
+
as the host.
|
|
80
|
+
|
|
81
|
+
## Values migrated to a 1.4 rule
|
|
82
|
+
|
|
83
|
+
These changes are deterministic. Each one is counted and reported as a warning in the manifest.
|
|
84
|
+
The columns concerned are labelled `DERIVED`, the weakest lineage the rule can produce. In the
|
|
85
|
+
per-value `lineage_summary`, a migrated value counts as `DERIVED` and a value copied unchanged
|
|
86
|
+
(including those kept under `FDT-MIG-004`) as `OBSERVED`.
|
|
87
|
+
|
|
88
|
+
| Code | Rule | What changes |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `FDT-MIG-001` | CAU-EffectiveCost-C-017: `EffectiveCost` MUST equal `BilledCost` when `ChargeCategory` is "Tax" or "Credit". In 1.3 a tax's effective cost followed the related charges' effective cost (rule C-009, removed in 1.4). | On **Tax** rows, `EffectiveCost` takes the `BilledCost` value. A non-null `PricingCurrencyEffectiveCost` follows it when `PricingCurrency` equals `BillingCurrency`. The diagnostic gives the row count and the net `EffectiveCost` change per billing currency (exact Decimal), so a reconciliation against the source can explain the difference. **Credit** rows are not rewritten. 1.2 and 1.3 tied the equivalent rule to whether a charge relates to other charges (Credit was only an example), which a row does not reveal; a credit that breaks C-017 is copied as is. |
|
|
91
|
+
| `FDT-MIG-002` | The pricing and quantity columns MUST be null when `SkuPriceId` is null. The columns concerned: `ListUnitPrice` (C-012), `ContractedUnitPrice` (C-014), both pricing-currency unit prices (C-011), `PricingCategory` (C-012), `PricingQuantity` (C-011), `ConsumedQuantity` (C-009) and `CommitmentDiscountQuantity` (C-016). 1.3 already states these rules, so they migrate 1.2 sources; 1.2 only said these values "MAY be null". | When the source carries a `SkuPriceId` column and the value is null, those columns are nulled on Tax, Credit and Adjustment rows and on any Correction row. Each unit follows its quantity (`PricingUnit`, `ConsumedUnit`, `CommitmentDiscountUnit`, rules C-005/C-006). A source without a `SkuPriceId` column is left untouched: the column's absence does not mean a null SKU price. |
|
|
92
|
+
| `FDT-MIG-004` | On a Usage or Purchase row that is not a correction, the same columns MUST NOT be null (for example `ListUnitPrice` C-005, `PricingQuantity` C-005, `ConsumedQuantity` C-006, `PricingCategory` C-004). With a null `SkuPriceId`, FOCUS 1.4 requires them both null and non-null. A row whose `ChargeCategory` is missing or not an allowed value cannot tell which rule applies. | The values are **kept** on every row the `FDT-MIG-002` nulling does not cover, because real consumption is never discarded. The rows are counted per charge category (`(null)` when missing). The source is at fault: a Usage or Purchase row should carry a `SkuPriceId`, and every row a valid `ChargeCategory`. |
|
|
93
|
+
| `FDT-MIG-003` | `PricingCurrency` and `PricingCurrencyEffectiveCost` MUST NOT be null. 1.2 already required this; only its constraint table said otherwise, which erratum #9/#10 later corrected. | Null values in a source column that is present are backfilled from `BillingCurrency` and `EffectiveCost`. When the source omits the columns entirely, the same backfill applies without a warning, because the provider does not price in another currency. |
|
|
94
|
+
|
|
95
|
+
## Values the converter refuses to invent
|
|
96
|
+
|
|
97
|
+
Conversion stops with a `ConversionError` and publishes nothing:
|
|
98
|
+
|
|
99
|
+
- `FDT-MIG-010`: a Tax row's `EffectiveCost` must change (`FDT-MIG-001`), but its
|
|
100
|
+
`PricingCurrency` differs from `BillingCurrency`. Restating `PricingCurrencyEffectiveCost` would
|
|
101
|
+
need an exchange rate the source does not carry.
|
|
102
|
+
- `FDT-MIG-011`: `PricingCurrencyEffectiveCost` is null, and `PricingCurrency` is set but
|
|
103
|
+
differs from `BillingCurrency` or `BillingCurrency` is null. Copying `EffectiveCost`, an amount
|
|
104
|
+
in the billing currency, would label it with another or an unknown currency.
|
|
105
|
+
- A `ContractApplied` value that does not fit the 1.4 object schema: an element carrying both
|
|
106
|
+
cost and quantity, or a custom key without the `x_` prefix. The pre-erratum 1.3 key casing is
|
|
107
|
+
normalized instead, with the warning `FDT-CA-001`.
|
|
108
|
+
|
|
109
|
+
Every refusal is a `ConversionError`, and the CLI exits with code 2. The `FDT-MIG-010` and
|
|
110
|
+
`FDT-MIG-011` messages start with the code and name the first offending row.
|
|
111
|
+
|
|
112
|
+
## 1.4 rules the linter checks on the output
|
|
113
|
+
|
|
114
|
+
Besides the column formats, the linter enforces these static rules of the v1.4 requirements model
|
|
115
|
+
on every Cost and Usage row. Strict publication (`write_result`, `convert_files`) refuses output
|
|
116
|
+
that breaks them.
|
|
117
|
+
|
|
118
|
+
| Rule (v1.4 model) | Lint rule | When |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| CAU-EffectiveCost-C-017 | `effective_cost_differs_from_billed` | `ChargeCategory` is "Tax" or "Credit" |
|
|
121
|
+
| CAU-ListCost-C-011, CAU-ContractedCost-C-011 | `cost_not_unit_price_times_quantity`, `cost_identity_not_computable` | unit price and `PricingQuantity` both present, Correction rows included |
|
|
122
|
+
| CAU-PricingUnit/ConsumedUnit/CommitmentDiscountUnit-C-005/006 | `unit_without_quantity`, `quantity_without_unit` | always |
|
|
123
|
+
| CAU-ListUnitPrice-C-013, CAU-ContractedUnitPrice-C-015 | `required_with_sku_price_id` | `SkuPriceId` is set and the column is present |
|
|
124
|
+
| C-009 to C-016 ("MUST be null when `SkuPriceId` is null") | `must_be_null_without_sku_price_id` | the caller declares the `SupportsUnitPricing` condition |
|
|
125
|
+
|
|
126
|
+
- **Cost identity.** The tolerance is the official focus-validator's:
|
|
127
|
+
`|unit price × quantity − cost| ≤ 1e-9 × max(|cost|, 1)`. The arithmetic is exact whatever the
|
|
128
|
+
caller's Decimal context. An operand whose exponent lies beyond ±1000 is reported as not
|
|
129
|
+
computable rather than computed.
|
|
130
|
+
- **Unit prices.** C-013 and C-015 carry no applicability criteria in the model, so they apply
|
|
131
|
+
in every profile. A unit-price column the source lacks is omitted from the output (see above)
|
|
132
|
+
and is therefore not evaluated.
|
|
133
|
+
- **Not enforced.** The two pricing-currency unit prices' C-012 is not enforced. Their presence
|
|
134
|
+
depends on conditions a row does not show. In addition, the model's condition for
|
|
135
|
+
`CAU-PricingCurrencyContractedUnitPrice-C-012-C` contradicts its own text. That defect is
|
|
136
|
+
reported for 1.3 in
|
|
137
|
+
[FOCUS_Spec#2598](https://github.com/FinOps-Open-Cost-and-Usage-Spec/FOCUS_Spec/issues/2598) and
|
|
138
|
+
is still present in the 1.4 model.
|
|
139
|
+
- **Contract Commitment.** `ContractCommitmentDurationType` follows the 1.4 recommended format
|
|
140
|
+
("[positive integer] [unit]", e.g. `1 Year`) when the converter derives it or a supplement
|
|
141
|
+
supplies it. That format is a SHOULD (CCT-ContractCommitmentDurationType-C-004-O/C-005-O), so
|
|
142
|
+
the lint does not refuse another form in a 1.4 file it did not produce.
|
|
143
|
+
|
|
144
|
+
## 1.4 rules the converter cannot verify or meet
|
|
145
|
+
|
|
146
|
+
- **Meaning shifts that the row data cannot reveal.**
|
|
147
|
+
- `ChargeClass` "Correction" now refers to a previously *closed* billing period, where 1.3 said
|
|
148
|
+
*invoiced*.
|
|
149
|
+
- Rebates tied to usage thresholds may be "Adjustment" rather than "Credit" in 1.4.
|
|
150
|
+
- A Purchase covering related, postpaid charges must have `EffectiveCost` 0.
|
|
151
|
+
|
|
152
|
+
Values are copied as they are.
|
|
153
|
+
- **Credit `EffectiveCost`.** CAU-EffectiveCost-C-017 requires `EffectiveCost` to equal
|
|
154
|
+
`BilledCost` on Credit rows as well as Tax rows. 1.2 and 1.3 tied the equivalent rule to
|
|
155
|
+
whether a charge relates to other charges, which a row does not reveal, so a Credit row is
|
|
156
|
+
copied as is (see `FDT-MIG-001`) and may break C-017.
|
|
157
|
+
- **Virtual currencies.** 1.4 CurrencyFormat requires ISO 4217 codes, while the `PricingCurrency`
|
|
158
|
+
definition and the v1.4 example data still use virtual currencies such as "Token". The linter
|
|
159
|
+
applies CurrencyFormat, so a virtual pricing currency fails the lint. This is a conflict inside
|
|
160
|
+
the specification, not a converter choice.
|
|
161
|
+
- **A source without a `SkuPriceId` column.** The output still carries the 1.4 `SkuPriceId`
|
|
162
|
+
column, emitted null. A copied price or quantity beside it breaks the "MUST be null when
|
|
163
|
+
`SkuPriceId` is null" rules (C-009 to C-016). The converter cannot tell a provider without SKU
|
|
164
|
+
prices from a missing column, so this is a known non-conformance of such outputs.
|
|
165
|
+
- **Amounts with an exponent beyond ±1000.** They are not money, and exact bookkeeping on them
|
|
166
|
+
could need billions of digits. A Tax row carrying one is copied as is, like an unparseable
|
|
167
|
+
amount, instead of being migrated by `FDT-MIG-001`.
|
|
168
|
+
- **Columns that cannot be derived.** `CommitmentProgramEligibilityDetails`, `InvoiceDetailId`,
|
|
169
|
+
and an `InvoiceId` the source omits are emitted null. `InvoiceDetailId` can come from an
|
|
170
|
+
`invoice_line` supplement.
|
|
171
|
+
- **Contract Commitment.** The 1.4 dataset adds 13 non-null mandatory columns that a 1.3 Contract
|
|
172
|
+
Commitment dataset does not carry. In strict mode they come only from
|
|
173
|
+
[supplements](supplements.md).
|
|
174
|
+
- **Dataset-level obligations.** Dataset completeness, delivery and configuration attributes
|
|
175
|
+
(`CorrectionHandling`, `DeliveryHandling`, `DatasetCompleteness`) concern how a provider
|
|
176
|
+
publishes data, not individual rows.
|
|
@@ -34,7 +34,7 @@ FOCUS 1.x columns. No supplement can fabricate those — fix the export instead.
|
|
|
34
34
|
| `billing_period` | Billing Period | `InvoiceIssuerName`, `BillingPeriodStart`, `BillingPeriodEnd` | `BillingPeriodCreated`, `BillingPeriodLastUpdated`, `BillingPeriodStatus` |
|
|
35
35
|
| `invoice` | Invoice Detail (header level) | `InvoiceIssuerName`, `InvoiceId` | `InvoiceIssueDate`, `InvoiceIssueStatus`, `PaymentTerms`, `PaymentDueDate`, `ReferenceInvoiceId`, `PurchaseOrderNumber`, `PaymentCurrency` |
|
|
36
36
|
| `invoice_line` | Invoice Detail (line level) | the full business grain (`InvoiceIssuerName`, `InvoiceId`, `BillingAccountId`, `BillingCurrency`, `BillingPeriodStart`, `BillingPeriodEnd`, `ChargeCategory`) | `InvoiceDetailId`, `InvoiceDetailCreated`, `InvoiceDetailLastUpdated`, `InvoiceDetailDescription`, `InvoiceDetailGrain`, `PaymentCurrencyBilledCost`, `PaymentCurrencyInvoiceDetailId` (+ `BilledCost`, accepted only as a reconciliation check) |
|
|
37
|
-
| `contract_commitment` | Contract Commitment | `ContractCommitmentId` | the 1.4-new commercial terms (`ContractCommitmentCreated/LastUpdated`, `ContractCommitmentApplicability`, `…BenefitCategory`, `…FulfillmentInterval`, `…LifecycleStatus`, `…Model`, `…OfferCategory`, `…PaymentInterval`, `…PaymentModel`, `…PaymentUpfrontPercentage`, `…DiscountPercentage`) plus optional `ServiceProviderName` / `InvoiceIssuerName` overrides |
|
|
37
|
+
| `contract_commitment` | Contract Commitment | `ContractCommitmentId` | the 1.4-new commercial terms (`ContractCommitmentCreated/LastUpdated`, `ContractCommitmentApplicability`, `…BenefitCategory`, `…FulfillmentInterval`, `…LifecycleStatus`, `…Model`, `…OfferCategory`, `…PaymentInterval`, `…PaymentModel`, `…PaymentUpfrontPercentage`, `…DiscountPercentage`), the explicit term `ContractCommitmentDurationType` (`1 Year`, `3 Years`, `36 Months`: a positive whole number and a v1.4 unit; it wins over the value derived from a period spanning whole calendar months, and is required in strict mode when the period does not), plus optional `ServiceProviderName` / `InvoiceIssuerName` overrides |
|
|
38
38
|
|
|
39
39
|
Rules that apply to every kind:
|
|
40
40
|
|
|
@@ -96,7 +96,7 @@ $ focus-toolkit supplements adapters
|
|
|
96
96
|
aws-invoice-summary (v1) -> invoice
|
|
97
97
|
source: AWS Invoicing API — InvoiceSummary (ListInvoiceSummaries)
|
|
98
98
|
doc: https://docs.aws.amazon.com/aws-cost-management/latest/APIReference/API_invoicing_InvoiceSummary.html
|
|
99
|
-
aws-savings-plans (
|
|
99
|
+
aws-savings-plans (v2) -> contract_commitment
|
|
100
100
|
azure-invoice (v1) -> invoice
|
|
101
101
|
gcp-compute-commitments (v1) -> contract_commitment
|
|
102
102
|
...
|
|
@@ -107,10 +107,32 @@ Shipped adapters:
|
|
|
107
107
|
| Adapter | Provider export | Target kind |
|
|
108
108
|
|---|---|---|
|
|
109
109
|
| `aws-invoice-summary` | AWS Invoicing API `InvoiceSummary` (`aws invoicing list-invoice-summaries`) | `invoice` |
|
|
110
|
-
| `aws-savings-plans` | AWS Savings Plans inventory (`aws savingsplans describe-savings-plans`) | `contract_commitment` |
|
|
110
|
+
| `aws-savings-plans` | AWS Savings Plans inventory (`aws savingsplans describe-savings-plans`), keyed by `savingsPlanArn` (v2), so it joins a Contract Commitment keyed by the fully qualified ARN | `contract_commitment` |
|
|
111
111
|
| `azure-invoice` | Azure Billing Invoices REST API (`az billing invoice list`) | `invoice` |
|
|
112
|
+
| `azure-reservation-orders` | Azure reservation orders, REST shape (`az rest --method get --url "/providers/Microsoft.Capacity/reservationOrders?api-version=2022-11-01"`), one row per order keyed by its ARM id **lowercased** | `contract_commitment` |
|
|
113
|
+
| `azure-savings-plan-orders` | Azure savings plan orders, REST shape (`az rest --method get --url "/providers/Microsoft.BillingBenefits/savingsPlanOrders?api-version=2022-11-01"`), one row per order keyed by its ARM id **lowercased** | `contract_commitment` |
|
|
112
114
|
| `gcp-compute-commitments` | GCP Compute Engine commitments (`gcloud compute commitments list --format=json`) | `contract_commitment` |
|
|
113
115
|
|
|
116
|
+
The Azure order adapters key each commitment by the **order**, the purchase that carries
|
|
117
|
+
the term, billing plan and price; splits and merges stay inside it. The ARM id is
|
|
118
|
+
lowercased because Azure ids are case-insensitive and Microsoft's APIs and exports mix
|
|
119
|
+
casings. A Contract Commitment source must use the same key. They emit the term
|
|
120
|
+
(`ContractCommitmentDurationType`: `1 Year`, `3 Years`, `5 Years`, or `1 Month` for a monthly
|
|
121
|
+
savings plan term), the payment model and interval, the lifecycle status, the creation time
|
|
122
|
+
(`createdDateTime` for a reservation order, ARM `systemData.createdAt` for a savings plan
|
|
123
|
+
order when the response carries it) and the benefit category (`Discount`). The lifecycle status
|
|
124
|
+
comes from `provisioningState`, without a clock: `Expired`, `Cancelled` and the creation and
|
|
125
|
+
billing states map; `Succeeded` does not, because finished provisioning cannot tell a
|
|
126
|
+
running term from an exhausted pre-purchase pool. They leave out what an order does not
|
|
127
|
+
determine: the commitment model and fulfillment interval (the commitment grain belongs to
|
|
128
|
+
the plan or the reserved resource), the offer category, `LastUpdated`, applicability, the
|
|
129
|
+
discount percentage and the invoice issuer. Supply those in a `contract_commitment` file
|
|
130
|
+
keyed the same way; it merges with the adapter's columns.
|
|
131
|
+
|
|
132
|
+
The two list operations are paged: each response holds one page in `value` and a
|
|
133
|
+
`nextLink` to the next. Export every page, then pass the pages concatenated into one
|
|
134
|
+
`value` array, or each page as its own supplement file (files of one kind are merged).
|
|
135
|
+
|
|
114
136
|
Then just pass the export straight to `convert` / `supplements validate`:
|
|
115
137
|
|
|
116
138
|
```console
|
|
@@ -137,6 +159,8 @@ value is auditable back to the native export. Honesty rules:
|
|
|
137
159
|
applicability scope) are **not** emitted — the coverage report shows the residual gap and
|
|
138
160
|
you supply those separately.
|
|
139
161
|
- An export that matches no adapter falls back to the generic FOCUS-named path with a clear
|
|
140
|
-
message. Nothing is ever guessed.
|
|
162
|
+
message. Nothing is ever guessed. When it almost matches one (typically an export made
|
|
163
|
+
for an older adapter version), the error names the adapter and the fields it lacks, e.g.
|
|
164
|
+
`aws-savings-plans@2 requires savingsPlanArn`.
|
|
141
165
|
- Account-type / format variants are distinct versioned tables; an unrecognized variant
|
|
142
166
|
falls back rather than half-matching.
|
|
@@ -78,6 +78,15 @@ Affected monetary Parquet columns widen to `decimal128(38,17)` (21 integer digit
|
|
|
78
78
|
values exceeding the precision or scale fail rather than rounding. Existing CSV
|
|
79
79
|
columns remain unchanged. See [the migration evidence](generator-corrections.md).
|
|
80
80
|
|
|
81
|
+
The 0.14.0 release changes converted output in documented ways:
|
|
82
|
+
- values migrated to FOCUS 1.4 rules, with `DERIVED` manifest labels;
|
|
83
|
+
- `ContractCommitmentDurationType` in whole years;
|
|
84
|
+
- a unit-price column the source lacks is omitted, a breaking change to the output layout;
|
|
85
|
+
- the `aws-savings-plans` adapter is keyed by `savingsPlanArn`, also breaking.
|
|
86
|
+
|
|
87
|
+
Generated sample bytes are unchanged. See [CHANGELOG.md](../CHANGELOG.md) and the
|
|
88
|
+
[conversion rules](conversion-rules.md).
|
|
89
|
+
|
|
81
90
|
The toolkit's own version is independent of the FOCUS specification versions it
|
|
82
91
|
supports (generate 1.2/1.3, convert toward 1.4, detect 1.2/1.3/1.4). The FOCUS
|
|
83
92
|
version support matrix and the FOCUS compatibility policy live in
|
|
@@ -72,7 +72,7 @@ dev = [
|
|
|
72
72
|
"pytest>=8",
|
|
73
73
|
"pytest-cov>=5,<8",
|
|
74
74
|
"ruff>=0.6",
|
|
75
|
-
"mypy>=1.11,<2.
|
|
75
|
+
"mypy>=1.11,<2.5",
|
|
76
76
|
# Validates the FOCUS model-provenance JSON Schema in CI (test_model_provenance). Dev/CI only;
|
|
77
77
|
# the runtime core and the stdlib provenance verifier do not require it.
|
|
78
78
|
"jsonschema>=4.21,<5",
|