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.
Files changed (277) hide show
  1. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/CHANGELOG.md +170 -1
  2. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/PKG-INFO +35 -7
  3. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/README.md +33 -5
  4. focus_data_toolkit-0.14.0/docs/conversion-rules.md +176 -0
  5. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/supplements.md +28 -4
  6. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/versioning.md +9 -0
  7. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/pyproject.toml +1 -1
  8. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/__init__.py +27 -0
  9. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/_version.py +1 -1
  10. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/cli.py +23 -1
  11. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/context/__init__.py +9 -3
  12. focus_data_toolkit-0.14.0/src/focus_data_toolkit/context/provider.py +173 -0
  13. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/__init__.py +46 -23
  14. focus_data_toolkit-0.14.0/src/focus_data_toolkit/convert/contract_commitment.py +296 -0
  15. focus_data_toolkit-0.14.0/src/focus_data_toolkit/convert/cost_and_usage.py +732 -0
  16. focus_data_toolkit-0.14.0/src/focus_data_toolkit/convert/exceptions.py +20 -0
  17. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/streaming.py +42 -9
  18. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/validator.py +164 -8
  19. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/adapters_provenance.json +18 -2
  20. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/aws_savings_plans.json +7 -5
  21. focus_data_toolkit-0.14.0/src/focus_data_toolkit/supplement/adapters/azure_reservation_orders.json +33 -0
  22. focus_data_toolkit-0.14.0/src/focus_data_toolkit/supplement/adapters/azure_savings_plan_orders.json +35 -0
  23. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/registry.py +45 -0
  24. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/apply.py +2 -0
  25. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/gaps.py +18 -1
  26. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/kinds.py +4 -1
  27. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/loader.py +10 -1
  28. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/codes.py +22 -0
  29. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/PKG-INFO +35 -7
  30. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/SOURCES.txt +9 -0
  31. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/requires.txt +1 -1
  32. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/client_like/SOURCES.md +1 -1
  33. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/client_like/consolidated_multi_provider_1_3.csv +4 -4
  34. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/aws_1.2_CostAndUsage.log +625 -625
  35. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/aws_1.3_ContractCommitment.log +117 -117
  36. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/aws_1.3_CostAndUsage.log +865 -865
  37. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/azure_1.2_CostAndUsage.log +617 -617
  38. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/azure_1.3_ContractCommitment.log +117 -117
  39. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/azure_1.3_CostAndUsage.log +857 -857
  40. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/baseline.json +10 -10
  41. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/gcp_1.2_CostAndUsage.log +617 -617
  42. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/gcp_1.3_ContractCommitment.log +117 -117
  43. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/gcp_1.3_CostAndUsage.log +857 -857
  44. focus_data_toolkit-0.14.0/tests/test_adapters_aws.py +321 -0
  45. focus_data_toolkit-0.14.0/tests/test_adapters_azure_gcp.py +402 -0
  46. focus_data_toolkit-0.14.0/tests/test_contract_commitment_duration.py +357 -0
  47. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_contract_commitment_semantics.py +17 -7
  48. focus_data_toolkit-0.14.0/tests/test_focus_1_4_value_migrations.py +276 -0
  49. focus_data_toolkit-0.14.0/tests/test_lint_focus_1_4_rules.py +322 -0
  50. focus_data_toolkit-0.14.0/tests/test_provider_roles_1_2.py +230 -0
  51. focus_data_toolkit-0.14.0/tests/test_public_api.py +83 -0
  52. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_streaming.py +63 -0
  53. focus_data_toolkit-0.13.0/src/focus_data_toolkit/context/provider.py +0 -90
  54. focus_data_toolkit-0.13.0/src/focus_data_toolkit/convert/contract_commitment.py +0 -182
  55. focus_data_toolkit-0.13.0/src/focus_data_toolkit/convert/cost_and_usage.py +0 -212
  56. focus_data_toolkit-0.13.0/tests/test_adapters_aws.py +0 -179
  57. focus_data_toolkit-0.13.0/tests/test_adapters_azure_gcp.py +0 -142
  58. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/CONTRIBUTING.md +0 -0
  59. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/LICENSE +0 -0
  60. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/LICENSES/CC-BY-4.0.txt +0 -0
  61. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/MANIFEST.in +0 -0
  62. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/NOTICE +0 -0
  63. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/SECURITY.md +0 -0
  64. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/audit/2026-07-improvement-plan.md +0 -0
  65. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/audit/2026-07-repo-audit.md +0 -0
  66. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/compatibility.md +0 -0
  67. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/generator-corrections.md +0 -0
  68. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/generator-statistics.json +0 -0
  69. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/model-provenance.md +0 -0
  70. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/pr53-review-followup.md +0 -0
  71. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/releasing.md +0 -0
  72. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/runner.md +0 -0
  73. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/security-model.md +0 -0
  74. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/docs/studio.md +0 -0
  75. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/schema/model_provenance.schema.json +0 -0
  76. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/__init__.py +0 -0
  77. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/check_pinned_actions.py +0 -0
  78. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/describe_generated_samples.py +0 -0
  79. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/generate_resolved_sbom.py +0 -0
  80. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/generate_sbom.py +0 -0
  81. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/regenerate_golden_fixtures.py +0 -0
  82. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/sample_audit.py +0 -0
  83. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/validate_official_samples.py +0 -0
  84. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/verify_model_provenance.py +0 -0
  85. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/scripts/verify_release.py +0 -0
  86. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/setup.cfg +0 -0
  87. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/__main__.py +0 -0
  88. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/context/billing.py +0 -0
  89. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/billing_period.py +0 -0
  90. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/contract_applied.py +0 -0
  91. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/detect.py +0 -0
  92. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/convert/invoice_detail.py +0 -0
  93. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/errors.py +0 -0
  94. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/focus_json.py +0 -0
  95. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/__init__.py +0 -0
  96. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/_shim.py +0 -0
  97. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/__init__.py +0 -0
  98. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/allocation_math.py +0 -0
  99. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/context.py +0 -0
  100. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/determinism.py +0 -0
  101. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/json_focus.py +0 -0
  102. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/ladder.py +0 -0
  103. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/scenarios_core.py +0 -0
  104. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/engine/serialize.py +0 -0
  105. {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
  106. {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
  107. {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
  108. {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
  109. {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
  110. {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
  111. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/providers/__init__.py +0 -0
  112. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/providers/aws.py +0 -0
  113. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/providers/azure.py +0 -0
  114. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/providers/gcp.py +0 -0
  115. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/providers/profile.py +0 -0
  116. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/scenarios.py +0 -0
  117. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/versions/__init__.py +0 -0
  118. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/versions/adapter.py +0 -0
  119. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/versions/v1_2.py +0 -0
  120. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/generators/versions/v1_3.py +0 -0
  121. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/__init__.py +0 -0
  122. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/atomic_writer.py +0 -0
  123. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/csv_io.py +0 -0
  124. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/parquet_io.py +0 -0
  125. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/records.py +0 -0
  126. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/io/row_source.py +0 -0
  127. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/lifecycle.py +0 -0
  128. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/manifest.py +0 -0
  129. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/__init__.py +0 -0
  130. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/capabilities.py +0 -0
  131. {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
  132. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/focus_1_4_model.json +0 -0
  133. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/focus_1_4_servicesubcategory.json +0 -0
  134. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/focus_json_keys.py +0 -0
  135. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/iso_4217_currencies.json +0 -0
  136. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schema_check.py +0 -0
  137. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schemas/allocatedmethoddetailsobjectschema.json +0 -0
  138. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schemas/commitmentprogrameligibilitydetailsobjectschema.json +0 -0
  139. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schemas/contractappliedobjectschema.json +0 -0
  140. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/json_schemas/contractcommitmentapplicabilityobjectschema.json +0 -0
  141. {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
  142. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/model/model_provenance.json +0 -0
  143. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/modes.py +0 -0
  144. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/official_report.py +0 -0
  145. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/official_validator.py +0 -0
  146. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/progress.py +0 -0
  147. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/provenance.py +0 -0
  148. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/py.typed +0 -0
  149. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/runtime.py +0 -0
  150. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/schema/__init__.py +0 -0
  151. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/schema/detection.py +0 -0
  152. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/schema/registry.py +0 -0
  153. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/storage/__init__.py +0 -0
  154. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/storage/external_index.py +0 -0
  155. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/storage/spill.py +0 -0
  156. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/__init__.py +0 -0
  157. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/app.py +0 -0
  158. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/config.py +0 -0
  159. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/frontend/app.js +0 -0
  160. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/frontend/index.html +0 -0
  161. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/frontend/style.css +0 -0
  162. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/jobs.py +0 -0
  163. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/preview.py +0 -0
  164. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/security.py +0 -0
  165. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/studio/server.py +0 -0
  166. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/__init__.py +0 -0
  167. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/__init__.py +0 -0
  168. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/aws_invoice_summary.json +0 -0
  169. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/azure_invoice.json +0 -0
  170. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/adapters/gcp_compute_commitments.json +0 -0
  171. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/spec.py +0 -0
  172. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/supplement/validate.py +0 -0
  173. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/__init__.py +0 -0
  174. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/allocation.py +0 -0
  175. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/bundle.py +0 -0
  176. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/corrections.py +0 -0
  177. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/reconciliation.py +0 -0
  178. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit/validate/referential.py +0 -0
  179. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/dependency_links.txt +0 -0
  180. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/entry_points.txt +0 -0
  181. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/src/focus_data_toolkit.egg-info/top_level.txt +0 -0
  182. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/conftest.py +0 -0
  183. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/README.md +0 -0
  184. {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
  185. {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
  186. {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
  187. {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
  188. {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
  189. {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
  190. {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
  191. {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
  192. {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
  193. {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
  194. {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
  195. {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
  196. {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
  197. {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
  198. {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
  199. {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
  200. {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
  201. {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
  202. {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
  203. {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
  204. {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
  205. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/scenarios_correction_set.csv +0 -0
  206. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_equal.csv +0 -0
  207. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_negative.csv +0 -0
  208. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_weighted.csv +0 -0
  209. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/correctness_migration/README.md +0 -0
  210. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/golden/correctness_migration/pr67_before.json +0 -0
  211. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/SOURCES.md +0 -0
  212. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/generator_validation/README.md +0 -0
  213. {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
  214. {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
  215. {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
  216. {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
  217. {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
  218. {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
  219. {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
  220. {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
  221. {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
  222. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/invoice_detail_grain_example.json +0 -0
  223. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/fixtures/official/numeric_format_examples.json +0 -0
  224. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_atomic_write.py +0 -0
  225. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_billing_lifecycle.py +0 -0
  226. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_bundle_gate.py +0 -0
  227. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_bundle_validation.py +0 -0
  228. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_capabilities.py +0 -0
  229. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_cli.py +0 -0
  230. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_client_like.py +0 -0
  231. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_container.py +0 -0
  232. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_contract_applied.py +0 -0
  233. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_convert_roundtrip.py +0 -0
  234. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_corrections.py +0 -0
  235. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_cross_provider.py +0 -0
  236. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_detect.py +0 -0
  237. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_fake_provider.py +0 -0
  238. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_fixtures_are_synthetic.py +0 -0
  239. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_focus_json.py +0 -0
  240. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_gaps.py +0 -0
  241. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_generated_conformance.py +0 -0
  242. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_generator_golden.py +0 -0
  243. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_generators.py +0 -0
  244. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_grouping_keys.py +0 -0
  245. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_lifecycle_chains.py +0 -0
  246. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_lineage_counters.py +0 -0
  247. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_lint_focus.py +0 -0
  248. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_manifest.py +0 -0
  249. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_model_provenance.py +0 -0
  250. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_multi_provider.py +0 -0
  251. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_official_json_schemas.py +0 -0
  252. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_official_report.py +0 -0
  253. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_official_validator.py +0 -0
  254. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_packaging.py +0 -0
  255. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_parquet.py +0 -0
  256. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_parquet_input.py +0 -0
  257. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_participant_entities.py +0 -0
  258. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_partitioning.py +0 -0
  259. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_progress_cancel.py +0 -0
  260. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_release_tooling.py +0 -0
  261. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_replace_recovery.py +0 -0
  262. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_reviewed_roundtrip.py +0 -0
  263. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_reviewed_sample_semantics.py +0 -0
  264. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_runtime.py +0 -0
  265. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_schema_detection.py +0 -0
  266. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_single_source_focus_rules.py +0 -0
  267. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_spill.py +0 -0
  268. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_split_allocation.py +0 -0
  269. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_strict_conversion.py +0 -0
  270. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_studio.py +0 -0
  271. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_supplement_apply.py +0 -0
  272. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_supplement_loader.py +0 -0
  273. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_supplement_streaming.py +0 -0
  274. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_synthetic_conversion.py +0 -0
  275. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_validator.py +0 -0
  276. {focus_data_toolkit-0.13.0 → focus_data_toolkit-0.14.0}/tests/test_workflow_pins.py +0 -0
  277. {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.12.0...HEAD
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.13.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.4,>=1.11; extra == "dev"
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.13.0** with corrected subscriptions, traceable taxes,
67
- stable offers and partially used fleets of 500 machines. See the
68
- [generator corrections and validation evidence](docs/generator-corrections.md)
69
- for the new byte baseline and sample assumptions. The Docker examples below
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.13.0** with corrected subscriptions, traceable taxes,
14
- stable offers and partially used fleets of 500 machines. See the
15
- [generator corrections and validation evidence](docs/generator-corrections.md)
16
- for the new byte baseline and sample assumptions. The Docker examples below
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 (v1) -> contract_commitment
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.4",
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",