mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
fixup rates
This commit is contained in:
+45
-36
@@ -2,6 +2,7 @@ use kinovi_web_client::generate::video::generate_seedance_2p0_fast::{
|
|||||||
GenerateSeedance2p0FastRequest, KinoviSeedance2p0FastBatchCount,
|
GenerateSeedance2p0FastRequest, KinoviSeedance2p0FastBatchCount,
|
||||||
KinoviSeedance2p0FastOutputResolution,
|
KinoviSeedance2p0FastOutputResolution,
|
||||||
};
|
};
|
||||||
|
use kinovi_web_client::pricing::kinovi_cost_calculator_trait::KinoviCostCalculatorTrait;
|
||||||
|
|
||||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::draft::KinoviSeedance2p0FastDraftState;
|
use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::draft::KinoviSeedance2p0FastDraftState;
|
||||||
@@ -66,8 +67,13 @@ impl KinoviSeedance2p0FastCostState {
|
|||||||
bitrate: None,
|
bitrate: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let costs = pricing_request.calculate_costs();
|
// Enterprise tier: what generations actually cost us (our discounted
|
||||||
let cost_in_credits = costs.total_cost.kinovi_credits;
|
// per-model credit rate at our bulk credit purchase rate).
|
||||||
|
let costs = pricing_request.calculate_enterprise_costs();
|
||||||
|
// Enterprise Fast credits can be fractional (e.g. 480p 5s = 52.5). The
|
||||||
|
// router's credit field is an integer, so round to the nearest credit;
|
||||||
|
// the USD cents (the authoritative charge) are already rounded up.
|
||||||
|
let cost_in_credits = costs.total_cost.kinovi_credits.round() as u64;
|
||||||
let cost_in_usd_cents = costs.total_cost.usd_cents_rounded_up;
|
let cost_in_usd_cents = costs.total_cost.usd_cents_rounded_up;
|
||||||
|
|
||||||
VideoGenerationCostEstimate {
|
VideoGenerationCostEstimate {
|
||||||
@@ -101,61 +107,61 @@ mod tests {
|
|||||||
// ── Direct estimate_cost() tests ──
|
// ── Direct estimate_cost() tests ──
|
||||||
// These must match the reference implementation values exactly.
|
// These must match the reference implementation values exactly.
|
||||||
|
|
||||||
// -- 720p (28 credits/sec, 243 credits/$1) --
|
// -- 720p (enterprise 21 credits/sec, 243.16 credits/$1) --
|
||||||
|
|
||||||
mod pricing_720p {
|
mod pricing_720p {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cost_720p_batch_1() {
|
fn cost_720p_batch_1() {
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 47);
|
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 35);
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 58);
|
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 44);
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 116);
|
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 87);
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 173);
|
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 130);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cost_720p_batch_2() {
|
fn cost_720p_batch_2() {
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 116);
|
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 87);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cost_720p_batch_3() {
|
fn cost_720p_batch_3() {
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Three), 173);
|
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Three), 130);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cost_720p_batch_4() {
|
fn cost_720p_batch_4() {
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 231);
|
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 173);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- 480p (14 credits/sec, 243 credits/$1) --
|
// -- 480p (enterprise 10.5 credits/sec, 243.16 credits/$1) --
|
||||||
|
|
||||||
mod pricing_480p {
|
mod pricing_480p {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cost_480p_batch_1() {
|
fn cost_480p_batch_1() {
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 24);
|
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 18);
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 29);
|
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 22);
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One), 58);
|
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One), 44);
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One), 87);
|
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One), 65);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cost_480p_batch_2() {
|
fn cost_480p_batch_2() {
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Two), 58);
|
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Two), 44);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cost_480p_batch_3() {
|
fn cost_480p_batch_3() {
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Three), 87);
|
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Three), 65);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cost_480p_batch_4() {
|
fn cost_480p_batch_4() {
|
||||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 116);
|
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 87);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,10 +208,11 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let without = base.estimate_cost();
|
let without = base.estimate_cost();
|
||||||
let with = KinoviSeedance2p0FastCostState { has_video_reference: true, ..base }.estimate_cost();
|
let with = KinoviSeedance2p0FastCostState { has_video_reference: true, ..base }.estimate_cost();
|
||||||
// Fast 720p surcharge is +6 credits/s: 140 -> 170 credits (17000/243 = 69.96 -> 70 cents).
|
// Enterprise Fast 720p surcharge is +4.5 credits/s: 105 -> 127.5 credits
|
||||||
assert_eq!(without.cost_in_credits, Some(140));
|
// (12750/243.16 = 52.43 -> 53 cents; credits round to 128).
|
||||||
assert_eq!(with.cost_in_credits, Some(170));
|
assert_eq!(without.cost_in_credits, Some(105));
|
||||||
assert_eq!(with.cost_in_usd_cents, Some(70));
|
assert_eq!(with.cost_in_credits, Some(128));
|
||||||
|
assert_eq!(with.cost_in_usd_cents, Some(53));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── from_request() tests ──
|
// ── from_request() tests ──
|
||||||
@@ -221,7 +228,7 @@ mod tests {
|
|||||||
assert_eq!(cost.duration_seconds, 5);
|
assert_eq!(cost.duration_seconds, 5);
|
||||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||||
assert!(!cost.has_video_reference);
|
assert!(!cost.has_video_reference);
|
||||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(58));
|
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(44));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -229,14 +236,14 @@ mod tests {
|
|||||||
let req = make_request_state(None, 5, KinoviBatchCount::One, false);
|
let req = make_request_state(None, 5, KinoviBatchCount::One, false);
|
||||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||||
assert!(cost.resolution.is_none());
|
assert!(cost.resolution.is_none());
|
||||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(58));
|
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(44));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_request_480p() {
|
fn from_request_480p() {
|
||||||
let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 5, KinoviBatchCount::One, false);
|
let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 5, KinoviBatchCount::One, false);
|
||||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(29));
|
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(22));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -244,9 +251,9 @@ mod tests {
|
|||||||
let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, true);
|
let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, true);
|
||||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||||
assert!(cost.has_video_reference);
|
assert!(cost.has_video_reference);
|
||||||
// Video refs add a +6 credits/s surcharge at 720p: 170 credits -> 89 cents.
|
// Enterprise video refs add +4.5 credits/s at 720p: 127.5 credits -> 53 cents.
|
||||||
assert_eq!(cost.estimate_cost().cost_in_credits, Some(170));
|
assert_eq!(cost.estimate_cost().cost_in_credits, Some(128));
|
||||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(70));
|
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(53));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -271,7 +278,7 @@ mod tests {
|
|||||||
assert_eq!(cost.duration_seconds, 5);
|
assert_eq!(cost.duration_seconds, 5);
|
||||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||||
assert!(!cost.has_video_reference);
|
assert!(!cost.has_video_reference);
|
||||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(58));
|
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(44));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -279,7 +286,7 @@ mod tests {
|
|||||||
let draft = make_draft(5, 1, Some(RouterResolution::FourEightyP), false);
|
let draft = make_draft(5, 1, Some(RouterResolution::FourEightyP), false);
|
||||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::FourEightyP)));
|
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::FourEightyP)));
|
||||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(29));
|
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(22));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -287,9 +294,9 @@ mod tests {
|
|||||||
let draft = make_draft(5, 1, None, true);
|
let draft = make_draft(5, 1, None, true);
|
||||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||||
assert!(cost.has_video_reference);
|
assert!(cost.has_video_reference);
|
||||||
// Video refs add a +6 credits/s surcharge at 720p: 170 credits -> 89 cents.
|
// Enterprise video refs add +4.5 credits/s at 720p: 127.5 credits -> 53 cents.
|
||||||
assert_eq!(cost.estimate_cost().cost_in_credits, Some(170));
|
assert_eq!(cost.estimate_cost().cost_in_credits, Some(128));
|
||||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(70));
|
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(53));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -305,7 +312,7 @@ mod tests {
|
|||||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||||
assert_eq!(cost.duration_seconds, 15);
|
assert_eq!(cost.duration_seconds, 15);
|
||||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Two)));
|
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Two)));
|
||||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(346));
|
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(260));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,12 +350,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn credits_720p() {
|
fn credits_720p() {
|
||||||
assert_eq!(credits(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 140);
|
// Enterprise 21 credits/s × 5s = 105.
|
||||||
|
assert_eq!(credits(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 105);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn credits_480p() {
|
fn credits_480p() {
|
||||||
assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 70);
|
// Enterprise 10.5 credits/s × 5s = 52.5, rounded to 53.
|
||||||
|
assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 53);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+446
-519
File diff suppressed because it is too large
Load Diff
+5
-25
@@ -1,32 +1,12 @@
|
|||||||
use crate::pricing::cost::kinovi_fractional_generation_cost::KinoviFractionalGenerationCost;
|
use crate::pricing::cost::kinovi_fractional_generation_cost::KinoviFractionalGenerationCost;
|
||||||
use crate::pricing::kinovi_pricing_tier::KinoviPricingTier;
|
use crate::pricing::kinovi_pricing_tier::KinoviPricingTier;
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/// The cost of a Kinovi Seedance generation (Seedance 2.0, Fast, and Mini),
|
||||||
// WHY THIS TYPE EXISTS (don't merge it into KinoviSeedanceGenerationCost):
|
/// with the video-reference surcharge broken out from the base price.
|
||||||
//
|
|
||||||
// This is the fractional twin of `KinoviSeedanceGenerationCost`. It is
|
|
||||||
// structurally identical (base + optional video-reference surcharge = total)
|
|
||||||
// and prices the SAME way conceptually — but its credits are fractional
|
|
||||||
// (`KinoviFractionalGenerationCost`, `f64`) rather than whole
|
|
||||||
// (`KinoviGenerationCost`, `u64`).
|
|
||||||
//
|
|
||||||
// Seedance 2.0 Mini at 480p bills 7.5 credits/sec, so odd durations land on
|
|
||||||
// FRACTIONAL credits (5s = 37.5, 15s = 112.5), and Seedance 2.0's enterprise
|
|
||||||
// rates are fractional (37.9 credits/sec at 720p). The remaining whole-credit
|
|
||||||
// Seedance video models use `KinoviSeedanceGenerationCost` (u64 credits).
|
|
||||||
//
|
|
||||||
// Making the shared `u64` credit type fractional would ripple `f64` through
|
|
||||||
// those whole-credit models AND `artcraft_router` (which stores credits as
|
|
||||||
// `u64`), forcing hundreds of integer-literal edits. Keeping fractional
|
|
||||||
// credits in their own small type is the cheaper, better-isolated choice.
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// The cost of a fractional-credit Kinovi Seedance generation (Seedance 2.0,
|
|
||||||
/// Seedance 2.0 Mini), with the video-reference surcharge broken out from the
|
|
||||||
/// base price.
|
|
||||||
///
|
///
|
||||||
/// Mirrors [`crate::pricing::cost::kinovi_seedance_generation_cost::KinoviSeedanceGenerationCost`],
|
/// Credits are fractional (`f64`): Mini's consumer 480p rate is 7.5/sec, and
|
||||||
/// but with FRACTIONAL credits (see the module note above).
|
/// the enterprise discount rates land on fractional credits at most
|
||||||
|
/// durations (e.g. Seedance 2.0 at 37.9/sec).
|
||||||
///
|
///
|
||||||
/// `total_cost` covers base + surcharge. NB: the total's USD conversions are
|
/// `total_cost` covers base + surcharge. NB: the total's USD conversions are
|
||||||
/// computed from the SUMMED credits (rounded once), so they may differ by a
|
/// computed from the SUMMED credits (rounded once), so they may differ by a
|
||||||
|
|||||||
-117
@@ -1,117 +0,0 @@
|
|||||||
use crate::pricing::cost::kinovi_generation_cost::KinoviGenerationCost;
|
|
||||||
|
|
||||||
/// The cost of a Kinovi Seedance generation, with the video-reference
|
|
||||||
/// surcharge broken out from the base price.
|
|
||||||
///
|
|
||||||
/// Used by the WHOLE-credit Seedance video models (currently Fast). Models
|
|
||||||
/// that bill fractional credits (Seedance 2.0, Seedance 2.0 Mini) use the
|
|
||||||
/// `f64` twin,
|
|
||||||
/// [`crate::pricing::cost::kinovi_seedance_fractional_generation_cost::KinoviSeedanceFractionalGenerationCost`]
|
|
||||||
/// — see that file for why they aren't merged.
|
|
||||||
///
|
|
||||||
/// `total_cost` covers base + surcharge. NB: the total's USD conversions are
|
|
||||||
/// computed from the SUMMED credits (rounded once), so they may differ by a
|
|
||||||
/// cent from adding the parts' rounded USD values together.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub struct KinoviSeedanceGenerationCost {
|
|
||||||
/// The full cost (base + any surcharge), in credits and USD cents.
|
|
||||||
pub total_cost: KinoviGenerationCost,
|
|
||||||
|
|
||||||
/// The base generation cost (resolution rate × output duration × batch
|
|
||||||
/// count), excluding any surcharges.
|
|
||||||
pub base_cost: KinoviGenerationCost,
|
|
||||||
|
|
||||||
/// The video-reference surcharge, when one or more reference videos are
|
|
||||||
/// attached. `None` when no reference videos are attached.
|
|
||||||
pub video_reference_surcharge_cost: Option<KinoviGenerationCost>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KinoviSeedanceGenerationCost {
|
|
||||||
/// Build a cost from base credits plus an optional video-reference
|
|
||||||
/// surcharge (both in Kinovi credits). The per-part and total USD
|
|
||||||
/// conversions are derived.
|
|
||||||
pub fn from_base_and_surcharge(
|
|
||||||
base_credits: u64,
|
|
||||||
maybe_video_reference_surcharge_credits: Option<u64>,
|
|
||||||
) -> Self {
|
|
||||||
let total_credits = base_credits + maybe_video_reference_surcharge_credits.unwrap_or(0);
|
|
||||||
Self {
|
|
||||||
total_cost: KinoviGenerationCost::from_kinovi_credits(total_credits),
|
|
||||||
base_cost: KinoviGenerationCost::from_kinovi_credits(base_credits),
|
|
||||||
video_reference_surcharge_cost: maybe_video_reference_surcharge_credits
|
|
||||||
.map(KinoviGenerationCost::from_kinovi_credits),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const FLOAT_TOLERANCE: f64 = 1e-9;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn without_surcharge() {
|
|
||||||
let cost = KinoviSeedanceGenerationCost::from_base_and_surcharge(200, None);
|
|
||||||
|
|
||||||
assert!(cost.video_reference_surcharge_cost.is_none());
|
|
||||||
|
|
||||||
// Base: 200 credits; 20000/243 = 82.30.
|
|
||||||
assert_eq!(cost.base_cost.kinovi_credits, 200);
|
|
||||||
assert_eq!(cost.base_cost.usd_cents_rounded_up, 83);
|
|
||||||
assert_eq!(cost.base_cost.usd_cents_rounded_down, 82);
|
|
||||||
assert!((cost.base_cost.usd_cents_fractional - (20000.0 / 243.0)).abs() < FLOAT_TOLERANCE);
|
|
||||||
|
|
||||||
// Total equals base when there's no surcharge.
|
|
||||||
assert_eq!(cost.total_cost, cost.base_cost);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn with_surcharge() {
|
|
||||||
let cost = KinoviSeedanceGenerationCost::from_base_and_surcharge(200, Some(40));
|
|
||||||
|
|
||||||
// Base: 200 credits; 20000/243 = 82.30.
|
|
||||||
assert_eq!(cost.base_cost.kinovi_credits, 200);
|
|
||||||
assert_eq!(cost.base_cost.usd_cents_rounded_up, 83);
|
|
||||||
assert_eq!(cost.base_cost.usd_cents_rounded_down, 82);
|
|
||||||
assert!((cost.base_cost.usd_cents_fractional - (20000.0 / 243.0)).abs() < FLOAT_TOLERANCE);
|
|
||||||
|
|
||||||
// Surcharge: 40 credits; 4000/243 = 16.46.
|
|
||||||
let surcharge = cost.video_reference_surcharge_cost.expect("should have surcharge");
|
|
||||||
assert_eq!(surcharge.kinovi_credits, 40);
|
|
||||||
assert_eq!(surcharge.usd_cents_rounded_up, 17);
|
|
||||||
assert_eq!(surcharge.usd_cents_rounded_down, 16);
|
|
||||||
assert!((surcharge.usd_cents_fractional - (4000.0 / 243.0)).abs() < FLOAT_TOLERANCE);
|
|
||||||
|
|
||||||
// Total: 240 credits; 24000/243 = 98.77.
|
|
||||||
assert_eq!(cost.total_cost.kinovi_credits, 240);
|
|
||||||
assert_eq!(cost.total_cost.usd_cents_rounded_up, 99);
|
|
||||||
assert_eq!(cost.total_cost.usd_cents_rounded_down, 98);
|
|
||||||
assert!((cost.total_cost.usd_cents_fractional - (24000.0 / 243.0)).abs() < FLOAT_TOLERANCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn total_credits_are_base_plus_surcharge() {
|
|
||||||
let cost = KinoviSeedanceGenerationCost::from_base_and_surcharge(450, Some(90));
|
|
||||||
assert_eq!(
|
|
||||||
cost.total_cost.kinovi_credits,
|
|
||||||
cost.base_cost.kinovi_credits + cost.video_reference_surcharge_cost.unwrap().kinovi_credits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The total's USD is rounded ONCE on the summed credits, so it can be a
|
|
||||||
/// cent under the sum of the parts' rounded-up values.
|
|
||||||
#[test]
|
|
||||||
fn total_usd_is_rounded_once_not_per_part() {
|
|
||||||
let cost = KinoviSeedanceGenerationCost::from_base_and_surcharge(200, Some(40));
|
|
||||||
let surcharge = cost.video_reference_surcharge_cost.unwrap();
|
|
||||||
|
|
||||||
// Parts round up to 83 + 17 = 100, but the total (240 credits) rounds
|
|
||||||
// once to 99 — one cent less than summing the rounded parts.
|
|
||||||
assert_eq!(cost.base_cost.usd_cents_rounded_up + surcharge.usd_cents_rounded_up, 100);
|
|
||||||
assert_eq!(cost.total_cost.usd_cents_rounded_up, 99);
|
|
||||||
|
|
||||||
// …but the fractional parts always sum exactly to the fractional total.
|
|
||||||
let parts = cost.base_cost.usd_cents_fractional + surcharge.usd_cents_fractional;
|
|
||||||
assert!((parts - cost.total_cost.usd_cents_fractional).abs() < FLOAT_TOLERANCE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
pub mod kinovi_fractional_generation_cost;
|
pub mod kinovi_fractional_generation_cost;
|
||||||
pub mod kinovi_generation_cost;
|
pub mod kinovi_generation_cost;
|
||||||
pub mod kinovi_seedance_fractional_generation_cost;
|
pub mod kinovi_seedance_fractional_generation_cost;
|
||||||
pub mod kinovi_seedance_generation_cost;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user