170 lines
4.6 KiB
TypeScript
Vendored
170 lines
4.6 KiB
TypeScript
Vendored
import { formatBillingCurrencyFromUSD } from '@/lib/currency'
|
|
import { TOKEN_UNIT_DIVISORS } from '../constants'
|
|
import {
|
|
BILLING_PRICING_VARS,
|
|
parseTiersFromExpr,
|
|
splitBillingExprAndRequestRules,
|
|
tryParseRequestRuleExpr,
|
|
type BillingVar,
|
|
type ParsedTier,
|
|
} from './billing-expr'
|
|
import type { PricingModel, TokenUnit } from '../types'
|
|
|
|
type DynamicPriceOptions = {
|
|
tokenUnit: TokenUnit
|
|
showRechargePrice?: boolean
|
|
priceRate?: number
|
|
usdExchangeRate?: number
|
|
groupRatioMultiplier?: number
|
|
}
|
|
|
|
export type DynamicPriceEntry = {
|
|
key: string
|
|
field: string
|
|
label: string
|
|
shortLabel: string
|
|
value: number
|
|
formatted: string
|
|
variable: BillingVar
|
|
}
|
|
|
|
export type DynamicPricingSummary = {
|
|
tiers: ParsedTier[]
|
|
tier: ParsedTier | null
|
|
tierCount: number
|
|
hasRequestRules: boolean
|
|
isSpecialExpression: boolean
|
|
rawExpression: string
|
|
entries: DynamicPriceEntry[]
|
|
primaryEntries: DynamicPriceEntry[]
|
|
secondaryEntries: DynamicPriceEntry[]
|
|
}
|
|
|
|
const PRIMARY_DYNAMIC_FIELDS = new Set(['inputPrice', 'outputPrice'])
|
|
|
|
export function isDynamicPricingModel(model: PricingModel): boolean {
|
|
return model.billing_mode === 'tiered_expr' && Boolean(model.billing_expr)
|
|
}
|
|
|
|
export function getDynamicDisplayGroupRatio(model: PricingModel): number {
|
|
const groups = Array.isArray(model.enable_groups) ? model.enable_groups : []
|
|
const ratios = model.group_ratio || {}
|
|
if (groups.length === 0) return 1
|
|
|
|
let minRatio = Number.POSITIVE_INFINITY
|
|
for (const group of groups) {
|
|
const ratio = ratios[group]
|
|
if (ratio !== undefined && ratio < minRatio) {
|
|
minRatio = ratio
|
|
}
|
|
}
|
|
|
|
return minRatio === Number.POSITIVE_INFINITY ? 1 : minRatio
|
|
}
|
|
|
|
function applyRechargeRate(
|
|
price: number,
|
|
showWithRecharge: boolean,
|
|
priceRate: number,
|
|
usdExchangeRate: number
|
|
): number {
|
|
if (!showWithRecharge) return price
|
|
return (price * priceRate) / usdExchangeRate
|
|
}
|
|
|
|
export function formatDynamicUnitPrice(
|
|
valuePerMillionTokens: number,
|
|
options: DynamicPriceOptions
|
|
): string {
|
|
const groupRatio = options.groupRatioMultiplier ?? 1
|
|
const priceRate = options.priceRate ?? 1
|
|
const usdExchangeRate = options.usdExchangeRate ?? 1
|
|
const priceUSD =
|
|
(valuePerMillionTokens * groupRatio) /
|
|
TOKEN_UNIT_DIVISORS[options.tokenUnit]
|
|
const displayPrice = applyRechargeRate(
|
|
priceUSD,
|
|
options.showRechargePrice ?? false,
|
|
priceRate,
|
|
usdExchangeRate
|
|
)
|
|
|
|
return formatBillingCurrencyFromUSD(displayPrice, {
|
|
digitsLarge: 4,
|
|
digitsSmall: 6,
|
|
abbreviate: false,
|
|
})
|
|
}
|
|
|
|
export function getDynamicPricingTiers(model: PricingModel): ParsedTier[] {
|
|
if (!isDynamicPricingModel(model)) return []
|
|
const { billingExpr } = splitBillingExprAndRequestRules(model.billing_expr || '')
|
|
return parseTiersFromExpr(billingExpr)
|
|
}
|
|
|
|
export function hasDynamicRequestRules(model: PricingModel): boolean {
|
|
if (!isDynamicPricingModel(model)) return false
|
|
const { requestRuleExpr } = splitBillingExprAndRequestRules(
|
|
model.billing_expr || ''
|
|
)
|
|
return Boolean(tryParseRequestRuleExpr(requestRuleExpr || '')?.length)
|
|
}
|
|
|
|
export function getDynamicPriceEntries(
|
|
tier: ParsedTier | null,
|
|
options: DynamicPriceOptions
|
|
): DynamicPriceEntry[] {
|
|
if (!tier) return []
|
|
|
|
return BILLING_PRICING_VARS.flatMap((variable) => {
|
|
if (!variable.field) return []
|
|
const value = Number(tier[variable.field])
|
|
if (!Number.isFinite(value) || value <= 0) return []
|
|
|
|
return [
|
|
{
|
|
key: variable.key,
|
|
field: variable.field,
|
|
label: variable.label,
|
|
shortLabel: variable.shortLabel,
|
|
value,
|
|
formatted: formatDynamicUnitPrice(value, options),
|
|
variable,
|
|
},
|
|
]
|
|
}).sort((a, b) => {
|
|
const aPrimary = PRIMARY_DYNAMIC_FIELDS.has(a.field)
|
|
const bPrimary = PRIMARY_DYNAMIC_FIELDS.has(b.field)
|
|
if (aPrimary !== bPrimary) return aPrimary ? -1 : 1
|
|
return 0
|
|
})
|
|
}
|
|
|
|
export function getDynamicPricingSummary(
|
|
model: PricingModel,
|
|
options: DynamicPriceOptions
|
|
): DynamicPricingSummary | null {
|
|
if (!isDynamicPricingModel(model)) return null
|
|
|
|
const tiers = getDynamicPricingTiers(model)
|
|
const tier = tiers[0] || null
|
|
const entries = getDynamicPriceEntries(tier, options)
|
|
const rawExpression = model.billing_expr || ''
|
|
|
|
return {
|
|
tiers,
|
|
tier,
|
|
tierCount: tiers.length,
|
|
hasRequestRules: hasDynamicRequestRules(model),
|
|
isSpecialExpression: rawExpression.trim().length > 0 && tiers.length === 0,
|
|
rawExpression,
|
|
entries,
|
|
primaryEntries: entries.filter((entry) =>
|
|
PRIMARY_DYNAMIC_FIELDS.has(entry.field)
|
|
),
|
|
secondaryEntries: entries.filter(
|
|
(entry) => !PRIMARY_DYNAMIC_FIELDS.has(entry.field)
|
|
),
|
|
}
|
|
}
|