@spottoai/types-package 1.0.2-dev1138.3 → 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 (206) 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/financialAuthorityProjectionKernel.d.ts +7 -0
  72. package/dist/azure/financialAuthorityProjectionKernel.d.ts.map +1 -0
  73. package/dist/azure/financialAuthorityProjectionKernel.js +165 -0
  74. package/dist/azure/financialAuthorityProjectionKernel.js.map +1 -0
  75. package/dist/azure/financialAuthorityViewValidation.d.ts +3 -1
  76. package/dist/azure/financialAuthorityViewValidation.d.ts.map +1 -1
  77. package/dist/azure/financialAuthorityViewValidation.js +167 -2
  78. package/dist/azure/financialAuthorityViewValidation.js.map +1 -1
  79. package/dist/azure/financialChargeCompositionKernel.d.ts.map +1 -1
  80. package/dist/azure/financialChargeCompositionKernel.js +5 -1
  81. package/dist/azure/financialChargeCompositionKernel.js.map +1 -1
  82. package/dist/azure/financialDataflowKernel.d.ts.map +1 -1
  83. package/dist/azure/financialDataflowKernel.js +2 -1
  84. package/dist/azure/financialDataflowKernel.js.map +1 -1
  85. package/dist/azure/financialProjection.d.ts +11 -1
  86. package/dist/azure/financialProjection.d.ts.map +1 -1
  87. package/dist/azure/financialProjection.js.map +1 -1
  88. package/dist/azure/financialProjectionValidation.d.ts.map +1 -1
  89. package/dist/azure/financialProjectionValidation.js +43 -3
  90. package/dist/azure/financialProjectionValidation.js.map +1 -1
  91. package/dist/azure/financialSavingsAuthority.d.ts +25 -4
  92. package/dist/azure/financialSavingsAuthority.d.ts.map +1 -1
  93. package/dist/azure/financialSavingsAuthorityValidation.d.ts +6 -2
  94. package/dist/azure/financialSavingsAuthorityValidation.d.ts.map +1 -1
  95. package/dist/azure/financialSavingsAuthorityValidation.js +126 -2
  96. package/dist/azure/financialSavingsAuthorityValidation.js.map +1 -1
  97. package/dist/azure/financialSavingsCoordinateValidation.d.ts.map +1 -1
  98. package/dist/azure/financialSavingsCoordinateValidation.js +62 -19
  99. package/dist/azure/financialSavingsCoordinateValidation.js.map +1 -1
  100. package/dist/azure/financialSavingsSurfaceProjection.d.ts +58 -3
  101. package/dist/azure/financialSavingsSurfaceProjection.d.ts.map +1 -1
  102. package/dist/azure/financialSavingsSurfaceProjection.js +3 -1
  103. package/dist/azure/financialSavingsSurfaceProjection.js.map +1 -1
  104. package/dist/azure/financialSavingsSurfaceProjectionKernel.d.ts +14 -0
  105. package/dist/azure/financialSavingsSurfaceProjectionKernel.d.ts.map +1 -0
  106. package/dist/azure/financialSavingsSurfaceProjectionKernel.js +326 -0
  107. package/dist/azure/financialSavingsSurfaceProjectionKernel.js.map +1 -0
  108. package/dist/azure/financialSavingsSurfaceProjectionValidation.d.ts.map +1 -1
  109. package/dist/azure/financialSavingsSurfaceProjectionValidation.js +126 -12
  110. package/dist/azure/financialSavingsSurfaceProjectionValidation.js.map +1 -1
  111. package/dist/azure/financialScope.d.ts +2 -0
  112. package/dist/azure/financialScope.d.ts.map +1 -1
  113. package/dist/azure/financialScope.js +2 -0
  114. package/dist/azure/financialScope.js.map +1 -1
  115. package/dist/azure/governance.d.ts +145 -0
  116. package/dist/azure/governance.d.ts.map +1 -1
  117. package/dist/azure/governance.js +2 -1
  118. package/dist/azure/governance.js.map +1 -1
  119. package/dist/azure/payloads.d.ts +19 -0
  120. package/dist/azure/payloads.d.ts.map +1 -1
  121. package/dist/azure/recommendationState.d.ts +1 -1
  122. package/dist/azure/recommendationState.d.ts.map +1 -1
  123. package/dist/azure/recommendations.d.ts +3 -1
  124. package/dist/azure/recommendations.d.ts.map +1 -1
  125. package/dist/azure/serviceRetirement.d.ts +33 -2
  126. package/dist/azure/serviceRetirement.d.ts.map +1 -1
  127. package/dist/azure/subscriptions.d.ts +9 -0
  128. package/dist/azure/subscriptions.d.ts.map +1 -1
  129. package/dist/azure/views.d.ts +19 -1
  130. package/dist/azure/views.d.ts.map +1 -1
  131. package/dist/azure/views.js.map +1 -1
  132. package/dist/azure/vmPatchManagement.d.ts +109 -0
  133. package/dist/azure/vmPatchManagement.d.ts.map +1 -0
  134. package/dist/azure/vmPatchManagement.js +7 -0
  135. package/dist/azure/vmPatchManagement.js.map +1 -0
  136. package/dist/common/provider.d.ts +53 -0
  137. package/dist/common/provider.d.ts.map +1 -1
  138. package/dist/common/provider.js.map +1 -1
  139. package/dist/environment/canonicalization.d.ts +12 -0
  140. package/dist/environment/canonicalization.d.ts.map +1 -0
  141. package/dist/environment/canonicalization.js +40 -0
  142. package/dist/environment/canonicalization.js.map +1 -0
  143. package/dist/environment/contracts.d.ts +196 -0
  144. package/dist/environment/contracts.d.ts.map +1 -0
  145. package/dist/environment/contracts.js +21 -0
  146. package/dist/environment/contracts.js.map +1 -0
  147. package/dist/environment/index.d.ts +5 -0
  148. package/dist/environment/index.d.ts.map +1 -0
  149. package/dist/environment/index.js +21 -0
  150. package/dist/environment/index.js.map +1 -0
  151. package/dist/environment/internal.d.ts +21 -0
  152. package/dist/environment/internal.d.ts.map +1 -0
  153. package/dist/environment/internal.js +113 -0
  154. package/dist/environment/internal.js.map +1 -0
  155. package/dist/environment/references.d.ts +18 -0
  156. package/dist/environment/references.d.ts.map +1 -0
  157. package/dist/environment/references.js +206 -0
  158. package/dist/environment/references.js.map +1 -0
  159. package/dist/environment/validation.d.ts +28 -0
  160. package/dist/environment/validation.d.ts.map +1 -0
  161. package/dist/environment/validation.js +320 -0
  162. package/dist/environment/validation.js.map +1 -0
  163. package/dist/esm/accounts/accounts.js +1 -0
  164. package/dist/esm/accounts/azureManualOnboarding.js +8 -0
  165. package/dist/esm/accounts/azureSpSetup.js +14 -1
  166. package/dist/esm/accounts/azureSpSetupDurable.js +27 -1
  167. package/dist/esm/accounts/index.js +1 -0
  168. package/dist/esm/ai/index.js +10 -1
  169. package/dist/esm/aws/commitments-planning.js +5 -0
  170. package/dist/esm/aws/estates.js +7 -1
  171. package/dist/esm/aws/index.js +5 -0
  172. package/dist/esm/aws/organizationCommitments.js +48 -0
  173. package/dist/esm/aws/organizationCommitmentsApiValidation.js +220 -0
  174. package/dist/esm/aws/organizationCommitmentsValidation.js +224 -0
  175. package/dist/esm/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js +402 -0
  176. package/dist/esm/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js +2 -0
  177. package/dist/esm/aws/requests.js +1 -1
  178. package/dist/esm/azure/financialAuthorityProjectionKernel.js +159 -0
  179. package/dist/esm/azure/financialAuthorityViewValidation.js +165 -1
  180. package/dist/esm/azure/financialChargeCompositionKernel.js +5 -1
  181. package/dist/esm/azure/financialDataflowKernel.js +2 -1
  182. package/dist/esm/azure/financialProjectionValidation.js +43 -3
  183. package/dist/esm/azure/financialSavingsAuthorityValidation.js +124 -2
  184. package/dist/esm/azure/financialSavingsCoordinateValidation.js +62 -19
  185. package/dist/esm/azure/financialSavingsSurfaceProjection.js +2 -0
  186. package/dist/esm/azure/financialSavingsSurfaceProjectionKernel.js +318 -0
  187. package/dist/esm/azure/financialSavingsSurfaceProjectionValidation.js +126 -12
  188. package/dist/esm/azure/financialScope.js +2 -0
  189. package/dist/esm/azure/governance.js +1 -0
  190. package/dist/esm/azure/vmPatchManagement.js +3 -0
  191. package/dist/esm/entries/commitments-planning.js +2 -2
  192. package/dist/esm/entries/environment.js +3 -0
  193. package/dist/esm/environment/canonicalization.js +34 -0
  194. package/dist/esm/environment/contracts.js +17 -0
  195. package/dist/esm/environment/index.js +4 -0
  196. package/dist/esm/environment/internal.js +97 -0
  197. package/dist/esm/environment/references.js +194 -0
  198. package/dist/esm/environment/validation.js +303 -0
  199. package/dist/esm/index.js +2 -0
  200. package/dist/index.d.ts +2 -0
  201. package/dist/index.d.ts.map +1 -1
  202. package/dist/index.js +2 -0
  203. package/dist/index.js.map +1 -1
  204. package/dist/portfolio/portfolioOperations.d.ts +1 -1
  205. package/dist/portfolio/portfolioOperations.d.ts.map +1 -1
  206. package/package.json +14 -3
@@ -0,0 +1,402 @@
1
+ import { AWS_ORGANIZATION_COMMITMENTS_ATTRIBUTION_UNAVAILABLE_REASONS, } from './organizationCommitments.js';
2
+ import { validateAwsOrganizationCommitmentsPlanningViewIdentity, } from './organizationCommitmentsValidation.js';
3
+ import { AWS_ORGANIZATION_COMMITMENTS_PLANNING_LOGICAL_NAME, AWS_ORGANIZATION_COMMITMENTS_PUBLIC_ARTIFACT_SCHEMA_VERSION, } from './portalOrganizationCommitmentsPlanningPublicArtifacts.js';
4
+ import { AWS_PORTAL_PUBLIC_ARTIFACT_SCHEMA_VERSION } from './portalPublicArtifacts.js';
5
+ import { asRecord, assertExactKeys, assertPublicJson, assertValue, finiteNumber, isoTimestamp, requiredEnum, requiredString, validateGeneration, } from './portalPublicArtifactValidationCommon.js';
6
+ const TOP_LEVEL_KEYS = [
7
+ 'schemaVersion',
8
+ 'portalSchemaVersion',
9
+ 'provider',
10
+ 'artifactType',
11
+ 'artifactGeneration',
12
+ 'logicalName',
13
+ 'manifestRevision',
14
+ 'version',
15
+ 'generatedAt',
16
+ 'month',
17
+ 'providerScope',
18
+ 'accounts',
19
+ 'utilizationSummary',
20
+ 'expirySummary',
21
+ 'inventory',
22
+ 'payerAggregates',
23
+ 'sharingPosture',
24
+ 'pricingContext',
25
+ 'freshness',
26
+ 'purchaseRecommendations',
27
+ 'allocation',
28
+ 'resourceAttribution',
29
+ ];
30
+ /** Validates one untrusted organization Commitments Planning view without its Portal envelope. */
31
+ export function validateAwsOrganizationCommitmentsPlanningView(value, expected) {
32
+ validateAwsOrganizationCommitmentsPlanningViewIdentity(value, expected);
33
+ validateBody(value);
34
+ assertPublicJson(value, 'organizationCommitmentsPlanning');
35
+ return value;
36
+ }
37
+ /** Validates one untrusted immutable AWS organization Commitments Planning artifact. */
38
+ export function validateAwsPortalOrganizationCommitmentsPlanningArtifact(value, expected) {
39
+ const artifact = asRecord(value, 'artifact');
40
+ assertExactKeys(artifact, TOP_LEVEL_KEYS, 'artifact');
41
+ assertValue(artifact.schemaVersion, AWS_ORGANIZATION_COMMITMENTS_PUBLIC_ARTIFACT_SCHEMA_VERSION, 'artifact.schemaVersion');
42
+ assertValue(artifact.portalSchemaVersion, AWS_PORTAL_PUBLIC_ARTIFACT_SCHEMA_VERSION, 'artifact.portalSchemaVersion');
43
+ assertValue(artifact.provider, 'aws', 'artifact.provider');
44
+ assertValue(artifact.artifactType, 'organization-commitments-planning', 'artifact.artifactType');
45
+ assertValue(artifact.logicalName, AWS_ORGANIZATION_COMMITMENTS_PLANNING_LOGICAL_NAME, 'artifact.logicalName');
46
+ requiredString(artifact.manifestRevision, 'artifact.manifestRevision');
47
+ assertValue(artifact.version, '1.0', 'artifact.version');
48
+ const generation = validateGeneration(artifact.artifactGeneration, 'artifact.artifactGeneration');
49
+ const generatedAt = isoTimestamp(artifact.generatedAt, 'artifact.generatedAt');
50
+ if (Date.parse(generatedAt) > Date.parse(generation.generatedAt)) {
51
+ throw new Error('artifact.generatedAt must not be newer than the Portal generation.');
52
+ }
53
+ if (artifact.month !== undefined)
54
+ requiredString(artifact.month, 'artifact.month');
55
+ validateAwsOrganizationCommitmentsPlanningView(artifact, expected);
56
+ assertPublicJson(artifact, 'artifact');
57
+ return value;
58
+ }
59
+ function validateBody(view) {
60
+ records(view.accounts, 'artifact.accounts', validateAccount);
61
+ validateUtilizationSummary(view.utilizationSummary, 'artifact.utilizationSummary');
62
+ exact(view.expirySummary, ['expired', 'expiring30d', 'expiring60d', 'expiring90d', 'expiring180d'], 'artifact.expirySummary');
63
+ records(view.inventory, 'artifact.inventory', validateInventory);
64
+ records(view.payerAggregates, 'artifact.payerAggregates', validatePayerAggregate);
65
+ validateSharingPosture(view.sharingPosture, 'artifact.sharingPosture');
66
+ validatePricingContext(view.pricingContext, 'artifact.pricingContext');
67
+ optionalRecord(view.freshness, 'artifact.freshness', validateFreshness);
68
+ optionalRecords(view.purchaseRecommendations, 'artifact.purchaseRecommendations', validatePurchaseRecommendation);
69
+ validateAttribution(view.allocation, 'artifact.allocation', validateAllocationRecord);
70
+ validateAttribution(view.resourceAttribution, 'artifact.resourceAttribution', validateResourceAttributionRecord);
71
+ }
72
+ function validateSharingPosture(value, field) {
73
+ const posture = exact(value, ['status', 'reason'], field);
74
+ assertValue(posture.status, 'unknown', `${field}.status`);
75
+ assertValue(posture.reason, 'not-collected', `${field}.reason`);
76
+ }
77
+ function validateAccount(value, field) {
78
+ const account = exact(value, ['accountId', 'displayName', 'role', 'inventoryStatus', 'lastSuccessfulRefreshAt'], field);
79
+ requiredString(account.accountId, `${field}.accountId`);
80
+ optionalString(account.displayName, `${field}.displayName`);
81
+ requiredEnum(account.role, ['management', 'member'], `${field}.role`);
82
+ requiredEnum(account.inventoryStatus, ['current', 'stale', 'partial', 'unavailable'], `${field}.inventoryStatus`);
83
+ optionalTimestamp(account.lastSuccessfulRefreshAt, `${field}.lastSuccessfulRefreshAt`);
84
+ }
85
+ function validateUtilizationSummary(value, field) {
86
+ const summary = exact(value, ['total', 'withData', 'sevenDayAverage', 'thirtyDayAverage', 'sevenDayAggregates', 'thirtyDayAggregates', 'byBenefitType'], field);
87
+ nonNegative(summary.total, `${field}.total`);
88
+ nonNegative(summary.withData, `${field}.withData`);
89
+ optionalNumber(summary.sevenDayAverage, `${field}.sevenDayAverage`);
90
+ optionalNumber(summary.thirtyDayAverage, `${field}.thirtyDayAverage`);
91
+ optionalRecords(summary.sevenDayAggregates, `${field}.sevenDayAggregates`, validateWeightedAggregate);
92
+ optionalRecords(summary.thirtyDayAggregates, `${field}.thirtyDayAggregates`, validateWeightedAggregate);
93
+ records(summary.byBenefitType, `${field}.byBenefitType`, (entry, itemField) => {
94
+ const item = exact(entry, ['benefitType', 'total', 'withData', 'sevenDayAverage', 'thirtyDayAverage', 'sevenDayAggregates', 'thirtyDayAggregates'], itemField);
95
+ requiredEnum(item.benefitType, ['reservation', 'savings-plan'], `${itemField}.benefitType`);
96
+ nonNegative(item.total, `${itemField}.total`);
97
+ nonNegative(item.withData, `${itemField}.withData`);
98
+ optionalNumber(item.sevenDayAverage, `${itemField}.sevenDayAverage`);
99
+ optionalNumber(item.thirtyDayAverage, `${itemField}.thirtyDayAverage`);
100
+ optionalRecords(item.sevenDayAggregates, `${itemField}.sevenDayAggregates`, validateWeightedAggregate);
101
+ optionalRecords(item.thirtyDayAggregates, `${itemField}.thirtyDayAggregates`, validateWeightedAggregate);
102
+ });
103
+ }
104
+ function validateWeightedAggregate(value, field) {
105
+ const aggregate = exact(value, ['used', 'reserved', 'percentage', 'sampleCount', 'unit', 'currency'], field);
106
+ finiteNumber(aggregate.used, `${field}.used`);
107
+ finiteNumber(aggregate.reserved, `${field}.reserved`);
108
+ optionalNumber(aggregate.percentage, `${field}.percentage`);
109
+ if (aggregate.sampleCount !== undefined)
110
+ nonNegative(aggregate.sampleCount, `${field}.sampleCount`);
111
+ requiredEnum(aggregate.unit, ['hours', 'quantity', 'currency'], `${field}.unit`);
112
+ optionalString(aggregate.currency, `${field}.currency`);
113
+ }
114
+ function validateInventory(value, field) {
115
+ const item = exact(value, [
116
+ 'id',
117
+ 'benefitType',
118
+ 'commitmentFamily',
119
+ 'sourceKind',
120
+ 'sourceId',
121
+ 'provider',
122
+ 'ownerAccountId',
123
+ 'shape',
124
+ 'scope',
125
+ 'type',
126
+ 'displayName',
127
+ 'status',
128
+ 'purchaseDate',
129
+ 'expiryDate',
130
+ 'daysToExpiry',
131
+ 'commitmentAmount',
132
+ 'commitmentCurrencyCode',
133
+ 'commitmentGrain',
134
+ 'commitmentUnit',
135
+ 'skuName',
136
+ 'location',
137
+ 'term',
138
+ 'termMonths',
139
+ 'billingPlan',
140
+ 'utilization',
141
+ ], field);
142
+ requiredString(item.id, `${field}.id`);
143
+ requiredEnum(item.benefitType, ['reservation', 'savings-plan'], `${field}.benefitType`);
144
+ optionalString(item.commitmentFamily, `${field}.commitmentFamily`);
145
+ assertValue(item.sourceKind, 'aws-native', `${field}.sourceKind`);
146
+ optionalString(item.sourceId, `${field}.sourceId`);
147
+ assertValue(item.provider, 'aws', `${field}.provider`);
148
+ requiredString(item.ownerAccountId, `${field}.ownerAccountId`);
149
+ optionalRecord(item.shape, `${field}.shape`, validateShape);
150
+ requiredEnum(item.scope, ['Shared', 'Single', 'ManagementGroup', 'Unknown'], `${field}.scope`);
151
+ requiredString(item.type, `${field}.type`);
152
+ optionalString(item.displayName, `${field}.displayName`);
153
+ requiredString(item.status, `${field}.status`);
154
+ optionalString(item.purchaseDate, `${field}.purchaseDate`);
155
+ optionalString(item.expiryDate, `${field}.expiryDate`);
156
+ optionalNumber(item.daysToExpiry, `${field}.daysToExpiry`);
157
+ optionalNumber(item.commitmentAmount, `${field}.commitmentAmount`);
158
+ optionalString(item.commitmentCurrencyCode, `${field}.commitmentCurrencyCode`);
159
+ optionalString(item.commitmentGrain, `${field}.commitmentGrain`);
160
+ optionalString(item.commitmentUnit, `${field}.commitmentUnit`);
161
+ optionalString(item.skuName, `${field}.skuName`);
162
+ optionalString(item.location, `${field}.location`);
163
+ optionalString(item.term, `${field}.term`);
164
+ optionalNumber(item.termMonths, `${field}.termMonths`);
165
+ optionalString(item.billingPlan, `${field}.billingPlan`);
166
+ optionalRecord(item.utilization, `${field}.utilization`, validateUtilization);
167
+ }
168
+ function validatePayerAggregate(value, field) {
169
+ const aggregate = exact(value, [
170
+ 'benefitType',
171
+ 'commitmentFamily',
172
+ 'payerAccountId',
173
+ 'windowStart',
174
+ 'windowEnd',
175
+ 'eligibleCost',
176
+ 'coveredCost',
177
+ 'uncoveredCost',
178
+ 'coveragePercent',
179
+ 'utilization',
180
+ 'source',
181
+ ], field);
182
+ requiredEnum(aggregate.benefitType, ['reservation', 'savings-plan'], `${field}.benefitType`);
183
+ requiredString(aggregate.commitmentFamily, `${field}.commitmentFamily`);
184
+ requiredString(aggregate.payerAccountId, `${field}.payerAccountId`);
185
+ requiredString(aggregate.windowStart, `${field}.windowStart`);
186
+ requiredString(aggregate.windowEnd, `${field}.windowEnd`);
187
+ optionalRecord(aggregate.eligibleCost, `${field}.eligibleCost`, validateMoney);
188
+ optionalRecord(aggregate.coveredCost, `${field}.coveredCost`, validateMoney);
189
+ optionalRecord(aggregate.uncoveredCost, `${field}.uncoveredCost`, validateMoney);
190
+ optionalNumber(aggregate.coveragePercent, `${field}.coveragePercent`);
191
+ optionalRecord(aggregate.utilization, `${field}.utilization`, validateUtilization);
192
+ validateSource(aggregate.source, `${field}.source`, false);
193
+ }
194
+ function validatePurchaseRecommendation(value, field) {
195
+ const recommendation = exact(value, [
196
+ 'id',
197
+ 'groupKey',
198
+ 'commitmentFamily',
199
+ 'action',
200
+ 'purchaseScope',
201
+ 'payerAccountId',
202
+ 'recommendedAccountId',
203
+ 'source',
204
+ 'targetShape',
205
+ 'title',
206
+ 'description',
207
+ 'confidence',
208
+ 'riskLevel',
209
+ 'termMonths',
210
+ 'quantity',
211
+ 'estimatedAnnualSavings',
212
+ 'estimatedAnnualCost',
213
+ 'estimatedTermSavings',
214
+ 'estimatedTermCost',
215
+ 'notes',
216
+ ], field);
217
+ requiredString(recommendation.id, `${field}.id`);
218
+ requiredString(recommendation.groupKey, `${field}.groupKey`);
219
+ requiredString(recommendation.commitmentFamily, `${field}.commitmentFamily`);
220
+ requiredEnum(recommendation.action, ['buy', 'unlock', 'savings-plan', 'none'], `${field}.action`);
221
+ assertValue(recommendation.purchaseScope, 'payer', `${field}.purchaseScope`);
222
+ requiredString(recommendation.payerAccountId, `${field}.payerAccountId`);
223
+ optionalString(recommendation.recommendedAccountId, `${field}.recommendedAccountId`);
224
+ validateSource(recommendation.source, `${field}.source`, false);
225
+ validateShape(recommendation.targetShape, `${field}.targetShape`);
226
+ optionalString(recommendation.title, `${field}.title`);
227
+ optionalString(recommendation.description, `${field}.description`);
228
+ optionalString(recommendation.confidence, `${field}.confidence`);
229
+ optionalString(recommendation.riskLevel, `${field}.riskLevel`);
230
+ optionalNumber(recommendation.termMonths, `${field}.termMonths`);
231
+ optionalNumber(recommendation.quantity, `${field}.quantity`);
232
+ optionalRecord(recommendation.estimatedAnnualSavings, `${field}.estimatedAnnualSavings`, validateMoney);
233
+ optionalRecord(recommendation.estimatedAnnualCost, `${field}.estimatedAnnualCost`, validateMoney);
234
+ optionalRecord(recommendation.estimatedTermSavings, `${field}.estimatedTermSavings`, validateMoney);
235
+ optionalRecord(recommendation.estimatedTermCost, `${field}.estimatedTermCost`, validateMoney);
236
+ optionalStrings(recommendation.notes, `${field}.notes`);
237
+ }
238
+ function validateAttribution(value, field, validateRecord) {
239
+ const evidence = asRecord(value, field);
240
+ if (evidence.status === 'unavailable') {
241
+ assertExactKeys(evidence, ['status', 'reason'], field);
242
+ requiredEnum(evidence.reason, AWS_ORGANIZATION_COMMITMENTS_ATTRIBUTION_UNAVAILABLE_REASONS, `${field}.reason`);
243
+ return;
244
+ }
245
+ assertValue(evidence.status, 'available', `${field}.status`);
246
+ assertExactKeys(evidence, ['status', 'source', 'records'], field);
247
+ validateSource(evidence.source, `${field}.source`, true);
248
+ records(evidence.records, `${field}.records`, validateRecord, true);
249
+ }
250
+ function validateAllocationRecord(value, field) {
251
+ const record = exact(value, [
252
+ 'beneficiaryAccountId',
253
+ 'ownerAccountId',
254
+ 'commitmentId',
255
+ 'benefitType',
256
+ 'commitmentFamily',
257
+ 'windowStart',
258
+ 'windowEnd',
259
+ 'eligibleCost',
260
+ 'coveredCost',
261
+ 'uncoveredCost',
262
+ ], field);
263
+ requiredString(record.beneficiaryAccountId, `${field}.beneficiaryAccountId`);
264
+ optionalString(record.ownerAccountId, `${field}.ownerAccountId`);
265
+ optionalString(record.commitmentId, `${field}.commitmentId`);
266
+ requiredEnum(record.benefitType, ['reservation', 'savings-plan'], `${field}.benefitType`);
267
+ requiredString(record.commitmentFamily, `${field}.commitmentFamily`);
268
+ requiredString(record.windowStart, `${field}.windowStart`);
269
+ requiredString(record.windowEnd, `${field}.windowEnd`);
270
+ optionalRecord(record.eligibleCost, `${field}.eligibleCost`, validateMoney);
271
+ optionalRecord(record.coveredCost, `${field}.coveredCost`, validateMoney);
272
+ optionalRecord(record.uncoveredCost, `${field}.uncoveredCost`, validateMoney);
273
+ }
274
+ function validateResourceAttributionRecord(value, field) {
275
+ const record = exact(value, ['accountId', 'resourceId', 'resourceName', 'resourceType', 'benefitIds', 'windowStart', 'windowEnd', 'coveredCost'], field);
276
+ requiredString(record.accountId, `${field}.accountId`);
277
+ requiredString(record.resourceId, `${field}.resourceId`);
278
+ optionalString(record.resourceName, `${field}.resourceName`);
279
+ optionalString(record.resourceType, `${field}.resourceType`);
280
+ strings(record.benefitIds, `${field}.benefitIds`);
281
+ requiredString(record.windowStart, `${field}.windowStart`);
282
+ requiredString(record.windowEnd, `${field}.windowEnd`);
283
+ optionalRecord(record.coveredCost, `${field}.coveredCost`, validateMoney);
284
+ }
285
+ function validateShape(value, field) {
286
+ const shape = exact(value, [
287
+ 'provider',
288
+ 'resourceType',
289
+ 'commitmentFamily',
290
+ 'skuName',
291
+ 'normalizedSkuName',
292
+ 'location',
293
+ 'region',
294
+ 'availabilityZone',
295
+ 'platform',
296
+ 'reservationProductName',
297
+ 'reservedResourceType',
298
+ 'meterCategory',
299
+ 'meterSubCategory',
300
+ 'meterName',
301
+ 'unit',
302
+ 'attributes',
303
+ ], field);
304
+ assertValue(shape.provider, 'aws', `${field}.provider`);
305
+ if (shape.attributes !== undefined)
306
+ asRecord(shape.attributes, `${field}.attributes`);
307
+ }
308
+ function validateSource(value, field, allowDerived) {
309
+ const source = exact(value, ['sourceKind', 'sourceId', 'sourceName', 'generatedAt', 'observedAt', 'notes'], field);
310
+ requiredEnum(source.sourceKind, allowDerived ? ['aws-native', 'spotto-derived'] : ['aws-native'], `${field}.sourceKind`);
311
+ optionalString(source.sourceId, `${field}.sourceId`);
312
+ optionalString(source.sourceName, `${field}.sourceName`);
313
+ optionalTimestamp(source.generatedAt, `${field}.generatedAt`);
314
+ optionalTimestamp(source.observedAt, `${field}.observedAt`);
315
+ optionalStrings(source.notes, `${field}.notes`);
316
+ }
317
+ function validateMoney(value, field) {
318
+ const money = exact(value, ['amount', 'currency', 'source', 'windowStart', 'windowEnd', 'flags'], field);
319
+ finiteNumber(money.amount, `${field}.amount`);
320
+ requiredString(money.currency, `${field}.currency`);
321
+ optionalString(money.source, `${field}.source`);
322
+ optionalString(money.windowStart, `${field}.windowStart`);
323
+ optionalString(money.windowEnd, `${field}.windowEnd`);
324
+ optionalStrings(money.flags, `${field}.flags`);
325
+ }
326
+ function validateUtilization(value, field) {
327
+ const utilization = exact(value, ['trend', 'oneDay', 'sevenDay', 'thirtyDay', 'source'], field);
328
+ optionalString(utilization.trend, `${field}.trend`);
329
+ optionalNumber(utilization.oneDay, `${field}.oneDay`);
330
+ optionalNumber(utilization.sevenDay, `${field}.sevenDay`);
331
+ optionalNumber(utilization.thirtyDay, `${field}.thirtyDay`);
332
+ optionalString(utilization.source, `${field}.source`);
333
+ }
334
+ function validatePricingContext(value, field) {
335
+ const pricing = exact(value, ['source', 'currency', 'assumptions', 'confidenceNotes', 'calculatorDeepLink'], field);
336
+ requiredEnum(pricing.source, ['retail', 'negotiated', 'unknown', 'recommendation-apis'], `${field}.source`);
337
+ optionalString(pricing.currency, `${field}.currency`);
338
+ optionalStrings(pricing.assumptions, `${field}.assumptions`);
339
+ optionalString(pricing.confidenceNotes, `${field}.confidenceNotes`);
340
+ optionalString(pricing.calculatorDeepLink, `${field}.calculatorDeepLink`);
341
+ }
342
+ function validateFreshness(value, field) {
343
+ const freshness = exact(value, ['status', 'generatedAt', 'entries', 'warnings'], field);
344
+ requiredEnum(freshness.status, ['current', 'stale', 'partial', 'unavailable'], `${field}.status`);
345
+ isoTimestamp(freshness.generatedAt, `${field}.generatedAt`);
346
+ records(freshness.entries, `${field}.entries`, (entry, itemField) => {
347
+ const item = exact(entry, ['section', 'status', 'generatedAt', 'observedAt', 'lastSuccessfulSyncAt', 'reason', 'sourceKind'], itemField);
348
+ requiredString(item.section, `${itemField}.section`);
349
+ requiredEnum(item.status, ['current', 'stale', 'partial', 'unavailable'], `${itemField}.status`);
350
+ optionalTimestamp(item.generatedAt, `${itemField}.generatedAt`);
351
+ optionalTimestamp(item.observedAt, `${itemField}.observedAt`);
352
+ optionalTimestamp(item.lastSuccessfulSyncAt, `${itemField}.lastSuccessfulSyncAt`);
353
+ optionalString(item.reason, `${itemField}.reason`);
354
+ optionalString(item.sourceKind, `${itemField}.sourceKind`);
355
+ });
356
+ optionalStrings(freshness.warnings, `${field}.warnings`);
357
+ }
358
+ function exact(value, allowed, field) {
359
+ const record = asRecord(value, field);
360
+ assertExactKeys(record, allowed, field);
361
+ return record;
362
+ }
363
+ function records(value, field, validate, nonEmpty = false) {
364
+ if (!Array.isArray(value) || (nonEmpty && value.length === 0)) {
365
+ throw new Error(`${field} must be ${nonEmpty ? 'a non-empty' : 'an'} array.`);
366
+ }
367
+ value.forEach((entry, index) => validate(entry, `${field}[${index}]`));
368
+ }
369
+ function optionalRecords(value, field, validate) {
370
+ if (value !== undefined)
371
+ records(value, field, validate);
372
+ }
373
+ function optionalRecord(value, field, validate) {
374
+ if (value !== undefined)
375
+ validate(value, field);
376
+ }
377
+ function strings(value, field) {
378
+ if (!Array.isArray(value))
379
+ throw new Error(`${field} must be an array.`);
380
+ value.forEach((entry, index) => requiredString(entry, `${field}[${index}]`));
381
+ }
382
+ function optionalStrings(value, field) {
383
+ if (value !== undefined)
384
+ strings(value, field);
385
+ }
386
+ function optionalString(value, field) {
387
+ if (value !== undefined)
388
+ requiredString(value, field);
389
+ }
390
+ function optionalTimestamp(value, field) {
391
+ if (value !== undefined)
392
+ isoTimestamp(value, field);
393
+ }
394
+ function optionalNumber(value, field) {
395
+ if (value !== undefined)
396
+ finiteNumber(value, field);
397
+ }
398
+ function nonNegative(value, field) {
399
+ const number = finiteNumber(value, field);
400
+ if (number < 0)
401
+ throw new Error(`${field} must not be negative.`);
402
+ }
@@ -0,0 +1,2 @@
1
+ export const AWS_ORGANIZATION_COMMITMENTS_PUBLIC_ARTIFACT_SCHEMA_VERSION = 1;
2
+ export const AWS_ORGANIZATION_COMMITMENTS_PLANNING_LOGICAL_NAME = 'organization-commitments-planning.json.gz';
@@ -3,7 +3,7 @@ export const AWS_COMMAND_SCHEMA_VERSION = 1;
3
3
  /** Canonical provider wire value for AWS commands. */
4
4
  export const AWS_COMMAND_PROVIDER = 'aws';
5
5
  /** Entities handled by AWS estate orchestration. */
6
- export const AWS_COMMAND_ENTITIES = ['estate', 'account', 'billing-source'];
6
+ export const AWS_COMMAND_ENTITIES = ['estate', 'account', 'billing-source', 'organization-commitments'];
7
7
  /** Actions supported across AWS estate orchestration commands. */
8
8
  export const AWS_COMMAND_ACTIONS = ['reconcile', 'refresh', 'delete'];
9
9
  /** Credential-shaped keys forbidden from shared AWS configuration and commands. */
@@ -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
+ };