@spottoai/types-package 1.0.2-beta.413 → 1.0.2-beta.415
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/dist/azure/reportDailySpend.d.ts +9 -2
- package/dist/azure/reportDailySpend.d.ts.map +1 -1
- package/dist/azure/reportDailySpend.js.map +1 -1
- package/dist/azure/reportDailySpendValidation.d.ts.map +1 -1
- package/dist/azure/reportDailySpendValidation.js +25 -17
- package/dist/azure/reportDailySpendValidation.js.map +1 -1
- package/dist/azure/reportEvidence.d.ts +10 -0
- package/dist/azure/reportEvidence.d.ts.map +1 -1
- package/dist/azure/reportEvidence.js.map +1 -1
- package/dist/azure/reportEvidenceCatalogueValidation.d.ts.map +1 -1
- package/dist/azure/reportEvidenceCatalogueValidation.js +4 -0
- package/dist/azure/reportEvidenceCatalogueValidation.js.map +1 -1
- package/dist/azure/reportEvidenceValidation.d.ts.map +1 -1
- package/dist/azure/reportEvidenceValidation.js +15 -1
- package/dist/azure/reportEvidenceValidation.js.map +1 -1
- package/dist/azure/reportSpend.d.ts +87 -0
- package/dist/azure/reportSpend.d.ts.map +1 -0
- package/dist/azure/reportSpend.js +5 -0
- package/dist/azure/reportSpend.js.map +1 -0
- package/dist/azure/reportSpendValidation.d.ts +7 -0
- package/dist/azure/reportSpendValidation.d.ts.map +1 -0
- package/dist/azure/reportSpendValidation.js +197 -0
- package/dist/azure/reportSpendValidation.js.map +1 -0
- package/dist/azure/reportSpendValidationHelpers.d.ts +7 -0
- package/dist/azure/reportSpendValidationHelpers.d.ts.map +1 -0
- package/dist/azure/reportSpendValidationHelpers.js +31 -0
- package/dist/azure/reportSpendValidationHelpers.js.map +1 -0
- package/dist/esm/azure/reportDailySpendValidation.js +20 -12
- package/dist/esm/azure/reportEvidenceCatalogueValidation.js +4 -0
- package/dist/esm/azure/reportEvidenceValidation.js +15 -1
- package/dist/esm/azure/reportSpend.js +1 -0
- package/dist/esm/azure/reportSpendValidation.js +189 -0
- package/dist/esm/azure/reportSpendValidationHelpers.js +22 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.vite.js +6 -0
- package/dist/features-and-permissions/access.d.ts +65 -0
- package/dist/features-and-permissions/access.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/index.vite.d.ts +6 -0
- package/dist/index.vite.d.ts.map +1 -1
- package/dist/index.vite.js +10 -0
- package/dist/index.vite.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REPORT_SPEND_LIMITS = void 0;
|
|
4
|
+
exports.REPORT_SPEND_LIMITS = { periods: 14, servicesPerPeriod: 100, periodDays: 92, assumptions: 10, textLength: 500 };
|
|
5
|
+
//# sourceMappingURL=reportSpend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportSpend.js","sourceRoot":"","sources":["../../src/azure/reportSpend.ts"],"names":[],"mappings":";;;AAGa,QAAA,mBAAmB,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAW,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ReportSavingsBasis, type ReportSpendAmounts, type ReportSpendProjection } from './reportSpend';
|
|
2
|
+
export declare const isReportSavingsBasis: (value: unknown) => value is ReportSavingsBasis;
|
|
3
|
+
export declare function isReportSpendAmounts(value: unknown, currency?: string, generatedAt?: string): value is ReportSpendAmounts;
|
|
4
|
+
export declare const hasReportSpendValue: (value: ReportSpendAmounts) => boolean;
|
|
5
|
+
/** Dates, population completeness and actual/estimated selection remain independent checks. */
|
|
6
|
+
export declare const isReportSpendProjection: (value: unknown) => value is ReportSpendProjection;
|
|
7
|
+
//# sourceMappingURL=reportSpendValidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportSpendValidation.d.ts","sourceRoot":"","sources":["../../src/azure/reportSpendValidation.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAE3B,MAAM,eAAe,CAAC;AASvB,eAAO,MAAM,oBAAoB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,kBACqC,CAAC;AAuCrG,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,IAAI,kBAAkB,CAyDzH;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,kBAAkB,KAAG,OAI7D,CAAC;AAEJ,+FAA+F;AAC/F,eAAO,MAAM,uBAAuB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,qBAmEjE,CAAC"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isReportSpendProjection = exports.hasReportSpendValue = exports.isReportSavingsBasis = void 0;
|
|
4
|
+
exports.isReportSpendAmounts = isReportSpendAmounts;
|
|
5
|
+
const costComposition_1 = require("./costComposition");
|
|
6
|
+
const reportEvidenceValidationHelpers_1 = require("./reportEvidenceValidationHelpers");
|
|
7
|
+
const reportSpend_1 = require("./reportSpend");
|
|
8
|
+
const reportSpendValidationHelpers_1 = require("./reportSpendValidationHelpers");
|
|
9
|
+
const bases = ['billed', 'amortized'];
|
|
10
|
+
const coverageStates = new Set(['complete', 'partial', 'unavailable']);
|
|
11
|
+
const exactFields = (value, required, optional = []) => required.every(key => Object.prototype.hasOwnProperty.call(value, key)) &&
|
|
12
|
+
Object.keys(value).every(key => [...required, ...optional].includes(key));
|
|
13
|
+
const isReportSavingsBasis = (value) => typeof value === 'string' && ['billed', 'amortized', 'retail', 'mixed', 'unknown'].includes(value);
|
|
14
|
+
exports.isReportSavingsBasis = isReportSavingsBasis;
|
|
15
|
+
const amountOf = (value) => value.status === 'available' ? (0, reportSpendValidationHelpers_1.reportMoneyUnits)(value.component.amount) : undefined;
|
|
16
|
+
function isBasisConsistent(basis, coverage, lens) {
|
|
17
|
+
if (!(0, reportEvidenceValidationHelpers_1.isRecord)(coverage) || !exactFields(coverage, ['actual', 'estimated', 'combined']))
|
|
18
|
+
return false;
|
|
19
|
+
const components = { actual: basis.actual.availability, estimated: basis.estimated.availability, combined: basis.combined };
|
|
20
|
+
for (const key of ['actual', 'estimated', 'combined']) {
|
|
21
|
+
if (!coverageStates.has(coverage[key]))
|
|
22
|
+
return false;
|
|
23
|
+
const available = components[key].status === 'available';
|
|
24
|
+
if (available !== (coverage[key] !== 'unavailable'))
|
|
25
|
+
return false;
|
|
26
|
+
if (available && amountOf(components[key]) === undefined)
|
|
27
|
+
return false;
|
|
28
|
+
if (key !== 'combined' && available && basis[key].support !== 'supported')
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const actual = amountOf(components.actual);
|
|
32
|
+
const estimated = amountOf(components.estimated);
|
|
33
|
+
const expectedStatus = actual !== undefined
|
|
34
|
+
? estimated !== undefined
|
|
35
|
+
? 'actual-plus-estimated'
|
|
36
|
+
: 'actual-only'
|
|
37
|
+
: estimated !== undefined
|
|
38
|
+
? 'estimated-only'
|
|
39
|
+
: 'unavailable';
|
|
40
|
+
if (basis.status !== expectedStatus)
|
|
41
|
+
return false;
|
|
42
|
+
const combined = amountOf(basis.combined);
|
|
43
|
+
// Reconciliation may withhold the combined amount even when individual components are available.
|
|
44
|
+
if (combined === undefined)
|
|
45
|
+
return true;
|
|
46
|
+
if (lens === 'actual-only')
|
|
47
|
+
return actual === combined && coverage.combined === coverage.actual;
|
|
48
|
+
if (lens === 'estimates-only')
|
|
49
|
+
return estimated === combined && coverage.combined === coverage.estimated;
|
|
50
|
+
if (actual === undefined && estimated === undefined)
|
|
51
|
+
return false;
|
|
52
|
+
if (combined !== (actual ?? 0n) + (estimated ?? 0n))
|
|
53
|
+
return false;
|
|
54
|
+
// A single partial component cannot become a complete combined total. Two components require producer coverage proof.
|
|
55
|
+
if (actual === undefined)
|
|
56
|
+
return coverage.combined === coverage.estimated;
|
|
57
|
+
if (estimated === undefined)
|
|
58
|
+
return coverage.combined === coverage.actual;
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
function isReportSpendAmounts(value, currency, generatedAt) {
|
|
62
|
+
if (!(0, reportEvidenceValidationHelpers_1.isRecord)(value) || !exactFields(value, ['composition', 'coverage', 'retail'], ['estimates']) || !(0, costComposition_1.isPublicCostComposition)(value.composition))
|
|
63
|
+
return false;
|
|
64
|
+
if (!(0, reportEvidenceValidationHelpers_1.isRecord)(value.coverage) || !exactFields(value.coverage, [...bases]))
|
|
65
|
+
return false;
|
|
66
|
+
if (currency !== undefined && !(0, reportSpendValidationHelpers_1.isReportCurrency)(currency))
|
|
67
|
+
return false;
|
|
68
|
+
if (generatedAt !== undefined && !(0, reportSpendValidationHelpers_1.isReportUtcTimestamp)(generatedAt))
|
|
69
|
+
return false;
|
|
70
|
+
if (value.estimates !== undefined && (!(0, reportEvidenceValidationHelpers_1.isRecord)(value.estimates) || !exactFields(value.estimates, [], [...bases])))
|
|
71
|
+
return false;
|
|
72
|
+
const currencies = new Set();
|
|
73
|
+
if (currency)
|
|
74
|
+
currencies.add(currency);
|
|
75
|
+
for (const key of bases) {
|
|
76
|
+
const basis = value.composition[key];
|
|
77
|
+
if (!isBasisConsistent(basis, value.coverage[key], value.composition.selectedLens))
|
|
78
|
+
return false;
|
|
79
|
+
for (const item of [basis.actual.availability, basis.estimated.availability, basis.combined]) {
|
|
80
|
+
if (item.status === 'available') {
|
|
81
|
+
if (!(0, reportSpendValidationHelpers_1.isReportCurrency)(item.component.currencyCode))
|
|
82
|
+
return false;
|
|
83
|
+
currencies.add(item.component.currencyCode);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const estimate = (0, reportEvidenceValidationHelpers_1.isRecord)(value.estimates) ? value.estimates[key] : undefined;
|
|
87
|
+
if (basis.estimated.availability.status === 'available') {
|
|
88
|
+
if (!(0, reportEvidenceValidationHelpers_1.isRecord)(estimate) ||
|
|
89
|
+
!exactFields(estimate, ['reason', 'method', 'observedAt']) ||
|
|
90
|
+
!['billing-lag', 'billing-unavailable-sponsorship', 'other'].includes(estimate.reason) ||
|
|
91
|
+
!(0, reportSpendValidationHelpers_1.isReportText)(estimate.method) ||
|
|
92
|
+
!(0, reportSpendValidationHelpers_1.isReportUtcTimestamp)(estimate.observedAt) ||
|
|
93
|
+
(generatedAt !== undefined && Date.parse(estimate.observedAt) > Date.parse(generatedAt)))
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
else if (estimate !== undefined)
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const retail = value.retail;
|
|
100
|
+
if (!(0, reportEvidenceValidationHelpers_1.isRecord)(retail))
|
|
101
|
+
return false;
|
|
102
|
+
if (retail.status === 'unavailable') {
|
|
103
|
+
if (!exactFields(retail, ['status']))
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
if (retail.status !== 'available' ||
|
|
108
|
+
!exactFields(retail, ['status', 'component', 'coverage', 'scopeDescription', 'pricingSource', 'pricedAt', 'assumptions']) ||
|
|
109
|
+
!(0, reportEvidenceValidationHelpers_1.isRecord)(retail.component) ||
|
|
110
|
+
!exactFields(retail.component, ['amount', 'currencyCode']) ||
|
|
111
|
+
!(0, reportSpendValidationHelpers_1.isReportCurrency)(retail.component.currencyCode) ||
|
|
112
|
+
!['complete', 'partial'].includes(retail.coverage) ||
|
|
113
|
+
!(0, reportSpendValidationHelpers_1.isReportText)(retail.scopeDescription) ||
|
|
114
|
+
!(0, reportSpendValidationHelpers_1.isReportText)(retail.pricingSource) ||
|
|
115
|
+
!(0, reportSpendValidationHelpers_1.isReportUtcTimestamp)(retail.pricedAt) ||
|
|
116
|
+
(generatedAt !== undefined && Date.parse(retail.pricedAt) > Date.parse(generatedAt)) ||
|
|
117
|
+
!Array.isArray(retail.assumptions) ||
|
|
118
|
+
retail.assumptions.length > reportSpend_1.REPORT_SPEND_LIMITS.assumptions ||
|
|
119
|
+
!retail.assumptions.every(reportSpendValidationHelpers_1.isReportText))
|
|
120
|
+
return false;
|
|
121
|
+
const amount = (0, reportSpendValidationHelpers_1.reportMoneyUnits)(retail.component.amount);
|
|
122
|
+
if (amount === undefined || amount < 0n)
|
|
123
|
+
return false;
|
|
124
|
+
currencies.add(retail.component.currencyCode);
|
|
125
|
+
}
|
|
126
|
+
return currencies.size <= 1;
|
|
127
|
+
}
|
|
128
|
+
const hasReportSpendValue = (value) => value.retail.status === 'available' ||
|
|
129
|
+
bases.some(key => value.composition[key].actual.availability.status === 'available' || value.composition[key].estimated.availability.status === 'available');
|
|
130
|
+
exports.hasReportSpendValue = hasReportSpendValue;
|
|
131
|
+
/** Dates, population completeness and actual/estimated selection remain independent checks. */
|
|
132
|
+
const isReportSpendProjection = (value) => {
|
|
133
|
+
if (!(0, reportEvidenceValidationHelpers_1.isRecord)(value) ||
|
|
134
|
+
!exactFields(value, ['currency', 'dateBasis', 'generatedAt', 'freshness', 'periods'], ['sourceObservedAt']) ||
|
|
135
|
+
!(0, reportSpendValidationHelpers_1.isReportCurrency)(value.currency) ||
|
|
136
|
+
value.dateBasis !== 'billing-calendar' ||
|
|
137
|
+
!(0, reportSpendValidationHelpers_1.isReportUtcTimestamp)(value.generatedAt) ||
|
|
138
|
+
!['current', 'stale', 'unavailable'].includes(value.freshness) ||
|
|
139
|
+
!Array.isArray(value.periods) ||
|
|
140
|
+
value.periods.length > reportSpend_1.REPORT_SPEND_LIMITS.periods)
|
|
141
|
+
return false;
|
|
142
|
+
if (value.sourceObservedAt !== undefined &&
|
|
143
|
+
(!(0, reportSpendValidationHelpers_1.isReportUtcTimestamp)(value.sourceObservedAt) || Date.parse(value.sourceObservedAt) > Date.parse(value.generatedAt)))
|
|
144
|
+
return false;
|
|
145
|
+
const seen = new Set();
|
|
146
|
+
let rollingCount = 0;
|
|
147
|
+
let historicalCount = 0;
|
|
148
|
+
let available = false;
|
|
149
|
+
for (const period of value.periods) {
|
|
150
|
+
if (!(0, reportEvidenceValidationHelpers_1.isRecord)(period) ||
|
|
151
|
+
!exactFields(period, ['kind', 'startDate', 'endDate', 'amounts'], ['services']) ||
|
|
152
|
+
!['calendar-month', 'billing-period', 'rolling-30-days'].includes(period.kind) ||
|
|
153
|
+
!(0, reportSpendValidationHelpers_1.isReportCalendarDate)(period.startDate) ||
|
|
154
|
+
!(0, reportSpendValidationHelpers_1.isReportCalendarDate)(period.endDate))
|
|
155
|
+
return false;
|
|
156
|
+
const days = (Date.parse(period.endDate) - Date.parse(period.startDate)) / 86400000 + 1;
|
|
157
|
+
if (days < 1 || days > reportSpend_1.REPORT_SPEND_LIMITS.periodDays)
|
|
158
|
+
return false;
|
|
159
|
+
if (period.kind === 'calendar-month') {
|
|
160
|
+
const following = new Date(Date.parse(period.endDate) + 86400000).toISOString().slice(0, 10);
|
|
161
|
+
if (!period.startDate.endsWith('-01') || period.startDate.slice(0, 7) !== period.endDate.slice(0, 7) || !following.endsWith('-01'))
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
if (period.kind === 'rolling-30-days') {
|
|
165
|
+
if (days !== 30 || ++rollingCount > 1)
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
else if (++historicalCount > 13)
|
|
169
|
+
return false;
|
|
170
|
+
const key = `${period.kind}:${period.startDate}:${period.endDate}`;
|
|
171
|
+
if (seen.has(key))
|
|
172
|
+
return false;
|
|
173
|
+
seen.add(key);
|
|
174
|
+
if (!isReportSpendAmounts(period.amounts, value.currency, value.generatedAt))
|
|
175
|
+
return false;
|
|
176
|
+
available || (available = (0, exports.hasReportSpendValue)(period.amounts));
|
|
177
|
+
if (period.services !== undefined) {
|
|
178
|
+
const serviceKeys = new Set();
|
|
179
|
+
if (!(0, reportEvidenceValidationHelpers_1.isBoundedRows)(period.services, reportSpend_1.REPORT_SPEND_LIMITS.servicesPerPeriod, (row) => {
|
|
180
|
+
if (!(0, reportEvidenceValidationHelpers_1.isRecord)(row) ||
|
|
181
|
+
!exactFields(row, ['serviceKey', 'name', 'amounts']) ||
|
|
182
|
+
!(0, reportSpendValidationHelpers_1.isReportText)(row.serviceKey) ||
|
|
183
|
+
!(0, reportSpendValidationHelpers_1.isReportText)(row.name) ||
|
|
184
|
+
serviceKeys.has(row.serviceKey) ||
|
|
185
|
+
!isReportSpendAmounts(row.amounts, value.currency, value.generatedAt))
|
|
186
|
+
return false;
|
|
187
|
+
serviceKeys.add(row.serviceKey);
|
|
188
|
+
available || (available = (0, exports.hasReportSpendValue)(row.amounts));
|
|
189
|
+
return true;
|
|
190
|
+
}))
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return available ? value.sourceObservedAt !== undefined && value.freshness !== 'unavailable' : value.freshness === 'unavailable';
|
|
195
|
+
};
|
|
196
|
+
exports.isReportSpendProjection = isReportSpendProjection;
|
|
197
|
+
//# sourceMappingURL=reportSpendValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportSpendValidation.js","sourceRoot":"","sources":["../../src/azure/reportSpendValidation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const isReportCalendarDate: (value: unknown) => value is string;
|
|
2
|
+
export declare const isReportUtcTimestamp: (value: unknown) => value is string;
|
|
3
|
+
export declare const isReportCurrency: (value: unknown) => value is string;
|
|
4
|
+
export declare const isReportText: (value: unknown) => value is string;
|
|
5
|
+
/** Bounded exact arithmetic avoids binary floating point and pathological decimal-string input. */
|
|
6
|
+
export declare const reportMoneyUnits: (value: unknown) => bigint | undefined;
|
|
7
|
+
//# sourceMappingURL=reportSpendValidationHelpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportSpendValidationHelpers.d.ts","sourceRoot":"","sources":["../../src/azure/reportSpendValidationHelpers.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MAI9D,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MAKgC,CAAC;AAEhG,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MAA+D,CAAC;AAC3H,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MACiD,CAAC;AAEzG,mGAAmG;AACnG,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAK1D,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reportMoneyUnits = exports.isReportText = exports.isReportCurrency = exports.isReportUtcTimestamp = exports.isReportCalendarDate = void 0;
|
|
4
|
+
const reportSpend_1 = require("./reportSpend");
|
|
5
|
+
const isReportCalendarDate = (value) => {
|
|
6
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
7
|
+
return false;
|
|
8
|
+
const time = Date.parse(`${value}T00:00:00.000Z`);
|
|
9
|
+
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === value;
|
|
10
|
+
};
|
|
11
|
+
exports.isReportCalendarDate = isReportCalendarDate;
|
|
12
|
+
const isReportUtcTimestamp = (value) => typeof value === 'string' &&
|
|
13
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value) &&
|
|
14
|
+
(0, exports.isReportCalendarDate)(value.slice(0, 10)) &&
|
|
15
|
+
Number.isFinite(Date.parse(value)) &&
|
|
16
|
+
new Date(value).toISOString() === (value.length === 20 ? value.replace('Z', '.000Z') : value);
|
|
17
|
+
exports.isReportUtcTimestamp = isReportUtcTimestamp;
|
|
18
|
+
const isReportCurrency = (value) => typeof value === 'string' && /^[A-Z]{3}$/.test(value);
|
|
19
|
+
exports.isReportCurrency = isReportCurrency;
|
|
20
|
+
const isReportText = (value) => typeof value === 'string' && value.trim().length > 0 && value.length <= reportSpend_1.REPORT_SPEND_LIMITS.textLength;
|
|
21
|
+
exports.isReportText = isReportText;
|
|
22
|
+
/** Bounded exact arithmetic avoids binary floating point and pathological decimal-string input. */
|
|
23
|
+
const reportMoneyUnits = (value) => {
|
|
24
|
+
if (typeof value !== 'string' || value.length > 25 || !/^-?(?:0|[1-9]\d{0,14})(?:\.\d{1,8})?$/.test(value))
|
|
25
|
+
return undefined;
|
|
26
|
+
const [whole, fraction = ''] = value.replace(/^-/, '').split('.');
|
|
27
|
+
const amount = BigInt(whole) * 100000000n + BigInt(fraction.padEnd(8, '0'));
|
|
28
|
+
return value.startsWith('-') ? -amount : amount;
|
|
29
|
+
};
|
|
30
|
+
exports.reportMoneyUnits = reportMoneyUnits;
|
|
31
|
+
//# sourceMappingURL=reportSpendValidationHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportSpendValidationHelpers.js","sourceRoot":"","sources":["../../src/azure/reportSpendValidationHelpers.ts"],"names":[],"mappings":";;;AAAA,+CAAoD;AAE7C,MAAM,oBAAoB,GAAG,CAAC,KAAc,EAAmB,EAAE;IACtE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAClF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,gBAAgB,CAAC,CAAC;IAClD,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC;AACtF,CAAC,CAAC;AAJW,QAAA,oBAAoB,wBAI/B;AAEK,MAAM,oBAAoB,GAAG,CAAC,KAAc,EAAmB,EAAE,CACtE,OAAO,KAAK,KAAK,QAAQ;IACzB,oDAAoD,CAAC,IAAI,CAAC,KAAK,CAAC;IAChE,IAAA,4BAAoB,EAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AALnF,QAAA,oBAAoB,wBAK+D;AAEzF,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAA9G,QAAA,gBAAgB,oBAA8F;AACpH,MAAM,YAAY,GAAG,CAAC,KAAc,EAAmB,EAAE,CAC9D,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,iCAAmB,CAAC,UAAU,CAAC;AAD5F,QAAA,YAAY,gBACgF;AAEzG,mGAAmG;AAC5F,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAsB,EAAE;IACrE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,IAAI,CAAC,uCAAuC,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7H,MAAM,CAAC,KAAK,EAAE,QAAQ,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC5E,OAAO,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;AAClD,CAAC,CAAC;AALW,QAAA,gBAAgB,oBAK3B"}
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import { REPORT_DAILY_SPEND_MAX_DAYS } from './reportDailySpend.js';
|
|
2
2
|
import { isCount, isFiniteNumber, isRecord } from './reportEvidenceValidationHelpers.js';
|
|
3
|
+
import { hasReportSpendValue, isReportSpendAmounts } from './reportSpendValidation.js';
|
|
4
|
+
import { isReportCalendarDate as isCalendarDate, isReportUtcTimestamp as isUtcTimestamp } from './reportSpendValidationHelpers.js';
|
|
3
5
|
const DAY_MS = 86400000;
|
|
4
|
-
const isCalendarDate = (value) => {
|
|
5
|
-
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
6
|
-
return false;
|
|
7
|
-
const time = Date.parse(`${value}T00:00:00.000Z`);
|
|
8
|
-
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === value;
|
|
9
|
-
};
|
|
10
|
-
const isUtcTimestamp = (value) => typeof value === 'string' &&
|
|
11
|
-
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value) &&
|
|
12
|
-
isCalendarDate(value.slice(0, 10)) &&
|
|
13
|
-
Number.isFinite(Date.parse(value)) &&
|
|
14
|
-
new Date(value).toISOString() === (value.length === 20 ? value.replace('Z', '.000Z') : value);
|
|
15
6
|
const isCoverage = (value, covered, expected) => {
|
|
16
7
|
const status = covered === expected ? 'complete' : covered === 0 ? 'unavailable' : 'partial';
|
|
17
8
|
return isRecord(value) && isCount(value.coveredDayCount) && value.coveredDayCount === covered && value.status === status;
|
|
@@ -45,10 +36,27 @@ export const isReportDailySpend = (value) => {
|
|
|
45
36
|
entry.date <= previousDate ||
|
|
46
37
|
entry.date < value.startDate ||
|
|
47
38
|
entry.date > value.endDate ||
|
|
48
|
-
(entry.cost === undefined && entry.costAmortized === undefined) ||
|
|
39
|
+
(entry.cost === undefined && entry.costAmortized === undefined && entry.financials === undefined) ||
|
|
49
40
|
(entry.cost !== undefined && !isFiniteNumber(entry.cost)) ||
|
|
50
41
|
(entry.costAmortized !== undefined && !isFiniteNumber(entry.costAmortized)))
|
|
51
42
|
return false;
|
|
43
|
+
if (entry.financials !== undefined) {
|
|
44
|
+
if (!isReportSpendAmounts(entry.financials, value.currency, value.generatedAt) || !hasReportSpendValue(entry.financials))
|
|
45
|
+
return false;
|
|
46
|
+
for (const [basis, field] of [
|
|
47
|
+
['billed', 'cost'],
|
|
48
|
+
['amortized', 'costAmortized'],
|
|
49
|
+
]) {
|
|
50
|
+
const actual = entry.financials.composition[basis].actual.availability;
|
|
51
|
+
const complete = entry.financials.coverage[basis].actual === 'complete';
|
|
52
|
+
if (actual.status === 'available' && complete) {
|
|
53
|
+
if (entry[field] !== Number(actual.component.amount))
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
else if (entry[field] !== undefined)
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
52
60
|
previousDate = entry.date;
|
|
53
61
|
if (entry.cost !== undefined)
|
|
54
62
|
billedDays++;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { REPORT_EVIDENCE_LIMITS } from './reportEvidence.js';
|
|
2
|
+
import { isReportSavingsBasis } from './reportSpendValidation.js';
|
|
2
3
|
import { isRecord, isString, isOptionalString, isOptionalFiniteNumber, isOptionalBoolean, isFiniteNumber, isCount, isBoundedRows, hasOptionalStrings, hasOptionalNumbers, } from './reportEvidenceValidationHelpers.js';
|
|
3
4
|
const isCompactRecommendationResource = (value) => isRecord(value) &&
|
|
4
5
|
isString(value.id) &&
|
|
5
6
|
['name', 'type', 'resourceGroup', 'location', 'currency', 'currencySymbol'].every(key => isOptionalString(value[key])) &&
|
|
6
7
|
['spend', 'spendAmortized'].every(key => isOptionalFiniteNumber(value[key])) &&
|
|
8
|
+
(value.savingsBasis === undefined || isReportSavingsBasis(value.savingsBasis)) &&
|
|
7
9
|
(value.savings === undefined ||
|
|
8
10
|
(isRecord(value.savings) && isOptionalFiniteNumber(value.savings.minAmount) && isOptionalFiniteNumber(value.savings.maxAmount)));
|
|
9
11
|
export const isCompactRecommendation = (value) => {
|
|
10
12
|
if (!isRecord(value) || !isRecord(value.recommendation) || !Array.isArray(value.resources))
|
|
11
13
|
return false;
|
|
14
|
+
if (value.savingsBasis !== undefined && !isReportSavingsBasis(value.savingsBasis))
|
|
15
|
+
return false;
|
|
12
16
|
const recommendation = value.recommendation;
|
|
13
17
|
const assessment = recommendation.securityAssessmentSummary;
|
|
14
18
|
if (assessment !== undefined &&
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isCompactRecommendation, isInventoryCatalogueResource } from './reportEvidenceCatalogueValidation.js';
|
|
2
2
|
import { isReportDailySpend } from './reportDailySpendValidation.js';
|
|
3
|
+
import { isReportSavingsBasis, isReportSpendProjection } from './reportSpendValidation.js';
|
|
3
4
|
import { REPORT_EVIDENCE_LIMITS, } from './reportEvidence.js';
|
|
4
5
|
import { countTotal, hasOptionalNumbers, hasOptionalStrings, hasRequiredRecords, isBoundedRows, isCommitmentExpirySummary, isCostSummary, isCount, isCountRecord, isDateTime, isEvidenceReference, isFiniteNumber, isOptionalBoolean, isOptionalFiniteNumber, isOptionalString, isProjectionRows, isRecommendationSummary, isRecord, isResourceSummary, isRetirementSummary, isSourceFileStatus, isString, isStringArray, isTagCoverage, } from './reportEvidenceValidationHelpers.js';
|
|
5
6
|
export const isReportSecureScoreEvidence = (value) => {
|
|
@@ -15,6 +16,7 @@ export const isReportSecureScoreEvidence = (value) => {
|
|
|
15
16
|
return true;
|
|
16
17
|
};
|
|
17
18
|
const isCostSavingsCategory = (value) => isRecord(value) &&
|
|
19
|
+
(value.savingsBasis === undefined || isReportSavingsBasis(value.savingsBasis)) &&
|
|
18
20
|
isString(value.key) &&
|
|
19
21
|
isString(value.label) &&
|
|
20
22
|
isCount(value.recommendationCount) &&
|
|
@@ -64,7 +66,10 @@ const isRecommendationPortfolio = (value) => {
|
|
|
64
66
|
}
|
|
65
67
|
if (value.costSavings === undefined)
|
|
66
68
|
return true;
|
|
67
|
-
if (!isRecord(value.costSavings) ||
|
|
69
|
+
if (!isRecord(value.costSavings) ||
|
|
70
|
+
!isString(value.costSavings.currency) ||
|
|
71
|
+
!isCount(value.costSavings.contributingRecommendationCount) ||
|
|
72
|
+
(value.costSavings.savingsBasis !== undefined && !isReportSavingsBasis(value.costSavings.savingsBasis))) {
|
|
68
73
|
return false;
|
|
69
74
|
}
|
|
70
75
|
const monthly = value.costSavings.monthly;
|
|
@@ -166,6 +171,8 @@ const isReportingProjection = (value) => {
|
|
|
166
171
|
return false;
|
|
167
172
|
if (value.dailySpend !== undefined && !isReportDailySpend(value.dailySpend))
|
|
168
173
|
return false;
|
|
174
|
+
if (value.spend !== undefined && !isReportSpendProjection(value.spend))
|
|
175
|
+
return false;
|
|
169
176
|
const subscription = isRecord(value.dashboard.subscription) ? value.dashboard.subscription : undefined;
|
|
170
177
|
const properties = subscription && isRecord(subscription.properties) ? subscription.properties : undefined;
|
|
171
178
|
if (properties?.secureScoreEvidence !== undefined && !isReportSecureScoreEvidence(properties.secureScoreEvidence))
|
|
@@ -269,11 +276,18 @@ export const isSubscriptionReportEvidencePack = (value) => {
|
|
|
269
276
|
const dailySpend = value.reporting.dailySpend;
|
|
270
277
|
if (dailySpend && value.scope.currency !== undefined && dailySpend.currency !== value.scope.currency)
|
|
271
278
|
return false;
|
|
279
|
+
const spend = value.reporting.spend;
|
|
280
|
+
if (spend &&
|
|
281
|
+
((value.scope.currency !== undefined && spend.currency !== value.scope.currency) ||
|
|
282
|
+
(dailySpend && spend.currency !== dailySpend.currency) ||
|
|
283
|
+
Date.parse(spend.generatedAt) > Date.parse(value.generatedAt)))
|
|
284
|
+
return false;
|
|
272
285
|
return (value.reliability.relationshipGraph === undefined ||
|
|
273
286
|
(isRecord(value.reliability.relationshipGraph) &&
|
|
274
287
|
hasOptionalNumbers(value.reliability.relationshipGraph, ['totalNodes', 'totalEdges', 'unresolvedCount', 'buildMs'])));
|
|
275
288
|
};
|
|
276
289
|
const isRecommendationFingerprint = (value) => isRecord(value) &&
|
|
290
|
+
(value.savingsBasis === undefined || isReportSavingsBasis(value.savingsBasis)) &&
|
|
277
291
|
isString(value.id) &&
|
|
278
292
|
isString(value.title) &&
|
|
279
293
|
hasOptionalStrings(value, ['category', 'impact', 'severity', 'currency']) &&
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const REPORT_SPEND_LIMITS = { periods: 14, servicesPerPeriod: 100, periodDays: 92, assumptions: 10, textLength: 500 };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { isPublicCostComposition } from './costComposition.js';
|
|
2
|
+
import { isBoundedRows, isRecord } from './reportEvidenceValidationHelpers.js';
|
|
3
|
+
import { REPORT_SPEND_LIMITS, } from './reportSpend.js';
|
|
4
|
+
import { isReportCalendarDate, isReportCurrency, isReportText, isReportUtcTimestamp, reportMoneyUnits } from './reportSpendValidationHelpers.js';
|
|
5
|
+
const bases = ['billed', 'amortized'];
|
|
6
|
+
const coverageStates = new Set(['complete', 'partial', 'unavailable']);
|
|
7
|
+
const exactFields = (value, required, optional = []) => required.every(key => Object.prototype.hasOwnProperty.call(value, key)) &&
|
|
8
|
+
Object.keys(value).every(key => [...required, ...optional].includes(key));
|
|
9
|
+
export const isReportSavingsBasis = (value) => typeof value === 'string' && ['billed', 'amortized', 'retail', 'mixed', 'unknown'].includes(value);
|
|
10
|
+
const amountOf = (value) => value.status === 'available' ? reportMoneyUnits(value.component.amount) : undefined;
|
|
11
|
+
function isBasisConsistent(basis, coverage, lens) {
|
|
12
|
+
if (!isRecord(coverage) || !exactFields(coverage, ['actual', 'estimated', 'combined']))
|
|
13
|
+
return false;
|
|
14
|
+
const components = { actual: basis.actual.availability, estimated: basis.estimated.availability, combined: basis.combined };
|
|
15
|
+
for (const key of ['actual', 'estimated', 'combined']) {
|
|
16
|
+
if (!coverageStates.has(coverage[key]))
|
|
17
|
+
return false;
|
|
18
|
+
const available = components[key].status === 'available';
|
|
19
|
+
if (available !== (coverage[key] !== 'unavailable'))
|
|
20
|
+
return false;
|
|
21
|
+
if (available && amountOf(components[key]) === undefined)
|
|
22
|
+
return false;
|
|
23
|
+
if (key !== 'combined' && available && basis[key].support !== 'supported')
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const actual = amountOf(components.actual);
|
|
27
|
+
const estimated = amountOf(components.estimated);
|
|
28
|
+
const expectedStatus = actual !== undefined
|
|
29
|
+
? estimated !== undefined
|
|
30
|
+
? 'actual-plus-estimated'
|
|
31
|
+
: 'actual-only'
|
|
32
|
+
: estimated !== undefined
|
|
33
|
+
? 'estimated-only'
|
|
34
|
+
: 'unavailable';
|
|
35
|
+
if (basis.status !== expectedStatus)
|
|
36
|
+
return false;
|
|
37
|
+
const combined = amountOf(basis.combined);
|
|
38
|
+
// Reconciliation may withhold the combined amount even when individual components are available.
|
|
39
|
+
if (combined === undefined)
|
|
40
|
+
return true;
|
|
41
|
+
if (lens === 'actual-only')
|
|
42
|
+
return actual === combined && coverage.combined === coverage.actual;
|
|
43
|
+
if (lens === 'estimates-only')
|
|
44
|
+
return estimated === combined && coverage.combined === coverage.estimated;
|
|
45
|
+
if (actual === undefined && estimated === undefined)
|
|
46
|
+
return false;
|
|
47
|
+
if (combined !== (actual ?? 0n) + (estimated ?? 0n))
|
|
48
|
+
return false;
|
|
49
|
+
// A single partial component cannot become a complete combined total. Two components require producer coverage proof.
|
|
50
|
+
if (actual === undefined)
|
|
51
|
+
return coverage.combined === coverage.estimated;
|
|
52
|
+
if (estimated === undefined)
|
|
53
|
+
return coverage.combined === coverage.actual;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
export function isReportSpendAmounts(value, currency, generatedAt) {
|
|
57
|
+
if (!isRecord(value) || !exactFields(value, ['composition', 'coverage', 'retail'], ['estimates']) || !isPublicCostComposition(value.composition))
|
|
58
|
+
return false;
|
|
59
|
+
if (!isRecord(value.coverage) || !exactFields(value.coverage, [...bases]))
|
|
60
|
+
return false;
|
|
61
|
+
if (currency !== undefined && !isReportCurrency(currency))
|
|
62
|
+
return false;
|
|
63
|
+
if (generatedAt !== undefined && !isReportUtcTimestamp(generatedAt))
|
|
64
|
+
return false;
|
|
65
|
+
if (value.estimates !== undefined && (!isRecord(value.estimates) || !exactFields(value.estimates, [], [...bases])))
|
|
66
|
+
return false;
|
|
67
|
+
const currencies = new Set();
|
|
68
|
+
if (currency)
|
|
69
|
+
currencies.add(currency);
|
|
70
|
+
for (const key of bases) {
|
|
71
|
+
const basis = value.composition[key];
|
|
72
|
+
if (!isBasisConsistent(basis, value.coverage[key], value.composition.selectedLens))
|
|
73
|
+
return false;
|
|
74
|
+
for (const item of [basis.actual.availability, basis.estimated.availability, basis.combined]) {
|
|
75
|
+
if (item.status === 'available') {
|
|
76
|
+
if (!isReportCurrency(item.component.currencyCode))
|
|
77
|
+
return false;
|
|
78
|
+
currencies.add(item.component.currencyCode);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const estimate = isRecord(value.estimates) ? value.estimates[key] : undefined;
|
|
82
|
+
if (basis.estimated.availability.status === 'available') {
|
|
83
|
+
if (!isRecord(estimate) ||
|
|
84
|
+
!exactFields(estimate, ['reason', 'method', 'observedAt']) ||
|
|
85
|
+
!['billing-lag', 'billing-unavailable-sponsorship', 'other'].includes(estimate.reason) ||
|
|
86
|
+
!isReportText(estimate.method) ||
|
|
87
|
+
!isReportUtcTimestamp(estimate.observedAt) ||
|
|
88
|
+
(generatedAt !== undefined && Date.parse(estimate.observedAt) > Date.parse(generatedAt)))
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
else if (estimate !== undefined)
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const retail = value.retail;
|
|
95
|
+
if (!isRecord(retail))
|
|
96
|
+
return false;
|
|
97
|
+
if (retail.status === 'unavailable') {
|
|
98
|
+
if (!exactFields(retail, ['status']))
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
if (retail.status !== 'available' ||
|
|
103
|
+
!exactFields(retail, ['status', 'component', 'coverage', 'scopeDescription', 'pricingSource', 'pricedAt', 'assumptions']) ||
|
|
104
|
+
!isRecord(retail.component) ||
|
|
105
|
+
!exactFields(retail.component, ['amount', 'currencyCode']) ||
|
|
106
|
+
!isReportCurrency(retail.component.currencyCode) ||
|
|
107
|
+
!['complete', 'partial'].includes(retail.coverage) ||
|
|
108
|
+
!isReportText(retail.scopeDescription) ||
|
|
109
|
+
!isReportText(retail.pricingSource) ||
|
|
110
|
+
!isReportUtcTimestamp(retail.pricedAt) ||
|
|
111
|
+
(generatedAt !== undefined && Date.parse(retail.pricedAt) > Date.parse(generatedAt)) ||
|
|
112
|
+
!Array.isArray(retail.assumptions) ||
|
|
113
|
+
retail.assumptions.length > REPORT_SPEND_LIMITS.assumptions ||
|
|
114
|
+
!retail.assumptions.every(isReportText))
|
|
115
|
+
return false;
|
|
116
|
+
const amount = reportMoneyUnits(retail.component.amount);
|
|
117
|
+
if (amount === undefined || amount < 0n)
|
|
118
|
+
return false;
|
|
119
|
+
currencies.add(retail.component.currencyCode);
|
|
120
|
+
}
|
|
121
|
+
return currencies.size <= 1;
|
|
122
|
+
}
|
|
123
|
+
export const hasReportSpendValue = (value) => value.retail.status === 'available' ||
|
|
124
|
+
bases.some(key => value.composition[key].actual.availability.status === 'available' || value.composition[key].estimated.availability.status === 'available');
|
|
125
|
+
/** Dates, population completeness and actual/estimated selection remain independent checks. */
|
|
126
|
+
export const isReportSpendProjection = (value) => {
|
|
127
|
+
if (!isRecord(value) ||
|
|
128
|
+
!exactFields(value, ['currency', 'dateBasis', 'generatedAt', 'freshness', 'periods'], ['sourceObservedAt']) ||
|
|
129
|
+
!isReportCurrency(value.currency) ||
|
|
130
|
+
value.dateBasis !== 'billing-calendar' ||
|
|
131
|
+
!isReportUtcTimestamp(value.generatedAt) ||
|
|
132
|
+
!['current', 'stale', 'unavailable'].includes(value.freshness) ||
|
|
133
|
+
!Array.isArray(value.periods) ||
|
|
134
|
+
value.periods.length > REPORT_SPEND_LIMITS.periods)
|
|
135
|
+
return false;
|
|
136
|
+
if (value.sourceObservedAt !== undefined &&
|
|
137
|
+
(!isReportUtcTimestamp(value.sourceObservedAt) || Date.parse(value.sourceObservedAt) > Date.parse(value.generatedAt)))
|
|
138
|
+
return false;
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
let rollingCount = 0;
|
|
141
|
+
let historicalCount = 0;
|
|
142
|
+
let available = false;
|
|
143
|
+
for (const period of value.periods) {
|
|
144
|
+
if (!isRecord(period) ||
|
|
145
|
+
!exactFields(period, ['kind', 'startDate', 'endDate', 'amounts'], ['services']) ||
|
|
146
|
+
!['calendar-month', 'billing-period', 'rolling-30-days'].includes(period.kind) ||
|
|
147
|
+
!isReportCalendarDate(period.startDate) ||
|
|
148
|
+
!isReportCalendarDate(period.endDate))
|
|
149
|
+
return false;
|
|
150
|
+
const days = (Date.parse(period.endDate) - Date.parse(period.startDate)) / 86400000 + 1;
|
|
151
|
+
if (days < 1 || days > REPORT_SPEND_LIMITS.periodDays)
|
|
152
|
+
return false;
|
|
153
|
+
if (period.kind === 'calendar-month') {
|
|
154
|
+
const following = new Date(Date.parse(period.endDate) + 86400000).toISOString().slice(0, 10);
|
|
155
|
+
if (!period.startDate.endsWith('-01') || period.startDate.slice(0, 7) !== period.endDate.slice(0, 7) || !following.endsWith('-01'))
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
if (period.kind === 'rolling-30-days') {
|
|
159
|
+
if (days !== 30 || ++rollingCount > 1)
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
else if (++historicalCount > 13)
|
|
163
|
+
return false;
|
|
164
|
+
const key = `${period.kind}:${period.startDate}:${period.endDate}`;
|
|
165
|
+
if (seen.has(key))
|
|
166
|
+
return false;
|
|
167
|
+
seen.add(key);
|
|
168
|
+
if (!isReportSpendAmounts(period.amounts, value.currency, value.generatedAt))
|
|
169
|
+
return false;
|
|
170
|
+
available || (available = hasReportSpendValue(period.amounts));
|
|
171
|
+
if (period.services !== undefined) {
|
|
172
|
+
const serviceKeys = new Set();
|
|
173
|
+
if (!isBoundedRows(period.services, REPORT_SPEND_LIMITS.servicesPerPeriod, (row) => {
|
|
174
|
+
if (!isRecord(row) ||
|
|
175
|
+
!exactFields(row, ['serviceKey', 'name', 'amounts']) ||
|
|
176
|
+
!isReportText(row.serviceKey) ||
|
|
177
|
+
!isReportText(row.name) ||
|
|
178
|
+
serviceKeys.has(row.serviceKey) ||
|
|
179
|
+
!isReportSpendAmounts(row.amounts, value.currency, value.generatedAt))
|
|
180
|
+
return false;
|
|
181
|
+
serviceKeys.add(row.serviceKey);
|
|
182
|
+
available || (available = hasReportSpendValue(row.amounts));
|
|
183
|
+
return true;
|
|
184
|
+
}))
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return available ? value.sourceObservedAt !== undefined && value.freshness !== 'unavailable' : value.freshness === 'unavailable';
|
|
189
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { REPORT_SPEND_LIMITS } from './reportSpend.js';
|
|
2
|
+
export const isReportCalendarDate = (value) => {
|
|
3
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
4
|
+
return false;
|
|
5
|
+
const time = Date.parse(`${value}T00:00:00.000Z`);
|
|
6
|
+
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === value;
|
|
7
|
+
};
|
|
8
|
+
export const isReportUtcTimestamp = (value) => typeof value === 'string' &&
|
|
9
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value) &&
|
|
10
|
+
isReportCalendarDate(value.slice(0, 10)) &&
|
|
11
|
+
Number.isFinite(Date.parse(value)) &&
|
|
12
|
+
new Date(value).toISOString() === (value.length === 20 ? value.replace('Z', '.000Z') : value);
|
|
13
|
+
export const isReportCurrency = (value) => typeof value === 'string' && /^[A-Z]{3}$/.test(value);
|
|
14
|
+
export const isReportText = (value) => typeof value === 'string' && value.trim().length > 0 && value.length <= REPORT_SPEND_LIMITS.textLength;
|
|
15
|
+
/** Bounded exact arithmetic avoids binary floating point and pathological decimal-string input. */
|
|
16
|
+
export const reportMoneyUnits = (value) => {
|
|
17
|
+
if (typeof value !== 'string' || value.length > 25 || !/^-?(?:0|[1-9]\d{0,14})(?:\.\d{1,8})?$/.test(value))
|
|
18
|
+
return undefined;
|
|
19
|
+
const [whole, fraction = ''] = value.replace(/^-/, '').split('.');
|
|
20
|
+
const amount = BigInt(whole) * 100000000n + BigInt(fraction.padEnd(8, '0'));
|
|
21
|
+
return value.startsWith('-') ? -amount : amount;
|
|
22
|
+
};
|