@spottoai/types-package 1.0.2-dev1138.2 → 1.0.2-dev1138.5

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 (261) hide show
  1. package/README.md +9 -0
  2. package/dist/accounts/accounts.d.ts +77 -4
  3. package/dist/accounts/accounts.d.ts.map +1 -1
  4. package/dist/accounts/accounts.js +2 -1
  5. package/dist/accounts/accounts.js.map +1 -1
  6. package/dist/accounts/azureManualOnboarding.d.ts +114 -0
  7. package/dist/accounts/azureManualOnboarding.d.ts.map +1 -0
  8. package/dist/accounts/azureManualOnboarding.js +12 -0
  9. package/dist/accounts/azureManualOnboarding.js.map +1 -0
  10. package/dist/accounts/azureSpSetup.d.ts +128 -31
  11. package/dist/accounts/azureSpSetup.d.ts.map +1 -1
  12. package/dist/accounts/azureSpSetup.js +15 -0
  13. package/dist/accounts/azureSpSetup.js.map +1 -1
  14. package/dist/accounts/azureSpSetupDurable.d.ts +8 -0
  15. package/dist/accounts/azureSpSetupDurable.d.ts.map +1 -1
  16. package/dist/accounts/azureSpSetupDurable.js +28 -0
  17. package/dist/accounts/azureSpSetupDurable.js.map +1 -1
  18. package/dist/accounts/index.d.ts +1 -0
  19. package/dist/accounts/index.d.ts.map +1 -1
  20. package/dist/accounts/index.js +1 -0
  21. package/dist/accounts/index.js.map +1 -1
  22. package/dist/accounts/readPermissions.d.ts +1 -1
  23. package/dist/accounts/readPermissions.d.ts.map +1 -1
  24. package/dist/accounts/readPermissions.js.map +1 -1
  25. package/dist/accounts/validation.d.ts +24 -0
  26. package/dist/accounts/validation.d.ts.map +1 -1
  27. package/dist/ai/index.d.ts +14 -1
  28. package/dist/ai/index.d.ts.map +1 -1
  29. package/dist/ai/index.js +12 -0
  30. package/dist/ai/index.js.map +1 -1
  31. package/dist/aws/commitments-planning.d.ts +5 -0
  32. package/dist/aws/commitments-planning.d.ts.map +1 -1
  33. package/dist/aws/commitments-planning.js +19 -0
  34. package/dist/aws/commitments-planning.js.map +1 -1
  35. package/dist/aws/estates.d.ts +1 -1
  36. package/dist/aws/estates.d.ts.map +1 -1
  37. package/dist/aws/estates.js +7 -1
  38. package/dist/aws/estates.js.map +1 -1
  39. package/dist/aws/index.d.ts +5 -0
  40. package/dist/aws/index.d.ts.map +1 -1
  41. package/dist/aws/index.js +5 -0
  42. package/dist/aws/index.js.map +1 -1
  43. package/dist/aws/onboarding.d.ts +3 -0
  44. package/dist/aws/onboarding.d.ts.map +1 -1
  45. package/dist/aws/organizationCommitments.d.ts +260 -0
  46. package/dist/aws/organizationCommitments.d.ts.map +1 -0
  47. package/dist/aws/organizationCommitments.js +53 -0
  48. package/dist/aws/organizationCommitments.js.map +1 -0
  49. package/dist/aws/organizationCommitmentsApiValidation.d.ts +21 -0
  50. package/dist/aws/organizationCommitmentsApiValidation.d.ts.map +1 -0
  51. package/dist/aws/organizationCommitmentsApiValidation.js +226 -0
  52. package/dist/aws/organizationCommitmentsApiValidation.js.map +1 -0
  53. package/dist/aws/organizationCommitmentsValidation.d.ts +12 -0
  54. package/dist/aws/organizationCommitmentsValidation.d.ts.map +1 -0
  55. package/dist/aws/organizationCommitmentsValidation.js +228 -0
  56. package/dist/aws/organizationCommitmentsValidation.js.map +1 -0
  57. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.d.ts +9 -0
  58. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.d.ts.map +1 -0
  59. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js +407 -0
  60. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js.map +1 -0
  61. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.d.ts +17 -0
  62. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.d.ts.map +1 -0
  63. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js +6 -0
  64. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js.map +1 -0
  65. package/dist/aws/requests.d.ts +13 -2
  66. package/dist/aws/requests.d.ts.map +1 -1
  67. package/dist/aws/requests.js +1 -1
  68. package/dist/aws/requests.js.map +1 -1
  69. package/dist/azure/commitmentsPlanning.d.ts +2 -2
  70. package/dist/azure/commitmentsPlanning.d.ts.map +1 -1
  71. package/dist/azure/financialAnalytics.d.ts +68 -2
  72. package/dist/azure/financialAnalytics.d.ts.map +1 -1
  73. package/dist/azure/financialAnalyticsDelivery.d.ts +2 -0
  74. package/dist/azure/financialAnalyticsDelivery.d.ts.map +1 -1
  75. package/dist/azure/financialAnalyticsDelivery.js +4 -1
  76. package/dist/azure/financialAnalyticsDelivery.js.map +1 -1
  77. package/dist/azure/financialAnalyticsDisplayKernel.d.ts +12 -0
  78. package/dist/azure/financialAnalyticsDisplayKernel.d.ts.map +1 -0
  79. package/dist/azure/financialAnalyticsDisplayKernel.js +144 -0
  80. package/dist/azure/financialAnalyticsDisplayKernel.js.map +1 -0
  81. package/dist/azure/financialAnalyticsValidation.d.ts.map +1 -1
  82. package/dist/azure/financialAnalyticsValidation.js +61 -7
  83. package/dist/azure/financialAnalyticsValidation.js.map +1 -1
  84. package/dist/azure/financialAuthorityProjectionKernel.d.ts +7 -0
  85. package/dist/azure/financialAuthorityProjectionKernel.d.ts.map +1 -0
  86. package/dist/azure/financialAuthorityProjectionKernel.js +165 -0
  87. package/dist/azure/financialAuthorityProjectionKernel.js.map +1 -0
  88. package/dist/azure/financialAuthorityView.d.ts +4 -0
  89. package/dist/azure/financialAuthorityView.d.ts.map +1 -1
  90. package/dist/azure/financialAuthorityView.js.map +1 -1
  91. package/dist/azure/financialAuthorityViewValidation.d.ts +3 -1
  92. package/dist/azure/financialAuthorityViewValidation.d.ts.map +1 -1
  93. package/dist/azure/financialAuthorityViewValidation.js +206 -2
  94. package/dist/azure/financialAuthorityViewValidation.js.map +1 -1
  95. package/dist/azure/financialChargeComposition.d.ts +81 -0
  96. package/dist/azure/financialChargeComposition.d.ts.map +1 -0
  97. package/dist/azure/financialChargeComposition.js +41 -0
  98. package/dist/azure/financialChargeComposition.js.map +1 -0
  99. package/dist/azure/financialChargeCompositionKernel.d.ts +3 -0
  100. package/dist/azure/financialChargeCompositionKernel.d.ts.map +1 -0
  101. package/dist/azure/financialChargeCompositionKernel.js +53 -0
  102. package/dist/azure/financialChargeCompositionKernel.js.map +1 -0
  103. package/dist/azure/financialChargeCompositionValidation.d.ts +8 -0
  104. package/dist/azure/financialChargeCompositionValidation.d.ts.map +1 -0
  105. package/dist/azure/financialChargeCompositionValidation.js +164 -0
  106. package/dist/azure/financialChargeCompositionValidation.js.map +1 -0
  107. package/dist/azure/financialDataflow.d.ts +8 -1
  108. package/dist/azure/financialDataflow.d.ts.map +1 -1
  109. package/dist/azure/financialDataflow.js.map +1 -1
  110. package/dist/azure/financialDataflowKernel.d.ts +2 -0
  111. package/dist/azure/financialDataflowKernel.d.ts.map +1 -1
  112. package/dist/azure/financialDataflowKernel.js +60 -7
  113. package/dist/azure/financialDataflowKernel.js.map +1 -1
  114. package/dist/azure/financialDataflowValidation.d.ts +1 -1
  115. package/dist/azure/financialDataflowValidation.d.ts.map +1 -1
  116. package/dist/azure/financialDataflowValidation.js +146 -28
  117. package/dist/azure/financialDataflowValidation.js.map +1 -1
  118. package/dist/azure/financialPolicy.d.ts +3 -0
  119. package/dist/azure/financialPolicy.d.ts.map +1 -1
  120. package/dist/azure/financialPolicy.js.map +1 -1
  121. package/dist/azure/financialPolicyValidation.d.ts.map +1 -1
  122. package/dist/azure/financialPolicyValidation.js +5 -0
  123. package/dist/azure/financialPolicyValidation.js.map +1 -1
  124. package/dist/azure/financialProjection.d.ts +11 -1
  125. package/dist/azure/financialProjection.d.ts.map +1 -1
  126. package/dist/azure/financialProjection.js.map +1 -1
  127. package/dist/azure/financialProjectionValidation.d.ts.map +1 -1
  128. package/dist/azure/financialProjectionValidation.js +43 -3
  129. package/dist/azure/financialProjectionValidation.js.map +1 -1
  130. package/dist/azure/financialSavingsAuthority.d.ts +25 -4
  131. package/dist/azure/financialSavingsAuthority.d.ts.map +1 -1
  132. package/dist/azure/financialSavingsAuthorityValidation.d.ts +6 -2
  133. package/dist/azure/financialSavingsAuthorityValidation.d.ts.map +1 -1
  134. package/dist/azure/financialSavingsAuthorityValidation.js +126 -2
  135. package/dist/azure/financialSavingsAuthorityValidation.js.map +1 -1
  136. package/dist/azure/financialSavingsCoordinateValidation.d.ts.map +1 -1
  137. package/dist/azure/financialSavingsCoordinateValidation.js +62 -19
  138. package/dist/azure/financialSavingsCoordinateValidation.js.map +1 -1
  139. package/dist/azure/financialSavingsSurfaceProjection.d.ts +58 -3
  140. package/dist/azure/financialSavingsSurfaceProjection.d.ts.map +1 -1
  141. package/dist/azure/financialSavingsSurfaceProjection.js +3 -1
  142. package/dist/azure/financialSavingsSurfaceProjection.js.map +1 -1
  143. package/dist/azure/financialSavingsSurfaceProjectionKernel.d.ts +14 -0
  144. package/dist/azure/financialSavingsSurfaceProjectionKernel.d.ts.map +1 -0
  145. package/dist/azure/financialSavingsSurfaceProjectionKernel.js +326 -0
  146. package/dist/azure/financialSavingsSurfaceProjectionKernel.js.map +1 -0
  147. package/dist/azure/financialSavingsSurfaceProjectionValidation.d.ts.map +1 -1
  148. package/dist/azure/financialSavingsSurfaceProjectionValidation.js +126 -12
  149. package/dist/azure/financialSavingsSurfaceProjectionValidation.js.map +1 -1
  150. package/dist/azure/financialScope.d.ts +6 -0
  151. package/dist/azure/financialScope.d.ts.map +1 -1
  152. package/dist/azure/financialScope.js +6 -0
  153. package/dist/azure/financialScope.js.map +1 -1
  154. package/dist/azure/governance.d.ts +145 -0
  155. package/dist/azure/governance.d.ts.map +1 -1
  156. package/dist/azure/governance.js +2 -1
  157. package/dist/azure/governance.js.map +1 -1
  158. package/dist/azure/metrics.d.ts +2 -2
  159. package/dist/azure/metrics.d.ts.map +1 -1
  160. package/dist/azure/payloads.d.ts +19 -0
  161. package/dist/azure/payloads.d.ts.map +1 -1
  162. package/dist/azure/prices.d.ts +16 -3
  163. package/dist/azure/prices.d.ts.map +1 -1
  164. package/dist/azure/recommendationState.d.ts +1 -1
  165. package/dist/azure/recommendationState.d.ts.map +1 -1
  166. package/dist/azure/recommendations.d.ts +15 -5
  167. package/dist/azure/recommendations.d.ts.map +1 -1
  168. package/dist/azure/reports.d.ts +3 -2
  169. package/dist/azure/reports.d.ts.map +1 -1
  170. package/dist/azure/reports.js.map +1 -1
  171. package/dist/azure/resources.d.ts +12 -0
  172. package/dist/azure/resources.d.ts.map +1 -1
  173. package/dist/azure/serviceRetirement.d.ts +33 -2
  174. package/dist/azure/serviceRetirement.d.ts.map +1 -1
  175. package/dist/azure/subscriptions.d.ts +13 -2
  176. package/dist/azure/subscriptions.d.ts.map +1 -1
  177. package/dist/azure/views.d.ts +26 -3
  178. package/dist/azure/views.d.ts.map +1 -1
  179. package/dist/azure/views.js.map +1 -1
  180. package/dist/azure/vmPatchManagement.d.ts +109 -0
  181. package/dist/azure/vmPatchManagement.d.ts.map +1 -0
  182. package/dist/azure/vmPatchManagement.js +7 -0
  183. package/dist/azure/vmPatchManagement.js.map +1 -0
  184. package/dist/common/provider.d.ts +53 -0
  185. package/dist/common/provider.d.ts.map +1 -1
  186. package/dist/common/provider.js.map +1 -1
  187. package/dist/environment/canonicalization.d.ts +12 -0
  188. package/dist/environment/canonicalization.d.ts.map +1 -0
  189. package/dist/environment/canonicalization.js +40 -0
  190. package/dist/environment/canonicalization.js.map +1 -0
  191. package/dist/environment/contracts.d.ts +196 -0
  192. package/dist/environment/contracts.d.ts.map +1 -0
  193. package/dist/environment/contracts.js +21 -0
  194. package/dist/environment/contracts.js.map +1 -0
  195. package/dist/environment/index.d.ts +5 -0
  196. package/dist/environment/index.d.ts.map +1 -0
  197. package/dist/environment/index.js +21 -0
  198. package/dist/environment/index.js.map +1 -0
  199. package/dist/environment/internal.d.ts +21 -0
  200. package/dist/environment/internal.d.ts.map +1 -0
  201. package/dist/environment/internal.js +113 -0
  202. package/dist/environment/internal.js.map +1 -0
  203. package/dist/environment/references.d.ts +18 -0
  204. package/dist/environment/references.d.ts.map +1 -0
  205. package/dist/environment/references.js +206 -0
  206. package/dist/environment/references.js.map +1 -0
  207. package/dist/environment/validation.d.ts +28 -0
  208. package/dist/environment/validation.d.ts.map +1 -0
  209. package/dist/environment/validation.js +320 -0
  210. package/dist/environment/validation.js.map +1 -0
  211. package/dist/esm/accounts/accounts.js +1 -0
  212. package/dist/esm/accounts/azureManualOnboarding.js +8 -0
  213. package/dist/esm/accounts/azureSpSetup.js +14 -1
  214. package/dist/esm/accounts/azureSpSetupDurable.js +27 -1
  215. package/dist/esm/accounts/index.js +1 -0
  216. package/dist/esm/ai/index.js +10 -1
  217. package/dist/esm/aws/commitments-planning.js +5 -0
  218. package/dist/esm/aws/estates.js +7 -1
  219. package/dist/esm/aws/index.js +5 -0
  220. package/dist/esm/aws/organizationCommitments.js +48 -0
  221. package/dist/esm/aws/organizationCommitmentsApiValidation.js +220 -0
  222. package/dist/esm/aws/organizationCommitmentsValidation.js +224 -0
  223. package/dist/esm/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js +402 -0
  224. package/dist/esm/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js +2 -0
  225. package/dist/esm/aws/requests.js +1 -1
  226. package/dist/esm/azure/financialAnalyticsDelivery.js +2 -0
  227. package/dist/esm/azure/financialAnalyticsDisplayKernel.js +139 -0
  228. package/dist/esm/azure/financialAnalyticsValidation.js +61 -7
  229. package/dist/esm/azure/financialAuthorityProjectionKernel.js +159 -0
  230. package/dist/esm/azure/financialAuthorityViewValidation.js +204 -1
  231. package/dist/esm/azure/financialChargeComposition.js +36 -0
  232. package/dist/esm/azure/financialChargeCompositionKernel.js +48 -0
  233. package/dist/esm/azure/financialChargeCompositionValidation.js +155 -0
  234. package/dist/esm/azure/financialDataflowKernel.js +60 -7
  235. package/dist/esm/azure/financialDataflowValidation.js +146 -28
  236. package/dist/esm/azure/financialPolicyValidation.js +5 -0
  237. package/dist/esm/azure/financialProjectionValidation.js +43 -3
  238. package/dist/esm/azure/financialSavingsAuthorityValidation.js +124 -2
  239. package/dist/esm/azure/financialSavingsCoordinateValidation.js +62 -19
  240. package/dist/esm/azure/financialSavingsSurfaceProjection.js +2 -0
  241. package/dist/esm/azure/financialSavingsSurfaceProjectionKernel.js +318 -0
  242. package/dist/esm/azure/financialSavingsSurfaceProjectionValidation.js +126 -12
  243. package/dist/esm/azure/financialScope.js +6 -0
  244. package/dist/esm/azure/governance.js +1 -0
  245. package/dist/esm/azure/vmPatchManagement.js +3 -0
  246. package/dist/esm/entries/commitments-planning.js +2 -2
  247. package/dist/esm/entries/environment.js +3 -0
  248. package/dist/esm/environment/canonicalization.js +34 -0
  249. package/dist/esm/environment/contracts.js +17 -0
  250. package/dist/esm/environment/index.js +4 -0
  251. package/dist/esm/environment/internal.js +97 -0
  252. package/dist/esm/environment/references.js +194 -0
  253. package/dist/esm/environment/validation.js +303 -0
  254. package/dist/esm/index.js +2 -0
  255. package/dist/index.d.ts +2 -0
  256. package/dist/index.d.ts.map +1 -1
  257. package/dist/index.js +2 -0
  258. package/dist/index.js.map +1 -1
  259. package/dist/portfolio/portfolioOperations.d.ts +1 -1
  260. package/dist/portfolio/portfolioOperations.d.ts.map +1 -1
  261. package/package.json +15 -3
@@ -30,6 +30,7 @@ const comparableCoordinate = (value) => ({
30
30
  estimateLens: value.estimateLens,
31
31
  requestedCurrencyCode: value.requestedCurrencyCode,
32
32
  accountingCurrency: value.accountingCurrency,
33
+ chargeInclusionPolicyRef: value.chargeInclusionPolicyRef,
33
34
  });
34
35
  const hasSameFinancialDimensions = (left, right) => canonicalizeFinancialDataflowJsonV1(comparableCoordinate(left)) === canonicalizeFinancialDataflowJsonV1(comparableCoordinate(right));
35
36
  const hasComparableTrendPeriods = (current, comparison) => {
@@ -105,16 +106,47 @@ const isInputIdentity = (value) => {
105
106
  return false;
106
107
  }
107
108
  dates.add(point.date);
109
+ const pointForecastFields = [
110
+ point.forecastEligibleAmount,
111
+ point.oneTimeAmount,
112
+ point.unknownRecurrenceAmount,
113
+ ];
114
+ if (!pointForecastFields.every(amount => isCanonicalExactMoney({ amount, currencyCode: coordinate.accountingCurrency.currencyCode })) ||
115
+ (point.forecastStatus !== 'available' && point.forecastStatus !== 'partial') ||
116
+ (point.forecastStatus === 'available'
117
+ ? point.forecastReasonCodes !== undefined || point.unknownRecurrenceAmount !== '0'
118
+ : !isReasonCodes(point.forecastReasonCodes))) {
119
+ return false;
120
+ }
108
121
  if (point.status === 'available') {
109
- if (!hasFinancialDataflowExactFieldsV1(point, ['date', 'compositionId', 'status', 'amount']) ||
110
- !isCanonicalExactMoney({ amount: point.amount, currencyCode: 'AUD' }))
122
+ if (!hasFinancialDataflowExactFieldsV1(point, [
123
+ 'date',
124
+ 'compositionId',
125
+ 'status',
126
+ 'amount',
127
+ 'forecastEligibleAmount',
128
+ 'oneTimeAmount',
129
+ 'unknownRecurrenceAmount',
130
+ 'forecastStatus',
131
+ ], ['forecastReasonCodes']) ||
132
+ !isCanonicalExactMoney({ amount: point.amount, currencyCode: coordinate.accountingCurrency.currencyCode }))
111
133
  return false;
112
134
  availableDayCount += 1;
113
135
  }
114
136
  else {
115
137
  if (point.status !== 'partial' ||
116
- !hasFinancialDataflowExactFieldsV1(point, ['date', 'compositionId', 'status', 'knownAmount', 'reasonCodes']) ||
117
- !isCanonicalExactMoney({ amount: point.knownAmount, currencyCode: 'AUD' }) ||
138
+ !hasFinancialDataflowExactFieldsV1(point, [
139
+ 'date',
140
+ 'compositionId',
141
+ 'status',
142
+ 'knownAmount',
143
+ 'reasonCodes',
144
+ 'forecastEligibleAmount',
145
+ 'oneTimeAmount',
146
+ 'unknownRecurrenceAmount',
147
+ 'forecastStatus',
148
+ ], ['forecastReasonCodes']) ||
149
+ !isCanonicalExactMoney({ amount: point.knownAmount, currencyCode: coordinate.accountingCurrency.currencyCode }) ||
118
150
  !isReasonCodes(point.reasonCodes))
119
151
  return false;
120
152
  partialDayCount += 1;
@@ -174,7 +206,13 @@ export const canonicalizeFinancialAnalyticsInputIdentityV1 = (value) => {
174
206
  referenceCompositions: [...value.referenceCompositions].sort((left, right) => left.compositionId.localeCompare(right.compositionId)),
175
207
  points: [...value.points]
176
208
  .sort((left, right) => left.date.localeCompare(right.date))
177
- .map(point => (point.status === 'partial' ? { ...point, reasonCodes: [...point.reasonCodes].sort() } : point)),
209
+ .map(point => ({
210
+ ...point,
211
+ ...(point.status === 'partial' ? { reasonCodes: [...point.reasonCodes].sort() } : {}),
212
+ ...(point.forecastReasonCodes === undefined
213
+ ? {}
214
+ : { forecastReasonCodes: [...point.forecastReasonCodes].sort() }),
215
+ })),
178
216
  gaps: [...value.gaps]
179
217
  .sort((left, right) => `${left.startDate}\u0000${left.endDateExclusive}`.localeCompare(`${right.startDate}\u0000${right.endDateExclusive}`))
180
218
  .map(gap => ({ ...gap, reasonCodes: [...gap.reasonCodes].sort() })),
@@ -191,6 +229,18 @@ export const isFinancialAnalyticsInputSeriesV1 = (value) => {
191
229
  };
192
230
  const nextCalendarDate = (value) => new Date(Date.parse(`${value}T00:00:00.000Z`) + 86400000).toISOString().slice(0, 10);
193
231
  const hasSameReasonCodes = (left, right) => left.length === right.length && left.every(reasonCode => right.includes(reasonCode));
232
+ const hasSameForecastSlice = (point, composition) => {
233
+ const selection = composition.chargeSelection;
234
+ return selection !== undefined &&
235
+ point.forecastEligibleAmount === selection.forecastEligibleAmount &&
236
+ point.oneTimeAmount === selection.oneTimeAmount &&
237
+ point.unknownRecurrenceAmount === selection.unknownRecurrenceAmount &&
238
+ point.forecastStatus === selection.forecastStatus &&
239
+ (point.forecastReasonCodes === undefined
240
+ ? selection.forecastReasonCodes === undefined
241
+ : selection.forecastReasonCodes !== undefined &&
242
+ hasSameReasonCodes(point.forecastReasonCodes, selection.forecastReasonCodes));
243
+ };
194
244
  /** Proves that every analytics point is an exact projection of its referenced daily composition. */
195
245
  export const isFinancialAnalyticsInputSeriesCompatibleV1 = (input, dailyCompositions) => {
196
246
  if (!isFinancialAnalyticsInputSeriesV1(input) ||
@@ -206,6 +256,7 @@ export const isFinancialAnalyticsInputSeriesCompatibleV1 = (input, dailyComposit
206
256
  return input.points.every(point => {
207
257
  const composition = compositionById.get(point.compositionId);
208
258
  if (composition === undefined ||
259
+ composition.amount.status === 'unavailable' ||
209
260
  composition.coordinate.periodRole !== 'current-spend' ||
210
261
  composition.coordinate.period.windowKind !== 'daily' ||
211
262
  composition.coordinate.period.requested.startDate !== point.date ||
@@ -214,11 +265,14 @@ export const isFinancialAnalyticsInputSeriesCompatibleV1 = (input, dailyComposit
214
265
  return false;
215
266
  }
216
267
  if (point.status === 'available') {
217
- return composition.amount.status === 'available' && point.amount === composition.amount.amount;
268
+ return (composition.amount.status === 'available' &&
269
+ point.amount === composition.amount.amount &&
270
+ hasSameForecastSlice(point, composition));
218
271
  }
219
272
  return (composition.amount.status === 'partial' &&
220
273
  point.knownAmount === composition.amount.knownAmount &&
221
- hasSameReasonCodes(point.reasonCodes, composition.amount.reasonCodes));
274
+ hasSameReasonCodes(point.reasonCodes, composition.amount.reasonCodes) &&
275
+ hasSameForecastSlice(point, composition));
222
276
  });
223
277
  };
224
278
  const isResult = (value, currency) => {
@@ -0,0 +1,159 @@
1
+ import { FINANCIAL_AUTHORITY_RESOURCE_PROJECTION_CONTRACT_VERSION_V1, } from './financialAuthorityView.js';
2
+ import { FINANCIAL_SAVINGS_RESOURCE_PROJECTION_CONTRACT_VERSION_V1, } from './financialSavingsAuthority.js';
3
+ const normalize = (value) => value.trim().toLowerCase().replace(/\/+$/, '');
4
+ /** Derives one compact, non-additive resource view from a validated canonical authority. */
5
+ export const projectFinancialAuthorityResourceV1 = (authority, resourceType, scopeId) => {
6
+ const normalizedType = normalize(resourceType);
7
+ const normalizedScopeId = normalize(scopeId);
8
+ const coverage = authority.scopeCoverage.find(entry => normalize(entry.resourceType) === normalizedType && entry.scopeIds.some(candidate => normalize(candidate) === normalizedScopeId));
9
+ if (!coverage)
10
+ return undefined;
11
+ const coordinates = authority.coordinates.map(coordinate => {
12
+ const owners = coordinate.ownerBaselines.filter(baseline => normalize(baseline.scopeId) === normalizedScopeId);
13
+ const ownerBaseline = owners[0];
14
+ if (owners.length !== 1 || !ownerBaseline)
15
+ throw new TypeError('Financial authority resource owner is missing or ambiguous.');
16
+ const chargeComposition = ownerBaseline.status === 'available' && ownerBaseline.baselineKind === 'owner'
17
+ ? coordinate.chargeCompositions.find(composition => composition.baselineId === ownerBaseline.baselineId)
18
+ : undefined;
19
+ if (ownerBaseline.status === 'available' && ownerBaseline.baselineKind === 'owner' && !chargeComposition) {
20
+ throw new TypeError('Financial authority resource charge composition is missing.');
21
+ }
22
+ const displayRollups = coordinate.displayRollups.filter(rollup => normalize(rollup.displayScopeId) === normalizedScopeId);
23
+ const displayMemberships = new Set(displayRollups.flatMap(rollup => rollup.members.map(member => `${member.baselineId}\u0000${member.componentId}`)));
24
+ return {
25
+ coordinateId: coordinate.coordinateId,
26
+ periodRole: coordinate.periodRole,
27
+ period: coordinate.period,
28
+ costBasis: coordinate.costBasis,
29
+ estimateLens: coordinate.estimateLens,
30
+ ...(coordinate.requestedCurrencyCode === undefined ? {} : { requestedCurrencyCode: coordinate.requestedCurrencyCode }),
31
+ ownerBaseline,
32
+ ...(chargeComposition === undefined ? {} : { chargeComposition }),
33
+ componentDescriptors: coordinate.componentDescriptors.filter(descriptor => displayMemberships.has(`${descriptor.baselineId}\u0000${descriptor.componentId}`)),
34
+ displayRollups,
35
+ projections: coordinate.projections.filter(projection => normalize(projection.scopeId) === normalizedScopeId),
36
+ };
37
+ });
38
+ const firstCoordinate = coordinates[0];
39
+ if (!firstCoordinate)
40
+ throw new TypeError('Financial authority resource projection has no coordinates.');
41
+ const assessmentIds = new Set();
42
+ const bundleIds = new Set();
43
+ coordinates.forEach(coordinate => {
44
+ assessmentIds.add(coordinate.ownerBaseline.assessmentId);
45
+ if (coordinate.ownerBaseline.status === 'available' && coordinate.ownerBaseline.baselineKind === 'owner') {
46
+ bundleIds.add(coordinate.ownerBaseline.evidenceBundleId);
47
+ }
48
+ coordinate.projections.forEach(projection => {
49
+ if (projection.targetAssessmentId)
50
+ assessmentIds.add(projection.targetAssessmentId);
51
+ if (projection.targetEvidenceBundleId)
52
+ bundleIds.add(projection.targetEvidenceBundleId);
53
+ });
54
+ });
55
+ const evidenceAssessments = authority.evidenceAssessments.filter(assessment => assessmentIds.has(assessment.assessmentId));
56
+ evidenceAssessments.forEach(assessment => {
57
+ if (assessment.evidenceBundleId)
58
+ bundleIds.add(assessment.evidenceBundleId);
59
+ });
60
+ return {
61
+ contractVersion: FINANCIAL_AUTHORITY_RESOURCE_PROJECTION_CONTRACT_VERSION_V1,
62
+ authorityId: authority.authorityId,
63
+ provider: authority.provider,
64
+ providerAccountRefs: authority.providerAccountRefs,
65
+ artifactGeneration: authority.artifactGeneration,
66
+ billingGenerationId: authority.billingGenerationId,
67
+ resourceType: coverage.resourceType,
68
+ financialRole: coverage.financialRole,
69
+ scopeId: normalizedScopeId,
70
+ evidenceBundles: authority.evidenceBundles.filter(bundle => bundleIds.has(bundle.bundleId)),
71
+ evidenceAssessments,
72
+ coordinates: [firstCoordinate, ...coordinates.slice(1)],
73
+ };
74
+ };
75
+ /** Derives the matching compact savings view for one canonical resource owner. */
76
+ export const projectFinancialSavingsResourceV1 = (savingsAuthority, financialProjection, financialAuthority) => {
77
+ if (savingsAuthority.financialAuthorityId !== financialProjection.authorityId ||
78
+ savingsAuthority.artifactGeneration.runId !== financialProjection.artifactGeneration.runId ||
79
+ savingsAuthority.artifactGeneration.generatedAt !== financialProjection.artifactGeneration.generatedAt) {
80
+ throw new TypeError('Financial savings authority is not bound to the resource Financial Authority projection.');
81
+ }
82
+ if (financialAuthority !== undefined &&
83
+ (financialAuthority.authorityId !== financialProjection.authorityId ||
84
+ financialAuthority.artifactGeneration.runId !== financialProjection.artifactGeneration.runId ||
85
+ financialAuthority.artifactGeneration.generatedAt !== financialProjection.artifactGeneration.generatedAt)) {
86
+ throw new TypeError('Full Financial Authority is not bound to the resource projection.');
87
+ }
88
+ const normalizedScopeId = normalize(financialProjection.scopeId);
89
+ const financialCoordinateIds = new Set(financialProjection.coordinates.map(coordinate => coordinate.coordinateId));
90
+ const savingsByCoordinateId = new Map(savingsAuthority.coordinates.map(coordinate => [coordinate.coordinateId, coordinate]));
91
+ if (financialCoordinateIds.size !== financialProjection.coordinates.length ||
92
+ savingsAuthority.coordinates.length !== financialProjection.coordinates.length ||
93
+ savingsAuthority.coordinates.some(coordinate => !financialCoordinateIds.has(coordinate.coordinateId))) {
94
+ throw new TypeError('Financial savings coordinates do not match the resource Financial Authority projection.');
95
+ }
96
+ const coordinates = financialProjection.coordinates.map(financialCoordinate => {
97
+ const coordinate = savingsByCoordinateId.get(financialCoordinate.coordinateId);
98
+ if (!coordinate)
99
+ throw new TypeError('Financial savings coordinate is missing.');
100
+ if (coordinate.status === 'unavailable')
101
+ return { ...coordinate };
102
+ const resourceContributions = coordinate.resourceContributions.filter(contribution => normalize(contribution.ownerScopeId) === normalizedScopeId);
103
+ if (resourceContributions.length > 1)
104
+ throw new TypeError('Financial savings resource contribution is ambiguous.');
105
+ const recommendationContributions = coordinate.recommendationContributions.filter(contribution => normalize(contribution.ownerScopeId) === normalizedScopeId);
106
+ const resourceContribution = resourceContributions[0];
107
+ const recommendationSavings = recommendationContributions.reduce((total, contribution) => {
108
+ const next = total + contribution.savingsMinorUnits;
109
+ if (!Number.isSafeInteger(next))
110
+ throw new TypeError('Financial savings recommendation contribution overflows safe minor units.');
111
+ return next;
112
+ }, 0);
113
+ if ((resourceContribution?.savingsMinorUnits ?? 0) !== recommendationSavings) {
114
+ throw new TypeError('Financial savings recommendation contributions do not reconcile to the resource contribution.');
115
+ }
116
+ const unavailableActivationIds = new Set(coordinate.status === 'partial'
117
+ ? coordinate.activations.filter(activation => activation.result === 'unavailable').map(activation => activation.scenarioId)
118
+ : []);
119
+ const authorityCoordinate = financialAuthority?.coordinates.find(candidate => candidate.coordinateId === financialCoordinate.coordinateId);
120
+ if (financialAuthority !== undefined && !authorityCoordinate) {
121
+ throw new TypeError('Full Financial Authority coordinate is missing for the resource projection.');
122
+ }
123
+ const unavailableScenarioIds = [...unavailableActivationIds]
124
+ .filter(scenarioId => {
125
+ if (!authorityCoordinate) {
126
+ // Without the full authority there is no proof that an absent target
127
+ // projection belongs elsewhere. Preserve the gap on this resource.
128
+ return true;
129
+ }
130
+ const scenarioProjections = authorityCoordinate.projections.filter(projection => projection.scenarioId === scenarioId);
131
+ return (scenarioProjections.length === 0 ||
132
+ scenarioProjections.some(projection => normalize(projection.scopeId) === normalizedScopeId));
133
+ })
134
+ .sort();
135
+ const composed = {
136
+ coordinateId: coordinate.coordinateId,
137
+ currentAggregateBaselineId: coordinate.currentAggregateBaselineId,
138
+ accountingCurrencyCode: coordinate.accountingCurrencyCode,
139
+ minorUnitScale: coordinate.minorUnitScale,
140
+ roundingMode: coordinate.roundingMode,
141
+ ...(resourceContribution === undefined ? {} : { resourceContribution }),
142
+ recommendationContributions,
143
+ };
144
+ return unavailableScenarioIds.length > 0
145
+ ? { ...composed, status: 'partial', unavailableScenarioIds: unavailableScenarioIds }
146
+ : { ...composed, status: 'available' };
147
+ });
148
+ const firstCoordinate = coordinates[0];
149
+ if (!firstCoordinate)
150
+ throw new TypeError('Financial savings resource projection has no coordinates.');
151
+ return {
152
+ contractVersion: FINANCIAL_SAVINGS_RESOURCE_PROJECTION_CONTRACT_VERSION_V1,
153
+ savingsAuthorityId: savingsAuthority.savingsAuthorityId,
154
+ financialAuthorityId: savingsAuthority.financialAuthorityId,
155
+ artifactGeneration: savingsAuthority.artifactGeneration,
156
+ scopeId: normalizedScopeId,
157
+ coordinates: [firstCoordinate, ...coordinates.slice(1)],
158
+ };
159
+ };
@@ -5,6 +5,8 @@ import { isFinancialEvidenceAssessmentV1 } from './financialEvidenceAssessmentVa
5
5
  import { formatExactDecimalValue, multiplyExactDecimalValues, parseCanonicalDecimal, sumCanonicalDecimals } from '../common/exactDecimal.js';
6
6
  import { isFinancialBaselinePeriodV2, isFinancialEvidenceBundleV1, isFinancialScopeBaselineEnvelopeV2 } from './financialScopeBaselineValidation.js';
7
7
  import { isFinancialAuthorityComponentDescriptorV1, isFinancialDisplayRollupV1 } from './financialDisplayRollupValidation.js';
8
+ import { AZURE_BILLED_ALL_CHARGES_POLICY_V1 } from './financialChargeComposition.js';
9
+ import { isFinancialChargeCompositionV1 } from './financialChargeCompositionValidation.js';
8
10
  const SHA256_ID = /^sha256:[0-9a-f]{64}$/;
9
11
  const CURRENCY = /^[A-Z]{3}$/;
10
12
  const AZURE_RESOURCE_TYPE = /^[a-z0-9.-]+\/[a-z0-9._/-]+$/;
@@ -55,6 +57,7 @@ const canonicalCoordinatePreimage = (value) => ({
55
57
  ownerBaselines: [...value.ownerBaselines].sort((left, right) => left.scopeId.localeCompare(right.scopeId)),
56
58
  residualBaseline: value.residualBaseline,
57
59
  aggregateBaseline: value.aggregateBaseline,
60
+ chargeCompositions: [...value.chargeCompositions].sort((left, right) => left.baselineId.localeCompare(right.baselineId)),
58
61
  componentDescriptors: [...value.componentDescriptors].sort((left, right) => `${left.baselineId}\u0000${left.componentId}`.localeCompare(`${right.baselineId}\u0000${right.componentId}`)),
59
62
  displayRollups: [...value.displayRollups].sort((left, right) => left.displayRollupId.localeCompare(right.displayRollupId)),
60
63
  projections: [...value.projections].sort((left, right) => left.scenarioId.localeCompare(right.scenarioId)),
@@ -95,6 +98,7 @@ const isCoordinate = (value, coveredScopeIds, financialRoleByScope, providerAcco
95
98
  'ownerBaselines',
96
99
  'residualBaseline',
97
100
  'aggregateBaseline',
101
+ 'chargeCompositions',
98
102
  'componentDescriptors',
99
103
  'displayRollups',
100
104
  'projections',
@@ -113,6 +117,9 @@ const isCoordinate = (value, coveredScopeIds, financialRoleByScope, providerAcco
113
117
  !value.ownerBaselines.every(isFinancialScopeBaselineEnvelopeV2) ||
114
118
  !isFinancialScopeBaselineEnvelopeV2(value.residualBaseline) ||
115
119
  !isFinancialScopeBaselineEnvelopeV2(value.aggregateBaseline) ||
120
+ !Array.isArray(value.chargeCompositions) ||
121
+ value.chargeCompositions.length > 20001 ||
122
+ !value.chargeCompositions.every(isFinancialChargeCompositionV1) ||
116
123
  !Array.isArray(value.componentDescriptors) ||
117
124
  value.componentDescriptors.length > 20000 ||
118
125
  !value.componentDescriptors.every(isFinancialAuthorityComponentDescriptorV1) ||
@@ -173,6 +180,38 @@ const isCoordinate = (value, coveredScopeIds, financialRoleByScope, providerAcco
173
180
  }
174
181
  if (coordinate.residualBaseline.status === 'available' && !bundleIds.has(coordinate.residualBaseline.evidenceBundleId))
175
182
  return false;
183
+ const composableBaselines = [...coordinate.ownerBaselines, coordinate.residualBaseline].filter((baseline) => baseline.status === 'available' && baseline.baselineKind === 'owner');
184
+ const chargeCompositionByBaselineId = new Map(coordinate.chargeCompositions.map(composition => [composition.baselineId, composition]));
185
+ if (chargeCompositionByBaselineId.size !== coordinate.chargeCompositions.length ||
186
+ coordinate.chargeCompositions.length !== composableBaselines.length)
187
+ return false;
188
+ for (const baseline of composableBaselines) {
189
+ const composition = chargeCompositionByBaselineId.get(baseline.baselineId);
190
+ if (composition === undefined ||
191
+ canonicalText(baseline.chargeInclusionPolicyRef) !== canonicalText(AZURE_BILLED_ALL_CHARGES_POLICY_V1.policyRef) ||
192
+ composition.ownerScopeId !== baseline.scopeId ||
193
+ periodText(composition.period) !== periodText(baseline.period) ||
194
+ composition.costBasis !== baseline.costBasis ||
195
+ composition.estimateLens !== baseline.estimateLens ||
196
+ composition.accountingCurrencyCode !== baseline.total.currencyCode ||
197
+ composition.reconciliation.sourceTotal !== baseline.total.amount)
198
+ return false;
199
+ const baselineComponentById = new Map(baseline.components.map(component => [component.componentId, component]));
200
+ const partitionedComponentIds = composition.buckets.flatMap(bucket => bucket.componentIds);
201
+ if (partitionedComponentIds.length !== baseline.components.length ||
202
+ new Set(partitionedComponentIds).size !== partitionedComponentIds.length ||
203
+ partitionedComponentIds.some(componentId => !baselineComponentById.has(componentId)))
204
+ return false;
205
+ for (const bucket of composition.buckets) {
206
+ try {
207
+ if (formatExactDecimalValue(sumCanonicalDecimals(bucket.componentIds.map(componentId => baselineComponentById.get(componentId).amount))) !== bucket.amount)
208
+ return false;
209
+ }
210
+ catch {
211
+ return false;
212
+ }
213
+ }
214
+ }
176
215
  const financialOwnerBaselines = coordinate.ownerBaselines.filter(baseline => financialRoleByScope.get(baseline.scopeId) === 'owner');
177
216
  const availableMembers = [...financialOwnerBaselines, coordinate.residualBaseline]
178
217
  .filter(baseline => baseline.status === 'available')
@@ -256,7 +295,7 @@ const isCoordinate = (value, coveredScopeIds, financialRoleByScope, providerAcco
256
295
  (projection.targetEvidenceBundleId !== undefined && !bundleIds.has(projection.targetEvidenceBundleId)) ||
257
296
  (targetAssessment !== undefined &&
258
297
  (targetAssessment.request.scopeId !== projection.scopeId ||
259
- targetAssessment.result !== 'available' ||
298
+ (projection.status === 'available' && targetAssessment.result !== 'available') ||
260
299
  targetAssessment.evidenceBundleId !== projection.targetEvidenceBundleId)) ||
261
300
  (projection.baselineId !== undefined && (owner.status !== 'available' || projection.baselineId !== owner.baselineId)) ||
262
301
  (projection.status === 'available' && owner.status !== 'available'))
@@ -328,6 +367,15 @@ const isCoordinate = (value, coveredScopeIds, financialRoleByScope, providerAcco
328
367
  return false;
329
368
  continue;
330
369
  }
370
+ if (projection.operationKind === 'replace-quantity-and-rate') {
371
+ if (!('targetQuantity' in applied) || !('targetRate' in applied))
372
+ return false;
373
+ if (applied.targetQuantity.unit !== applied.targetRate.quantityUnit ||
374
+ applied.targetRate.currencyCode !== projection.accountingCurrencyCode ||
375
+ formatExactDecimalValue(multiplyExactDecimalValues(parseCanonicalDecimal(applied.targetQuantity.amount), parseCanonicalDecimal(applied.targetRate.amount))) !== applied.targetAmount)
376
+ return false;
377
+ continue;
378
+ }
331
379
  if (!('sourceRate' in applied) || !('targetQuantity' in applied) || component.effectiveRate === undefined)
332
380
  return false;
333
381
  if (applied.sourceRate.amount !== component.effectiveRate.amount ||
@@ -495,3 +543,158 @@ export const isFinancialAuthorityViewV1 = (value) => {
495
543
  export const isFinancialAuthorityViewBoundToArtifactGenerationV1 = (value, expected) => isFinancialAuthorityViewV1(value) &&
496
544
  value.artifactGeneration.runId === expected.runId &&
497
545
  value.artifactGeneration.generatedAt === expected.generatedAt;
546
+ const isSameProviderAccountSet = (left, right) => {
547
+ if (left.length !== right.length)
548
+ return false;
549
+ const sortedLeft = [...left].sort();
550
+ const sortedRight = [...right].sort();
551
+ return sortedLeft.every((value, index) => value === sortedRight[index]);
552
+ };
553
+ const isResourceProjectionCoordinate = (value, projection, assessmentById, bundleIds) => {
554
+ if (!isRecord(value) ||
555
+ !hasExactFields(value, [
556
+ 'coordinateId',
557
+ 'periodRole',
558
+ 'period',
559
+ 'costBasis',
560
+ 'estimateLens',
561
+ 'ownerBaseline',
562
+ 'componentDescriptors',
563
+ 'displayRollups',
564
+ 'projections',
565
+ ], ['requestedCurrencyCode', 'chargeComposition']) ||
566
+ !isHash(value.coordinateId) ||
567
+ (value.periodRole !== 'current' && value.periodRole !== 'previous') ||
568
+ !isFinancialBaselinePeriodV2(value.period) ||
569
+ typeof value.costBasis !== 'string' ||
570
+ !COST_BASES.has(value.costBasis) ||
571
+ typeof value.estimateLens !== 'string' ||
572
+ !ESTIMATE_LENSES.has(value.estimateLens) ||
573
+ (value.requestedCurrencyCode !== undefined && (typeof value.requestedCurrencyCode !== 'string' || !CURRENCY.test(value.requestedCurrencyCode))) ||
574
+ !isFinancialScopeBaselineEnvelopeV2(value.ownerBaseline) ||
575
+ (value.chargeComposition !== undefined && !isFinancialChargeCompositionV1(value.chargeComposition)) ||
576
+ !Array.isArray(value.componentDescriptors) ||
577
+ value.componentDescriptors.length > 20000 ||
578
+ !value.componentDescriptors.every(isFinancialAuthorityComponentDescriptorV1) ||
579
+ !Array.isArray(value.displayRollups) ||
580
+ value.displayRollups.length > 20000 ||
581
+ !value.displayRollups.every(isFinancialDisplayRollupV1) ||
582
+ !Array.isArray(value.projections) ||
583
+ value.projections.length > 20000 ||
584
+ !value.projections.every(isFinancialProjectionEnvelopeV1))
585
+ return false;
586
+ const coordinate = value;
587
+ const baseline = coordinate.ownerBaseline;
588
+ const assessment = assessmentById.get(baseline.assessmentId);
589
+ if (normalizeScopeId(baseline.scopeId) !== projection.scopeId ||
590
+ baseline.costBasis !== coordinate.costBasis ||
591
+ baseline.estimateLens !== coordinate.estimateLens ||
592
+ periodText(baseline.period) !== periodText(coordinate.period) ||
593
+ baseline.requestedCurrencyCode !== coordinate.requestedCurrencyCode ||
594
+ !isSameProviderAccountSet(baseline.providerAccountRefs, projection.providerAccountRefs) ||
595
+ !assessment ||
596
+ assessment.request.scopeId !== baseline.scopeId ||
597
+ assessment.request.scopeKind !== baseline.scopeKind ||
598
+ assessment.result !== baseline.status)
599
+ return false;
600
+ if (projection.financialRole === 'display-only' && (baseline.status !== 'unavailable' || baseline.unavailableReason !== 'unsupported-scope')) {
601
+ return false;
602
+ }
603
+ if (projection.financialRole === 'unclassified' && (baseline.status !== 'unavailable' || baseline.unavailableReason !== 'ownership-unresolved')) {
604
+ return false;
605
+ }
606
+ if (baseline.status !== 'available' || baseline.baselineKind !== 'owner') {
607
+ return (coordinate.chargeComposition === undefined &&
608
+ coordinate.componentDescriptors.length === 0 &&
609
+ coordinate.displayRollups.length === 0 &&
610
+ coordinate.projections.every(projectionValue => normalizeScopeId(projectionValue.scopeId) === projection.scopeId) &&
611
+ new Set(coordinate.projections.map(projectionValue => projectionValue.scenarioId)).size === coordinate.projections.length);
612
+ }
613
+ if (baseline.scopeKind !== 'canonical-resource-owner' && baseline.scopeKind !== 'composite-resource')
614
+ return false;
615
+ if (!bundleIds.has(baseline.evidenceBundleId) || assessment.evidenceBundleId !== baseline.evidenceBundleId)
616
+ return false;
617
+ const composition = coordinate.chargeComposition;
618
+ if (!composition ||
619
+ composition.baselineId !== baseline.baselineId ||
620
+ normalizeScopeId(composition.ownerScopeId) !== projection.scopeId ||
621
+ periodText(composition.period) !== periodText(baseline.period) ||
622
+ composition.costBasis !== baseline.costBasis ||
623
+ composition.estimateLens !== baseline.estimateLens ||
624
+ composition.accountingCurrencyCode !== baseline.total.currencyCode ||
625
+ composition.reconciliation.sourceTotal !== baseline.total.amount)
626
+ return false;
627
+ const componentKeys = new Set(baseline.components.map(component => `${baseline.baselineId}\u0000${component.componentId}`));
628
+ const descriptorKeys = coordinate.componentDescriptors.map(descriptor => `${descriptor.baselineId}\u0000${descriptor.componentId}`);
629
+ if (descriptorKeys.length !== componentKeys.size ||
630
+ new Set(descriptorKeys).size !== descriptorKeys.length ||
631
+ descriptorKeys.some(key => !componentKeys.has(key)) ||
632
+ coordinate.displayRollups.some(rollup => normalizeScopeId(rollup.displayScopeId) !== projection.scopeId ||
633
+ rollup.members.some(member => !componentKeys.has(`${member.baselineId}\u0000${member.componentId}`))) ||
634
+ coordinate.projections.some(projectionValue => normalizeScopeId(projectionValue.scopeId) !== projection.scopeId))
635
+ return false;
636
+ const rolledUpKeys = coordinate.displayRollups.flatMap(rollup => rollup.members.map(member => `${member.baselineId}\u0000${member.componentId}`));
637
+ return (rolledUpKeys.length === componentKeys.size &&
638
+ new Set(rolledUpKeys).size === rolledUpKeys.length &&
639
+ rolledUpKeys.every(key => componentKeys.has(key)) &&
640
+ new Set(coordinate.projections.map(projectionValue => projectionValue.scenarioId)).size === coordinate.projections.length);
641
+ };
642
+ /** Strictly validates one bounded, non-additive resource projection without requiring the full authority ledger. */
643
+ export const isFinancialAuthorityResourceProjectionV1 = (value) => {
644
+ if (!isRecord(value) ||
645
+ !hasExactFields(value, [
646
+ 'contractVersion',
647
+ 'authorityId',
648
+ 'provider',
649
+ 'providerAccountRefs',
650
+ 'artifactGeneration',
651
+ 'billingGenerationId',
652
+ 'resourceType',
653
+ 'financialRole',
654
+ 'scopeId',
655
+ 'evidenceBundles',
656
+ 'evidenceAssessments',
657
+ 'coordinates',
658
+ ]) ||
659
+ value.contractVersion !== 'financial-authority-resource-projection/v1' ||
660
+ !isHash(value.authorityId) ||
661
+ value.provider !== 'azure' ||
662
+ !Array.isArray(value.providerAccountRefs) ||
663
+ value.providerAccountRefs.length === 0 ||
664
+ value.providerAccountRefs.length > 64 ||
665
+ !value.providerAccountRefs.every(isIdentity) ||
666
+ new Set(value.providerAccountRefs).size !== value.providerAccountRefs.length ||
667
+ !isRecord(value.artifactGeneration) ||
668
+ !hasExactFields(value.artifactGeneration, ['runId', 'generatedAt']) ||
669
+ !isIdentity(value.artifactGeneration.runId) ||
670
+ !isIsoInstant(value.artifactGeneration.generatedAt) ||
671
+ !isIdentity(value.billingGenerationId) ||
672
+ typeof value.resourceType !== 'string' ||
673
+ !AZURE_RESOURCE_TYPE.test(value.resourceType) ||
674
+ value.resourceType !== normalizeScopeId(value.resourceType) ||
675
+ (value.financialRole !== 'owner' && value.financialRole !== 'display-only' && value.financialRole !== 'unclassified') ||
676
+ !isIdentity(value.scopeId) ||
677
+ value.scopeId !== normalizeScopeId(value.scopeId) ||
678
+ !Array.isArray(value.evidenceBundles) ||
679
+ value.evidenceBundles.length > 20000 ||
680
+ !value.evidenceBundles.every(isFinancialEvidenceBundleV1) ||
681
+ !Array.isArray(value.evidenceAssessments) ||
682
+ value.evidenceAssessments.length > 20000 ||
683
+ !value.evidenceAssessments.every(isFinancialEvidenceAssessmentV1) ||
684
+ !Array.isArray(value.coordinates) ||
685
+ value.coordinates.length === 0 ||
686
+ value.coordinates.length > 128)
687
+ return false;
688
+ const projection = value;
689
+ const bundleIds = new Set(projection.evidenceBundles.map(bundle => bundle.bundleId));
690
+ const assessmentById = new Map(projection.evidenceAssessments.map(assessment => [assessment.assessmentId, assessment]));
691
+ if (bundleIds.size !== projection.evidenceBundles.length || assessmentById.size !== projection.evidenceAssessments.length)
692
+ return false;
693
+ if (projection.evidenceAssessments.some(assessment => (assessment.evidenceBundleId !== undefined && !bundleIds.has(assessment.evidenceBundleId)) ||
694
+ !isSameProviderAccountSet(assessment.request.providerAccountRefs, projection.providerAccountRefs) ||
695
+ normalizeScopeId(assessment.request.scopeId) !== projection.scopeId) ||
696
+ !projection.coordinates.every(coordinate => isResourceProjectionCoordinate(coordinate, projection, assessmentById, bundleIds)) ||
697
+ new Set(projection.coordinates.map(coordinate => coordinate.coordinateId)).size !== projection.coordinates.length)
698
+ return false;
699
+ return true;
700
+ };
@@ -0,0 +1,36 @@
1
+ import { sha256Utf8 } from '../common/sha256.js';
2
+ export const FINANCIAL_CHARGE_COMPOSITION_SCHEMA_VERSION_V1 = 1;
3
+ export const FINANCIAL_CHARGE_COMPOSITION_CONTRACT_VERSION_V1 = 'financial-charge-composition/v1';
4
+ export const FINANCIAL_CHARGE_INCLUSION_POLICY_CONTRACT_VERSION_V1 = 'financial-charge-inclusion-policy/v1';
5
+ const createRegisteredPolicy = (definition) => {
6
+ const { policyId, ...policy } = definition;
7
+ const registered = {
8
+ ...policy,
9
+ includeSources: Object.freeze([...policy.includeSources]),
10
+ excludeSources: Object.freeze([...policy.excludeSources]),
11
+ withholdSources: Object.freeze([...policy.withholdSources]),
12
+ policyRef: Object.freeze({ policyId, policyDigest: `sha256:${sha256Utf8(JSON.stringify(definition))}` }),
13
+ };
14
+ return Object.freeze(registered);
15
+ };
16
+ export const AZURE_BILLED_ALL_CHARGES_POLICY_V1 = createRegisteredPolicy({
17
+ schemaVersion: 1,
18
+ contractVersion: FINANCIAL_CHARGE_INCLUSION_POLICY_CONTRACT_VERSION_V1,
19
+ policyId: 'azure-billed-all-charges/v1',
20
+ includeSources: ['azure-native', 'marketplace', 'unknown'],
21
+ excludeSources: [],
22
+ withholdSources: [],
23
+ });
24
+ export const AZURE_CLOUD_SERVICES_EXCLUDING_MARKETPLACE_POLICY_V1 = createRegisteredPolicy({
25
+ schemaVersion: 1,
26
+ contractVersion: FINANCIAL_CHARGE_INCLUSION_POLICY_CONTRACT_VERSION_V1,
27
+ policyId: 'azure-cloud-services-excluding-marketplace/v1',
28
+ includeSources: ['azure-native'],
29
+ excludeSources: ['marketplace'],
30
+ withholdSources: ['unknown'],
31
+ });
32
+ export const FINANCIAL_CHARGE_INCLUSION_POLICIES_V1 = Object.freeze([
33
+ AZURE_BILLED_ALL_CHARGES_POLICY_V1,
34
+ AZURE_CLOUD_SERVICES_EXCLUDING_MARKETPLACE_POLICY_V1,
35
+ ]);
36
+ export const resolveFinancialChargeInclusionPolicyV1 = (policyRef) => policyRef !== null && typeof policyRef === 'object' && FINANCIAL_CHARGE_INCLUSION_POLICIES_V1.find(policy => policy.policyRef.policyId === policyRef.policyId && policy.policyRef.policyDigest === policyRef.policyDigest) || undefined;
@@ -0,0 +1,48 @@
1
+ import { formatExactDecimalValue, sumCanonicalDecimals } from '../common/exactDecimal.js';
2
+ import { FINANCIAL_CHARGE_COMPOSITION_CONTRACT_VERSION_V1, FINANCIAL_CHARGE_COMPOSITION_SCHEMA_VERSION_V1, } from './financialChargeComposition.js';
3
+ import { createFinancialChargeCompositionIdV1 } from './financialChargeCompositionValidation.js';
4
+ const bucketKey = (component) => `${component.chargeSource}\u0000${component.chargeRecurrence}\u0000${component.chargeClassification}`;
5
+ export const createFinancialChargeCompositionV1 = (request) => {
6
+ if (!Array.isArray(request.components) || request.components.length === 0 || request.components.length > 20000) {
7
+ throw new TypeError('Financial charge composition requires a bounded non-empty component collection.');
8
+ }
9
+ if (new Set(request.components.map(component => component.componentId)).size !== request.components.length) {
10
+ throw new TypeError('Financial charge composition cannot classify one component more than once.');
11
+ }
12
+ const groups = new Map();
13
+ for (const component of request.components) {
14
+ const key = bucketKey(component);
15
+ const group = groups.get(key);
16
+ if (group)
17
+ group.push(component);
18
+ else
19
+ groups.set(key, [component]);
20
+ }
21
+ const buckets = [...groups.values()]
22
+ .map(group => ({
23
+ chargeSource: group[0].chargeSource,
24
+ chargeRecurrence: group[0].chargeRecurrence,
25
+ chargeClassification: group[0].chargeClassification,
26
+ amount: formatExactDecimalValue(sumCanonicalDecimals(group.map(component => component.amount))),
27
+ componentIds: [...new Set(group.map(component => component.componentId))].sort(),
28
+ evidenceRefIds: [...new Set(group.flatMap(component => component.evidenceRefIds))].sort(),
29
+ }))
30
+ .sort((left, right) => `${left.chargeSource}\u0000${left.chargeRecurrence}\u0000${left.chargeClassification}`.localeCompare(`${right.chargeSource}\u0000${right.chargeRecurrence}\u0000${right.chargeClassification}`));
31
+ const bucketTotal = formatExactDecimalValue(sumCanonicalDecimals(buckets.map(bucket => bucket.amount)));
32
+ if (bucketTotal !== request.sourceTotal)
33
+ throw new TypeError('Financial charge composition does not reconcile to its source total.');
34
+ const identity = {
35
+ schemaVersion: FINANCIAL_CHARGE_COMPOSITION_SCHEMA_VERSION_V1,
36
+ contractVersion: FINANCIAL_CHARGE_COMPOSITION_CONTRACT_VERSION_V1,
37
+ baselineId: request.baselineId,
38
+ ownerScopeId: request.ownerScopeId,
39
+ period: request.period,
40
+ costBasis: request.costBasis,
41
+ estimateLens: request.estimateLens,
42
+ accountingCurrencyCode: request.accountingCurrencyCode,
43
+ buckets,
44
+ reconciliation: { status: 'reconciled', bucketTotal, sourceTotal: request.sourceTotal, difference: '0' },
45
+ algorithmVersion: request.algorithmVersion,
46
+ };
47
+ return { ...identity, chargeCompositionId: createFinancialChargeCompositionIdV1(identity) };
48
+ };