Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/__tests__/utils/receipt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,107 @@ it('retains a valid zero alongside invalid costs', () => {
separatelyBilledCredits: 0,
});
});

const enrichmentResponse = (
cost: unknown,
complete: unknown = true,
overrides = {}
) => ({
data: {
creditsCost: 0,
alexandria: [
{
provider: 'firecrawl',
capability: 'enrich',
data: {
status: 'matched',
providerCredits: cost,
billingComplete: complete,
steps: [{ creditsCost: cost }],
},
...overrides,
},
],
},
});
it('shows enrichment provider costs once while retaining the zero outer charge', () => {
const receipt = receiptFor(enrichmentResponse(30), 'scrape');
expect(receipt).toEqual({
creditsUsed: 0,
separatelyBilledCredits: 30,
providerCostsComplete: true,
});
const log = vi.spyOn(console, 'error').mockImplementation(() => {});
try {
printReceipt(receipt);
expect(log).toHaveBeenCalledWith(
'Credits: 30 (0 outer request + 30 separately billed provider calls)'
);
expect(log).toHaveBeenCalledTimes(1);
} finally {
log.mockRestore();
}
});
it('labels partial enrichment costs without claiming they are the final total', () => {
const receipt = receiptFor(enrichmentResponse(5, false), 'scrape');
expect(receipt).toMatchObject({
separatelyBilledCredits: 5,
providerCostsComplete: false,
});
const log = vi.spyOn(console, 'error').mockImplementation(() => {});
try {
printReceipt(receipt);
expect(log).toHaveBeenCalledWith(
'Known credits: 5 (0 outer request + 5 separately billed provider calls)'
);
expect(log).toHaveBeenCalledWith(
'Provider costs are incomplete; additional credits may have been incurred.'
);
} finally {
log.mockRestore();
}
});
it('preserves confirmed zero-cost enrichment', () => {
expect(receiptFor(enrichmentResponse(0), 'scrape')).toEqual({
creditsUsed: 0,
separatelyBilledCredits: 0,
providerCostsComplete: true,
});
});
it.each([undefined, -1, NaN, Infinity, '30'])(
'does not present malformed enrichment costs as complete: %s',
(cost) => {
expect(receiptFor(enrichmentResponse(cost), 'scrape')).toEqual({
creditsUsed: 0,
providerCostsComplete: false,
});
}
);
it.each([
{ provider: 'other' },
{ capability: 'bash' },
{ error: { code: 'unauthorized' } },
])('ignores unrelated or rejected enrichment-like data: %j', (overrides) => {
expect(receiptFor(enrichmentResponse(30, true, overrides), 'scrape')).toEqual(
{ creditsUsed: 0 }
);
});
it('preserves separate SQL and enrichment costs without changing search receipts', () => {
const value = {
data: {
creditsCost: 0,
alexandria: [
...sqlResponse(10).data.alexandria,
...enrichmentResponse(30).data.alexandria,
],
},
};
expect(receiptFor(value, 'scrape')).toEqual({
creditsUsed: 0,
separatelyBilledCredits: 40,
providerCostsComplete: true,
});
expect(receiptFor({ ...value, creditsUsed: 2 }, 'search')).toEqual({
creditsUsed: 2,
});
});
56 changes: 39 additions & 17 deletions src/utils/receipt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface Receipt {
creditsUsed?: number;
separatelyBilledCredits?: number;
providerCostsComplete?: boolean;
requestId?: string;
operationId?: string;
operationType?: 'scrape' | 'search';
Expand All @@ -22,27 +23,42 @@ export function receiptFor(
? value?.id
: (value?.metadata?.scrapeId ?? value?.scrape_id ?? value?.scrapeId);
const entries = value?.data?.alexandria;
const sqlCosts =
const providerResults =
operationType === 'scrape' && Array.isArray(entries)
? entries
.filter(
(entry: any) =>
entry?.provider === 'firecrawl' &&
entry?.capability === 'sql' &&
!entry.error &&
entry?.data?.kind === 'result'
)
.map((entry: any) => entry.data.creditsCost)
? entries.filter(
(entry: any) => entry?.provider === 'firecrawl' && !entry.error
)
: [];
const validSqlCosts = sqlCosts.filter(
(cost: unknown): cost is number =>
typeof cost === 'number' && Number.isFinite(cost) && cost >= 0
const enrichmentResults = providerResults.filter(
(entry: any) => entry.capability === 'enrich' && entry.data
);
const validCost = (cost: unknown): cost is number =>
typeof cost === 'number' && Number.isFinite(cost) && cost >= 0;
const costs = providerResults
.flatMap((entry: any) => {
if (entry.capability === 'sql' && entry.data?.kind === 'result')
return [entry.data.creditsCost];
if (entry.capability === 'enrich' && entry.data)
return [entry.data.providerCredits];
return [];
})
.filter(validCost);
const separatelyBilledCredits =
validSqlCosts.length > 0
? validSqlCosts.reduce((sum: number, cost: number) => sum + cost, 0)
costs.length > 0
? costs.reduce((sum: number, cost: number) => sum + cost, 0)
: undefined;
const providerCostsComplete =
enrichmentResults.length > 0
? enrichmentResults.every(
(entry: any) =>
entry.data.billingComplete === true &&
validCost(entry.data.providerCredits)
) &&
(separatelyBilledCredits === undefined ||
Number.isFinite(separatelyBilledCredits))
: undefined;
return {
...(providerCostsComplete !== undefined ? { providerCostsComplete } : {}),
...(separatelyBilledCredits !== undefined &&
Number.isFinite(separatelyBilledCredits)
? { separatelyBilledCredits }
Expand All @@ -64,19 +80,25 @@ export function printReceipt(receipt: Receipt, includeRequestId = true): void {
console.error(
`${receipt.operationType === 'search' ? 'Search' : 'Scrape'} ID: ${receipt.operationId}`
);
const label =
receipt.providerCostsComplete === false ? 'Known credits' : 'Credits';
if (receipt.separatelyBilledCredits !== undefined) {
if (receipt.creditsUsed !== undefined) {
console.error(
`Credits: ${receipt.creditsUsed + receipt.separatelyBilledCredits} (${receipt.creditsUsed} outer request + ${receipt.separatelyBilledCredits} separately billed provider calls)`
`${label}: ${receipt.creditsUsed + receipt.separatelyBilledCredits} (${receipt.creditsUsed} outer request + ${receipt.separatelyBilledCredits} separately billed provider calls)`
);
} else {
console.error(
`Provider credits (billed separately): ${receipt.separatelyBilledCredits}`
);
}
} else if (receipt.creditsUsed !== undefined) {
console.error(`Credits: ${receipt.creditsUsed}`);
console.error(`${label}: ${receipt.creditsUsed}`);
}
if (receipt.providerCostsComplete === false)
console.error(
'Provider costs are incomplete; additional credits may have been incurred.'
);
}

export function printRetry(failure: Record<string, unknown>): void {
Expand Down
Loading