@@ -203,6 +203,17 @@ pub fn has_pricing_for_provider(provider: ApiProvider, model: &str) -> bool {
203203 calculate_turn_cost_estimate_for_provider ( provider, model, & Usage :: default ( ) ) . is_some ( )
204204}
205205
206+ /// Return whether a provider/model route has authoritative pricing for an
207+ /// already-classified billing surface.
208+ #[ must_use]
209+ pub ( crate ) fn has_pricing_for_billing_surface (
210+ provider : ApiProvider ,
211+ model : & str ,
212+ billing_surface : Option < & str > ,
213+ ) -> bool {
214+ pricing_for_billing_surface ( provider, model, billing_surface) . is_some ( )
215+ }
216+
206217fn pricing_for_model_at ( model : & str , now : DateTime < Utc > ) -> Option < ModelPricing > {
207218 let lower = model. to_lowercase ( ) ;
208219 if lower. starts_with ( "deepseek-ai/" ) {
@@ -561,7 +572,16 @@ pub(crate) fn calculate_turn_cost_estimate_for_provider_at(
561572 crate :: provider_lake:: catalog_offering_for_model ( provider, & catalog_model)
562573 && OfferingPricing :: from_catalog_offering ( & offering) . is_some ( )
563574 {
564- return catalog_cost_estimate_for_route ( provider, & catalog_model, & offering, usage) ;
575+ if let Some ( estimate) =
576+ catalog_cost_estimate_for_route ( provider, & catalog_model, & offering, usage)
577+ {
578+ return Some ( estimate) ;
579+ }
580+ if catalog_gap_uses_documented_hand_price ( provider, & catalog_model, & offering, usage) {
581+ let pricing = provider_owned_hand_pricing_at ( provider, & catalog_model, recorded_at) ?;
582+ return Some ( cost_estimate_with_pricing ( pricing, usage) ) ;
583+ }
584+ return None ;
565585 }
566586
567587 // A few first-party rows predate or intentionally omit a Models.dev entry
@@ -644,6 +664,30 @@ fn provider_owned_hand_pricing_at(
644664 . flatten ( )
645665}
646666
667+ /// Whether a failed catalog estimate is missing only a class whose billing is
668+ /// explicitly documented by the provider-owned row. Keep this narrow: a hand
669+ /// row must not fill unrelated catalog gaps (for example an unpublished cache
670+ /// read rate) merely because the model name is known locally.
671+ fn catalog_gap_uses_documented_hand_price (
672+ provider : ApiProvider ,
673+ model : & str ,
674+ offering : & codewhale_config:: catalog:: CatalogOffering ,
675+ usage : & Usage ,
676+ ) -> bool {
677+ if provider != ApiProvider :: Openai || !model. eq_ignore_ascii_case ( "gpt-5.5" ) {
678+ return false ;
679+ }
680+ let Some ( pricing) = OfferingPricing :: from_catalog_offering ( offering) else {
681+ return false ;
682+ } ;
683+ let usage = token_usage_for_pricing ( usage) ;
684+ usage. cache_write > 0
685+ && pricing. cache_write_per_million . is_none ( )
686+ && ( usage. input == 0 || pricing. input_per_million . is_some ( ) )
687+ && ( usage. output == 0 || pricing. output_per_million . is_some ( ) )
688+ && ( usage. cache_read == 0 || pricing. cache_read_per_million . is_some ( ) )
689+ }
690+
647691/// Estimate usage only from the exact provider offering. Missing prices for a
648692/// used token class fail closed, except on the two documented first-party
649693/// routes where cache tokens are explicitly billed at the input rate.
@@ -1267,6 +1311,34 @@ mod tests {
12671311 assert ! ( has_pricing_for_provider( ApiProvider :: Openai , "gpt-5-codex" ) ) ;
12681312 }
12691313
1314+ #[ test]
1315+ fn provider_hand_price_fills_catalog_missing_used_class ( ) {
1316+ let offering =
1317+ crate :: provider_lake:: catalog_offering_for_model ( ApiProvider :: Openai , "gpt-5.5" )
1318+ . expect ( "bundled OpenAI route" ) ;
1319+ let catalog_pricing =
1320+ OfferingPricing :: from_catalog_offering ( & offering) . expect ( "catalog pricing" ) ;
1321+ assert ! ( catalog_pricing. cache_write_per_million. is_none( ) ) ;
1322+ let usage = Usage {
1323+ input_tokens : 1_000_000 ,
1324+ output_tokens : 0 ,
1325+ prompt_cache_miss_tokens : Some ( 0 ) ,
1326+ prompt_cache_write_tokens : Some ( 1_000_000 ) ,
1327+ ..Default :: default ( )
1328+ } ;
1329+
1330+ let estimate = calculate_turn_cost_estimate_for_provider_at (
1331+ ApiProvider :: Openai ,
1332+ "gpt-5.5" ,
1333+ & usage,
1334+ Utc :: now ( ) ,
1335+ )
1336+ . expect ( "provider hand price supplies the missing cache-write class" ) ;
1337+
1338+ assert ! ( ( estimate. usd - 5.0 ) . abs( ) < f64 :: EPSILON ) ;
1339+ assert_eq ! ( estimate. cny, 0.0 ) ;
1340+ }
1341+
12701342 #[ test]
12711343 fn provider_cost_does_not_fabricate_price_for_costless_catalog_route ( ) {
12721344 let offering = crate :: provider_lake:: catalog_offering_for_model (
0 commit comments