@agent-finops/core 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +5 -3
  2. package/dist/actionPlanner.d.ts +140 -0
  3. package/dist/actionPlanner.js +938 -0
  4. package/dist/actionVerification.d.ts +1240 -0
  5. package/dist/actionVerification.js +1028 -0
  6. package/dist/activitySnapshot.d.ts +101 -9
  7. package/dist/activitySnapshot.js +145 -6
  8. package/dist/activitySnapshotCache.d.ts +8 -1
  9. package/dist/activitySnapshotCache.js +103 -7
  10. package/dist/agentEconomicsReceipt.d.ts +58 -58
  11. package/dist/analyze.js +3 -1
  12. package/dist/cutList.js +1 -1
  13. package/dist/glance.d.ts +30 -2
  14. package/dist/glance.js +265 -84
  15. package/dist/index.d.ts +11 -2
  16. package/dist/index.js +10 -1
  17. package/dist/insights.js +3 -1
  18. package/dist/localAgentFormats/gemini.js +2 -2
  19. package/dist/localAgentFormats/registry.js +6 -2
  20. package/dist/localAgentFormats/runtimeRegistry.js +5 -2
  21. package/dist/localAgentFormats/types.d.ts +2 -1
  22. package/dist/localAgentLogs.d.ts +362 -3
  23. package/dist/localAgentLogs.js +1964 -165
  24. package/dist/modelPricing.d.ts +1 -1
  25. package/dist/modelPricing.js +4 -1
  26. package/dist/planMath.js +12 -7
  27. package/dist/projectEconomics.d.ts +617 -0
  28. package/dist/projectEconomics.js +620 -0
  29. package/dist/projectEconomicsBuilder.d.ts +89 -0
  30. package/dist/projectEconomicsBuilder.js +473 -0
  31. package/dist/projectIndexStore.d.ts +545 -0
  32. package/dist/projectIndexStore.js +606 -0
  33. package/dist/providerConnectors.d.ts +59 -1
  34. package/dist/providerConnectors.js +192 -12
  35. package/dist/qualitativeIndexCache.d.ts +494 -0
  36. package/dist/qualitativeIndexCache.js +930 -0
  37. package/dist/resultCard.d.ts +350 -0
  38. package/dist/resultCard.js +604 -0
  39. package/dist/runtimeCommands.d.ts +21 -0
  40. package/dist/runtimeCommands.js +27 -0
  41. package/dist/scanGuard.d.ts +3 -1
  42. package/dist/scanGuard.js +164 -4
  43. package/dist/schema.d.ts +31 -31
  44. package/dist/sessionVitals.d.ts +145 -0
  45. package/dist/sessionVitals.js +521 -0
  46. package/dist/sourceRegistry.js +90 -52
  47. package/dist/toolInvocations.d.ts +40 -1
  48. package/dist/toolInvocations.js +101 -20
  49. package/package.json +1 -1
@@ -0,0 +1,89 @@
1
+ import { type TokenReductionExperimentV0 } from "./actionVerification.js";
2
+ import { type AcceptedOutcomeV0, type ApprovalEventV0, type ConfirmedOwnershipV0, type ProjectEconomicsReceiptV0 } from "./projectEconomics.js";
3
+ import { type UsageRecord } from "./schema.js";
4
+ export declare const projectEconomicsProjectionMissingValues: readonly ["confirmed_ownership", "ownership_project_scope", "token_experiment", "experiment_project_scope", "experiment_work_unit_scope", "intervention_application", "approval_event", "approval_experiment_link", "accepted_outcome", "outcome_work_unit_scope", "financial_project_attribution", "financial_work_unit_attribution", "financial_cost_evidence", "financial_line_limit", "measured_token_result", "provider_bill_reconciliation"];
5
+ export type ProjectEconomicsProjectionMissingCodeV0 = typeof projectEconomicsProjectionMissingValues[number];
6
+ export type ProjectEconomicsProjectionMissingV0 = {
7
+ code: ProjectEconomicsProjectionMissingCodeV0;
8
+ evidence: "missing";
9
+ blocksReceipt: boolean;
10
+ };
11
+ export type ProjectEconomicsScopeV0 = {
12
+ /** Receipt/accountability reference for the exact project. */
13
+ projectRef: string;
14
+ /** Receipt/accountability reference for the exact accepted work unit. */
15
+ workUnitRef: string;
16
+ /** Action-verification reference for the same exact project. */
17
+ actionProjectRef: string;
18
+ /** Action-verification reference for the same exact accepted work unit. */
19
+ actionWorkUnitRef: string;
20
+ };
21
+ /**
22
+ * Explicit attribution bridge for one candidate financial record.
23
+ *
24
+ * The builder never hashes or guesses `UsageRecord.projectId` into receipt
25
+ * scope. The caller must supply the already-confirmed opaque references and
26
+ * their evidence labels; unknown attribution remains null + missing.
27
+ */
28
+ export type ProjectEconomicsUsageBindingV0 = {
29
+ record: UsageRecord;
30
+ projectRef: string | null;
31
+ projectEvidence: "verified" | "observed" | "user_declared" | "missing";
32
+ workUnitRef: string | null;
33
+ workUnitEvidence: "verified" | "observed" | "user_declared" | "missing";
34
+ };
35
+ export type BuildProjectEconomicsProjectionV0Input = {
36
+ generatedAt: string;
37
+ scope: ProjectEconomicsScopeV0;
38
+ financialRecords: readonly ProjectEconomicsUsageBindingV0[];
39
+ ownership?: ConfirmedOwnershipV0;
40
+ approvalEvent?: ApprovalEventV0;
41
+ outcome?: AcceptedOutcomeV0;
42
+ tokenExperiment?: TokenReductionExperimentV0;
43
+ };
44
+ export type ProjectEconomicsProjectionV0 = {
45
+ schemaVersion: 0;
46
+ status: "receipt_ready" | "incomplete";
47
+ generatedAt: string;
48
+ scope: ProjectEconomicsScopeV0;
49
+ costs: ProjectEconomicsReceiptV0["costs"];
50
+ action: {
51
+ wasteFindingRef: string | null;
52
+ tokenExperimentRef: string | null;
53
+ tokenExperimentRevisionRef: string | null;
54
+ };
55
+ measuredTokenResult: ProjectEconomicsReceiptV0["measuredTokenResult"];
56
+ billReconciliation: ProjectEconomicsReceiptV0["billReconciliation"];
57
+ claims: ProjectEconomicsReceiptV0["claims"];
58
+ missing: ProjectEconomicsProjectionMissingV0[];
59
+ receipt: ProjectEconomicsReceiptV0 | null;
60
+ };
61
+ export type ProjectEconomicsPlannedActionRefsV0 = {
62
+ changeRef: string;
63
+ rollbackRef: string;
64
+ canaryRef: string;
65
+ };
66
+ /**
67
+ * Stable pre-change reference for one exact reversible plan. Approval binds
68
+ * the immutable experiment lineage plus the opaque change, rollback, and
69
+ * canary descriptions before the plan is handed to an agent. Timestamps and
70
+ * the eventual canary outcome are deliberately excluded: they are execution
71
+ * evidence, not part of what the human approved.
72
+ */
73
+ export declare function createProjectEconomicsPlannedActionRefV0(tokenExperiment: TokenReductionExperimentV0, references: ProjectEconomicsPlannedActionRefsV0): string;
74
+ /**
75
+ * Resolve the same pre-change action reference from its later applied form.
76
+ * Post-change evidence may advance the experiment revision without rewriting
77
+ * the human's earlier approval boundary.
78
+ */
79
+ export declare function createProjectEconomicsApprovalActionRefV0(tokenExperiment: TokenReductionExperimentV0): string;
80
+ /**
81
+ * Build the smallest read-only project-economics view.
82
+ *
83
+ * Invalid or forged supplied contracts throw. Absent evidence and valid but
84
+ * unlinked evidence return an incomplete projection with stable missing codes.
85
+ * No state is written, no native identity is returned, and no ROI, complete
86
+ * invoice reconciliation, or RBAC claim is created.
87
+ */
88
+ export declare function buildProjectEconomicsProjectionV0(input: BuildProjectEconomicsProjectionV0Input): ProjectEconomicsProjectionV0;
89
+ //# sourceMappingURL=projectEconomicsBuilder.d.ts.map
@@ -0,0 +1,473 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { createActionVerificationReference, parseTokenReductionExperimentV0 } from "./actionVerification.js";
4
+ import { MAX_PROJECT_COST_LINES_V0, PROJECT_ECONOMICS_RECEIPT_V0_KIND, PROJECT_ECONOMICS_V0_VERSION, createProjectEconomicsReceiptV0, createProjectEconomicsReference, parseAcceptedOutcomeV0, parseApprovalEventV0, parseConfirmedOwnershipV0, projectEconomicsReferenceSchema } from "./projectEconomics.js";
5
+ import { usageRecordSchema } from "./schema.js";
6
+ const utcTimestampSchema = z.string()
7
+ .datetime({ offset: true })
8
+ .transform((value) => new Date(value).toISOString());
9
+ const actionVerificationReferenceSchema = z.string()
10
+ .regex(/^avref_[a-f0-9]{64}$/);
11
+ const attributionEvidenceSchema = z.enum([
12
+ "verified",
13
+ "observed",
14
+ "user_declared",
15
+ "missing"
16
+ ]);
17
+ const API_EQUIVALENT_COST_TYPES = new Set([
18
+ "local_agent_logs",
19
+ "anthropic_claude_code_usage",
20
+ "anthropic_claude_code_api_equivalent"
21
+ ]);
22
+ export const projectEconomicsProjectionMissingValues = [
23
+ "confirmed_ownership",
24
+ "ownership_project_scope",
25
+ "token_experiment",
26
+ "experiment_project_scope",
27
+ "experiment_work_unit_scope",
28
+ "intervention_application",
29
+ "approval_event",
30
+ "approval_experiment_link",
31
+ "accepted_outcome",
32
+ "outcome_work_unit_scope",
33
+ "financial_project_attribution",
34
+ "financial_work_unit_attribution",
35
+ "financial_cost_evidence",
36
+ "financial_line_limit",
37
+ "measured_token_result",
38
+ "provider_bill_reconciliation"
39
+ ];
40
+ /**
41
+ * Stable pre-change reference for one exact reversible plan. Approval binds
42
+ * the immutable experiment lineage plus the opaque change, rollback, and
43
+ * canary descriptions before the plan is handed to an agent. Timestamps and
44
+ * the eventual canary outcome are deliberately excluded: they are execution
45
+ * evidence, not part of what the human approved.
46
+ */
47
+ export function createProjectEconomicsPlannedActionRefV0(tokenExperiment, references) {
48
+ // This constructor binds only canonical lineage IDs and opaque plan refs.
49
+ // The receipt builder still parses the complete experiment before making
50
+ // any linkage claim; requiring unrelated future experiment fields here
51
+ // would make a pre-change reference depend on post-change evidence.
52
+ const experiment = tokenExperiment;
53
+ if (!/^tre_v0_[a-f0-9]{64}$/.test(experiment.id) ||
54
+ !/^wf_v0_[a-f0-9]{64}$/.test(experiment.finding?.id ?? "")) {
55
+ throw new TypeError("A planned-action reference requires canonical experiment and finding lineage.");
56
+ }
57
+ if (experiment.lifecycle !== "baseline_ready" && !hasAppliedIntervention(experiment)) {
58
+ throw new TypeError("A planned-action reference requires a complete baseline or its applied continuation.");
59
+ }
60
+ for (const reference of [
61
+ references.changeRef,
62
+ references.rollbackRef,
63
+ references.canaryRef
64
+ ]) {
65
+ if (!/^avref_[a-f0-9]{64}$/.test(reference)) {
66
+ throw new TypeError("A planned-action reference requires opaque change, rollback, and canary references.");
67
+ }
68
+ }
69
+ return createActionVerificationReference("project-economics-approved-action", JSON.stringify({
70
+ experimentId: experiment.id,
71
+ findingId: experiment.finding.id,
72
+ changeRef: references.changeRef,
73
+ rollbackRef: references.rollbackRef,
74
+ canaryRef: references.canaryRef
75
+ }));
76
+ }
77
+ /**
78
+ * Resolve the same pre-change action reference from its later applied form.
79
+ * Post-change evidence may advance the experiment revision without rewriting
80
+ * the human's earlier approval boundary.
81
+ */
82
+ export function createProjectEconomicsApprovalActionRefV0(tokenExperiment) {
83
+ const experiment = parseTokenReductionExperimentV0(tokenExperiment);
84
+ if (!hasAppliedIntervention(experiment)) {
85
+ throw new TypeError("An approval action reference requires one explicitly approved, applied intervention.");
86
+ }
87
+ return createProjectEconomicsPlannedActionRefV0(experiment, {
88
+ changeRef: experiment.intervention.changeRef,
89
+ rollbackRef: experiment.intervention.rollbackRef,
90
+ canaryRef: experiment.intervention.canary.evidenceRef
91
+ });
92
+ }
93
+ /**
94
+ * Build the smallest read-only project-economics view.
95
+ *
96
+ * Invalid or forged supplied contracts throw. Absent evidence and valid but
97
+ * unlinked evidence return an incomplete projection with stable missing codes.
98
+ * No state is written, no native identity is returned, and no ROI, complete
99
+ * invoice reconciliation, or RBAC claim is created.
100
+ */
101
+ export function buildProjectEconomicsProjectionV0(input) {
102
+ const generatedAt = utcTimestampSchema.parse(input.generatedAt);
103
+ const scope = parseScope(input.scope);
104
+ const bindings = parseUsageBindings(input.financialRecords);
105
+ const missing = new Set();
106
+ const ownership = input.ownership === undefined
107
+ ? undefined
108
+ : parseConfirmedOwnershipV0(input.ownership);
109
+ if (!ownership)
110
+ missing.add("confirmed_ownership");
111
+ else if (ownership.projectRef !== scope.projectRef) {
112
+ missing.add("ownership_project_scope");
113
+ }
114
+ const experiment = input.tokenExperiment === undefined
115
+ ? undefined
116
+ : parseTokenReductionExperimentV0(input.tokenExperiment);
117
+ if (!experiment)
118
+ missing.add("token_experiment");
119
+ const experimentProjectExact = experiment?.cohort.projectRef === scope.actionProjectRef;
120
+ if (experiment && !experimentProjectExact) {
121
+ missing.add("experiment_project_scope");
122
+ }
123
+ const experimentWorkUnitExact = experiment
124
+ ? hasExactExperimentWorkUnit(experiment, scope.actionWorkUnitRef)
125
+ : false;
126
+ if (experiment && !experimentWorkUnitExact) {
127
+ missing.add("experiment_work_unit_scope");
128
+ }
129
+ if (experiment && !hasAppliedIntervention(experiment)) {
130
+ missing.add("intervention_application");
131
+ }
132
+ const approvalEvent = input.approvalEvent === undefined
133
+ ? undefined
134
+ : parseApprovalEventV0(input.approvalEvent);
135
+ if (!approvalEvent)
136
+ missing.add("approval_event");
137
+ else if (!experiment || !approvalLinksExactRevision(approvalEvent, experiment)) {
138
+ missing.add("approval_experiment_link");
139
+ }
140
+ const outcome = input.outcome === undefined
141
+ ? undefined
142
+ : parseAcceptedOutcomeV0(input.outcome);
143
+ if (!outcome)
144
+ missing.add("accepted_outcome");
145
+ else if (outcome.workUnitRef !== scope.workUnitRef) {
146
+ missing.add("outcome_work_unit_scope");
147
+ }
148
+ const costProjection = buildCostProjection(bindings, scope);
149
+ for (const code of costProjection.missing)
150
+ missing.add(code);
151
+ if (costProjection.costs.lines.length > MAX_PROJECT_COST_LINES_V0) {
152
+ missing.add("financial_line_limit");
153
+ }
154
+ const exactExperimentScope = Boolean(experimentProjectExact && experimentWorkUnitExact);
155
+ const measuredTokenResult = experiment
156
+ ? projectMeasuredTokenResult(experiment, exactExperimentScope)
157
+ : inconclusiveTokenResult(0, 0);
158
+ if (measuredTokenResult.status === "inconclusive") {
159
+ missing.add("measured_token_result");
160
+ }
161
+ missing.add("provider_bill_reconciliation");
162
+ const billReconciliation = {
163
+ status: "not_attempted",
164
+ evidence: "missing"
165
+ };
166
+ const claims = {
167
+ roi: "not_claimed",
168
+ invoiceReconciled: false,
169
+ rbacVerified: false
170
+ };
171
+ const blocksReceipt = [...missing].some((code) => missingBlocksReceipt(code));
172
+ const canCreateReceipt = !blocksReceipt && ownership !== undefined &&
173
+ experiment !== undefined && approvalEvent !== undefined && outcome !== undefined;
174
+ const receipt = canCreateReceipt
175
+ ? createProjectEconomicsReceiptV0({
176
+ kind: PROJECT_ECONOMICS_RECEIPT_V0_KIND,
177
+ schemaVersion: PROJECT_ECONOMICS_V0_VERSION,
178
+ generatedAt,
179
+ scope: {
180
+ projectRef: scope.projectRef,
181
+ workUnitRef: scope.workUnitRef
182
+ },
183
+ ownership,
184
+ costs: costProjection.costs,
185
+ action: {
186
+ wasteFindingRef: experiment.finding.id,
187
+ tokenExperimentRef: experiment.id,
188
+ tokenExperimentRevisionRef: experiment.revisionId,
189
+ approvalEvent
190
+ },
191
+ outcome,
192
+ measuredTokenResult,
193
+ billReconciliation,
194
+ claims
195
+ })
196
+ : null;
197
+ return {
198
+ schemaVersion: 0,
199
+ status: receipt ? "receipt_ready" : "incomplete",
200
+ generatedAt,
201
+ scope,
202
+ costs: costProjection.costs,
203
+ action: {
204
+ wasteFindingRef: experiment?.finding.id ?? null,
205
+ tokenExperimentRef: experiment?.id ?? null,
206
+ tokenExperimentRevisionRef: experiment?.revisionId ?? null
207
+ },
208
+ measuredTokenResult,
209
+ billReconciliation,
210
+ claims,
211
+ missing: projectEconomicsProjectionMissingValues
212
+ .filter((code) => missing.has(code))
213
+ .map((code) => ({
214
+ code,
215
+ evidence: "missing",
216
+ blocksReceipt: missingBlocksReceipt(code)
217
+ })),
218
+ receipt
219
+ };
220
+ }
221
+ function parseScope(scope) {
222
+ return {
223
+ projectRef: projectEconomicsReferenceSchema.parse(scope.projectRef),
224
+ workUnitRef: projectEconomicsReferenceSchema.parse(scope.workUnitRef),
225
+ actionProjectRef: actionVerificationReferenceSchema.parse(scope.actionProjectRef),
226
+ actionWorkUnitRef: actionVerificationReferenceSchema.parse(scope.actionWorkUnitRef)
227
+ };
228
+ }
229
+ function parseUsageBindings(bindings) {
230
+ const parsed = bindings.map((binding) => {
231
+ const record = usageRecordSchema.parse(binding.record);
232
+ const projectRef = binding.projectRef === null
233
+ ? null
234
+ : projectEconomicsReferenceSchema.parse(binding.projectRef);
235
+ const workUnitRef = binding.workUnitRef === null
236
+ ? null
237
+ : projectEconomicsReferenceSchema.parse(binding.workUnitRef);
238
+ const projectEvidence = attributionEvidenceSchema.parse(binding.projectEvidence);
239
+ const workUnitEvidence = attributionEvidenceSchema.parse(binding.workUnitEvidence);
240
+ requireReferenceEvidencePair(projectRef, projectEvidence, "project");
241
+ requireReferenceEvidencePair(workUnitRef, workUnitEvidence, "work unit");
242
+ return { record, projectRef, projectEvidence, workUnitRef, workUnitEvidence };
243
+ });
244
+ const ids = new Set();
245
+ for (const binding of parsed) {
246
+ if (ids.has(binding.record.id)) {
247
+ throw new TypeError("Duplicate financial usage record IDs are not allowed.");
248
+ }
249
+ ids.add(binding.record.id);
250
+ }
251
+ return parsed.sort((left, right) => left.record.id.localeCompare(right.record.id));
252
+ }
253
+ function requireReferenceEvidencePair(reference, evidence, label) {
254
+ if ((reference === null) !== (evidence === "missing")) {
255
+ throw new TypeError(`A ${label} reference and non-missing attribution evidence must be present together.`);
256
+ }
257
+ }
258
+ function hasExactExperimentWorkUnit(experiment, actionWorkUnitRef) {
259
+ if (experiment.matchingPolicy.basis !== "accepted_work_unit")
260
+ return false;
261
+ const excludedPostSessions = new Set(experiment.evaluation.exclusions
262
+ .filter((entry) => entry.phase === "post_change")
263
+ .map((entry) => entry.sessionRef));
264
+ // Accepted-work-unit cohorts compare successful tasks, not one PR against
265
+ // itself. Link the receipt's exact accepted outcome to one included post
266
+ // session while allowing the other matched baseline/post outcomes to retain
267
+ // their own opaque work-unit references.
268
+ return experiment.postSessions.some((session) => session.workUnitRef === actionWorkUnitRef &&
269
+ !excludedPostSessions.has(session.sessionRef));
270
+ }
271
+ function hasAppliedIntervention(experiment) {
272
+ return experiment.intervention.approval.status === "explicit" &&
273
+ experiment.intervention.approval.evidence !== "missing" &&
274
+ experiment.intervention.approval.approvedAt !== undefined &&
275
+ experiment.intervention.appliedAt !== undefined &&
276
+ experiment.intervention.changeRef !== undefined &&
277
+ experiment.intervention.rollbackRef !== undefined &&
278
+ experiment.intervention.canary?.status !== "missing" &&
279
+ experiment.intervention.canary?.evidenceRef !== undefined;
280
+ }
281
+ function approvalLinksExactRevision(approval, experiment) {
282
+ const intervention = experiment.intervention;
283
+ return hasAppliedIntervention(experiment) &&
284
+ approval.references.actionRef === createProjectEconomicsApprovalActionRefV0(experiment) &&
285
+ approval.references.changeRef === intervention.changeRef &&
286
+ approval.references.rollbackRef === intervention.rollbackRef &&
287
+ approval.references.canaryRef === intervention.canary?.evidenceRef &&
288
+ approval.approvedAt === intervention.approval.approvedAt;
289
+ }
290
+ function buildCostProjection(bindings, scope) {
291
+ const missing = new Set();
292
+ const groups = new Map();
293
+ let coveredRecords = 0;
294
+ for (const binding of bindings) {
295
+ const projectExact = binding.projectRef === scope.projectRef;
296
+ const workUnitExact = binding.workUnitRef === scope.workUnitRef;
297
+ if (!projectExact)
298
+ missing.add("financial_project_attribution");
299
+ if (!workUnitExact)
300
+ missing.add("financial_work_unit_attribution");
301
+ const classification = projectExact && workUnitExact
302
+ ? classifyFinancialRecord(binding.record)
303
+ : missingCostLine(binding.record);
304
+ if (classification.basis === "missing") {
305
+ missing.add("financial_cost_evidence");
306
+ }
307
+ else {
308
+ coveredRecords += 1;
309
+ }
310
+ const key = `${classification.sourceRef}\u0000${classification.basis}\u0000${classification.evidence}`;
311
+ const current = groups.get(key);
312
+ if (!current) {
313
+ groups.set(key, { ...classification, records: 1 });
314
+ continue;
315
+ }
316
+ if (classification.amountUsd === null || current.amountUsd === null) {
317
+ if (classification.amountUsd !== current.amountUsd) {
318
+ throw new TypeError("A financial cost group cannot mix missing and present amounts.");
319
+ }
320
+ current.records += 1;
321
+ continue;
322
+ }
323
+ const amountUsd = current.amountUsd + classification.amountUsd;
324
+ if (!Number.isFinite(amountUsd) || amountUsd < 0) {
325
+ throw new TypeError("A financial cost line total must remain finite and non-negative.");
326
+ }
327
+ current.amountUsd = Object.is(amountUsd, -0) ? 0 : amountUsd;
328
+ current.records += 1;
329
+ }
330
+ if (bindings.length === 0 || coveredRecords === 0) {
331
+ missing.add("financial_cost_evidence");
332
+ }
333
+ const status = bindings.length === 0 || coveredRecords === 0
334
+ ? "missing"
335
+ : coveredRecords === bindings.length ? "complete" : "partial";
336
+ return {
337
+ costs: {
338
+ lines: [...groups.values()]
339
+ .map(({ records: _records, ...line }) => line)
340
+ .sort((left, right) => costLineKey(left).localeCompare(costLineKey(right))),
341
+ coverage: {
342
+ status,
343
+ coveredRecords,
344
+ eligibleRecords: bindings.length,
345
+ evidence: status === "missing" ? "missing" : "calculated"
346
+ }
347
+ },
348
+ missing
349
+ };
350
+ }
351
+ function classifyFinancialRecord(record) {
352
+ const sourceRef = costSourceRef(record);
353
+ if (isBundledSampleRecord(record) || record.amountUsd === null ||
354
+ record.costConfidence === "missing" ||
355
+ record.costConfidence === "detected_unverified" ||
356
+ record.source.confidence === "missing" ||
357
+ record.source.confidence === "detected_unverified") {
358
+ return { sourceRef, basis: "missing", amountUsd: null, evidence: "missing" };
359
+ }
360
+ if (record.costConfidence === "verified" &&
361
+ record.source.confidence === "verified" &&
362
+ !API_EQUIVALENT_COST_TYPES.has(record.providerCostType ?? "") &&
363
+ record.usageGranularity === "billing_bucket") {
364
+ return {
365
+ sourceRef,
366
+ basis: "provider_billed",
367
+ amountUsd: record.amountUsd,
368
+ evidence: "verified"
369
+ };
370
+ }
371
+ if (record.costConfidence === "estimated" &&
372
+ API_EQUIVALENT_COST_TYPES.has(record.providerCostType ?? "")) {
373
+ return {
374
+ sourceRef,
375
+ basis: "api_equivalent_estimate",
376
+ amountUsd: record.amountUsd,
377
+ evidence: "calculated"
378
+ };
379
+ }
380
+ return { sourceRef, basis: "missing", amountUsd: null, evidence: "missing" };
381
+ }
382
+ function missingCostLine(record) {
383
+ return {
384
+ sourceRef: costSourceRef(record),
385
+ basis: "missing",
386
+ amountUsd: null,
387
+ evidence: "missing"
388
+ };
389
+ }
390
+ function isBundledSampleRecord(record) {
391
+ return record.source.observedFrom === "sample_csv" &&
392
+ /(?:^|-)sample$/i.test(record.source.id);
393
+ }
394
+ function costSourceRef(record) {
395
+ const sourceIdentity = JSON.stringify([
396
+ record.source.id,
397
+ record.source.name,
398
+ record.source.provider,
399
+ record.source.observedFrom,
400
+ record.providerCostType ?? null,
401
+ record.usageGranularity ?? null
402
+ ]);
403
+ const digest = createHash("sha256").update(sourceIdentity).digest("hex");
404
+ return createProjectEconomicsReference("cost-source", digest);
405
+ }
406
+ function costLineKey(line) {
407
+ return `${line.sourceRef}\u0000${line.basis}\u0000${line.evidence}\u0000${line.amountUsd ?? ""}`;
408
+ }
409
+ function projectMeasuredTokenResult(experiment, exactScope) {
410
+ const evaluation = experiment.evaluation;
411
+ if (!exactScope || !isConclusiveReceiptStatus(evaluation.status) ||
412
+ evaluation.metricEvidence !== "calculated" ||
413
+ evaluation.baseline.medianTotalTokens === null ||
414
+ evaluation.postChange.medianTotalTokens === null ||
415
+ evaluation.reductionPercent === null ||
416
+ evaluation.matchingEvidence === "missing" ||
417
+ evaluation.qualityEvidence === "missing") {
418
+ return inconclusiveTokenResult(evaluation.baseline.includedSessions, evaluation.postChange.includedSessions);
419
+ }
420
+ return {
421
+ status: evaluation.status,
422
+ baselineMedianTokens: evaluation.baseline.medianTotalTokens,
423
+ postChangeMedianTokens: evaluation.postChange.medianTotalTokens,
424
+ baselineSessions: evaluation.baseline.includedSessions,
425
+ postChangeSessions: evaluation.postChange.includedSessions,
426
+ reductionPercent: evaluation.reductionPercent,
427
+ metricEvidence: "calculated",
428
+ matchingEvidence: evaluation.matchingEvidence,
429
+ qualityStatus: evaluation.qualityStatus,
430
+ qualityEvidence: evaluation.qualityEvidence
431
+ };
432
+ }
433
+ function isConclusiveReceiptStatus(status) {
434
+ return status === "measured_token_reduction" ||
435
+ status === "no_measured_change" || status === "regressed";
436
+ }
437
+ function inconclusiveTokenResult(baselineSessions, postChangeSessions) {
438
+ return {
439
+ status: "inconclusive",
440
+ baselineMedianTokens: null,
441
+ postChangeMedianTokens: null,
442
+ baselineSessions,
443
+ postChangeSessions,
444
+ reductionPercent: null,
445
+ metricEvidence: "missing",
446
+ matchingEvidence: "missing",
447
+ qualityStatus: "insufficient",
448
+ qualityEvidence: "missing"
449
+ };
450
+ }
451
+ function missingBlocksReceipt(code) {
452
+ switch (code) {
453
+ case "confirmed_ownership":
454
+ case "ownership_project_scope":
455
+ case "token_experiment":
456
+ case "experiment_project_scope":
457
+ case "experiment_work_unit_scope":
458
+ case "intervention_application":
459
+ case "approval_event":
460
+ case "approval_experiment_link":
461
+ case "accepted_outcome":
462
+ case "outcome_work_unit_scope":
463
+ case "financial_line_limit":
464
+ return true;
465
+ case "financial_project_attribution":
466
+ case "financial_work_unit_attribution":
467
+ case "financial_cost_evidence":
468
+ case "measured_token_result":
469
+ case "provider_bill_reconciliation":
470
+ return false;
471
+ }
472
+ }
473
+ //# sourceMappingURL=projectEconomicsBuilder.js.map