| 1 | package billing |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "time" |
| 6 | ) |
| 7 | |
| 8 | const ( |
| 9 | ScheduleDeepSeekV4August2026 = "deepseek-v4-2026-08-17" |
| 10 | ScheduleDeepSeekV4September2026 = "deepseek-v4-2026-09-10" |
| 11 | RateBandPeak = "peak" |
| 12 | RateBandOffPeak = "off_peak" |
| 13 | RateBandMixed = "mixed" |
| 14 | ) |
| 15 | |
| 16 | var ( |
| 17 | deepSeekV4August2026EffectiveAt = time.Date(2026, time.August, 16, 16, 0, 0, 0, time.UTC) |
| 18 | // 2026-09-10 12:00 Beijing: the V4.1 Flash price cut plus its rename. |
| 19 | deepSeekV4September2026EffectiveAt = time.Date(2026, time.September, 10, 4, 0, 0, 0, time.UTC) |
| 20 | ) |
| 21 | |
| 22 | // deepSeekScheduleStart maps a schedule to the instant its peak/off-peak banding |
| 23 | // begins. A schedule absent from this map has no banded rows. |
| 24 | var deepSeekScheduleStart = map[string]time.Time{ |
| 25 | ScheduleDeepSeekV4August2026: deepSeekV4August2026EffectiveAt, |
| 26 | ScheduleDeepSeekV4September2026: deepSeekV4September2026EffectiveAt, |
| 27 | } |
| 28 | |
| 29 | // DeepSeekScheduledIDs lists the banded schedule ids newest first, so callers |
| 30 | // that bind a configured rate to its anchor always prefer the current one. |
| 31 | func DeepSeekScheduledIDs() []string { |
| 32 | return []string{ScheduleDeepSeekV4September2026, ScheduleDeepSeekV4August2026} |
| 33 | } |
| 34 | |
| 35 | // CatalogEntry is one official list price for a model in a billing currency. |
| 36 | type CatalogEntry struct { |
| 37 | Provider string // deepseek | longcat | mimo |
| 38 | Model string |
| 39 | Currency string // ISO billing currency for this row |
| 40 | CacheHit float64 |
| 41 | Input float64 |
| 42 | Output float64 |
| 43 | ScheduleID string |
| 44 | RateBand string // peak | off_peak; empty for static or legacy rows |
| 45 | EffectiveFrom time.Time |
| 46 | EffectiveTo time.Time |
| 47 | DocURL string |
| 48 | BillingMode string // payg | subscription_equivalent |
| 49 | Notes string |
| 50 | Fingerprint string // filled by OfficialCatalog |
| 51 | } |
| 52 | |
| 53 | // ResolvedRate is the occurrence-time rate selected from an official schedule. |
| 54 | type ResolvedRate struct { |
| 55 | Card RateCard |
| 56 | RateBand string |
| 57 | ScheduleID string |
| 58 | OccurredAt time.Time |
| 59 | } |
| 60 | |
| 61 | const ( |
| 62 | DocDeepSeekPricing = "https://api-docs.deepseek.com/quick_start/pricing" |
| 63 | DocLongCatPricingUSD = "https://longcat.chat/platform/docs/Pricing/LongCat-2.0.html" |
| 64 | DocLongCatPricingCNY = "https://longcat.chat/platform/docs/zh/pricing/long-cat-2.0" |
| 65 | DocMiMoPAYG = "https://mimo.mi.com/docs/price/pay-as-you-go" |
| 66 | DocMiMoTokenPlan = "https://platform.xiaomimimo.com/token-plan" |
| 67 | ) |
| 68 | |
| 69 | // OfficialCatalog is the built-in price book. DeepSeek's scheduled rows use |
| 70 | // exact UTC instants so historical quote fixtures do not depend on host locale. |
| 71 | func OfficialCatalog() []CatalogEntry { |
| 72 | cutover := deepSeekV4August2026EffectiveAt |
| 73 | sept := deepSeekV4September2026EffectiveAt |
| 74 | entries := []CatalogEntry{ |
| 75 | // August DeepSeek V4 regional tables. Peak rows are the persisted config |
| 76 | // anchors; occurrence-time resolution substitutes off-peak rows. Closed at |
| 77 | // the September cutover so the superseded prices stay queryable. |
| 78 | {Provider: "deepseek", Model: "deepseek-v4-flash", Currency: "CNY", CacheHit: 0.10, Input: 3, Output: 9, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 79 | {Provider: "deepseek", Model: "deepseek-v4-flash", Currency: "CNY", CacheHit: 0.05, Input: 1.5, Output: 4.5, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandOffPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 80 | {Provider: "deepseek", Model: "deepseek-v4-flash-vision-exp", Currency: "CNY", CacheHit: 0.10, Input: 3, Output: 9, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 81 | {Provider: "deepseek", Model: "deepseek-v4-flash-vision-exp", Currency: "CNY", CacheHit: 0.05, Input: 1.5, Output: 4.5, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandOffPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 82 | {Provider: "deepseek", Model: "deepseek-v4-pro", Currency: "CNY", CacheHit: 0.30, Input: 9, Output: 27, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 83 | {Provider: "deepseek", Model: "deepseek-v4-pro", Currency: "CNY", CacheHit: 0.15, Input: 4.5, Output: 13.5, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandOffPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 84 | {Provider: "deepseek", Model: "deepseek-v4-flash", Currency: "USD", CacheHit: 0.014, Input: 0.44, Output: 1.32, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 85 | {Provider: "deepseek", Model: "deepseek-v4-flash", Currency: "USD", CacheHit: 0.007, Input: 0.22, Output: 0.66, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandOffPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 86 | {Provider: "deepseek", Model: "deepseek-v4-flash-vision-exp", Currency: "USD", CacheHit: 0.014, Input: 0.44, Output: 1.32, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 87 | {Provider: "deepseek", Model: "deepseek-v4-flash-vision-exp", Currency: "USD", CacheHit: 0.007, Input: 0.22, Output: 0.66, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandOffPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 88 | {Provider: "deepseek", Model: "deepseek-v4-pro", Currency: "USD", CacheHit: 0.044, Input: 1.32, Output: 3.96, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 89 | {Provider: "deepseek", Model: "deepseek-v4-pro", Currency: "USD", CacheHit: 0.022, Input: 0.66, Output: 1.98, ScheduleID: ScheduleDeepSeekV4August2026, RateBand: RateBandOffPeak, EffectiveFrom: cutover, EffectiveTo: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 90 | |
| 91 | // Historical DeepSeek prices are available only to explicitly dated |
| 92 | // schedule resolution. Persisted quotes are never repriced from these rows. |
| 93 | {Provider: "deepseek", Model: "deepseek-v4-flash", Currency: "CNY", CacheHit: 0.02, Input: 1, Output: 2, ScheduleID: ScheduleDeepSeekV4August2026, EffectiveTo: cutover, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 94 | {Provider: "deepseek", Model: "deepseek-v4-flash-vision-exp", Currency: "CNY", CacheHit: 0.02, Input: 1, Output: 2, ScheduleID: ScheduleDeepSeekV4August2026, EffectiveTo: cutover, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 95 | {Provider: "deepseek", Model: "deepseek-v4-pro", Currency: "CNY", CacheHit: 0.025, Input: 3, Output: 6, ScheduleID: ScheduleDeepSeekV4August2026, EffectiveTo: cutover, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 96 | {Provider: "deepseek", Model: "deepseek-v4-flash", Currency: "USD", CacheHit: 0.0028, Input: 0.14, Output: 0.28, ScheduleID: ScheduleDeepSeekV4August2026, EffectiveTo: cutover, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 97 | {Provider: "deepseek", Model: "deepseek-v4-flash-vision-exp", Currency: "USD", CacheHit: 0.0028, Input: 0.14, Output: 0.28, ScheduleID: ScheduleDeepSeekV4August2026, EffectiveTo: cutover, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 98 | {Provider: "deepseek", Model: "deepseek-v4-pro", Currency: "USD", CacheHit: 0.003625, Input: 0.435, Output: 0.87, ScheduleID: ScheduleDeepSeekV4August2026, EffectiveTo: cutover, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 99 | |
| 100 | {Provider: "longcat", Model: "LongCat-2.0", Currency: "CNY", CacheHit: 0.04, Input: 2, Output: 8, DocURL: DocLongCatPricingCNY, BillingMode: BillingModePAYG}, |
| 101 | {Provider: "longcat", Model: "LongCat-2.0", Currency: "USD", CacheHit: 0.006, Input: 0.30, Output: 1.20, DocURL: DocLongCatPricingUSD, BillingMode: BillingModePAYG}, |
| 102 | {Provider: "mimo", Model: "mimo-v2.5-pro", Currency: "CNY", CacheHit: 0.025, Input: 3, Output: 6, DocURL: DocMiMoPAYG, BillingMode: BillingModePAYG}, |
| 103 | {Provider: "mimo", Model: "mimo-v2.5", Currency: "CNY", CacheHit: 0.02, Input: 1, Output: 2, DocURL: DocMiMoPAYG, BillingMode: BillingModePAYG}, |
| 104 | {Provider: "mimo", Model: "mimo-v2-flash", Currency: "CNY", CacheHit: 0.07, Input: 0.70, Output: 2.10, DocURL: DocMiMoPAYG, BillingMode: BillingModePAYG}, |
| 105 | {Provider: "mimo", Model: "mimo-v2.5-pro", Currency: "CNY", CacheHit: 0.025, Input: 3, Output: 6, DocURL: DocMiMoTokenPlan, BillingMode: BillingModeSubscriptionEquivalent, Notes: "payg_equivalent_not_plan_bill"}, |
| 106 | {Provider: "mimo", Model: "mimo-v2.5", Currency: "CNY", CacheHit: 0.02, Input: 1, Output: 2, DocURL: DocMiMoTokenPlan, BillingMode: BillingModeSubscriptionEquivalent, Notes: "payg_equivalent_not_plan_bill"}, |
| 107 | } |
| 108 | // September: V4.1 Flash cut its price and the vendor serves the retired |
| 109 | // deepseek-v4-flash / -vision-exp ids from it, so all three share one rate. |
| 110 | for _, model := range []string{"deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp"} { |
| 111 | entries = append(entries, |
| 112 | CatalogEntry{Provider: "deepseek", Model: model, Currency: "CNY", CacheHit: 0.04, Input: 2, Output: 8, ScheduleID: ScheduleDeepSeekV4September2026, RateBand: RateBandPeak, EffectiveFrom: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 113 | CatalogEntry{Provider: "deepseek", Model: model, Currency: "CNY", CacheHit: 0.02, Input: 1, Output: 4, ScheduleID: ScheduleDeepSeekV4September2026, RateBand: RateBandOffPeak, EffectiveFrom: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 114 | CatalogEntry{Provider: "deepseek", Model: model, Currency: "USD", CacheHit: 0.006, Input: 0.3, Output: 1.2, ScheduleID: ScheduleDeepSeekV4September2026, RateBand: RateBandPeak, EffectiveFrom: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 115 | CatalogEntry{Provider: "deepseek", Model: model, Currency: "USD", CacheHit: 0.003, Input: 0.15, Output: 0.6, ScheduleID: ScheduleDeepSeekV4September2026, RateBand: RateBandOffPeak, EffectiveFrom: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}) |
| 116 | } |
| 117 | // V4 Pro keeps its own price until the vendor routes it to V4.1 Flash. |
| 118 | entries = append(entries, |
| 119 | CatalogEntry{Provider: "deepseek", Model: "deepseek-v4-pro", Currency: "CNY", CacheHit: 0.30, Input: 9, Output: 27, ScheduleID: ScheduleDeepSeekV4September2026, RateBand: RateBandPeak, EffectiveFrom: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 120 | CatalogEntry{Provider: "deepseek", Model: "deepseek-v4-pro", Currency: "CNY", CacheHit: 0.15, Input: 4.5, Output: 13.5, ScheduleID: ScheduleDeepSeekV4September2026, RateBand: RateBandOffPeak, EffectiveFrom: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 121 | CatalogEntry{Provider: "deepseek", Model: "deepseek-v4-pro", Currency: "USD", CacheHit: 0.044, Input: 1.32, Output: 3.96, ScheduleID: ScheduleDeepSeekV4September2026, RateBand: RateBandPeak, EffectiveFrom: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}, |
| 122 | CatalogEntry{Provider: "deepseek", Model: "deepseek-v4-pro", Currency: "USD", CacheHit: 0.022, Input: 0.66, Output: 1.98, ScheduleID: ScheduleDeepSeekV4September2026, RateBand: RateBandOffPeak, EffectiveFrom: sept, DocURL: DocDeepSeekPricing, BillingMode: BillingModePAYG}) |
| 123 | for i := range entries { |
| 124 | entries[i].Fingerprint = PricingFingerprint(RateCardFromCatalog(entries[i])) |
| 125 | } |
| 126 | return entries |
| 127 | } |
| 128 | |
| 129 | func normalizeBillingMode(mode string) string { |
| 130 | if strings.TrimSpace(mode) == "" { |
| 131 | return BillingModePAYG |
| 132 | } |
| 133 | return strings.TrimSpace(mode) |
| 134 | } |
| 135 | |
| 136 | func catalogIdentityMatches(e CatalogEntry, provider, model, currency, billingMode string) bool { |
| 137 | return e.Provider == strings.ToLower(strings.TrimSpace(provider)) && |
| 138 | e.Model == strings.TrimSpace(model) && |
| 139 | NormalizeCurrency(e.Currency) == NormalizeCurrency(currency) && |
| 140 | e.BillingMode == normalizeBillingMode(billingMode) |
| 141 | } |
| 142 | |
| 143 | func catalogEntryEffective(e CatalogEntry, at time.Time) bool { |
| 144 | if !e.EffectiveFrom.IsZero() && at.Before(e.EffectiveFrom) { |
| 145 | return false |
| 146 | } |
| 147 | return e.EffectiveTo.IsZero() || at.Before(e.EffectiveTo) |
| 148 | } |
| 149 | |
| 150 | // DeepSeekRateBand selects the documented Beijing weekday peak windows by |
| 151 | // their stable UTC equivalents. |
| 152 | func DeepSeekRateBand(at time.Time) string { |
| 153 | at = at.UTC() |
| 154 | if at.Weekday() == time.Saturday || at.Weekday() == time.Sunday { |
| 155 | return RateBandOffPeak |
| 156 | } |
| 157 | minutes := at.Hour()*60 + at.Minute() |
| 158 | if (minutes >= 60 && minutes < 240) || (minutes >= 360 && minutes < 600) { |
| 159 | return RateBandPeak |
| 160 | } |
| 161 | return RateBandOffPeak |
| 162 | } |
| 163 | |
| 164 | // ResolveScheduledRate resolves an official occurrence-time rate. The schedule |
| 165 | // id must come from resolved official-provider config; a model name is not enough. |
| 166 | func ResolveScheduledRate(provider, model, currency, billingMode, scheduleID string, at time.Time) (ResolvedRate, bool) { |
| 167 | if strings.TrimSpace(scheduleID) == "" { |
| 168 | return ResolvedRate{}, false |
| 169 | } |
| 170 | if at.IsZero() { |
| 171 | at = time.Now().UTC() |
| 172 | } else { |
| 173 | at = at.UTC() |
| 174 | } |
| 175 | band := "" |
| 176 | if start, banded := deepSeekScheduleStart[scheduleID]; banded && !at.Before(start) { |
| 177 | band = DeepSeekRateBand(at) |
| 178 | } |
| 179 | for _, e := range OfficialCatalog() { |
| 180 | if e.ScheduleID != scheduleID || e.RateBand != band || !catalogEntryEffective(e, at) { |
| 181 | continue |
| 182 | } |
| 183 | if catalogIdentityMatches(e, provider, model, currency, billingMode) { |
| 184 | return ResolvedRate{Card: RateCardFromCatalog(e), RateBand: band, ScheduleID: scheduleID, OccurredAt: at}, true |
| 185 | } |
| 186 | } |
| 187 | return ResolvedRate{}, false |
| 188 | } |
| 189 | |
| 190 | // LookupCatalog finds the preferred current official entry. Scheduled models |
| 191 | // expose their peak row as the stable config anchor. |
| 192 | func LookupCatalog(provider, model, currency, billingMode string) (CatalogEntry, bool) { |
| 193 | var anchor, fallback CatalogEntry |
| 194 | for _, e := range OfficialCatalog() { |
| 195 | if !catalogIdentityMatches(e, provider, model, currency, billingMode) { |
| 196 | continue |
| 197 | } |
| 198 | if e.ScheduleID == "" { |
| 199 | return e, true |
| 200 | } |
| 201 | // A model carries one peak anchor per dated schedule; the config anchor is |
| 202 | // always the newest, so resolution must not depend on catalog order. |
| 203 | if e.RateBand == RateBandPeak { |
| 204 | if anchor.Provider == "" || e.EffectiveFrom.After(anchor.EffectiveFrom) { |
| 205 | anchor = e |
| 206 | } |
| 207 | continue |
| 208 | } |
| 209 | if fallback.Provider == "" { |
| 210 | fallback = e |
| 211 | } |
| 212 | } |
| 213 | if anchor.Provider != "" { |
| 214 | return anchor, true |
| 215 | } |
| 216 | return fallback, fallback.Provider != "" |
| 217 | } |
| 218 | |
| 219 | // LookupCatalogAt returns the matching occurrence-time peer row used for an |
| 220 | // official dual-currency valuation. |
| 221 | func LookupCatalogAt(provider, model, currency, billingMode, scheduleID, rateBand string, at time.Time) (CatalogEntry, bool) { |
| 222 | for _, e := range OfficialCatalog() { |
| 223 | if e.ScheduleID != scheduleID || e.RateBand != rateBand || !catalogEntryEffective(e, at) { |
| 224 | continue |
| 225 | } |
| 226 | if catalogIdentityMatches(e, provider, model, currency, billingMode) { |
| 227 | return e, true |
| 228 | } |
| 229 | } |
| 230 | return CatalogEntry{}, false |
| 231 | } |
| 232 | |
| 233 | func RateCardFromCatalog(e CatalogEntry) RateCard { |
| 234 | return RateCard{CacheHit: e.CacheHit, Input: e.Input, Output: e.Output, Currency: e.Currency} |
| 235 | } |
| 236 | |
| 237 | func MatchesCatalog(provider, model string, rates RateCard) (CatalogEntry, bool) { |
| 238 | cur := NormalizeCurrency(rates.Currency) |
| 239 | for _, e := range OfficialCatalog() { |
| 240 | // Historical and off-peak rows require a trusted schedule resolution. |
| 241 | // Static custom rates that merely equal one of those rows are not official. |
| 242 | if e.ScheduleID != "" && e.RateBand != RateBandPeak { |
| 243 | continue |
| 244 | } |
| 245 | if e.Provider != strings.ToLower(strings.TrimSpace(provider)) || e.Model != strings.TrimSpace(model) || NormalizeCurrency(e.Currency) != cur { |
| 246 | continue |
| 247 | } |
| 248 | if e.CacheHit == rates.CacheHit && e.Input == rates.Input && e.Output == rates.Output { |
| 249 | return e, true |
| 250 | } |
| 251 | } |
| 252 | return CatalogEntry{}, false |
| 253 | } |
| 254 | |
| 255 | // MatchesOfficialPeakAnchor reports whether rates is an unedited peak anchor for |
| 256 | // this model in any recorded schedule. A config that has not been re-saved since |
| 257 | // a dated price change still holds the previous generation's anchor; that is |
| 258 | // still an official row, so occurrence-time resolution must be allowed to price |
| 259 | // it. Which schedule that resolution uses is the caller's decision, not this |
| 260 | // check's — custom and off-peak-looking static prices still fail here. |
| 261 | func MatchesOfficialPeakAnchor(provider, model, currency, billingMode string, rates RateCard) bool { |
| 262 | for _, e := range OfficialCatalog() { |
| 263 | if e.ScheduleID == "" || e.RateBand != RateBandPeak { |
| 264 | continue |
| 265 | } |
| 266 | if !catalogIdentityMatches(e, provider, model, currency, billingMode) { |
| 267 | continue |
| 268 | } |
| 269 | if e.CacheHit == rates.CacheHit && e.Input == rates.Input && e.Output == rates.Output { |
| 270 | return true |
| 271 | } |
| 272 | } |
| 273 | return false |
| 274 | } |
| 275 | |
| 276 | // MatchesScheduleAnchor verifies that configured rates are the peak anchor of |
| 277 | // the named schedule. It is the stricter, schedule-scoped form of |
| 278 | // MatchesOfficialPeakAnchor. |
| 279 | func MatchesScheduleAnchor(provider, model, scheduleID string, rates RateCard) bool { |
| 280 | for _, e := range OfficialCatalog() { |
| 281 | if e.ScheduleID != scheduleID || e.RateBand != RateBandPeak { |
| 282 | continue |
| 283 | } |
| 284 | if !catalogIdentityMatches(e, provider, model, rates.Currency, BillingModePAYG) { |
| 285 | continue |
| 286 | } |
| 287 | return e.CacheHit == rates.CacheHit && e.Input == rates.Input && e.Output == rates.Output |
| 288 | } |
| 289 | return false |
| 290 | } |
| 291 |