mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 00:47:53 +00:00
fix for "impossible" condition (we always probe duration, but failsafe)
This commit is contained in:
1 parent
1fea22b65b
commit
d8dfcedc85
5 files changed
+71
-36
No files matched your search
+13
-10
@@ -13,7 +13,7 @@ pub struct ArtcraftSeedance2p5CostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub has_video_references: bool,
|
||||
pub total_input_seconds: u16,
|
||||
pub maybe_total_input_seconds: Option<u16>,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p5CostState {
|
||||
@@ -30,20 +30,21 @@ impl ArtcraftSeedance2p5CostState {
|
||||
resolution,
|
||||
duration_seconds,
|
||||
has_video_references,
|
||||
total_input_seconds: request.total_input_seconds.unwrap_or(0),
|
||||
maybe_total_input_seconds: request.total_input_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Input-second clamping (the 4..=30 billing range) happens inside the
|
||||
// shared pricing function.
|
||||
let total_input_seconds = self.total_input_seconds;
|
||||
// Input-second clamping (the 4..=30 billing range) and the
|
||||
// unknown-or-zero-input failsafe happen inside the shared pricing
|
||||
// function.
|
||||
let maybe_total_input_seconds = self.maybe_total_input_seconds;
|
||||
|
||||
let usd_cents = seedance_2p5_usd_cents(
|
||||
self.resolution,
|
||||
self.duration_seconds,
|
||||
self.has_video_references,
|
||||
total_input_seconds,
|
||||
maybe_total_input_seconds,
|
||||
);
|
||||
|
||||
// ArtCraft credits: 100 credits = $1.00, so credits = cents.
|
||||
@@ -115,10 +116,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_input_seconds_bill_the_four_second_minimum() {
|
||||
// No measured input still bills the 4-second total minimum:
|
||||
// 7.24279835 ¢/s × (10+4) = 101.40 → 102¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 102);
|
||||
fn missing_or_zero_input_seconds_bill_the_thirty_second_maximum() {
|
||||
// FAILSAFE: an unmeasured (or zero) input bills the 30-second MAXIMUM,
|
||||
// matching the provider client's fallback:
|
||||
// 7.24279835 ¢/s × (10+30) = 289.71 → 290¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 290);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, Some(0)), 290);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+13
-10
@@ -14,7 +14,7 @@ pub struct ArtcraftSeedance2p5UltraCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub has_video_references: bool,
|
||||
pub total_input_seconds: u16,
|
||||
pub maybe_total_input_seconds: Option<u16>,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p5UltraCostState {
|
||||
@@ -31,20 +31,21 @@ impl ArtcraftSeedance2p5UltraCostState {
|
||||
resolution,
|
||||
duration_seconds,
|
||||
has_video_references,
|
||||
total_input_seconds: request.total_input_seconds.unwrap_or(0),
|
||||
maybe_total_input_seconds: request.total_input_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Input-second clamping (the 4..=30 billing range) happens inside the
|
||||
// shared pricing function.
|
||||
let total_input_seconds = self.total_input_seconds;
|
||||
// Input-second clamping (the 4..=30 billing range) and the
|
||||
// unknown-or-zero-input failsafe happen inside the shared pricing
|
||||
// function.
|
||||
let maybe_total_input_seconds = self.maybe_total_input_seconds;
|
||||
|
||||
let usd_cents = seedance_2p5_ultra_usd_cents(
|
||||
self.resolution,
|
||||
self.duration_seconds,
|
||||
self.has_video_references,
|
||||
total_input_seconds,
|
||||
maybe_total_input_seconds,
|
||||
);
|
||||
|
||||
// ArtCraft credits: 100 credits = $1.00, so credits = cents.
|
||||
@@ -133,10 +134,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_input_seconds_bill_the_four_second_minimum() {
|
||||
// No measured input still bills the 4-second total minimum:
|
||||
// 8.55967078 ¢/s × (10+4) = 119.84 → 120¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 120);
|
||||
fn missing_or_zero_input_seconds_bill_the_thirty_second_maximum() {
|
||||
// FAILSAFE: an unmeasured (or zero) input bills the 30-second MAXIMUM,
|
||||
// matching the provider client's fallback:
|
||||
// 8.55967078 ¢/s × (10+30) = 342.39 → 343¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 343);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, Some(0)), 343);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+26
-6
@@ -154,7 +154,7 @@ pub fn seedance_2p5_usd_cents(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
has_video_references: bool,
|
||||
total_input_seconds: u16,
|
||||
maybe_total_input_seconds: Option<u16>,
|
||||
) -> u64 {
|
||||
let (cents_per_second, billed_seconds) = if has_video_references {
|
||||
let rate = match resolution {
|
||||
@@ -164,8 +164,18 @@ pub fn seedance_2p5_usd_cents(
|
||||
};
|
||||
// The TOTAL input duration clamps to the 4..=30 second billing range
|
||||
// (three 1s videos sum to 3 and bill 4; three 3s videos bill 9).
|
||||
let billed_input_seconds = total_input_seconds
|
||||
.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS));
|
||||
//
|
||||
// FAILSAFE: an unknown (never probed) or zero input duration bills the
|
||||
// 30-second MAXIMUM, matching the provider client's own fallback. It
|
||||
// must never default toward the minimum — billing 4 input seconds for
|
||||
// an unmeasured input while the provider assumes 30 sells input seconds
|
||||
// far below cost.
|
||||
let billed_input_seconds = match maybe_total_input_seconds {
|
||||
None | Some(0) => u16::from(MAX_BILLED_INPUT_SECONDS),
|
||||
Some(seconds) => {
|
||||
seconds.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS))
|
||||
}
|
||||
};
|
||||
(rate, u64::from(duration_seconds) + u64::from(billed_input_seconds))
|
||||
} else {
|
||||
let rate = match resolution {
|
||||
@@ -198,7 +208,7 @@ pub fn seedance_2p5_ultra_usd_cents(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
has_video_references: bool,
|
||||
total_input_seconds: u16,
|
||||
maybe_total_input_seconds: Option<u16>,
|
||||
) -> u64 {
|
||||
let (cents_per_second, billed_seconds) = if has_video_references {
|
||||
let rate = match resolution {
|
||||
@@ -208,8 +218,18 @@ pub fn seedance_2p5_ultra_usd_cents(
|
||||
};
|
||||
// The TOTAL input duration clamps to the 4..=30 second billing range
|
||||
// (three 1s videos sum to 3 and bill 4; three 3s videos bill 9).
|
||||
let billed_input_seconds = total_input_seconds
|
||||
.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS));
|
||||
//
|
||||
// FAILSAFE: an unknown (never probed) or zero input duration bills the
|
||||
// 30-second MAXIMUM, matching the provider client's own fallback. It
|
||||
// must never default toward the minimum — billing 4 input seconds for
|
||||
// an unmeasured input while the provider assumes 30 sells input seconds
|
||||
// far below cost.
|
||||
let billed_input_seconds = match maybe_total_input_seconds {
|
||||
None | Some(0) => u16::from(MAX_BILLED_INPUT_SECONDS),
|
||||
Some(seconds) => {
|
||||
seconds.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS))
|
||||
}
|
||||
};
|
||||
(rate, u64::from(duration_seconds) + u64::from(billed_input_seconds))
|
||||
} else {
|
||||
let rate = match resolution {
|
||||
|
||||
+8
-3
@@ -73,10 +73,15 @@ impl KinoviSeedance2p5CostState {
|
||||
}
|
||||
};
|
||||
|
||||
// NB: The kinovi calculator clamps too; clamping here as well keeps this
|
||||
// state's math correct even if it's read directly.
|
||||
// NB: The kinovi calculator clamps measured values and bills the
|
||||
// worst-case maximum for unknown (None) or zero inputs. Zero passes
|
||||
// through unclamped so that worst-case fallback applies — clamping it up
|
||||
// to the minimum here would understate the cost.
|
||||
let total_input_seconds = self.total_input_seconds
|
||||
.map(|seconds| seconds.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS));
|
||||
.map(|seconds| match seconds {
|
||||
0 => 0,
|
||||
measured => measured.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS),
|
||||
});
|
||||
|
||||
let pricing_request = GenerateSeedance2p5Request {
|
||||
prompt: String::new(),
|
||||
|
||||
@@ -145,9 +145,12 @@ impl GenerateSeedance2p5Request {
|
||||
Some(KinoviSeedance2p5OutputResolution::FourEightyP) => 16u64,
|
||||
Some(KinoviSeedance2p5OutputResolution::SevenTwentyP) | None => 35u64,
|
||||
};
|
||||
let input_seconds = self.total_input_seconds
|
||||
.unwrap_or(MAX_BILLED_INPUT_SECONDS)
|
||||
.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS);
|
||||
// An unknown (None) or zero input duration bills the worst-case
|
||||
// maximum — never default toward the minimum.
|
||||
let input_seconds = match self.total_input_seconds {
|
||||
None | Some(0) => MAX_BILLED_INPUT_SECONDS,
|
||||
Some(seconds) => seconds.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS),
|
||||
};
|
||||
let seconds = u64::from(self.duration_seconds) + u64::from(input_seconds);
|
||||
(rate, seconds)
|
||||
} else {
|
||||
@@ -483,11 +486,12 @@ mod tests {
|
||||
fn video_reference_rate_is_cheaper_per_second() {
|
||||
// 16 < 26 and 35 < 59: the with-references rate is lower per billed
|
||||
// second (the input seconds are where the money goes). Compare at
|
||||
// zero input seconds — the unknown-duration default assumes the
|
||||
// 30-second maximum, which would swamp the rate comparison.
|
||||
assert!(video_ref_480(10, Some(0)).calculate_costs().kinovi_credits
|
||||
// the minimum billed input (1s clamps to 4) — unknown or zero input
|
||||
// assumes the 30-second maximum, which would swamp the rate
|
||||
// comparison.
|
||||
assert!(video_ref_480(10, Some(1)).calculate_costs().kinovi_credits
|
||||
< t2v_480(10).calculate_costs().kinovi_credits);
|
||||
assert!(video_ref_720(10, Some(0)).calculate_costs().kinovi_credits
|
||||
assert!(video_ref_720(10, Some(1)).calculate_costs().kinovi_credits
|
||||
< t2v_720(10).calculate_costs().kinovi_credits);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user