@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.
- package/README.md +9 -0
- package/dist/accounts/accounts.d.ts +77 -4
- package/dist/accounts/accounts.d.ts.map +1 -1
- package/dist/accounts/accounts.js +2 -1
- package/dist/accounts/accounts.js.map +1 -1
- package/dist/accounts/azureManualOnboarding.d.ts +114 -0
- package/dist/accounts/azureManualOnboarding.d.ts.map +1 -0
- package/dist/accounts/azureManualOnboarding.js +12 -0
- package/dist/accounts/azureManualOnboarding.js.map +1 -0
- package/dist/accounts/azureSpSetup.d.ts +128 -31
- package/dist/accounts/azureSpSetup.d.ts.map +1 -1
- package/dist/accounts/azureSpSetup.js +15 -0
- package/dist/accounts/azureSpSetup.js.map +1 -1
- package/dist/accounts/azureSpSetupDurable.d.ts +8 -0
- package/dist/accounts/azureSpSetupDurable.d.ts.map +1 -1
- package/dist/accounts/azureSpSetupDurable.js +28 -0
- package/dist/accounts/azureSpSetupDurable.js.map +1 -1
- package/dist/accounts/index.d.ts +1 -0
- package/dist/accounts/index.d.ts.map +1 -1
- package/dist/accounts/index.js +1 -0
- package/dist/accounts/index.js.map +1 -1
- package/dist/accounts/readPermissions.d.ts +1 -1
- package/dist/accounts/readPermissions.d.ts.map +1 -1
- package/dist/accounts/readPermissions.js.map +1 -1
- package/dist/accounts/validation.d.ts +24 -0
- package/dist/accounts/validation.d.ts.map +1 -1
- package/dist/ai/index.d.ts +14 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +12 -0
- package/dist/ai/index.js.map +1 -1
- package/dist/aws/commitments-planning.d.ts +5 -0
- package/dist/aws/commitments-planning.d.ts.map +1 -1
- package/dist/aws/commitments-planning.js +19 -0
- package/dist/aws/commitments-planning.js.map +1 -1
- package/dist/aws/estates.d.ts +1 -1
- package/dist/aws/estates.d.ts.map +1 -1
- package/dist/aws/estates.js +7 -1
- package/dist/aws/estates.js.map +1 -1
- package/dist/aws/index.d.ts +5 -0
- package/dist/aws/index.d.ts.map +1 -1
- package/dist/aws/index.js +5 -0
- package/dist/aws/index.js.map +1 -1
- package/dist/aws/onboarding.d.ts +3 -0
- package/dist/aws/onboarding.d.ts.map +1 -1
- package/dist/aws/organizationCommitments.d.ts +260 -0
- package/dist/aws/organizationCommitments.d.ts.map +1 -0
- package/dist/aws/organizationCommitments.js +53 -0
- package/dist/aws/organizationCommitments.js.map +1 -0
- package/dist/aws/organizationCommitmentsApiValidation.d.ts +21 -0
- package/dist/aws/organizationCommitmentsApiValidation.d.ts.map +1 -0
- package/dist/aws/organizationCommitmentsApiValidation.js +226 -0
- package/dist/aws/organizationCommitmentsApiValidation.js.map +1 -0
- package/dist/aws/organizationCommitmentsValidation.d.ts +12 -0
- package/dist/aws/organizationCommitmentsValidation.d.ts.map +1 -0
- package/dist/aws/organizationCommitmentsValidation.js +228 -0
- package/dist/aws/organizationCommitmentsValidation.js.map +1 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.d.ts +9 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.d.ts.map +1 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js +407 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js.map +1 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.d.ts +17 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.d.ts.map +1 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js +6 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js.map +1 -0
- package/dist/aws/requests.d.ts +13 -2
- package/dist/aws/requests.d.ts.map +1 -1
- package/dist/aws/requests.js +1 -1
- package/dist/aws/requests.js.map +1 -1
- package/dist/azure/commitmentsPlanning.d.ts +2 -2
- package/dist/azure/commitmentsPlanning.d.ts.map +1 -1
- package/dist/azure/financialAuthorityProjectionKernel.d.ts +7 -0
- package/dist/azure/financialAuthorityProjectionKernel.d.ts.map +1 -0
- package/dist/azure/financialAuthorityProjectionKernel.js +165 -0
- package/dist/azure/financialAuthorityProjectionKernel.js.map +1 -0
- package/dist/azure/financialAuthorityViewValidation.d.ts +3 -1
- package/dist/azure/financialAuthorityViewValidation.d.ts.map +1 -1
- package/dist/azure/financialAuthorityViewValidation.js +167 -2
- package/dist/azure/financialAuthorityViewValidation.js.map +1 -1
- package/dist/azure/financialChargeCompositionKernel.d.ts.map +1 -1
- package/dist/azure/financialChargeCompositionKernel.js +5 -1
- package/dist/azure/financialChargeCompositionKernel.js.map +1 -1
- package/dist/azure/financialDataflowKernel.d.ts.map +1 -1
- package/dist/azure/financialDataflowKernel.js +2 -1
- package/dist/azure/financialDataflowKernel.js.map +1 -1
- package/dist/azure/financialProjection.d.ts +11 -1
- package/dist/azure/financialProjection.d.ts.map +1 -1
- package/dist/azure/financialProjection.js.map +1 -1
- package/dist/azure/financialProjectionValidation.d.ts.map +1 -1
- package/dist/azure/financialProjectionValidation.js +43 -3
- package/dist/azure/financialProjectionValidation.js.map +1 -1
- package/dist/azure/financialSavingsAuthority.d.ts +25 -4
- package/dist/azure/financialSavingsAuthority.d.ts.map +1 -1
- package/dist/azure/financialSavingsAuthorityValidation.d.ts +6 -2
- package/dist/azure/financialSavingsAuthorityValidation.d.ts.map +1 -1
- package/dist/azure/financialSavingsAuthorityValidation.js +126 -2
- package/dist/azure/financialSavingsAuthorityValidation.js.map +1 -1
- package/dist/azure/financialSavingsCoordinateValidation.d.ts.map +1 -1
- package/dist/azure/financialSavingsCoordinateValidation.js +62 -19
- package/dist/azure/financialSavingsCoordinateValidation.js.map +1 -1
- package/dist/azure/financialSavingsSurfaceProjection.d.ts +58 -3
- package/dist/azure/financialSavingsSurfaceProjection.d.ts.map +1 -1
- package/dist/azure/financialSavingsSurfaceProjection.js +3 -1
- package/dist/azure/financialSavingsSurfaceProjection.js.map +1 -1
- package/dist/azure/financialSavingsSurfaceProjectionKernel.d.ts +14 -0
- package/dist/azure/financialSavingsSurfaceProjectionKernel.d.ts.map +1 -0
- package/dist/azure/financialSavingsSurfaceProjectionKernel.js +326 -0
- package/dist/azure/financialSavingsSurfaceProjectionKernel.js.map +1 -0
- package/dist/azure/financialSavingsSurfaceProjectionValidation.d.ts.map +1 -1
- package/dist/azure/financialSavingsSurfaceProjectionValidation.js +126 -12
- package/dist/azure/financialSavingsSurfaceProjectionValidation.js.map +1 -1
- package/dist/azure/financialScope.d.ts +2 -0
- package/dist/azure/financialScope.d.ts.map +1 -1
- package/dist/azure/financialScope.js +2 -0
- package/dist/azure/financialScope.js.map +1 -1
- package/dist/azure/governance.d.ts +145 -0
- package/dist/azure/governance.d.ts.map +1 -1
- package/dist/azure/governance.js +2 -1
- package/dist/azure/governance.js.map +1 -1
- package/dist/azure/payloads.d.ts +19 -0
- package/dist/azure/payloads.d.ts.map +1 -1
- package/dist/azure/recommendationState.d.ts +1 -1
- package/dist/azure/recommendationState.d.ts.map +1 -1
- package/dist/azure/recommendations.d.ts +3 -1
- package/dist/azure/recommendations.d.ts.map +1 -1
- package/dist/azure/serviceRetirement.d.ts +33 -2
- package/dist/azure/serviceRetirement.d.ts.map +1 -1
- package/dist/azure/subscriptions.d.ts +9 -0
- package/dist/azure/subscriptions.d.ts.map +1 -1
- package/dist/azure/views.d.ts +19 -1
- package/dist/azure/views.d.ts.map +1 -1
- package/dist/azure/views.js.map +1 -1
- package/dist/azure/vmPatchManagement.d.ts +109 -0
- package/dist/azure/vmPatchManagement.d.ts.map +1 -0
- package/dist/azure/vmPatchManagement.js +7 -0
- package/dist/azure/vmPatchManagement.js.map +1 -0
- package/dist/common/provider.d.ts +53 -0
- package/dist/common/provider.d.ts.map +1 -1
- package/dist/common/provider.js.map +1 -1
- package/dist/environment/canonicalization.d.ts +12 -0
- package/dist/environment/canonicalization.d.ts.map +1 -0
- package/dist/environment/canonicalization.js +40 -0
- package/dist/environment/canonicalization.js.map +1 -0
- package/dist/environment/contracts.d.ts +196 -0
- package/dist/environment/contracts.d.ts.map +1 -0
- package/dist/environment/contracts.js +21 -0
- package/dist/environment/contracts.js.map +1 -0
- package/dist/environment/index.d.ts +5 -0
- package/dist/environment/index.d.ts.map +1 -0
- package/dist/environment/index.js +21 -0
- package/dist/environment/index.js.map +1 -0
- package/dist/environment/internal.d.ts +21 -0
- package/dist/environment/internal.d.ts.map +1 -0
- package/dist/environment/internal.js +113 -0
- package/dist/environment/internal.js.map +1 -0
- package/dist/environment/references.d.ts +18 -0
- package/dist/environment/references.d.ts.map +1 -0
- package/dist/environment/references.js +206 -0
- package/dist/environment/references.js.map +1 -0
- package/dist/environment/validation.d.ts +28 -0
- package/dist/environment/validation.d.ts.map +1 -0
- package/dist/environment/validation.js +320 -0
- package/dist/environment/validation.js.map +1 -0
- package/dist/esm/accounts/accounts.js +1 -0
- package/dist/esm/accounts/azureManualOnboarding.js +8 -0
- package/dist/esm/accounts/azureSpSetup.js +14 -1
- package/dist/esm/accounts/azureSpSetupDurable.js +27 -1
- package/dist/esm/accounts/index.js +1 -0
- package/dist/esm/ai/index.js +10 -1
- package/dist/esm/aws/commitments-planning.js +5 -0
- package/dist/esm/aws/estates.js +7 -1
- package/dist/esm/aws/index.js +5 -0
- package/dist/esm/aws/organizationCommitments.js +48 -0
- package/dist/esm/aws/organizationCommitmentsApiValidation.js +220 -0
- package/dist/esm/aws/organizationCommitmentsValidation.js +224 -0
- package/dist/esm/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js +402 -0
- package/dist/esm/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js +2 -0
- package/dist/esm/aws/requests.js +1 -1
- package/dist/esm/azure/financialAuthorityProjectionKernel.js +159 -0
- package/dist/esm/azure/financialAuthorityViewValidation.js +165 -1
- package/dist/esm/azure/financialChargeCompositionKernel.js +5 -1
- package/dist/esm/azure/financialDataflowKernel.js +2 -1
- package/dist/esm/azure/financialProjectionValidation.js +43 -3
- package/dist/esm/azure/financialSavingsAuthorityValidation.js +124 -2
- package/dist/esm/azure/financialSavingsCoordinateValidation.js +62 -19
- package/dist/esm/azure/financialSavingsSurfaceProjection.js +2 -0
- package/dist/esm/azure/financialSavingsSurfaceProjectionKernel.js +318 -0
- package/dist/esm/azure/financialSavingsSurfaceProjectionValidation.js +126 -12
- package/dist/esm/azure/financialScope.js +2 -0
- package/dist/esm/azure/governance.js +1 -0
- package/dist/esm/azure/vmPatchManagement.js +3 -0
- package/dist/esm/entries/commitments-planning.js +2 -2
- package/dist/esm/entries/environment.js +3 -0
- package/dist/esm/environment/canonicalization.js +34 -0
- package/dist/esm/environment/contracts.js +17 -0
- package/dist/esm/environment/index.js +4 -0
- package/dist/esm/environment/internal.js +97 -0
- package/dist/esm/environment/references.js +194 -0
- package/dist/esm/environment/validation.js +303 -0
- package/dist/esm/index.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/portfolio/portfolioOperations.d.ts +1 -1
- package/dist/portfolio/portfolioOperations.d.ts.map +1 -1
- package/package.json +14 -3
|
@@ -295,7 +295,7 @@ const isCoordinate = (value, coveredScopeIds, financialRoleByScope, providerAcco
|
|
|
295
295
|
(projection.targetEvidenceBundleId !== undefined && !bundleIds.has(projection.targetEvidenceBundleId)) ||
|
|
296
296
|
(targetAssessment !== undefined &&
|
|
297
297
|
(targetAssessment.request.scopeId !== projection.scopeId ||
|
|
298
|
-
targetAssessment.result !== 'available' ||
|
|
298
|
+
(projection.status === 'available' && targetAssessment.result !== 'available') ||
|
|
299
299
|
targetAssessment.evidenceBundleId !== projection.targetEvidenceBundleId)) ||
|
|
300
300
|
(projection.baselineId !== undefined && (owner.status !== 'available' || projection.baselineId !== owner.baselineId)) ||
|
|
301
301
|
(projection.status === 'available' && owner.status !== 'available'))
|
|
@@ -367,6 +367,15 @@ const isCoordinate = (value, coveredScopeIds, financialRoleByScope, providerAcco
|
|
|
367
367
|
return false;
|
|
368
368
|
continue;
|
|
369
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
|
+
}
|
|
370
379
|
if (!('sourceRate' in applied) || !('targetQuantity' in applied) || component.effectiveRate === undefined)
|
|
371
380
|
return false;
|
|
372
381
|
if (applied.sourceRate.amount !== component.effectiveRate.amount ||
|
|
@@ -534,3 +543,158 @@ export const isFinancialAuthorityViewV1 = (value) => {
|
|
|
534
543
|
export const isFinancialAuthorityViewBoundToArtifactGenerationV1 = (value, expected) => isFinancialAuthorityViewV1(value) &&
|
|
535
544
|
value.artifactGeneration.runId === expected.runId &&
|
|
536
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
|
+
};
|
|
@@ -12,7 +12,11 @@ export const createFinancialChargeCompositionV1 = (request) => {
|
|
|
12
12
|
const groups = new Map();
|
|
13
13
|
for (const component of request.components) {
|
|
14
14
|
const key = bucketKey(component);
|
|
15
|
-
|
|
15
|
+
const group = groups.get(key);
|
|
16
|
+
if (group)
|
|
17
|
+
group.push(component);
|
|
18
|
+
else
|
|
19
|
+
groups.set(key, [component]);
|
|
16
20
|
}
|
|
17
21
|
const buckets = [...groups.values()]
|
|
18
22
|
.map(group => ({
|
|
@@ -92,7 +92,8 @@ export const composeCurrentSpendV1 = (request) => {
|
|
|
92
92
|
};
|
|
93
93
|
});
|
|
94
94
|
const available = sortedBaselines.filter((baseline) => baseline.status === 'available');
|
|
95
|
-
const
|
|
95
|
+
const includedBaselineIds = new Set(members.flatMap(member => (member.status === 'included' ? [member.baselineId] : [])));
|
|
96
|
+
const includedBaselines = available.filter(baseline => includedBaselineIds.has(baseline.baselineId));
|
|
96
97
|
const unavailableReasons = new Set(sortedBaselines
|
|
97
98
|
.filter((baseline) => baseline.status === 'unavailable')
|
|
98
99
|
.map(baseline => baseline.unavailableReason));
|
|
@@ -4,7 +4,15 @@ import { isCanonicalExactMoney } from './financialValidationPrimitives.js';
|
|
|
4
4
|
import { FINANCIAL_PROJECTION_CONTRACT_VERSION_V1, FINANCIAL_PROJECTION_SCHEMA_VERSION_V1, FINANCIAL_PROJECTION_UNAVAILABLE_REASONS_V1, } from './financialProjection.js';
|
|
5
5
|
const SHA256_ID = /^sha256:[0-9a-f]{64}$/;
|
|
6
6
|
const CURRENCY = /^[A-Z]{3}$/;
|
|
7
|
-
const OPERATIONS = new Set([
|
|
7
|
+
const OPERATIONS = new Set([
|
|
8
|
+
'unclassified',
|
|
9
|
+
'replace-rate',
|
|
10
|
+
'replace-quantity',
|
|
11
|
+
'replace-quantity-and-rate',
|
|
12
|
+
'remove-component',
|
|
13
|
+
'schedule-quantity',
|
|
14
|
+
'commitment-coverage',
|
|
15
|
+
]);
|
|
8
16
|
const COST_BASES = new Set(['billed', 'amortized']);
|
|
9
17
|
const ESTIMATE_LENSES = new Set(['actual-only', 'actual-plus-estimated', 'estimates-only']);
|
|
10
18
|
const TARGET_PROVENANCE = new Set(['retail-derived', 'provider-quote-derived', 'billing-backed', 'estimated', 'configuration-derived']);
|
|
@@ -32,6 +40,16 @@ const isNonNegativeDecimal = (value, currency = 'AUD') => {
|
|
|
32
40
|
}
|
|
33
41
|
};
|
|
34
42
|
const isUnit = (value) => isIdentity(value) && value.length <= 512;
|
|
43
|
+
const isReplayedProduct = (quantity, rate, targetAmount, currency) => {
|
|
44
|
+
if (!isNonNegativeDecimal(quantity) || !isNonNegativeDecimal(rate, currency) || !isDecimal(targetAmount, currency))
|
|
45
|
+
return false;
|
|
46
|
+
try {
|
|
47
|
+
return equalsDecimal(multiplyExactDecimalValues(parseCanonicalDecimal(quantity), parseCanonicalDecimal(rate)), parseCanonicalDecimal(targetAmount));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
35
53
|
const isCanonicalDate = (value) => typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(`${value}T00:00:00.000Z`));
|
|
36
54
|
const isNormalizedAverageMonthProfile = (value) => isRecord(value) &&
|
|
37
55
|
hasExactFields(value, ['kind', 'annualDayCount', 'monthDivisor', 'hoursPerDay', 'normalizedHours']) &&
|
|
@@ -203,7 +221,8 @@ const isAppliedComponentTargets = (value, operationKind, targetProvenance, targe
|
|
|
203
221
|
!isNonNegativeDecimal(target.targetRate.amount) ||
|
|
204
222
|
target.targetRate.currencyCode !== accountingCurrencyCode ||
|
|
205
223
|
!isUnit(target.targetRate.quantityUnit) ||
|
|
206
|
-
target.targetRate.quantityUnit !== target.sourceQuantity.unit
|
|
224
|
+
target.targetRate.quantityUnit !== target.sourceQuantity.unit ||
|
|
225
|
+
!isReplayedProduct(target.sourceQuantity.amount, target.targetRate.amount, target.targetAmount, accountingCurrencyCode))
|
|
207
226
|
return false;
|
|
208
227
|
continue;
|
|
209
228
|
}
|
|
@@ -218,7 +237,24 @@ const isAppliedComponentTargets = (value, operationKind, targetProvenance, targe
|
|
|
218
237
|
!hasExactFields(target.targetQuantity, ['amount', 'unit']) ||
|
|
219
238
|
!isNonNegativeDecimal(target.targetQuantity.amount) ||
|
|
220
239
|
!isUnit(target.targetQuantity.unit) ||
|
|
221
|
-
target.targetQuantity.unit !== target.sourceRate.unit
|
|
240
|
+
target.targetQuantity.unit !== target.sourceRate.unit ||
|
|
241
|
+
!isReplayedProduct(target.targetQuantity.amount, target.sourceRate.amount, target.targetAmount, accountingCurrencyCode))
|
|
242
|
+
return false;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (operationKind === 'replace-quantity-and-rate') {
|
|
246
|
+
if (!hasExactFields(target, ['componentId', 'targetAmount', 'targetConfigurationId', 'targetEvidenceRefIds', 'targetQuantity', 'targetRate']) ||
|
|
247
|
+
!isRecord(target.targetQuantity) ||
|
|
248
|
+
!hasExactFields(target.targetQuantity, ['amount', 'unit']) ||
|
|
249
|
+
!isNonNegativeDecimal(target.targetQuantity.amount) ||
|
|
250
|
+
!isUnit(target.targetQuantity.unit) ||
|
|
251
|
+
!isRecord(target.targetRate) ||
|
|
252
|
+
!hasExactFields(target.targetRate, ['amount', 'currencyCode', 'quantityUnit']) ||
|
|
253
|
+
!isNonNegativeDecimal(target.targetRate.amount, accountingCurrencyCode) ||
|
|
254
|
+
target.targetRate.currencyCode !== accountingCurrencyCode ||
|
|
255
|
+
!isUnit(target.targetRate.quantityUnit) ||
|
|
256
|
+
target.targetRate.quantityUnit !== target.targetQuantity.unit ||
|
|
257
|
+
!isReplayedProduct(target.targetQuantity.amount, target.targetRate.amount, target.targetAmount, accountingCurrencyCode))
|
|
222
258
|
return false;
|
|
223
259
|
continue;
|
|
224
260
|
}
|
|
@@ -428,6 +464,10 @@ const isUnavailable = (value) => {
|
|
|
428
464
|
!TARGET_PERIOD_CONVENTIONS.has(value.targetPeriodConvention) ||
|
|
429
465
|
!isTargetPeriodProfileCompatible(value.targetPeriodConvention, value.targetPeriodProfile, false) ||
|
|
430
466
|
(isObservedPeriodProfile(value.targetPeriodProfile) && value.operationKind !== 'commitment-coverage') ||
|
|
467
|
+
(value.operationKind === 'unclassified' &&
|
|
468
|
+
(value.unavailableReason !== 'target-evidence-unavailable' ||
|
|
469
|
+
!Array.isArray(value.affectedComponentIds) ||
|
|
470
|
+
value.affectedComponentIds.length !== 0)) ||
|
|
431
471
|
(value.targetProvenance === 'configuration-derived' && value.operationKind !== 'remove-component') ||
|
|
432
472
|
(value.operationKind === 'commitment-coverage' && value.targetProvenance !== 'provider-quote-derived') ||
|
|
433
473
|
!isPossiblyEmptyHashArray(value.affectedComponentIds) ||
|
|
@@ -2,7 +2,7 @@ import { sha256Utf8 } from '../common/sha256.js';
|
|
|
2
2
|
import { FINANCIAL_SAVINGS_AUTHORITY_CONTRACT_VERSION_V1, FINANCIAL_SAVINGS_AUTHORITY_SCHEMA_VERSION_V1, } from './financialSavingsAuthority.js';
|
|
3
3
|
import { validateFinancialEligibilityAssessmentV1, validateFinancialEligibilityBaselineV1, } from './financialEligibilityAssessmentValidation.js';
|
|
4
4
|
import { validateFinancialSavingsCoordinateEnvelopeV1 } from './financialSavingsCoordinateValidation.js';
|
|
5
|
-
import { canonicalizeFinancialSavingsJsonValue, hasExactFinancialSavingsFields, isFinancialSavingsHash, isFinancialSavingsRecord, } from './financialSavingsAuthorityValidationPrimitives.js';
|
|
5
|
+
import { canonicalizeFinancialSavingsJsonValue, hasExactFinancialSavingsFields, haveSameFinancialSavingsSet, isFinancialSavingsHash, isFinancialSavingsIdentity, isFinancialSavingsIsoInstant, isFinancialSavingsMinorUnits, isFinancialSavingsRecord, sumFinancialSavingsMinorUnits, } from './financialSavingsAuthorityValidationPrimitives.js';
|
|
6
6
|
export { canonicalizeFinancialEligibilityAssessmentIdentityV1, createFinancialEligibilityAssessmentIdV1, } from './financialEligibilityAssessmentValidation.js';
|
|
7
7
|
export { canonicalizeFinancialSavingsActivationIdentityV1, canonicalizeFinancialSavingsAllocationIdentityV1, canonicalizeFinancialSavingsDenominatorIdentityV1, createFinancialSavingsActivationIdV1, createFinancialSavingsAllocationIdV1, createFinancialSavingsDenominatorIdV1, } from './financialSavingsCoordinateValidation.js';
|
|
8
8
|
export const canonicalizeFinancialSavingsAuthorityIdentityV1 = (value) => JSON.stringify(canonicalizeFinancialSavingsJsonValue({
|
|
@@ -28,7 +28,7 @@ export const canonicalizeFinancialSavingsAuthorityIdentityV1 = (value) => JSON.s
|
|
|
28
28
|
.map(allocation => ({
|
|
29
29
|
...allocation,
|
|
30
30
|
billableComponentIds: [...allocation.billableComponentIds].sort(),
|
|
31
|
-
eligibility: allocation.eligibility.kind
|
|
31
|
+
eligibility: allocation.eligibility.kind !== 'mapped'
|
|
32
32
|
? allocation.eligibility
|
|
33
33
|
: {
|
|
34
34
|
...allocation.eligibility,
|
|
@@ -115,3 +115,125 @@ export const isFinancialSavingsAuthorityBoundToFinancialAuthorityV1 = (value, au
|
|
|
115
115
|
const { savingsAuthorityId: _savingsAuthorityId, ...identity } = savingsAuthority;
|
|
116
116
|
return savingsAuthority.savingsAuthorityId === createFinancialSavingsAuthorityIdV1(identity);
|
|
117
117
|
};
|
|
118
|
+
const RESOURCE_PROJECTION_UNAVAILABLE_REASONS = new Set([
|
|
119
|
+
'scenario-coverage-unproven',
|
|
120
|
+
'unmigrated-scenario-producer',
|
|
121
|
+
'projection-unavailable',
|
|
122
|
+
'activation-unavailable',
|
|
123
|
+
'allocation-unavailable',
|
|
124
|
+
]);
|
|
125
|
+
const RESOURCE_PROJECTION_CURRENCY = /^[A-Z]{3}$/;
|
|
126
|
+
const normalizeResourceScope = (value) => value.trim().toLowerCase().replace(/\/+$/, '');
|
|
127
|
+
const isResourceContribution = (value, scopeId) => isFinancialSavingsRecord(value) &&
|
|
128
|
+
hasExactFinancialSavingsFields(value, ['ownerScopeId', 'allocationIds', 'savingsMinorUnits']) &&
|
|
129
|
+
isFinancialSavingsIdentity(value.ownerScopeId) &&
|
|
130
|
+
normalizeResourceScope(value.ownerScopeId) === scopeId &&
|
|
131
|
+
Array.isArray(value.allocationIds) &&
|
|
132
|
+
value.allocationIds.length > 0 &&
|
|
133
|
+
value.allocationIds.length <= 20000 &&
|
|
134
|
+
value.allocationIds.every(isFinancialSavingsHash) &&
|
|
135
|
+
new Set(value.allocationIds).size === value.allocationIds.length &&
|
|
136
|
+
isFinancialSavingsMinorUnits(value.savingsMinorUnits);
|
|
137
|
+
const isRecommendationContribution = (value, scopeId) => isFinancialSavingsRecord(value) &&
|
|
138
|
+
hasExactFinancialSavingsFields(value, ['ownerScopeId', 'recommendationId', 'allocationIds', 'savingsMinorUnits']) &&
|
|
139
|
+
isFinancialSavingsIdentity(value.ownerScopeId) &&
|
|
140
|
+
normalizeResourceScope(value.ownerScopeId) === scopeId &&
|
|
141
|
+
isFinancialSavingsIdentity(value.recommendationId) &&
|
|
142
|
+
Array.isArray(value.allocationIds) &&
|
|
143
|
+
value.allocationIds.length > 0 &&
|
|
144
|
+
value.allocationIds.length <= 20000 &&
|
|
145
|
+
value.allocationIds.every(isFinancialSavingsHash) &&
|
|
146
|
+
new Set(value.allocationIds).size === value.allocationIds.length &&
|
|
147
|
+
isFinancialSavingsMinorUnits(value.savingsMinorUnits);
|
|
148
|
+
const isSavingsResourceCoordinate = (value, scopeId) => {
|
|
149
|
+
if (!isFinancialSavingsRecord(value) || !isFinancialSavingsHash(value.coordinateId))
|
|
150
|
+
return false;
|
|
151
|
+
if (value.status === 'unavailable') {
|
|
152
|
+
return (hasExactFinancialSavingsFields(value, ['status', 'coordinateId', 'unavailableReason'], ['currentAggregateBaselineId']) &&
|
|
153
|
+
(value.currentAggregateBaselineId === undefined || isFinancialSavingsHash(value.currentAggregateBaselineId)) &&
|
|
154
|
+
typeof value.unavailableReason === 'string' &&
|
|
155
|
+
RESOURCE_PROJECTION_UNAVAILABLE_REASONS.has(value.unavailableReason));
|
|
156
|
+
}
|
|
157
|
+
const partial = value.status === 'partial';
|
|
158
|
+
if (value.status !== 'available' &&
|
|
159
|
+
!partial)
|
|
160
|
+
return false;
|
|
161
|
+
if (!hasExactFinancialSavingsFields(value, [
|
|
162
|
+
'status',
|
|
163
|
+
'coordinateId',
|
|
164
|
+
'currentAggregateBaselineId',
|
|
165
|
+
'accountingCurrencyCode',
|
|
166
|
+
'minorUnitScale',
|
|
167
|
+
'roundingMode',
|
|
168
|
+
'recommendationContributions',
|
|
169
|
+
...(partial ? ['unavailableScenarioIds'] : []),
|
|
170
|
+
], ['resourceContribution']) ||
|
|
171
|
+
!isFinancialSavingsHash(value.currentAggregateBaselineId) ||
|
|
172
|
+
typeof value.accountingCurrencyCode !== 'string' ||
|
|
173
|
+
!RESOURCE_PROJECTION_CURRENCY.test(value.accountingCurrencyCode) ||
|
|
174
|
+
!Number.isSafeInteger(value.minorUnitScale) ||
|
|
175
|
+
Number(value.minorUnitScale) < 0 ||
|
|
176
|
+
Number(value.minorUnitScale) > 6 ||
|
|
177
|
+
value.roundingMode !== 'half-away-from-zero' ||
|
|
178
|
+
(value.resourceContribution !== undefined && !isResourceContribution(value.resourceContribution, scopeId)) ||
|
|
179
|
+
!Array.isArray(value.recommendationContributions) ||
|
|
180
|
+
value.recommendationContributions.length > 20000 ||
|
|
181
|
+
!value.recommendationContributions.every(contribution => isRecommendationContribution(contribution, scopeId)) ||
|
|
182
|
+
(partial &&
|
|
183
|
+
(!Array.isArray(value.unavailableScenarioIds) ||
|
|
184
|
+
value.unavailableScenarioIds.length === 0 ||
|
|
185
|
+
value.unavailableScenarioIds.length > 20000 ||
|
|
186
|
+
!value.unavailableScenarioIds.every(isFinancialSavingsIdentity) ||
|
|
187
|
+
new Set(value.unavailableScenarioIds).size !== value.unavailableScenarioIds.length)))
|
|
188
|
+
return false;
|
|
189
|
+
const recommendationIds = value.recommendationContributions.map(contribution => contribution.recommendationId);
|
|
190
|
+
const allocationIds = value.recommendationContributions.flatMap(contribution => contribution.allocationIds);
|
|
191
|
+
const contributionSum = sumFinancialSavingsMinorUnits(value.recommendationContributions.map(contribution => contribution.savingsMinorUnits));
|
|
192
|
+
const resourceContribution = value.resourceContribution;
|
|
193
|
+
return (new Set(recommendationIds).size === recommendationIds.length &&
|
|
194
|
+
new Set(allocationIds).size === allocationIds.length &&
|
|
195
|
+
contributionSum !== undefined &&
|
|
196
|
+
(resourceContribution === undefined
|
|
197
|
+
? allocationIds.length === 0 && contributionSum === 0
|
|
198
|
+
: haveSameFinancialSavingsSet(allocationIds, resourceContribution.allocationIds) &&
|
|
199
|
+
contributionSum === resourceContribution.savingsMinorUnits));
|
|
200
|
+
};
|
|
201
|
+
/** Strict structural and arithmetic validation for one bounded resource savings projection. */
|
|
202
|
+
export const isFinancialSavingsResourceProjectionV1 = (value) => {
|
|
203
|
+
if (!isFinancialSavingsRecord(value) ||
|
|
204
|
+
!hasExactFinancialSavingsFields(value, [
|
|
205
|
+
'contractVersion',
|
|
206
|
+
'savingsAuthorityId',
|
|
207
|
+
'financialAuthorityId',
|
|
208
|
+
'artifactGeneration',
|
|
209
|
+
'scopeId',
|
|
210
|
+
'coordinates',
|
|
211
|
+
]) ||
|
|
212
|
+
value.contractVersion !== 'financial-savings-resource-projection/v1' ||
|
|
213
|
+
!isFinancialSavingsHash(value.savingsAuthorityId) ||
|
|
214
|
+
!isFinancialSavingsHash(value.financialAuthorityId) ||
|
|
215
|
+
!isFinancialSavingsRecord(value.artifactGeneration) ||
|
|
216
|
+
!hasExactFinancialSavingsFields(value.artifactGeneration, ['runId', 'generatedAt']) ||
|
|
217
|
+
!isFinancialSavingsIdentity(value.artifactGeneration.runId) ||
|
|
218
|
+
!isFinancialSavingsIsoInstant(value.artifactGeneration.generatedAt) ||
|
|
219
|
+
!isFinancialSavingsIdentity(value.scopeId) ||
|
|
220
|
+
value.scopeId !== normalizeResourceScope(value.scopeId) ||
|
|
221
|
+
!Array.isArray(value.coordinates) ||
|
|
222
|
+
value.coordinates.length === 0 ||
|
|
223
|
+
value.coordinates.length > 128)
|
|
224
|
+
return false;
|
|
225
|
+
const projection = value;
|
|
226
|
+
return (projection.coordinates.every(coordinate => isSavingsResourceCoordinate(coordinate, projection.scopeId)) &&
|
|
227
|
+
new Set(projection.coordinates.map(coordinate => coordinate.coordinateId)).size === projection.coordinates.length);
|
|
228
|
+
};
|
|
229
|
+
/** Verifies that the bounded savings projection is the exact companion of one bounded Financial Authority projection. */
|
|
230
|
+
export const isFinancialSavingsResourceProjectionBoundToFinancialProjectionV1 = (value, financialProjection) => {
|
|
231
|
+
if (!isFinancialSavingsResourceProjectionV1(value))
|
|
232
|
+
return false;
|
|
233
|
+
const financialCoordinateIds = financialProjection.coordinates.map(coordinate => coordinate.coordinateId);
|
|
234
|
+
return (value.financialAuthorityId === financialProjection.authorityId &&
|
|
235
|
+
normalizeResourceScope(value.scopeId) === normalizeResourceScope(financialProjection.scopeId) &&
|
|
236
|
+
value.artifactGeneration.runId === financialProjection.artifactGeneration.runId &&
|
|
237
|
+
value.artifactGeneration.generatedAt === financialProjection.artifactGeneration.generatedAt &&
|
|
238
|
+
haveSameFinancialSavingsSet(value.coordinates.map(coordinate => coordinate.coordinateId), financialCoordinateIds));
|
|
239
|
+
};
|
|
@@ -23,8 +23,17 @@ const ACTIVATION_REASONS = new Set([
|
|
|
23
23
|
'lifecycle-conflict',
|
|
24
24
|
'unrecognized-lifecycle',
|
|
25
25
|
'generation-mismatch',
|
|
26
|
+
'projection-unavailable',
|
|
27
|
+
'eligibility-unavailable',
|
|
28
|
+
]);
|
|
29
|
+
const UNAVAILABLE_ACTIVATION_REASONS = new Set([
|
|
30
|
+
'lifecycle-stale',
|
|
31
|
+
'lifecycle-unavailable',
|
|
32
|
+
'lifecycle-conflict',
|
|
33
|
+
'generation-mismatch',
|
|
34
|
+
'projection-unavailable',
|
|
35
|
+
'eligibility-unavailable',
|
|
26
36
|
]);
|
|
27
|
-
const UNAVAILABLE_ACTIVATION_REASONS = new Set(['lifecycle-stale', 'lifecycle-unavailable', 'lifecycle-conflict', 'generation-mismatch']);
|
|
28
37
|
export const canonicalizeFinancialSavingsDenominatorIdentityV1 = (value) => JSON.stringify(canonicalizeFinancialSavingsJsonValue({
|
|
29
38
|
kind: value.kind,
|
|
30
39
|
baselineId: value.baselineId,
|
|
@@ -35,7 +44,8 @@ export const canonicalizeFinancialSavingsDenominatorIdentityV1 = (value) => JSON
|
|
|
35
44
|
export const createFinancialSavingsDenominatorIdV1 = (value) => `sha256:${sha256Utf8(canonicalizeFinancialSavingsDenominatorIdentityV1(value))}`;
|
|
36
45
|
export const canonicalizeFinancialSavingsActivationIdentityV1 = (value) => JSON.stringify(canonicalizeFinancialSavingsJsonValue({
|
|
37
46
|
recommendationId: value.recommendationId,
|
|
38
|
-
|
|
47
|
+
scenarioId: value.scenarioId,
|
|
48
|
+
...(value.projectionId === undefined ? {} : { projectionId: value.projectionId }),
|
|
39
49
|
lifecycleState: value.lifecycleState,
|
|
40
50
|
lifecycleVersion: value.lifecycleVersion,
|
|
41
51
|
lifecycleEvidenceRefId: value.lifecycleEvidenceRefId,
|
|
@@ -49,10 +59,11 @@ export const canonicalizeFinancialSavingsAllocationIdentityV1 = (value) => JSON.
|
|
|
49
59
|
ownerScopeId: value.ownerScopeId,
|
|
50
60
|
billableComponentIds: [...value.billableComponentIds].sort(),
|
|
51
61
|
recommendationId: value.recommendationId,
|
|
62
|
+
scenarioId: value.scenarioId,
|
|
52
63
|
baselineId: value.baselineId,
|
|
53
64
|
projectionId: value.projectionId,
|
|
54
65
|
denominatorId: value.denominatorId,
|
|
55
|
-
eligibility: value.eligibility.kind
|
|
66
|
+
eligibility: value.eligibility.kind !== 'mapped'
|
|
56
67
|
? value.eligibility
|
|
57
68
|
: {
|
|
58
69
|
...value.eligibility,
|
|
@@ -98,10 +109,23 @@ const isActivationDecision = (state, result, reason) => {
|
|
|
98
109
|
return UNAVAILABLE_ACTIVATION_REASONS.has(reason);
|
|
99
110
|
};
|
|
100
111
|
const isEligibility = (value) => {
|
|
101
|
-
if (!isFinancialSavingsRecord(value) ||
|
|
112
|
+
if (!isFinancialSavingsRecord(value) || !['not-applicable', 'unavailable', 'mapped'].includes(String(value.kind)))
|
|
102
113
|
return false;
|
|
103
114
|
if (value.kind === 'not-applicable')
|
|
104
115
|
return Object.keys(value).length === 1;
|
|
116
|
+
if (value.kind === 'unavailable') {
|
|
117
|
+
return (Object.keys(value).length === 2 &&
|
|
118
|
+
typeof value.reason === 'string' &&
|
|
119
|
+
[
|
|
120
|
+
'rule-evidence-unavailable',
|
|
121
|
+
'eligibility-baseline-unavailable',
|
|
122
|
+
'eligible-components-unavailable',
|
|
123
|
+
'denominator-unavailable',
|
|
124
|
+
'current-baseline-mapping-unavailable',
|
|
125
|
+
'currency-conflict',
|
|
126
|
+
'reconciliation-failure',
|
|
127
|
+
].includes(value.reason));
|
|
128
|
+
}
|
|
105
129
|
return (Object.keys(value).length === 5 &&
|
|
106
130
|
isFinancialSavingsHash(value.eligibilityId) &&
|
|
107
131
|
isFinancialSavingsHash(value.eligibilityBaselineId) &&
|
|
@@ -114,7 +138,7 @@ const isEligibility = (value) => {
|
|
|
114
138
|
value.eligibilityComponentIds.every(isFinancialSavingsIdentity) &&
|
|
115
139
|
new Set(value.eligibilityComponentIds).size === value.eligibilityComponentIds.length);
|
|
116
140
|
};
|
|
117
|
-
const
|
|
141
|
+
const validateComposedCoordinate = (value, authorityCoordinate, eligibilityById, evidenceById, authorityGeneratedAt, authorityRunId) => {
|
|
118
142
|
if (!hasExactFinancialSavingsFields(value, [
|
|
119
143
|
'status',
|
|
120
144
|
'coordinateId',
|
|
@@ -140,7 +164,7 @@ const validateAvailableCoordinate = (value, authorityCoordinate, eligibilityById
|
|
|
140
164
|
value.roundingMode !== 'half-away-from-zero' ||
|
|
141
165
|
!isFinancialSavingsRecord(value.scenarioCoverage) ||
|
|
142
166
|
!hasExactFinancialSavingsFields(value.scenarioCoverage, ['status', 'evidenceRefId', 'scenarioIds']) ||
|
|
143
|
-
value.scenarioCoverage.status !== 'complete' ||
|
|
167
|
+
value.scenarioCoverage.status !== (value.status === 'available' ? 'complete' : 'partial') ||
|
|
144
168
|
!isFinancialSavingsHash(value.scenarioCoverage.evidenceRefId) ||
|
|
145
169
|
!Array.isArray(value.scenarioCoverage.scenarioIds) ||
|
|
146
170
|
value.scenarioCoverage.scenarioIds.length > 20000 ||
|
|
@@ -166,15 +190,17 @@ const validateAvailableCoordinate = (value, authorityCoordinate, eligibilityById
|
|
|
166
190
|
Date.parse(scenarioCoverageEvidence.intrinsicTime.at) > Date.parse(authorityGeneratedAt))
|
|
167
191
|
return false;
|
|
168
192
|
const projectionById = new Map(authorityCoordinate.projections.flatMap(projection => (projection.status === 'available' ? [[projection.projectionId, projection]] : [])));
|
|
193
|
+
const availableProjectionScenarioIds = new Set(authorityCoordinate.projections.flatMap(projection => (projection.status === 'available' ? [projection.scenarioId] : [])));
|
|
194
|
+
const unavailableEligibilityScenarioIds = new Set([...eligibilityById.values()].flatMap(assessment => (assessment.status === 'unavailable' ? [assessment.scenarioId] : [])));
|
|
169
195
|
const activationById = new Map();
|
|
170
|
-
const
|
|
196
|
+
const activationByScenarioId = new Map();
|
|
171
197
|
for (const activation of value.activations) {
|
|
172
198
|
const lifecycleEvidence = evidenceById.get(activation.lifecycleEvidenceRefId);
|
|
173
199
|
if (!isFinancialSavingsRecord(activation) ||
|
|
174
200
|
!hasExactFinancialSavingsFields(activation, [
|
|
175
201
|
'activationId',
|
|
202
|
+
'scenarioId',
|
|
176
203
|
'recommendationId',
|
|
177
|
-
'projectionId',
|
|
178
204
|
'lifecycleState',
|
|
179
205
|
'lifecycleVersion',
|
|
180
206
|
'lifecycleEvidenceRefId',
|
|
@@ -182,19 +208,29 @@ const validateAvailableCoordinate = (value, authorityCoordinate, eligibilityById
|
|
|
182
208
|
'reason',
|
|
183
209
|
'evaluatedAt',
|
|
184
210
|
'policyVersion',
|
|
185
|
-
]) ||
|
|
211
|
+
], ['projectionId']) ||
|
|
186
212
|
!isFinancialSavingsHash(activation.activationId) ||
|
|
187
213
|
activationById.has(activation.activationId) ||
|
|
188
|
-
|
|
214
|
+
activationByScenarioId.has(activation.scenarioId) ||
|
|
215
|
+
!isFinancialSavingsIdentity(activation.scenarioId) ||
|
|
189
216
|
!isFinancialSavingsIdentity(activation.recommendationId) ||
|
|
190
|
-
!isFinancialSavingsHash(activation.projectionId) ||
|
|
217
|
+
(activation.projectionId !== undefined && !isFinancialSavingsHash(activation.projectionId)) ||
|
|
191
218
|
!isFinancialSavingsIdentity(activation.lifecycleVersion) ||
|
|
192
219
|
!isFinancialSavingsHash(activation.lifecycleEvidenceRefId) ||
|
|
193
220
|
!isActivationDecision(activation.lifecycleState, activation.result, activation.reason) ||
|
|
194
221
|
!isFinancialSavingsIsoInstant(activation.evaluatedAt) ||
|
|
195
222
|
Date.parse(activation.evaluatedAt) > Date.parse(authorityGeneratedAt) ||
|
|
196
223
|
!isFinancialSavingsIdentity(activation.policyVersion) ||
|
|
197
|
-
|
|
224
|
+
(activation.result === 'included' &&
|
|
225
|
+
(activation.projectionId === undefined || projectionById.get(activation.projectionId)?.scenarioId !== activation.scenarioId)) ||
|
|
226
|
+
(activation.result === 'excluded' &&
|
|
227
|
+
activation.projectionId !== undefined &&
|
|
228
|
+
projectionById.get(activation.projectionId)?.scenarioId !== activation.scenarioId) ||
|
|
229
|
+
(activation.result === 'unavailable' &&
|
|
230
|
+
(activation.projectionId !== undefined ||
|
|
231
|
+
(activation.reason === 'projection-unavailable' && availableProjectionScenarioIds.has(activation.scenarioId)) ||
|
|
232
|
+
(activation.reason === 'eligibility-unavailable' &&
|
|
233
|
+
!unavailableEligibilityScenarioIds.has(activation.scenarioId)))) ||
|
|
198
234
|
lifecycleEvidence?.role !== 'recommendation-lifecycle' ||
|
|
199
235
|
lifecycleEvidence.revisionId !== activation.lifecycleVersion ||
|
|
200
236
|
Date.parse(lifecycleEvidence.intrinsicTime.at) > Date.parse(authorityGeneratedAt))
|
|
@@ -203,12 +239,15 @@ const validateAvailableCoordinate = (value, authorityCoordinate, eligibilityById
|
|
|
203
239
|
if (activation.activationId !== createFinancialSavingsActivationIdV1(activationIdentity))
|
|
204
240
|
return false;
|
|
205
241
|
activationById.set(activation.activationId, activation);
|
|
206
|
-
|
|
242
|
+
activationByScenarioId.set(activation.scenarioId, activation);
|
|
207
243
|
}
|
|
208
244
|
const coveredScenarioIds = new Set(value.scenarioCoverage.scenarioIds);
|
|
209
|
-
const activatedScenarioIds = new Set(value.activations.map(activation => activation.
|
|
245
|
+
const activatedScenarioIds = new Set(value.activations.map(activation => activation.scenarioId));
|
|
210
246
|
if (value.scenarioCoverage.scenarioIds.some(scenarioId => !activatedScenarioIds.has(scenarioId)) ||
|
|
211
|
-
value.activations.some(activation => !coveredScenarioIds.has(activation.
|
|
247
|
+
value.activations.some(activation => !coveredScenarioIds.has(activation.scenarioId)))
|
|
248
|
+
return false;
|
|
249
|
+
const hasUnavailableActivation = value.activations.some(activation => activation.result === 'unavailable');
|
|
250
|
+
if ((value.status === 'available' && hasUnavailableActivation) || (value.status === 'partial' && !hasUnavailableActivation))
|
|
212
251
|
return false;
|
|
213
252
|
const allocationById = new Map();
|
|
214
253
|
const allocatedBaselineComponents = new Set();
|
|
@@ -218,7 +257,7 @@ const validateAvailableCoordinate = (value, authorityCoordinate, eligibilityById
|
|
|
218
257
|
const eligibility = allocation.eligibility.kind === 'mapped' ? eligibilityById.get(allocation.eligibility.eligibilityId) : undefined;
|
|
219
258
|
const expectedDenominatorId = allocation.eligibility.kind === 'mapped' && eligibility?.status === 'available'
|
|
220
259
|
? eligibility.denominator.denominatorId
|
|
221
|
-
: projection
|
|
260
|
+
: allocation.eligibility.kind === 'not-applicable' && projection
|
|
222
261
|
? createFinancialSavingsDenominatorIdV1({
|
|
223
262
|
kind: 'projection-affected-current',
|
|
224
263
|
baselineId: projection.baselineId,
|
|
@@ -232,6 +271,7 @@ const validateAvailableCoordinate = (value, authorityCoordinate, eligibilityById
|
|
|
232
271
|
'allocationId',
|
|
233
272
|
'ownerScopeId',
|
|
234
273
|
'billableComponentIds',
|
|
274
|
+
'scenarioId',
|
|
235
275
|
'recommendationId',
|
|
236
276
|
'baselineId',
|
|
237
277
|
'projectionId',
|
|
@@ -248,6 +288,7 @@ const validateAvailableCoordinate = (value, authorityCoordinate, eligibilityById
|
|
|
248
288
|
allocation.billableComponentIds.length > 20000 ||
|
|
249
289
|
!allocation.billableComponentIds.every(isFinancialSavingsIdentity) ||
|
|
250
290
|
new Set(allocation.billableComponentIds).size !== allocation.billableComponentIds.length ||
|
|
291
|
+
!isFinancialSavingsIdentity(allocation.scenarioId) ||
|
|
251
292
|
!isFinancialSavingsIdentity(allocation.recommendationId) ||
|
|
252
293
|
!isFinancialSavingsHash(allocation.baselineId) ||
|
|
253
294
|
!isFinancialSavingsHash(allocation.projectionId) ||
|
|
@@ -261,15 +302,17 @@ const validateAvailableCoordinate = (value, authorityCoordinate, eligibilityById
|
|
|
261
302
|
projection.baselineId !== allocation.baselineId ||
|
|
262
303
|
!haveSameFinancialSavingsSet(projection.affectedComponentIds, allocation.billableComponentIds) ||
|
|
263
304
|
decimalToMinorUnits(projection.change.savings, value.minorUnitScale) !== allocation.savingsMinorUnits ||
|
|
305
|
+
projection.scenarioId !== allocation.scenarioId ||
|
|
264
306
|
!activation ||
|
|
265
307
|
activation.result !== 'included' ||
|
|
308
|
+
activation.scenarioId !== allocation.scenarioId ||
|
|
266
309
|
activation.recommendationId !== allocation.recommendationId ||
|
|
267
310
|
activation.projectionId !== allocation.projectionId ||
|
|
268
311
|
(allocation.eligibility.kind === 'mapped' &&
|
|
269
312
|
(!eligibility ||
|
|
270
313
|
eligibility.status !== 'available' ||
|
|
271
314
|
eligibility.scopeId !== allocation.ownerScopeId ||
|
|
272
|
-
eligibility.scenarioId !== allocation.
|
|
315
|
+
eligibility.scenarioId !== allocation.scenarioId ||
|
|
273
316
|
eligibility.eligibilityBaselineId !== allocation.eligibility.eligibilityBaselineId ||
|
|
274
317
|
eligibility.denominator.denominatorId !== allocation.denominatorId ||
|
|
275
318
|
eligibility.currentBaselineMapping.currentBaselineId !== allocation.baselineId ||
|
|
@@ -360,6 +403,6 @@ export const validateFinancialSavingsCoordinateEnvelopeV1 = (value, authorityCoo
|
|
|
360
403
|
? value.currentAggregateBaselineId === authorityCoordinate.aggregateBaseline.baselineId
|
|
361
404
|
: value.currentAggregateBaselineId === undefined));
|
|
362
405
|
}
|
|
363
|
-
return (value.status === 'available' &&
|
|
364
|
-
|
|
406
|
+
return ((value.status === 'available' || value.status === 'partial') &&
|
|
407
|
+
validateComposedCoordinate(value, authorityCoordinate, eligibilityById, evidenceById, authorityGeneratedAt, authorityRunId));
|
|
365
408
|
};
|